summaryrefslogtreecommitdiff
path: root/misc
diff options
context:
space:
mode:
authorChris Schlaeger <chris@linux.com>2014-08-12 21:56:44 +0200
committerChris Schlaeger <chris@linux.com>2014-08-12 21:56:44 +0200
commitea346a785dc1b3f7c156f6fc33da634e1f1a627b (patch)
treeaf67530553d20b6e82ad60fd79593e9c4abf5565 /misc
parent59741cd535c47f25971bf8c32b25da25ceadc6d5 (diff)
downloadpostrunner-ea346a785dc1b3f7c156f6fc33da634e1f1a627b.zip
Adding jquery, flot and openlayers to be included with the GEM.v0.0.4
Diffstat (limited to 'misc')
-rw-r--r--misc/flot/API.md1498
-rw-r--r--misc/flot/CONTRIBUTING.md98
-rw-r--r--misc/flot/FAQ.md75
-rw-r--r--misc/flot/LICENSE.txt22
-rw-r--r--misc/flot/Makefile12
-rw-r--r--misc/flot/NEWS.md1026
-rw-r--r--misc/flot/PLUGINS.md143
-rw-r--r--misc/flot/README.md110
-rw-r--r--misc/flot/examples/.DS_Storebin0 -> 15364 bytes
-rw-r--r--misc/flot/examples/ajax/data-eu-gdp-growth-1.json4
-rw-r--r--misc/flot/examples/ajax/data-eu-gdp-growth-2.json4
-rw-r--r--misc/flot/examples/ajax/data-eu-gdp-growth-3.json4
-rw-r--r--misc/flot/examples/ajax/data-eu-gdp-growth-4.json4
-rw-r--r--misc/flot/examples/ajax/data-eu-gdp-growth-5.json4
-rw-r--r--misc/flot/examples/ajax/data-eu-gdp-growth.json4
-rw-r--r--misc/flot/examples/ajax/data-japan-gdp-growth.json4
-rw-r--r--misc/flot/examples/ajax/data-usa-gdp-growth.json4
-rw-r--r--misc/flot/examples/ajax/index.html173
-rw-r--r--misc/flot/examples/annotating/index.html87
-rw-r--r--misc/flot/examples/axes-interacting/index.html97
-rw-r--r--misc/flot/examples/axes-multiple/index.html77
-rw-r--r--misc/flot/examples/axes-time-zones/date.js893
-rw-r--r--misc/flot/examples/axes-time-zones/index.html114
-rw-r--r--misc/flot/examples/axes-time-zones/tz/africa1181
-rw-r--r--misc/flot/examples/axes-time-zones/tz/antarctica413
-rw-r--r--misc/flot/examples/axes-time-zones/tz/asia2717
-rw-r--r--misc/flot/examples/axes-time-zones/tz/australasia1719
-rw-r--r--misc/flot/examples/axes-time-zones/tz/backward117
-rw-r--r--misc/flot/examples/axes-time-zones/tz/etcetera81
-rw-r--r--misc/flot/examples/axes-time-zones/tz/europe2856
-rw-r--r--misc/flot/examples/axes-time-zones/tz/factory10
-rw-r--r--misc/flot/examples/axes-time-zones/tz/iso3166.tab276
-rw-r--r--misc/flot/examples/axes-time-zones/tz/leapseconds100
-rw-r--r--misc/flot/examples/axes-time-zones/tz/northamerica3235
-rw-r--r--misc/flot/examples/axes-time-zones/tz/pacificnew28
-rw-r--r--misc/flot/examples/axes-time-zones/tz/solar87390
-rw-r--r--misc/flot/examples/axes-time-zones/tz/solar88390
-rw-r--r--misc/flot/examples/axes-time-zones/tz/solar89395
-rw-r--r--misc/flot/examples/axes-time-zones/tz/southamerica1711
-rw-r--r--misc/flot/examples/axes-time-zones/tz/systemv38
-rw-r--r--misc/flot/examples/axes-time-zones/tz/yearistype.sh38
-rw-r--r--misc/flot/examples/axes-time-zones/tz/zone.tab441
-rw-r--r--misc/flot/examples/axes-time/index.html137
-rw-r--r--misc/flot/examples/background.pngbin0 -> 231 bytes
-rw-r--r--misc/flot/examples/basic-options/index.html91
-rw-r--r--misc/flot/examples/basic-usage/index.html57
-rw-r--r--misc/flot/examples/canvas/index.html75
-rw-r--r--misc/flot/examples/categories/index.html64
-rw-r--r--misc/flot/examples/examples.css97
-rw-r--r--misc/flot/examples/image/hs-2004-27-a-large-web.jpgbin0 -> 34489 bytes
-rw-r--r--misc/flot/examples/image/index.html69
-rw-r--r--misc/flot/examples/index.html80
-rw-r--r--misc/flot/examples/interacting/index.html118
-rw-r--r--misc/flot/examples/navigate/arrow-down.gifbin0 -> 916 bytes
-rw-r--r--misc/flot/examples/navigate/arrow-left.gifbin0 -> 891 bytes
-rw-r--r--misc/flot/examples/navigate/arrow-right.gifbin0 -> 897 bytes
-rw-r--r--misc/flot/examples/navigate/arrow-up.gifbin0 -> 916 bytes
-rw-r--r--misc/flot/examples/navigate/index.html153
-rw-r--r--misc/flot/examples/percentiles/index.html79
-rw-r--r--misc/flot/examples/realtime/index.html122
-rw-r--r--misc/flot/examples/resize/index.html76
-rw-r--r--misc/flot/examples/selection/index.html152
-rw-r--r--misc/flot/examples/series-errorbars/index.html150
-rw-r--r--misc/flot/examples/series-pie/index.html818
-rw-r--r--misc/flot/examples/series-toggle/index.html121
-rw-r--r--misc/flot/examples/series-types/index.html90
-rw-r--r--misc/flot/examples/shared/jquery-ui/jquery-ui.min.css6
-rw-r--r--misc/flot/examples/shared/jquery-ui/jquery-ui.min.js6
-rw-r--r--misc/flot/examples/stacking/index.html107
-rw-r--r--misc/flot/examples/symbols/index.html76
-rw-r--r--misc/flot/examples/threshold/index.html76
-rw-r--r--misc/flot/examples/tracking/index.html135
-rw-r--r--misc/flot/examples/visitors/index.html147
-rw-r--r--misc/flot/examples/zooming/index.html144
-rw-r--r--misc/flot/excanvas.js1428
-rw-r--r--misc/flot/excanvas.min.js1
-rw-r--r--misc/flot/jquery.colorhelpers.js180
-rw-r--r--misc/flot/jquery.colorhelpers.min.js1
-rw-r--r--misc/flot/jquery.flot.canvas.js345
-rw-r--r--misc/flot/jquery.flot.canvas.min.js7
-rw-r--r--misc/flot/jquery.flot.categories.js190
-rw-r--r--misc/flot/jquery.flot.categories.min.js7
-rw-r--r--misc/flot/jquery.flot.crosshair.js176
-rw-r--r--misc/flot/jquery.flot.crosshair.min.js7
-rw-r--r--misc/flot/jquery.flot.errorbars.js353
-rw-r--r--misc/flot/jquery.flot.errorbars.min.js7
-rw-r--r--misc/flot/jquery.flot.fillbetween.js226
-rw-r--r--misc/flot/jquery.flot.fillbetween.min.js7
-rw-r--r--misc/flot/jquery.flot.image.js241
-rw-r--r--misc/flot/jquery.flot.image.min.js7
-rw-r--r--misc/flot/jquery.flot.js3168
-rw-r--r--misc/flot/jquery.flot.min.js8
-rw-r--r--misc/flot/jquery.flot.navigate.js346
-rw-r--r--misc/flot/jquery.flot.navigate.min.js7
-rw-r--r--misc/flot/jquery.flot.pie.js820
-rw-r--r--misc/flot/jquery.flot.pie.min.js7
-rw-r--r--misc/flot/jquery.flot.resize.js59
-rw-r--r--misc/flot/jquery.flot.resize.min.js7
-rw-r--r--misc/flot/jquery.flot.selection.js360
-rw-r--r--misc/flot/jquery.flot.selection.min.js7
-rw-r--r--misc/flot/jquery.flot.stack.js188
-rw-r--r--misc/flot/jquery.flot.stack.min.js7
-rw-r--r--misc/flot/jquery.flot.symbol.js71
-rw-r--r--misc/flot/jquery.flot.symbol.min.js7
-rw-r--r--misc/flot/jquery.flot.threshold.js142
-rw-r--r--misc/flot/jquery.flot.threshold.min.js7
-rw-r--r--misc/flot/jquery.flot.time.js432
-rw-r--r--misc/flot/jquery.flot.time.min.js7
-rw-r--r--misc/flot/jquery.js9472
-rw-r--r--misc/flot/jquery.min.js5
-rw-r--r--misc/jquery/jquery-2.1.1.min.js4
-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
1318 files changed, 443824 insertions, 0 deletions
diff --git a/misc/flot/API.md b/misc/flot/API.md
new file mode 100644
index 0000000..e08b44c
--- /dev/null
+++ b/misc/flot/API.md
@@ -0,0 +1,1498 @@
+# Flot Reference #
+
+**Table of Contents**
+
+[Introduction](#introduction)
+| [Data Format](#data-format)
+| [Plot Options](#plot-options)
+| [Customizing the legend](#customizing-the-legend)
+| [Customizing the axes](#customizing-the-axes)
+| [Multiple axes](#multiple-axes)
+| [Time series data](#time-series-data)
+| [Customizing the data series](#customizing-the-data-series)
+| [Customizing the grid](#customizing-the-grid)
+| [Specifying gradients](#specifying-gradients)
+| [Plot Methods](#plot-methods)
+| [Hooks](#hooks)
+| [Plugins](#plugins)
+| [Version number](#version-number)
+
+---
+
+## Introduction ##
+
+Consider a call to the plot function:
+
+```js
+var plot = $.plot(placeholder, data, options)
+```
+
+The placeholder is a jQuery object or DOM element or jQuery expression
+that the plot will be put into. This placeholder needs to have its
+width and height set as explained in the [README](README.md) (go read that now if
+you haven't, it's short). The plot will modify some properties of the
+placeholder so it's recommended you simply pass in a div that you
+don't use for anything else. Make sure you check any fancy styling
+you apply to the div, e.g. background images have been reported to be a
+problem on IE 7.
+
+The plot function can also be used as a jQuery chainable property. This form
+naturally can't return the plot object directly, but you can still access it
+via the 'plot' data key, like this:
+
+```js
+var plot = $("#placeholder").plot(data, options).data("plot");
+```
+
+The format of the data is documented below, as is the available
+options. The plot object returned from the call has some methods you
+can call. These are documented separately below.
+
+Note that in general Flot gives no guarantees if you change any of the
+objects you pass in to the plot function or get out of it since
+they're not necessarily deep-copied.
+
+
+## Data Format ##
+
+The data is an array of data series:
+
+```js
+[ series1, series2, ... ]
+```
+
+A series can either be raw data or an object with properties. The raw
+data format is an array of points:
+
+```js
+[ [x1, y1], [x2, y2], ... ]
+```
+
+E.g.
+
+```js
+[ [1, 3], [2, 14.01], [3.5, 3.14] ]
+```
+
+Note that to simplify the internal logic in Flot both the x and y
+values must be numbers (even if specifying time series, see below for
+how to do this). This is a common problem because you might retrieve
+data from the database and serialize them directly to JSON without
+noticing the wrong type. If you're getting mysterious errors, double
+check that you're inputting numbers and not strings.
+
+If a null is specified as a point or if one of the coordinates is null
+or couldn't be converted to a number, the point is ignored when
+drawing. As a special case, a null value for lines is interpreted as a
+line segment end, i.e. the points before and after the null value are
+not connected.
+
+Lines and points take two coordinates. For filled lines and bars, you
+can specify a third coordinate which is the bottom of the filled
+area/bar (defaults to 0).
+
+The format of a single series object is as follows:
+
+```js
+{
+ color: color or number
+ data: rawdata
+ label: string
+ lines: specific lines options
+ bars: specific bars options
+ points: specific points options
+ xaxis: number
+ yaxis: number
+ clickable: boolean
+ hoverable: boolean
+ shadowSize: number
+ highlightColor: color or number
+}
+```
+
+You don't have to specify any of them except the data, the rest are
+options that will get default values. Typically you'd only specify
+label and data, like this:
+
+```js
+{
+ label: "y = 3",
+ data: [[0, 3], [10, 3]]
+}
+```
+
+The label is used for the legend, if you don't specify one, the series
+will not show up in the legend.
+
+If you don't specify color, the series will get a color from the
+auto-generated colors. The color is either a CSS color specification
+(like "rgb(255, 100, 123)") or an integer that specifies which of
+auto-generated colors to select, e.g. 0 will get color no. 0, etc.
+
+The latter is mostly useful if you let the user add and remove series,
+in which case you can hard-code the color index to prevent the colors
+from jumping around between the series.
+
+The "xaxis" and "yaxis" options specify which axis to use. The axes
+are numbered from 1 (default), so { yaxis: 2} means that the series
+should be plotted against the second y axis.
+
+"clickable" and "hoverable" can be set to false to disable
+interactivity for specific series if interactivity is turned on in
+the plot, see below.
+
+The rest of the options are all documented below as they are the same
+as the default options passed in via the options parameter in the plot
+commmand. When you specify them for a specific data series, they will
+override the default options for the plot for that data series.
+
+Here's a complete example of a simple data specification:
+
+```js
+[ { label: "Foo", data: [ [10, 1], [17, -14], [30, 5] ] },
+ { label: "Bar", data: [ [11, 13], [19, 11], [30, -7] ] }
+]
+```
+
+
+## Plot Options ##
+
+All options are completely optional. They are documented individually
+below, to change them you just specify them in an object, e.g.
+
+```js
+var options = {
+ series: {
+ lines: { show: true },
+ points: { show: true }
+ }
+};
+
+$.plot(placeholder, data, options);
+```
+
+
+## Customizing the legend ##
+
+```js
+legend: {
+ show: boolean
+ labelFormatter: null or (fn: string, series object -> string)
+ labelBoxBorderColor: color
+ noColumns: number
+ position: "ne" or "nw" or "se" or "sw"
+ margin: number of pixels or [x margin, y margin]
+ backgroundColor: null or color
+ backgroundOpacity: number between 0 and 1
+ container: null or jQuery object/DOM element/jQuery expression
+ sorted: null/false, true, "ascending", "descending", "reverse", or a comparator
+}
+```
+
+The legend is generated as a table with the data series labels and
+small label boxes with the color of the series. If you want to format
+the labels in some way, e.g. make them to links, you can pass in a
+function for "labelFormatter". Here's an example that makes them
+clickable:
+
+```js
+labelFormatter: function(label, series) {
+ // series is the series object for the label
+ return '<a href="#' + label + '">' + label + '</a>';
+}
+```
+
+To prevent a series from showing up in the legend, simply have the function
+return null.
+
+"noColumns" is the number of columns to divide the legend table into.
+"position" specifies the overall placement of the legend within the
+plot (top-right, top-left, etc.) and margin the distance to the plot
+edge (this can be either a number or an array of two numbers like [x,
+y]). "backgroundColor" and "backgroundOpacity" specifies the
+background. The default is a partly transparent auto-detected
+background.
+
+If you want the legend to appear somewhere else in the DOM, you can
+specify "container" as a jQuery object/expression to put the legend
+table into. The "position" and "margin" etc. options will then be
+ignored. Note that Flot will overwrite the contents of the container.
+
+Legend entries appear in the same order as their series by default. If "sorted"
+is "reverse" then they appear in the opposite order from their series. To sort
+them alphabetically, you can specify true, "ascending" or "descending", where
+true and "ascending" are equivalent.
+
+You can also provide your own comparator function that accepts two
+objects with "label" and "color" properties, and returns zero if they
+are equal, a positive value if the first is greater than the second,
+and a negative value if the first is less than the second.
+
+```js
+sorted: function(a, b) {
+ // sort alphabetically in ascending order
+ return a.label == b.label ? 0 : (
+ a.label > b.label ? 1 : -1
+ )
+}
+```
+
+
+## Customizing the axes ##
+
+```js
+xaxis, yaxis: {
+ show: null or true/false
+ position: "bottom" or "top" or "left" or "right"
+ mode: null or "time" ("time" requires jquery.flot.time.js plugin)
+ timezone: null, "browser" or timezone (only makes sense for mode: "time")
+
+ color: null or color spec
+ tickColor: null or color spec
+ font: null or font spec object
+
+ min: null or number
+ max: null or number
+ autoscaleMargin: null or number
+
+ transform: null or fn: number -> number
+ inverseTransform: null or fn: number -> number
+
+ ticks: null or number or ticks array or (fn: axis -> ticks array)
+ tickSize: number or array
+ minTickSize: number or array
+ tickFormatter: (fn: number, object -> string) or string
+ tickDecimals: null or number
+
+ labelWidth: null or number
+ labelHeight: null or number
+ reserveSpace: null or true
+
+ tickLength: null or number
+
+ alignTicksWithAxis: null or number
+}
+```
+
+All axes have the same kind of options. The following describes how to
+configure one axis, see below for what to do if you've got more than
+one x axis or y axis.
+
+If you don't set the "show" option (i.e. it is null), visibility is
+auto-detected, i.e. the axis will show up if there's data associated
+with it. You can override this by setting the "show" option to true or
+false.
+
+The "position" option specifies where the axis is placed, bottom or
+top for x axes, left or right for y axes. The "mode" option determines
+how the data is interpreted, the default of null means as decimal
+numbers. Use "time" for time series data; see the time series data
+section. The time plugin (jquery.flot.time.js) is required for time
+series support.
+
+The "color" option determines the color of the line and ticks for the axis, and
+defaults to the grid color with transparency. For more fine-grained control you
+can also set the color of the ticks separately with "tickColor".
+
+You can customize the font and color used to draw the axis tick labels with CSS
+or directly via the "font" option. When "font" is null - the default - each
+tick label is given the 'flot-tick-label' class. For compatibility with Flot
+0.7 and earlier the labels are also given the 'tickLabel' class, but this is
+deprecated and scheduled to be removed with the release of version 1.0.0.
+
+To enable more granular control over styles, labels are divided between a set
+of text containers, with each holding the labels for one axis. These containers
+are given the classes 'flot-[x|y]-axis', and 'flot-[x|y]#-axis', where '#' is
+the number of the axis when there are multiple axes. For example, the x-axis
+labels for a simple plot with only a single x-axis might look like this:
+
+```html
+<div class='flot-x-axis flot-x1-axis'>
+ <div class='flot-tick-label'>January 2013</div>
+ ...
+</div>
+```
+
+For direct control over label styles you can also provide "font" as an object
+with this format:
+
+```js
+{
+ size: 11,
+ lineHeight: 13,
+ style: "italic",
+ weight: "bold",
+ family: "sans-serif",
+ variant: "small-caps",
+ color: "#545454"
+}
+```
+
+The size and lineHeight must be expressed in pixels; CSS units such as 'em'
+or 'smaller' are not allowed.
+
+The options "min"/"max" are the precise minimum/maximum value on the
+scale. If you don't specify either of them, a value will automatically
+be chosen based on the minimum/maximum data values. Note that Flot
+always examines all the data values you feed to it, even if a
+restriction on another axis may make some of them invisible (this
+makes interactive use more stable).
+
+The "autoscaleMargin" is a bit esoteric: it's the fraction of margin
+that the scaling algorithm will add to avoid that the outermost points
+ends up on the grid border. Note that this margin is only applied when
+a min or max value is not explicitly set. If a margin is specified,
+the plot will furthermore extend the axis end-point to the nearest
+whole tick. The default value is "null" for the x axes and 0.02 for y
+axes which seems appropriate for most cases.
+
+"transform" and "inverseTransform" are callbacks you can put in to
+change the way the data is drawn. You can design a function to
+compress or expand certain parts of the axis non-linearly, e.g.
+suppress weekends or compress far away points with a logarithm or some
+other means. When Flot draws the plot, each value is first put through
+the transform function. Here's an example, the x axis can be turned
+into a natural logarithm axis with the following code:
+
+```js
+xaxis: {
+ transform: function (v) { return Math.log(v); },
+ inverseTransform: function (v) { return Math.exp(v); }
+}
+```
+
+Similarly, for reversing the y axis so the values appear in inverse
+order:
+
+```js
+yaxis: {
+ transform: function (v) { return -v; },
+ inverseTransform: function (v) { return -v; }
+}
+```
+
+Note that for finding extrema, Flot assumes that the transform
+function does not reorder values (it should be monotone).
+
+The inverseTransform is simply the inverse of the transform function
+(so v == inverseTransform(transform(v)) for all relevant v). It is
+required for converting from canvas coordinates to data coordinates,
+e.g. for a mouse interaction where a certain pixel is clicked. If you
+don't use any interactive features of Flot, you may not need it.
+
+
+The rest of the options deal with the ticks.
+
+If you don't specify any ticks, a tick generator algorithm will make
+some for you. The algorithm has two passes. It first estimates how
+many ticks would be reasonable and uses this number to compute a nice
+round tick interval size. Then it generates the ticks.
+
+You can specify how many ticks the algorithm aims for by setting
+"ticks" to a number. The algorithm always tries to generate reasonably
+round tick values so even if you ask for three ticks, you might get
+five if that fits better with the rounding. If you don't want any
+ticks at all, set "ticks" to 0 or an empty array.
+
+Another option is to skip the rounding part and directly set the tick
+interval size with "tickSize". If you set it to 2, you'll get ticks at
+2, 4, 6, etc. Alternatively, you can specify that you just don't want
+ticks at a size less than a specific tick size with "minTickSize".
+Note that for time series, the format is an array like [2, "month"],
+see the next section.
+
+If you want to completely override the tick algorithm, you can specify
+an array for "ticks", either like this:
+
+```js
+ticks: [0, 1.2, 2.4]
+```
+
+Or like this where the labels are also customized:
+
+```js
+ticks: [[0, "zero"], [1.2, "one mark"], [2.4, "two marks"]]
+```
+
+You can mix the two if you like.
+
+For extra flexibility you can specify a function as the "ticks"
+parameter. The function will be called with an object with the axis
+min and max and should return a ticks array. Here's a simplistic tick
+generator that spits out intervals of pi, suitable for use on the x
+axis for trigonometric functions:
+
+```js
+function piTickGenerator(axis) {
+ var res = [], i = Math.floor(axis.min / Math.PI);
+ do {
+ var v = i * Math.PI;
+ res.push([v, i + "\u03c0"]);
+ ++i;
+ } while (v < axis.max);
+ return res;
+}
+```
+
+You can control how the ticks look like with "tickDecimals", the
+number of decimals to display (default is auto-detected).
+
+Alternatively, for ultimate control over how ticks are formatted you can
+provide a function to "tickFormatter". The function is passed two
+parameters, the tick value and an axis object with information, and
+should return a string. The default formatter looks like this:
+
+```js
+function formatter(val, axis) {
+ return val.toFixed(axis.tickDecimals);
+}
+```
+
+The axis object has "min" and "max" with the range of the axis,
+"tickDecimals" with the number of decimals to round the value to and
+"tickSize" with the size of the interval between ticks as calculated
+by the automatic axis scaling algorithm (or specified by you). Here's
+an example of a custom formatter:
+
+```js
+function suffixFormatter(val, axis) {
+ if (val > 1000000)
+ return (val / 1000000).toFixed(axis.tickDecimals) + " MB";
+ else if (val > 1000)
+ return (val / 1000).toFixed(axis.tickDecimals) + " kB";
+ else
+ return val.toFixed(axis.tickDecimals) + " B";
+}
+```
+
+"labelWidth" and "labelHeight" specifies a fixed size of the tick
+labels in pixels. They're useful in case you need to align several
+plots. "reserveSpace" means that even if an axis isn't shown, Flot
+should reserve space for it - it is useful in combination with
+labelWidth and labelHeight for aligning multi-axis charts.
+
+"tickLength" is the length of the tick lines in pixels. By default, the
+innermost axes will have ticks that extend all across the plot, while
+any extra axes use small ticks. A value of null means use the default,
+while a number means small ticks of that length - set it to 0 to hide
+the lines completely.
+
+If you set "alignTicksWithAxis" to the number of another axis, e.g.
+alignTicksWithAxis: 1, Flot will ensure that the autogenerated ticks
+of this axis are aligned with the ticks of the other axis. This may
+improve the looks, e.g. if you have one y axis to the left and one to
+the right, because the grid lines will then match the ticks in both
+ends. The trade-off is that the forced ticks won't necessarily be at
+natural places.
+
+
+## Multiple axes ##
+
+If you need more than one x axis or y axis, you need to specify for
+each data series which axis they are to use, as described under the
+format of the data series, e.g. { data: [...], yaxis: 2 } specifies
+that a series should be plotted against the second y axis.
+
+To actually configure that axis, you can't use the xaxis/yaxis options
+directly - instead there are two arrays in the options:
+
+```js
+xaxes: []
+yaxes: []
+```
+
+Here's an example of configuring a single x axis and two y axes (we
+can leave options of the first y axis empty as the defaults are fine):
+
+```js
+{
+ xaxes: [ { position: "top" } ],
+ yaxes: [ { }, { position: "right", min: 20 } ]
+}
+```
+
+The arrays get their default values from the xaxis/yaxis settings, so
+say you want to have all y axes start at zero, you can simply specify
+yaxis: { min: 0 } instead of adding a min parameter to all the axes.
+
+Generally, the various interfaces in Flot dealing with data points
+either accept an xaxis/yaxis parameter to specify which axis number to
+use (starting from 1), or lets you specify the coordinate directly as
+x2/x3/... or x2axis/x3axis/... instead of "x" or "xaxis".
+
+
+## Time series data ##
+
+Please note that it is now required to include the time plugin,
+jquery.flot.time.js, for time series support.
+
+Time series are a bit more difficult than scalar data because
+calendars don't follow a simple base 10 system. For many cases, Flot
+abstracts most of this away, but it can still be a bit difficult to
+get the data into Flot. So we'll first discuss the data format.
+
+The time series support in Flot is based on Javascript timestamps,
+i.e. everywhere a time value is expected or handed over, a Javascript
+timestamp number is used. This is a number, not a Date object. A
+Javascript timestamp is the number of milliseconds since January 1,
+1970 00:00:00 UTC. This is almost the same as Unix timestamps, except it's
+in milliseconds, so remember to multiply by 1000!
+
+You can see a timestamp like this
+
+```js
+alert((new Date()).getTime())
+```
+
+There are different schools of thought when it comes to display of
+timestamps. Many will want the timestamps to be displayed according to
+a certain time zone, usually the time zone in which the data has been
+produced. Some want the localized experience, where the timestamps are
+displayed according to the local time of the visitor. Flot supports
+both. Optionally you can include a third-party library to get
+additional timezone support.
+
+Default behavior is that Flot always displays timestamps according to
+UTC. The reason being that the core Javascript Date object does not
+support other fixed time zones. Often your data is at another time
+zone, so it may take a little bit of tweaking to work around this
+limitation.
+
+The easiest way to think about it is to pretend that the data
+production time zone is UTC, even if it isn't. So if you have a
+datapoint at 2002-02-20 08:00, you can generate a timestamp for eight
+o'clock UTC even if it really happened eight o'clock UTC+0200.
+
+In PHP you can get an appropriate timestamp with:
+
+```php
+strtotime("2002-02-20 UTC") * 1000
+```
+
+In Python you can get it with something like:
+
+```python
+calendar.timegm(datetime_object.timetuple()) * 1000
+```
+In Ruby you can get it using the `#to_i` method on the
+[`Time`](http://apidock.com/ruby/Time/to_i) object. If you're using the
+`active_support` gem (default for Ruby on Rails applications) `#to_i` is also
+available on the `DateTime` and `ActiveSupport::TimeWithZone` objects. You
+simply need to multiply the result by 1000:
+
+```ruby
+Time.now.to_i * 1000 # => 1383582043000
+# ActiveSupport examples:
+DateTime.now.to_i * 1000 # => 1383582043000
+ActiveSupport::TimeZone.new('Asia/Shanghai').now.to_i * 1000
+# => 1383582043000
+```
+
+In .NET you can get it with something like:
+
+```aspx
+public static int GetJavascriptTimestamp(System.DateTime input)
+{
+ System.TimeSpan span = new System.TimeSpan(System.DateTime.Parse("1/1/1970").Ticks);
+ System.DateTime time = input.Subtract(span);
+ return (long)(time.Ticks / 10000);
+}
+```
+
+Javascript also has some support for parsing date strings, so it is
+possible to generate the timestamps manually client-side.
+
+If you've already got the real UTC timestamp, it's too late to use the
+pretend trick described above. But you can fix up the timestamps by
+adding the time zone offset, e.g. for UTC+0200 you would add 2 hours
+to the UTC timestamp you got. Then it'll look right on the plot. Most
+programming environments have some means of getting the timezone
+offset for a specific date (note that you need to get the offset for
+each individual timestamp to account for daylight savings).
+
+The alternative with core Javascript is to interpret the timestamps
+according to the time zone that the visitor is in, which means that
+the ticks will shift with the time zone and daylight savings of each
+visitor. This behavior is enabled by setting the axis option
+"timezone" to the value "browser".
+
+If you need more time zone functionality than this, there is still
+another option. If you include the "timezone-js" library
+<https://github.com/mde/timezone-js> in the page and set axis.timezone
+to a value recognized by said library, Flot will use timezone-js to
+interpret the timestamps according to that time zone.
+
+Once you've gotten the timestamps into the data and specified "time"
+as the axis mode, Flot will automatically generate relevant ticks and
+format them. As always, you can tweak the ticks via the "ticks" option
+- just remember that the values should be timestamps (numbers), not
+Date objects.
+
+Tick generation and formatting can also be controlled separately
+through the following axis options:
+
+```js
+minTickSize: array
+timeformat: null or format string
+monthNames: null or array of size 12 of strings
+dayNames: null or array of size 7 of strings
+twelveHourClock: boolean
+```
+
+Here "timeformat" is a format string to use. You might use it like
+this:
+
+```js
+xaxis: {
+ mode: "time",
+ timeformat: "%Y/%m/%d"
+}
+```
+
+This will result in tick labels like "2000/12/24". A subset of the
+standard strftime specifiers are supported (plus the nonstandard %q):
+
+```js
+%a: weekday name (customizable)
+%b: month name (customizable)
+%d: day of month, zero-padded (01-31)
+%e: day of month, space-padded ( 1-31)
+%H: hours, 24-hour time, zero-padded (00-23)
+%I: hours, 12-hour time, zero-padded (01-12)
+%m: month, zero-padded (01-12)
+%M: minutes, zero-padded (00-59)
+%q: quarter (1-4)
+%S: seconds, zero-padded (00-59)
+%y: year (two digits)
+%Y: year (four digits)
+%p: am/pm
+%P: AM/PM (uppercase version of %p)
+%w: weekday as number (0-6, 0 being Sunday)
+```
+
+Flot 0.8 switched from %h to the standard %H hours specifier. The %h specifier
+is still available, for backwards-compatibility, but is deprecated and
+scheduled to be removed permanently with the release of version 1.0.
+
+You can customize the month names with the "monthNames" option. For
+instance, for Danish you might specify:
+
+```js
+monthNames: ["jan", "feb", "mar", "apr", "maj", "jun", "jul", "aug", "sep", "okt", "nov", "dec"]
+```
+
+Similarly you can customize the weekday names with the "dayNames"
+option. An example in French:
+
+```js
+dayNames: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"]
+```
+
+If you set "twelveHourClock" to true, the autogenerated timestamps
+will use 12 hour AM/PM timestamps instead of 24 hour. This only
+applies if you have not set "timeformat". Use the "%I" and "%p" or
+"%P" options if you want to build your own format string with 12-hour
+times.
+
+If the Date object has a strftime property (and it is a function), it
+will be used instead of the built-in formatter. Thus you can include
+a strftime library such as http://hacks.bluesmoon.info/strftime/ for
+more powerful date/time formatting.
+
+If everything else fails, you can control the formatting by specifying
+a custom tick formatter function as usual. Here's a simple example
+which will format December 24 as 24/12:
+
+```js
+tickFormatter: function (val, axis) {
+ var d = new Date(val);
+ return d.getUTCDate() + "/" + (d.getUTCMonth() + 1);
+}
+```
+
+Note that for the time mode "tickSize" and "minTickSize" are a bit
+special in that they are arrays on the form "[value, unit]" where unit
+is one of "second", "minute", "hour", "day", "month" and "year". So
+you can specify
+
+```js
+minTickSize: [1, "month"]
+```
+
+to get a tick interval size of at least 1 month and correspondingly,
+if axis.tickSize is [2, "day"] in the tick formatter, the ticks have
+been produced with two days in-between.
+
+
+## Customizing the data series ##
+
+```js
+series: {
+ lines, points, bars: {
+ show: boolean
+ lineWidth: number
+ fill: boolean or number
+ fillColor: null or color/gradient
+ }
+
+ lines, bars: {
+ zero: boolean
+ }
+
+ points: {
+ radius: number
+ symbol: "circle" or function
+ }
+
+ bars: {
+ barWidth: number
+ align: "left", "right" or "center"
+ horizontal: boolean
+ }
+
+ lines: {
+ steps: boolean
+ }
+
+ shadowSize: number
+ highlightColor: color or number
+}
+
+colors: [ color1, color2, ... ]
+```
+
+The options inside "series: {}" are copied to each of the series. So
+you can specify that all series should have bars by putting it in the
+global options, or override it for individual series by specifying
+bars in a particular the series object in the array of data.
+
+The most important options are "lines", "points" and "bars" that
+specify whether and how lines, points and bars should be shown for
+each data series. In case you don't specify anything at all, Flot will
+default to showing lines (you can turn this off with
+lines: { show: false }). You can specify the various types
+independently of each other, and Flot will happily draw each of them
+in turn (this is probably only useful for lines and points), e.g.
+
+```js
+var options = {
+ series: {
+ lines: { show: true, fill: true, fillColor: "rgba(255, 255, 255, 0.8)" },
+ points: { show: true, fill: false }
+ }
+};
+```
+
+"lineWidth" is the thickness of the line or outline in pixels. You can
+set it to 0 to prevent a line or outline from being drawn; this will
+also hide the shadow.
+
+"fill" is whether the shape should be filled. For lines, this produces
+area graphs. You can use "fillColor" to specify the color of the fill.
+If "fillColor" evaluates to false (default for everything except
+points which are filled with white), the fill color is auto-set to the
+color of the data series. You can adjust the opacity of the fill by
+setting fill to a number between 0 (fully transparent) and 1 (fully
+opaque).
+
+For bars, fillColor can be a gradient, see the gradient documentation
+below. "barWidth" is the width of the bars in units of the x axis (or
+the y axis if "horizontal" is true), contrary to most other measures
+that are specified in pixels. For instance, for time series the unit
+is milliseconds so 24 * 60 * 60 * 1000 produces bars with the width of
+a day. "align" specifies whether a bar should be left-aligned
+(default), right-aligned or centered on top of the value it represents.
+When "horizontal" is on, the bars are drawn horizontally, i.e. from the
+y axis instead of the x axis; note that the bar end points are still
+defined in the same way so you'll probably want to swap the
+coordinates if you've been plotting vertical bars first.
+
+Area and bar charts normally start from zero, regardless of the data's range.
+This is because they convey information through size, and starting from a
+different value would distort their meaning. In cases where the fill is purely
+for decorative purposes, however, "zero" allows you to override this behavior.
+It defaults to true for filled lines and bars; setting it to false tells the
+series to use the same automatic scaling as an un-filled line.
+
+For lines, "steps" specifies whether two adjacent data points are
+connected with a straight (possibly diagonal) line or with first a
+horizontal and then a vertical line. Note that this transforms the
+data by adding extra points.
+
+For points, you can specify the radius and the symbol. The only
+built-in symbol type is circles, for other types you can use a plugin
+or define them yourself by specifying a callback:
+
+```js
+function cross(ctx, x, y, radius, shadow) {
+ var size = radius * Math.sqrt(Math.PI) / 2;
+ ctx.moveTo(x - size, y - size);
+ ctx.lineTo(x + size, y + size);
+ ctx.moveTo(x - size, y + size);
+ ctx.lineTo(x + size, y - size);
+}
+```
+
+The parameters are the drawing context, x and y coordinates of the
+center of the point, a radius which corresponds to what the circle
+would have used and whether the call is to draw a shadow (due to
+limited canvas support, shadows are currently faked through extra
+draws). It's good practice to ensure that the area covered by the
+symbol is the same as for the circle with the given radius, this
+ensures that all symbols have approximately the same visual weight.
+
+"shadowSize" is the default size of shadows in pixels. Set it to 0 to
+remove shadows.
+
+"highlightColor" is the default color of the translucent overlay used
+to highlight the series when the mouse hovers over it.
+
+The "colors" array specifies a default color theme to get colors for
+the data series from. You can specify as many colors as you like, like
+this:
+
+```js
+colors: ["#d18b2c", "#dba255", "#919733"]
+```
+
+If there are more data series than colors, Flot will try to generate
+extra colors by lightening and darkening colors in the theme.
+
+
+## Customizing the grid ##
+
+```js
+grid: {
+ show: boolean
+ aboveData: boolean
+ color: color
+ backgroundColor: color/gradient or null
+ margin: number or margin object
+ labelMargin: number
+ axisMargin: number
+ markings: array of markings or (fn: axes -> array of markings)
+ borderWidth: number or object with "top", "right", "bottom" and "left" properties with different widths
+ borderColor: color or null or object with "top", "right", "bottom" and "left" properties with different colors
+ minBorderMargin: number or null
+ clickable: boolean
+ hoverable: boolean
+ autoHighlight: boolean
+ mouseActiveRadius: number
+}
+
+interaction: {
+ redrawOverlayInterval: number or -1
+}
+```
+
+The grid is the thing with the axes and a number of ticks. Many of the
+things in the grid are configured under the individual axes, but not
+all. "color" is the color of the grid itself whereas "backgroundColor"
+specifies the background color inside the grid area, here null means
+that the background is transparent. You can also set a gradient, see
+the gradient documentation below.
+
+You can turn off the whole grid including tick labels by setting
+"show" to false. "aboveData" determines whether the grid is drawn
+above the data or below (below is default).
+
+"margin" is the space in pixels between the canvas edge and the grid,
+which can be either a number or an object with individual margins for
+each side, in the form:
+
+```js
+margin: {
+ top: top margin in pixels
+ left: left margin in pixels
+ bottom: bottom margin in pixels
+ right: right margin in pixels
+}
+```
+
+"labelMargin" is the space in pixels between tick labels and axis
+line, and "axisMargin" is the space in pixels between axes when there
+are two next to each other.
+
+"borderWidth" is the width of the border around the plot. Set it to 0
+to disable the border. Set it to an object with "top", "right",
+"bottom" and "left" properties to use different widths. You can
+also set "borderColor" if you want the border to have a different color
+than the grid lines. Set it to an object with "top", "right", "bottom"
+and "left" properties to use different colors. "minBorderMargin" controls
+the default minimum margin around the border - it's used to make sure
+that points aren't accidentally clipped by the canvas edge so by default
+the value is computed from the point radius.
+
+"markings" is used to draw simple lines and rectangular areas in the
+background of the plot. You can either specify an array of ranges on
+the form { xaxis: { from, to }, yaxis: { from, to } } (with multiple
+axes, you can specify coordinates for other axes instead, e.g. as
+x2axis/x3axis/...) or with a function that returns such an array given
+the axes for the plot in an object as the first parameter.
+
+You can set the color of markings by specifying "color" in the ranges
+object. Here's an example array:
+
+```js
+markings: [ { xaxis: { from: 0, to: 2 }, yaxis: { from: 10, to: 10 }, color: "#bb0000" }, ... ]
+```
+
+If you leave out one of the values, that value is assumed to go to the
+border of the plot. So for example if you only specify { xaxis: {
+from: 0, to: 2 } } it means an area that extends from the top to the
+bottom of the plot in the x range 0-2.
+
+A line is drawn if from and to are the same, e.g.
+
+```js
+markings: [ { yaxis: { from: 1, to: 1 } }, ... ]
+```
+
+would draw a line parallel to the x axis at y = 1. You can control the
+line width with "lineWidth" in the range object.
+
+An example function that makes vertical stripes might look like this:
+
+```js
+markings: function (axes) {
+ var markings = [];
+ for (var x = Math.floor(axes.xaxis.min); x < axes.xaxis.max; x += 2)
+ markings.push({ xaxis: { from: x, to: x + 1 } });
+ return markings;
+}
+```
+
+If you set "clickable" to true, the plot will listen for click events
+on the plot area and fire a "plotclick" event on the placeholder with
+a position and a nearby data item object as parameters. The coordinates
+are available both in the unit of the axes (not in pixels) and in
+global screen coordinates.
+
+Likewise, if you set "hoverable" to true, the plot will listen for
+mouse move events on the plot area and fire a "plothover" event with
+the same parameters as the "plotclick" event. If "autoHighlight" is
+true (the default), nearby data items are highlighted automatically.
+If needed, you can disable highlighting and control it yourself with
+the highlight/unhighlight plot methods described elsewhere.
+
+You can use "plotclick" and "plothover" events like this:
+
+```js
+$.plot($("#placeholder"), [ d ], { grid: { clickable: true } });
+
+$("#placeholder").bind("plotclick", function (event, pos, item) {
+ alert("You clicked at " + pos.x + ", " + pos.y);
+ // axis coordinates for other axes, if present, are in pos.x2, pos.x3, ...
+ // if you need global screen coordinates, they are pos.pageX, pos.pageY
+
+ if (item) {
+ highlight(item.series, item.datapoint);
+ alert("You clicked a point!");
+ }
+});
+```
+
+The item object in this example is either null or a nearby object on the form:
+
+```js
+item: {
+ datapoint: the point, e.g. [0, 2]
+ dataIndex: the index of the point in the data array
+ series: the series object
+ seriesIndex: the index of the series
+ pageX, pageY: the global screen coordinates of the point
+}
+```
+
+For instance, if you have specified the data like this
+
+```js
+$.plot($("#placeholder"), [ { label: "Foo", data: [[0, 10], [7, 3]] } ], ...);
+```
+
+and the mouse is near the point (7, 3), "datapoint" is [7, 3],
+"dataIndex" will be 1, "series" is a normalized series object with
+among other things the "Foo" label in series.label and the color in
+series.color, and "seriesIndex" is 0. Note that plugins and options
+that transform the data can shift the indexes from what you specified
+in the original data array.
+
+If you use the above events to update some other information and want
+to clear out that info in case the mouse goes away, you'll probably
+also need to listen to "mouseout" events on the placeholder div.
+
+"mouseActiveRadius" specifies how far the mouse can be from an item
+and still activate it. If there are two or more points within this
+radius, Flot chooses the closest item. For bars, the top-most bar
+(from the latest specified data series) is chosen.
+
+If you want to disable interactivity for a specific data series, you
+can set "hoverable" and "clickable" to false in the options for that
+series, like this:
+
+```js
+{ data: [...], label: "Foo", clickable: false }
+```
+
+"redrawOverlayInterval" specifies the maximum time to delay a redraw
+of interactive things (this works as a rate limiting device). The
+default is capped to 60 frames per second. You can set it to -1 to
+disable the rate limiting.
+
+
+## Specifying gradients ##
+
+A gradient is specified like this:
+
+```js
+{ colors: [ color1, color2, ... ] }
+```
+
+For instance, you might specify a background on the grid going from
+black to gray like this:
+
+```js
+grid: {
+ backgroundColor: { colors: ["#000", "#999"] }
+}
+```
+
+For the series you can specify the gradient as an object that
+specifies the scaling of the brightness and the opacity of the series
+color, e.g.
+
+```js
+{ colors: [{ opacity: 0.8 }, { brightness: 0.6, opacity: 0.8 } ] }
+```
+
+where the first color simply has its alpha scaled, whereas the second
+is also darkened. For instance, for bars the following makes the bars
+gradually disappear, without outline:
+
+```js
+bars: {
+ show: true,
+ lineWidth: 0,
+ fill: true,
+ fillColor: { colors: [ { opacity: 0.8 }, { opacity: 0.1 } ] }
+}
+```
+
+Flot currently only supports vertical gradients drawn from top to
+bottom because that's what works with IE.
+
+
+## Plot Methods ##
+
+The Plot object returned from the plot function has some methods you
+can call:
+
+ - highlight(series, datapoint)
+
+ Highlight a specific datapoint in the data series. You can either
+ specify the actual objects, e.g. if you got them from a
+ "plotclick" event, or you can specify the indices, e.g.
+ highlight(1, 3) to highlight the fourth point in the second series
+ (remember, zero-based indexing).
+
+ - unhighlight(series, datapoint) or unhighlight()
+
+ Remove the highlighting of the point, same parameters as
+ highlight.
+
+ If you call unhighlight with no parameters, e.g. as
+ plot.unhighlight(), all current highlights are removed.
+
+ - setData(data)
+
+ You can use this to reset the data used. Note that axis scaling,
+ ticks, legend etc. will not be recomputed (use setupGrid() to do
+ that). You'll probably want to call draw() afterwards.
+
+ You can use this function to speed up redrawing a small plot if
+ you know that the axes won't change. Put in the new data with
+ setData(newdata), call draw(), and you're good to go. Note that
+ for large datasets, almost all the time is consumed in draw()
+ plotting the data so in this case don't bother.
+
+ - setupGrid()
+
+ Recalculate and set axis scaling, ticks, legend etc.
+
+ Note that because of the drawing model of the canvas, this
+ function will immediately redraw (actually reinsert in the DOM)
+ the labels and the legend, but not the actual tick lines because
+ they're drawn on the canvas. You need to call draw() to get the
+ canvas redrawn.
+
+ - draw()
+
+ Redraws the plot canvas.
+
+ - triggerRedrawOverlay()
+
+ Schedules an update of an overlay canvas used for drawing
+ interactive things like a selection and point highlights. This
+ is mostly useful for writing plugins. The redraw doesn't happen
+ immediately, instead a timer is set to catch multiple successive
+ redraws (e.g. from a mousemove). You can get to the overlay by
+ setting up a drawOverlay hook.
+
+ - width()/height()
+
+ Gets the width and height of the plotting area inside the grid.
+ This is smaller than the canvas or placeholder dimensions as some
+ extra space is needed (e.g. for labels).
+
+ - offset()
+
+ Returns the offset of the plotting area inside the grid relative
+ to the document, useful for instance for calculating mouse
+ positions (event.pageX/Y minus this offset is the pixel position
+ inside the plot).
+
+ - pointOffset({ x: xpos, y: ypos })
+
+ Returns the calculated offset of the data point at (x, y) in data
+ space within the placeholder div. If you are working with multiple
+ axes, you can specify the x and y axis references, e.g.
+
+ ```js
+ o = pointOffset({ x: xpos, y: ypos, xaxis: 2, yaxis: 3 })
+ // o.left and o.top now contains the offset within the div
+ ````
+
+ - resize()
+
+ Tells Flot to resize the drawing canvas to the size of the
+ placeholder. You need to run setupGrid() and draw() afterwards as
+ canvas resizing is a destructive operation. This is used
+ internally by the resize plugin.
+
+ - shutdown()
+
+ Cleans up any event handlers Flot has currently registered. This
+ is used internally.
+
+There are also some members that let you peek inside the internal
+workings of Flot which is useful in some cases. Note that if you change
+something in the objects returned, you're changing the objects used by
+Flot to keep track of its state, so be careful.
+
+ - getData()
+
+ Returns an array of the data series currently used in normalized
+ form with missing settings filled in according to the global
+ options. So for instance to find out what color Flot has assigned
+ to the data series, you could do this:
+
+ ```js
+ var series = plot.getData();
+ for (var i = 0; i < series.length; ++i)
+ alert(series[i].color);
+ ```
+
+ A notable other interesting field besides color is datapoints
+ which has a field "points" with the normalized data points in a
+ flat array (the field "pointsize" is the increment in the flat
+ array to get to the next point so for a dataset consisting only of
+ (x,y) pairs it would be 2).
+
+ - getAxes()
+
+ Gets an object with the axes. The axes are returned as the
+ attributes of the object, so for instance getAxes().xaxis is the
+ x axis.
+
+ Various things are stuffed inside an axis object, e.g. you could
+ use getAxes().xaxis.ticks to find out what the ticks are for the
+ xaxis. Two other useful attributes are p2c and c2p, functions for
+ transforming from data point space to the canvas plot space and
+ back. Both returns values that are offset with the plot offset.
+ Check the Flot source code for the complete set of attributes (or
+ output an axis with console.log() and inspect it).
+
+ With multiple axes, the extra axes are returned as x2axis, x3axis,
+ etc., e.g. getAxes().y2axis is the second y axis. You can check
+ y2axis.used to see whether the axis is associated with any data
+ points and y2axis.show to see if it is currently shown.
+
+ - getPlaceholder()
+
+ Returns placeholder that the plot was put into. This can be useful
+ for plugins for adding DOM elements or firing events.
+
+ - getCanvas()
+
+ Returns the canvas used for drawing in case you need to hack on it
+ yourself. You'll probably need to get the plot offset too.
+
+ - getPlotOffset()
+
+ Gets the offset that the grid has within the canvas as an object
+ with distances from the canvas edges as "left", "right", "top",
+ "bottom". I.e., if you draw a circle on the canvas with the center
+ placed at (left, top), its center will be at the top-most, left
+ corner of the grid.
+
+ - getOptions()
+
+ Gets the options for the plot, normalized, with default values
+ filled in. You get a reference to actual values used by Flot, so
+ if you modify the values in here, Flot will use the new values.
+ If you change something, you probably have to call draw() or
+ setupGrid() or triggerRedrawOverlay() to see the change.
+
+
+## Hooks ##
+
+In addition to the public methods, the Plot object also has some hooks
+that can be used to modify the plotting process. You can install a
+callback function at various points in the process, the function then
+gets access to the internal data structures in Flot.
+
+Here's an overview of the phases Flot goes through:
+
+ 1. Plugin initialization, parsing options
+
+ 2. Constructing the canvases used for drawing
+
+ 3. Set data: parsing data specification, calculating colors,
+ copying raw data points into internal format,
+ normalizing them, finding max/min for axis auto-scaling
+
+ 4. Grid setup: calculating axis spacing, ticks, inserting tick
+ labels, the legend
+
+ 5. Draw: drawing the grid, drawing each of the series in turn
+
+ 6. Setting up event handling for interactive features
+
+ 7. Responding to events, if any
+
+ 8. Shutdown: this mostly happens in case a plot is overwritten
+
+Each hook is simply a function which is put in the appropriate array.
+You can add them through the "hooks" option, and they are also available
+after the plot is constructed as the "hooks" attribute on the returned
+plot object, e.g.
+
+```js
+ // define a simple draw hook
+ function hellohook(plot, canvascontext) { alert("hello!"); };
+
+ // pass it in, in an array since we might want to specify several
+ var plot = $.plot(placeholder, data, { hooks: { draw: [hellohook] } });
+
+ // we can now find it again in plot.hooks.draw[0] unless a plugin
+ // has added other hooks
+```
+
+The available hooks are described below. All hook callbacks get the
+plot object as first parameter. You can find some examples of defined
+hooks in the plugins bundled with Flot.
+
+ - processOptions [phase 1]
+
+ ```function(plot, options)```
+
+ Called after Flot has parsed and merged options. Useful in the
+ instance where customizations beyond simple merging of default
+ values is needed. A plugin might use it to detect that it has been
+ enabled and then turn on or off other options.
+
+
+ - processRawData [phase 3]
+
+ ```function(plot, series, data, datapoints)```
+
+ Called before Flot copies and normalizes the raw data for the given
+ series. If the function fills in datapoints.points with normalized
+ points and sets datapoints.pointsize to the size of the points,
+ Flot will skip the copying/normalization step for this series.
+
+ In any case, you might be interested in setting datapoints.format,
+ an array of objects for specifying how a point is normalized and
+ how it interferes with axis scaling. It accepts the following options:
+
+ ```js
+ {
+ x, y: boolean,
+ number: boolean,
+ required: boolean,
+ defaultValue: value,
+ autoscale: boolean
+ }
+ ```
+
+ "x" and "y" specify whether the value is plotted against the x or y axis,
+ and is currently used only to calculate axis min-max ranges. The default
+ format array, for example, looks like this:
+
+ ```js
+ [
+ { x: true, number: true, required: true },
+ { y: true, number: true, required: true }
+ ]
+ ```
+
+ This indicates that a point, i.e. [0, 25], consists of two values, with the
+ first being plotted on the x axis and the second on the y axis.
+
+ If "number" is true, then the value must be numeric, and is set to null if
+ it cannot be converted to a number.
+
+ "defaultValue" provides a fallback in case the original value is null. This
+ is for instance handy for bars, where one can omit the third coordinate
+ (the bottom of the bar), which then defaults to zero.
+
+ If "required" is true, then the value must exist (be non-null) for the
+ point as a whole to be valid. If no value is provided, then the entire
+ point is cleared out with nulls, turning it into a gap in the series.
+
+ "autoscale" determines whether the value is considered when calculating an
+ automatic min-max range for the axes that the value is plotted against.
+
+ - processDatapoints [phase 3]
+
+ ```function(plot, series, datapoints)```
+
+ Called after normalization of the given series but before finding
+ min/max of the data points. This hook is useful for implementing data
+ transformations. "datapoints" contains the normalized data points in
+ a flat array as datapoints.points with the size of a single point
+ given in datapoints.pointsize. Here's a simple transform that
+ multiplies all y coordinates by 2:
+
+ ```js
+ function multiply(plot, series, datapoints) {
+ var points = datapoints.points, ps = datapoints.pointsize;
+ for (var i = 0; i < points.length; i += ps)
+ points[i + 1] *= 2;
+ }
+ ```
+
+ Note that you must leave datapoints in a good condition as Flot
+ doesn't check it or do any normalization on it afterwards.
+
+ - processOffset [phase 4]
+
+ ```function(plot, offset)```
+
+ Called after Flot has initialized the plot's offset, but before it
+ draws any axes or plot elements. This hook is useful for customizing
+ the margins between the grid and the edge of the canvas. "offset" is
+ an object with attributes "top", "bottom", "left" and "right",
+ corresponding to the margins on the four sides of the plot.
+
+ - drawBackground [phase 5]
+
+ ```function(plot, canvascontext)```
+
+ Called before all other drawing operations. Used to draw backgrounds
+ or other custom elements before the plot or axes have been drawn.
+
+ - drawSeries [phase 5]
+
+ ```function(plot, canvascontext, series)```
+
+ Hook for custom drawing of a single series. Called just before the
+ standard drawing routine has been called in the loop that draws
+ each series.
+
+ - draw [phase 5]
+
+ ```function(plot, canvascontext)```
+
+ Hook for drawing on the canvas. Called after the grid is drawn
+ (unless it's disabled or grid.aboveData is set) and the series have
+ been plotted (in case any points, lines or bars have been turned
+ on). For examples of how to draw things, look at the source code.
+
+ - bindEvents [phase 6]
+
+ ```function(plot, eventHolder)```
+
+ Called after Flot has setup its event handlers. Should set any
+ necessary event handlers on eventHolder, a jQuery object with the
+ canvas, e.g.
+
+ ```js
+ function (plot, eventHolder) {
+ eventHolder.mousedown(function (e) {
+ alert("You pressed the mouse at " + e.pageX + " " + e.pageY);
+ });
+ }
+ ```
+
+ Interesting events include click, mousemove, mouseup/down. You can
+ use all jQuery events. Usually, the event handlers will update the
+ state by drawing something (add a drawOverlay hook and call
+ triggerRedrawOverlay) or firing an externally visible event for
+ user code. See the crosshair plugin for an example.
+
+ Currently, eventHolder actually contains both the static canvas
+ used for the plot itself and the overlay canvas used for
+ interactive features because some versions of IE get the stacking
+ order wrong. The hook only gets one event, though (either for the
+ overlay or for the static canvas).
+
+ Note that custom plot events generated by Flot are not generated on
+ eventHolder, but on the div placeholder supplied as the first
+ argument to the plot call. You can get that with
+ plot.getPlaceholder() - that's probably also the one you should use
+ if you need to fire a custom event.
+
+ - drawOverlay [phase 7]
+
+ ```function (plot, canvascontext)```
+
+ The drawOverlay hook is used for interactive things that need a
+ canvas to draw on. The model currently used by Flot works the way
+ that an extra overlay canvas is positioned on top of the static
+ canvas. This overlay is cleared and then completely redrawn
+ whenever something interesting happens. This hook is called when
+ the overlay canvas is to be redrawn.
+
+ "canvascontext" is the 2D context of the overlay canvas. You can
+ use this to draw things. You'll most likely need some of the
+ metrics computed by Flot, e.g. plot.width()/plot.height(). See the
+ crosshair plugin for an example.
+
+ - shutdown [phase 8]
+
+ ```function (plot, eventHolder)```
+
+ Run when plot.shutdown() is called, which usually only happens in
+ case a plot is overwritten by a new plot. If you're writing a
+ plugin that adds extra DOM elements or event handlers, you should
+ add a callback to clean up after you. Take a look at the section in
+ the [PLUGINS](PLUGINS.md) document for more info.
+
+
+## Plugins ##
+
+Plugins extend the functionality of Flot. To use a plugin, simply
+include its Javascript file after Flot in the HTML page.
+
+If you're worried about download size/latency, you can concatenate all
+the plugins you use, and Flot itself for that matter, into one big file
+(make sure you get the order right), then optionally run it through a
+Javascript minifier such as YUI Compressor.
+
+Here's a brief explanation of how the plugin plumbings work:
+
+Each plugin registers itself in the global array $.plot.plugins. When
+you make a new plot object with $.plot, Flot goes through this array
+calling the "init" function of each plugin and merging default options
+from the "option" attribute of the plugin. The init function gets a
+reference to the plot object created and uses this to register hooks
+and add new public methods if needed.
+
+See the [PLUGINS](PLUGINS.md) document for details on how to write a plugin. As the
+above description hints, it's actually pretty easy.
+
+
+## Version number ##
+
+The version number of Flot is available in ```$.plot.version```.
diff --git a/misc/flot/CONTRIBUTING.md b/misc/flot/CONTRIBUTING.md
new file mode 100644
index 0000000..3e6e43a
--- /dev/null
+++ b/misc/flot/CONTRIBUTING.md
@@ -0,0 +1,98 @@
+## Contributing to Flot ##
+
+We welcome all contributions, but following these guidelines results in less
+work for us, and a faster and better response.
+
+### Issues ###
+
+Issues are not a way to ask general questions about Flot. If you see unexpected
+behavior but are not 100% certain that it is a bug, please try posting to the
+[forum](http://groups.google.com/group/flot-graphs) first, and confirm that
+what you see is really a Flot problem before creating a new issue for it. When
+reporting a bug, please include a working demonstration of the problem, if
+possible, or at least a clear description of the options you're using and the
+environment (browser and version, jQuery version, other libraries) that you're
+running under.
+
+If you have suggestions for new features, or changes to existing ones, we'd
+love to hear them! Please submit each suggestion as a separate new issue.
+
+If you would like to work on an existing issue, please make sure it is not
+already assigned to someone else. If an issue is assigned to someone, that
+person has already started working on it. So, pick unassigned issues to prevent
+duplicated effort.
+
+### Pull Requests ###
+
+To make merging as easy as possible, please keep these rules in mind:
+
+ 1. Submit new features or architectural changes to the *&lt;version&gt;-work*
+ branch for the next major release. Submit bug fixes to the master branch.
+
+ 2. Divide larger changes into a series of small, logical commits with
+ descriptive messages.
+
+ 3. Rebase, if necessary, before submitting your pull request, to reduce the
+ work we need to do to merge it.
+
+ 4. Format your code according to the style guidelines below.
+
+### Flot Style Guidelines ###
+
+Flot follows the [jQuery Core Style Guidelines](http://docs.jquery.com/JQuery_Core_Style_Guidelines),
+with the following updates and exceptions:
+
+#### Spacing ####
+
+Use four-space indents, no tabs. Do not add horizontal space around parameter
+lists, loop definitions, or array/object indices. For example:
+
+```js
+ for ( var i = 0; i < data.length; i++ ) { // This block is wrong!
+ if ( data[ i ] > 1 ) {
+ data[ i ] = 2;
+ }
+ }
+
+ for (var i = 0; i < data.length; i++) { // This block is correct!
+ if (data[i] > 1) {
+ data[i] = 2;
+ }
+ }
+```
+
+#### Comments ####
+
+Use [jsDoc](http://usejsdoc.org) comments for all file and function headers.
+Use // for all inline and block comments, regardless of length.
+
+All // comment blocks should have an empty line above *and* below them. For
+example:
+
+```js
+ var a = 5;
+
+ // We're going to loop here
+ // TODO: Make this loop faster, better, stronger!
+
+ for (var x = 0; x < 10; x++) {}
+```
+
+#### Wrapping ####
+
+Block comments should be wrapped at 80 characters.
+
+Code should attempt to wrap at 80 characters, but may run longer if wrapping
+would hurt readability more than having to scroll horizontally. This is a
+judgement call made on a situational basis.
+
+Statements containing complex logic should not be wrapped arbitrarily if they
+do not exceed 80 characters. For example:
+
+```js
+ if (a == 1 && // This block is wrong!
+ b == 2 &&
+ c == 3) {}
+
+ if (a == 1 && b == 2 && c == 3) {} // This block is correct!
+```
diff --git a/misc/flot/FAQ.md b/misc/flot/FAQ.md
new file mode 100644
index 0000000..9131e04
--- /dev/null
+++ b/misc/flot/FAQ.md
@@ -0,0 +1,75 @@
+## Frequently asked questions ##
+
+#### How much data can Flot cope with? ####
+
+Flot will happily draw everything you send to it so the answer
+depends on the browser. The excanvas emulation used for IE (built with
+VML) makes IE by far the slowest browser so be sure to test with that
+if IE users are in your target group (for large plots in IE, you can
+also check out Flashcanvas which may be faster).
+
+1000 points is not a problem, but as soon as you start having more
+points than the pixel width, you should probably start thinking about
+downsampling/aggregation as this is near the resolution limit of the
+chart anyway. If you downsample server-side, you also save bandwidth.
+
+
+#### Flot isn't working when I'm using JSON data as source! ####
+
+Actually, Flot loves JSON data, you just got the format wrong.
+Double check that you're not inputting strings instead of numbers,
+like [["0", "-2.13"], ["5", "4.3"]]. This is most common mistake, and
+the error might not show up immediately because Javascript can do some
+conversion automatically.
+
+
+#### Can I export the graph? ####
+
+You can grab the image rendered by the canvas element used by Flot
+as a PNG or JPEG (remember to set a background). Note that it won't
+include anything not drawn in the canvas (such as the legend). And it
+doesn't work with excanvas which uses VML, but you could try
+Flashcanvas.
+
+
+#### The bars are all tiny in time mode? ####
+
+It's not really possible to determine the bar width automatically.
+So you have to set the width with the barWidth option which is NOT in
+pixels, but in the units of the x axis (or the y axis for horizontal
+bars). For time mode that's milliseconds so the default value of 1
+makes the bars 1 millisecond wide.
+
+
+#### Can I use Flot with libraries like Mootools or Prototype? ####
+
+Yes, Flot supports it out of the box and it's easy! Just use jQuery
+instead of $, e.g. call jQuery.plot instead of $.plot and use
+jQuery(something) instead of $(something). As a convenience, you can
+put in a DOM element for the graph placeholder where the examples and
+the API documentation are using jQuery objects.
+
+Depending on how you include jQuery, you may have to add one line of
+code to prevent jQuery from overwriting functions from the other
+libraries, see the documentation in jQuery ("Using jQuery with other
+libraries") for details.
+
+
+#### Flot doesn't work with [insert name of Javascript UI framework]! ####
+
+Flot is using standard HTML to make charts. If this is not working,
+it's probably because the framework you're using is doing something
+weird with the DOM or with the CSS that is interfering with Flot.
+
+A common problem is that there's display:none on a container until the
+user does something. Many tab widgets work this way, and there's
+nothing wrong with it - you just can't call Flot inside a display:none
+container as explained in the README so you need to hold off the Flot
+call until the container is actually displayed (or use
+visibility:hidden instead of display:none or move the container
+off-screen).
+
+If you find there's a specific thing we can do to Flot to help, feel
+free to submit a bug report. Otherwise, you're welcome to ask for help
+on the forum/mailing list, but please don't submit a bug report to
+Flot.
diff --git a/misc/flot/LICENSE.txt b/misc/flot/LICENSE.txt
new file mode 100644
index 0000000..719da06
--- /dev/null
+++ b/misc/flot/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright (c) 2007-2014 IOLA and Ole Laursen
+
+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/flot/Makefile b/misc/flot/Makefile
new file mode 100644
index 0000000..2e070d0
--- /dev/null
+++ b/misc/flot/Makefile
@@ -0,0 +1,12 @@
+# Makefile for generating minified files
+
+.PHONY: all
+
+# we cheat and process all .js files instead of an exhaustive list
+all: $(patsubst %.js,%.min.js,$(filter-out %.min.js,$(wildcard *.js)))
+
+%.min.js: %.js
+ yui-compressor $< -o $@
+
+test:
+ ./node_modules/.bin/jshint *jquery.flot.js
diff --git a/misc/flot/NEWS.md b/misc/flot/NEWS.md
new file mode 100644
index 0000000..ad0303d
--- /dev/null
+++ b/misc/flot/NEWS.md
@@ -0,0 +1,1026 @@
+## Flot 0.8.3 ##
+
+### Changes ###
+
+- Updated example code to avoid encouraging unnecessary re-plots.
+ (patch by soenter, pull request #1221)
+
+### Bug fixes ###
+
+ - Added a work-around to disable the allocation of extra space for first and
+ last axis ticks, allowing plots to span the full width of their container.
+ A proper solution for this bug will be implemented in the 0.9 release.
+ (reported by Josh Pigford and andig, issue #1212, pull request #1290)
+
+ - Fixed a regression introduced in 0.8.1, where the last tick label would
+ sometimes wrap rather than extending the plot's offset to create space.
+ (reported by Elite Gamer, issue #1283)
+
+ - Fixed a regression introduced in 0.8.2, where the resize plugin would use
+ unexpectedly high amounts of CPU even when idle.
+ (reported by tommie, issue #1277, pull request #1289)
+
+ - Fixed the selection example to work with jQuery 1.9.x and later.
+ (reported by EGLadona and dmfalke, issue #1250, pull request #1285)
+
+ - Added a detach shim to fix support for jQuery versions earlier than 1.4.x.
+ (reported by ngavard, issue #1240, pull request #1286)
+
+ - Fixed a rare 'Uncaught TypeError' when using the resize plugin in IE 7/8.
+ (reported by tleish, issue #1265, pull request #1289)
+
+ - Fixed zoom constraints to apply only in the direction of the zoom.
+ (patch by Neil Katin, issue #1204, pull request #1205)
+
+ - Markings lines are no longer blurry when drawn on pixel boundaries.
+ (reported by btccointicker and Rouillard, issue #1210)
+
+ - Don't discard original pie data-series values when combining slices.
+ (patch by Phil Tsarik, pull request #1238)
+
+ - Fixed broken auto-scale behavior when using deprecated [x|y]2axis options.
+ (reported by jorese, issue #1228, pull request #1284)
+
+ - Exposed the dateGenerator function on the plot object, as it used to be
+ before time-mode was moved into a separate plugin.
+ (patch by Paolo Valleri, pull request #1028)
+
+
+## Flot 0.8.2 ##
+
+### Changes ###
+
+ - Added a plot.destroy method as a way to free memory when emptying the plot
+ placeholder and then re-using it for some other purpose.
+ (patch by Thodoris Greasidis, issue #1129, pull request #1130)
+
+ - Added a table of contents and PLUGINS link to the API documentation.
+ (patches by Brian Peiris, pull requests #1064 and #1127)
+
+ - Added Ruby code examples for time conversion.
+ (patch by Mike Połtyn, pull request #1182)
+
+ - Minor improvements to API.md and README.md.
+ (patches by Patrik Ragnarsson, pull requests #1085 and #1086)
+
+ - Updated inlined jQuery Resize to the latest version to fix errors.
+ (reported by Matthew Sabol and sloker, issues #997 ad #1081)
+
+### Bug fixes ###
+
+ - Fixed an unexpected change in behavior that resulted in duplicate tick
+ labels when using a plugin, like flot-tickrotor, that overrode tick labels.
+ (patch by Mark Cote, pull request #1091)
+
+ - Fixed a regression from 0.7 where axis labels were given the wrong width,
+ causing them to overlap at certain scales and ignore the labelWidth option.
+ (patch by Benjamin Gram, pull request #1177)
+
+ - Fixed a bug where the second axis in an xaxes/yaxes array incorrectly had
+ its 'innermost' property set to false or undefined, even if it was on the
+ other side of the plot from the first axis. This resulted in the axis bar
+ being visible when it shouldn't have been, which was especially obvious
+ when the grid had a left/right border width of zero.
+ (reported by Teq1, fix researched by ryleyb, issue #1056)
+
+ - Fixed an error when using a placeholder that has no font-size property.
+ (patch by Craig Oldford, pull request #1135)
+
+ - Fixed a regression from 0.7 where nulls at the end of a series were ignored
+ for purposes of determing the range of the x-axis.
+ (reported by Munsifali Rashid, issue #1095)
+
+ - If a font size is provided, base the default lineHeight on that size rather
+ that the font size of the plot placeholder, which may be very different.
+ (reported by Daniel Hoffmann Bernardes, issue #1131, pull request #1199)
+
+ - Fix broken highlighting for right-aligned bars.
+ (reported by BeWiBu and Mihai Stanciu, issues #975 and #1093, with further
+ assistance by Eric Byers, pull request #1120)
+
+ - Prevent white circles from sometimes showing up inside of pie charts.
+ (reported by Pierre Dubois and Jack Klink, issues #1128 and #1073)
+
+ - Label formatting no longer breaks when a page contains multiple pie charts.
+ (reported by Brend Wanders, issue #1055)
+
+ - When using multiple axes on opposite sides of the plot, the innermost axis
+ coming later in the list no longer has its bar drawn incorrectly.
+ (reported by ryleyb, issue #1056)
+
+ - When removing series labels and redrawing the plot, the legend now updates
+ correctly even when using an external container.
+ (patch by Luis Silva, issue #1159, pull request #1160)
+
+ - The pie plugin no longer ignores the value of the left offset option.
+ (reported by melanker, issue #1136)
+
+ - Fixed a regression from 0.7, where extra padding was added unnecessarily to
+ sides of the plot where there was no last tick label.
+ (reported by sknob001, issue #1048, pull request #1200)
+
+ - Fixed incorrect tooltip behavior in the interacting example.
+ (patch by cleroux, issue #686, pull request #1074)
+
+ - Fixed an error in CSS color extraction with elements outside the DOM.
+ (patch by execjosh, pull request #1084)
+
+ - Fixed :not selector error when using jQuery without Sizzle.
+ (patch by Anthony Ryan, pull request #1180)
+
+ - Worked around a browser issue that caused bars to appear un-filled.
+ (reported by irbian, issue #915)
+
+## Flot 0.8.1 ##
+
+### Bug fixes ###
+
+ - Fixed a regression in the time plugin, introduced in 0.8, that caused dates
+ to align to the minute rather than to the highest appropriate unit. This
+ caused many x-axes in 0.8 to have different ticks than they did in 0.7.
+ (reported by Tom Sheppard, patch by Daniel Shapiro, issue #1017, pull
+ request #1023)
+
+ - Fixed a regression in text rendering, introduced in 0.8, that caused axis
+ labels with the same text as another label on the same axis to disappear.
+ More generally, it's again possible to have the same text in two locations.
+ (issue #1032)
+
+ - Fixed a regression in text rendering, introduced in 0.8, where axis labels
+ were no longer assigned an explicit width, and their text could not wrap.
+ (reported by sabregreen, issue #1019)
+
+ - Fixed a regression in the pie plugin, introduced in 0.8, that prevented it
+ from accepting data in the format '[[x, y]]'.
+ (patch by Nicolas Morel, pull request #1024)
+
+ - The 'zero' series option and 'autoscale' format option are no longer
+ ignored when the series contains a null value.
+ (reported by Daniel Shapiro, issue #1033)
+
+ - Avoid triggering the time-mode plugin exception when there are zero series.
+ (reported by Daniel Rothig, patch by Mark Raymond, issue #1016)
+
+ - When a custom color palette has fewer colors than the default palette, Flot
+ no longer fills out the colors with the remainder of the default.
+ (patch by goorpy, issue #1031, pull request #1034)
+
+ - Fixed missing update for bar highlights after a zoom or other redraw.
+ (reported by Paolo Valleri, issue #1030)
+
+ - Fixed compatibility with jQuery versions earlier than 1.7.
+ (patch by Lee Willis, issue #1027, pull request #1027)
+
+ - The mouse wheel no longer scrolls the page when using the navigate plugin.
+ (patch by vird, pull request #1020)
+
+ - Fixed missing semicolons in the core library.
+ (reported by Michal Zglinski)
+
+
+## Flot 0.8.0 ##
+
+### API changes ###
+
+Support for time series has been moved into a plugin, jquery.flot.time.js.
+This results in less code if time series are not used. The functionality
+remains the same (plus timezone support, as described below); however, the
+plugin must be included if axis.mode is set to "time".
+
+When the axis mode is "time", the axis option "timezone" can be set to null,
+"browser", or a particular timezone (e.g. "America/New_York") to control how
+the dates are displayed. If null, the dates are displayed as UTC. If
+"browser", the dates are displayed in the time zone of the user's browser.
+
+Date/time formatting has changed and now follows a proper subset of the
+standard strftime specifiers, plus one nonstandard specifier for quarters.
+Additionally, if a strftime function is found in the Date object's prototype,
+it will be used instead of the built-in formatter.
+
+Axis tick labels now use the class 'flot-tick-label' instead of 'tickLabel'.
+The text containers for each axis now use the classes 'flot-[x|y]-axis' and
+'flot-[x|y]#-axis' instead of '[x|y]Axis' and '[x|y]#Axis'. For compatibility
+with Flot 0.7 and earlier text will continue to use the old classes as well,
+but they are considered deprecated and will be removed in a future version.
+
+In previous versions the axis 'color' option was used to set the color of tick
+marks and their label text. It now controls the color of the axis line, which
+previously could not be changed separately, and continues to act as a default
+for the tick-mark color. The color of tick label text is now set either by
+overriding the 'flot-tick-label' CSS rule or via the axis 'font' option.
+
+A new plugin, jquery.flot.canvas.js, allows axis tick labels to be rendered
+directly to the canvas, rather than using HTML elements. This feature can be
+toggled with a simple option, making it easy to create interactive plots in the
+browser using HTML, then re-render them to canvas for export as an image.
+
+The plugin tries to remain as faithful as possible to the original HTML render,
+and goes so far as to automatically extract styles from CSS, to avoid having to
+provide a separate set of styles when rendering to canvas. Due to limitations
+of the canvas text API, the plugin cannot reproduce certain features, including
+HTML markup embedded in labels, and advanced text styles such as 'em' units.
+
+The plugin requires support for canvas text, which may not be present in some
+older browsers, even if they support the canvas tag itself. To use the plugin
+with these browsers try using a shim such as canvas-text or FlashCanvas.
+
+The base and overlay canvas are now using the CSS classes "flot-base" and
+"flot-overlay" to prevent accidental clashes (issue 540).
+
+### Changes ###
+
+ - Addition of nonstandard %q specifier to date/time formatting. (patch
+ by risicle, issue 49)
+
+ - Date/time formatting follows proper subset of strftime specifiers, and
+ support added for Date.prototype.strftime, if found. (patch by Mark Cote,
+ issues 419 and 558)
+
+ - Fixed display of year ticks. (patch by Mark Cote, issue 195)
+
+ - Support for time series moved to plugin. (patch by Mark Cote)
+
+ - Display time series in different time zones. (patch by Knut Forkalsrud,
+ issue 141)
+
+ - Added a canvas plugin to enable rendering axis tick labels to the canvas.
+ (sponsored by YCharts.com, implementation by Ole Laursen and David Schnur)
+
+ - Support for setting the interval between redraws of the overlay canvas with
+ redrawOverlayInterval. (suggested in issue 185)
+
+ - Support for multiple thresholds in thresholds plugin. (patch by Arnaud
+ Bellec, issue 523)
+
+ - Support for plotting categories/textual data directly with new categories
+ plugin.
+
+ - Tick generators now get the whole axis rather than just min/max.
+
+ - Added processOffset and drawBackground hooks. (suggested in issue 639)
+
+ - Added a grid "margin" option to set the space between the canvas edge and
+ the grid.
+
+ - Prevent the pie example page from generating single-slice pies. (patch by
+ Shane Reustle)
+
+ - In addition to "left" and "center", bars now recognize "right" as an
+ alignment option. (patch by Michael Mayer, issue 520)
+
+ - Switched from toFixed to a much faster default tickFormatter. (patch by
+ Clemens Stolle)
+
+ - Added to a more helpful error when using a time-mode axis without including
+ the flot.time plugin. (patch by Yael Elmatad)
+
+ - Added a legend "sorted" option to control sorting of legend entries
+ independent of their series order. (patch by Tom Cleaveland)
+
+ - Added a series "highlightColor" option to control the color of the
+ translucent overlay that identifies the dataset when the mouse hovers over
+ it. (patch by Eric Wendelin and Nate Abele, issues 168 and 299)
+
+ - Added a plugin jquery.flot.errorbars, with an accompanying example, that
+ adds the ability to plot error bars, commonly used in many kinds of
+ statistical data visualizations. (patch by Rui Pereira, issue 215)
+
+ - The legend now omits entries whose labelFormatter returns null. (patch by
+ Tom Cleaveland, Christopher Lambert, and Simon Strandgaard)
+
+ - Added support for high pixel density (retina) displays, resulting in much
+ crisper charts on such devices. (patch by Olivier Guerriat, additional
+ fixes by Julien Thomas, maimairel, and Lau Bech Lauritzen)
+
+ - Added the ability to control pie shadow position and alpha via a new pie
+ 'shadow' option. (patch by Julien Thomas, pull request #78)
+
+ - Added the ability to set width and color for individual sides of the grid.
+ (patch by Ara Anjargolian, additional fixes by Karl Swedberg, pull requests #855
+ and #880)
+
+ - The selection plugin's getSelection now returns null when the selection
+ has been cleared. (patch by Nick Campbell, pull request #852)
+
+ - Added a new option called 'zero' to bars and filled lines series, to control
+ whether the y-axis minimum is scaled to fit the data or set to zero.
+ (patch by David Schnur, issues #316, #529, and #856, pull request #911)
+
+ - The plot function is now also a jQuery chainable property.
+ (patch by David Schnur, issues #734 and #816, pull request #953)
+
+ - When only a single pie slice is beneath the combine threshold it is no longer
+ replaced by an 'other' slice. (suggested by Devin Bayer, issue #638)
+
+ - Added lineJoin and minSize options to the selection plugin to control the
+ corner style and minimum size of the selection, respectively.
+ (patch by Ruth Linehan, pull request #963)
+
+### Bug fixes ###
+
+ - Fix problem with null values and pie plugin. (patch by gcruxifix,
+ issue 500)
+
+ - Fix problem with threshold plugin and bars. (based on patch by
+ kaarlenkaski, issue 348)
+
+ - Fix axis box calculations so the boxes include the outermost part of the
+ labels too.
+
+ - Fix problem with event clicking and hovering in IE 8 by updating Excanvas
+ and removing previous work-around. (test case by Ara Anjargolian)
+
+ - Fix issues with blurry 1px border when some measures aren't integer.
+ (reported by Ara Anjargolian)
+
+ - Fix bug with formats in the data processor. (reported by Peter Hull,
+ issue 534)
+
+ - Prevent i from being declared global in extractRange. (reported by
+ Alexander Obukhov, issue 627)
+
+ - Throw errors in a more cross-browser-compatible manner. (patch by
+ Eddie Kay)
+
+ - Prevent pie slice outlines from being drawn when the stroke width is zero.
+ (reported by Chris Minett, issue 585)
+
+ - Updated the navigate plugin's inline copy of jquery.mousewheel to fix
+ Webkit zoom problems. (reported by Hau Nguyen, issue 685)
+
+ - Axis labels no longer appear as decimals rather than integers in certain
+ cases. (patch by Clemens Stolle, issue 541)
+
+ - Automatic color generation no longer produces only whites and blacks when
+ there are many series. (patch by David Schnur and Tom Cleaveland)
+
+ - Fixed an error when custom tick labels weren't provided as strings. (patch
+ by Shad Downey)
+
+ - Prevented the local insertSteps and fmt variables from becoming global.
+ (first reported by Marc Bennewitz and Szymon Barglowski, patch by Nick
+ Campbell, issues #825 and #831, pull request #851)
+
+ - Prevented several threshold plugin variables from becoming global. (patch
+ by Lasse Dahl Ebert)
+
+ - Fixed various jQuery 1.8 compatibility issues. (issues #814 and #819,
+ pull request #877)
+
+ - Pie charts with a slice equal to or approaching 100% of the pie no longer
+ appear invisible. (patch by David Schnur, issues #444, #658, #726, #824
+ and #850, pull request #879)
+
+ - Prevented several local variables from becoming global. (patch by aaa707)
+
+ - Ensure that the overlay and primary canvases remain aligned. (issue #670,
+ pull request #901)
+
+ - Added support for jQuery 1.9 by removing and replacing uses of $.browser.
+ (analysis and patch by Anthony Ryan, pull request #905)
+
+ - Pie charts no longer disappear when redrawn during a resize or update.
+ (reported by Julien Bec, issue #656, pull request #910)
+
+ - Avoided floating-point precision errors when calculating pie percentages.
+ (patch by James Ward, pull request #918)
+
+ - Fixed compatibility with jQuery 1.2.6, which has no 'mouseleave' shortcut.
+ (reported by Bevan, original pull request #920, replaced by direct patch)
+
+ - Fixed sub-pixel rendering issues with crosshair and selection lines.
+ (patches by alanayoub and Daniel Shapiro, pull requests #17 and #925)
+
+ - Fixed rendering issues when using the threshold plugin with several series.
+ (patch by Ivan Novikov, pull request #934)
+
+ - Pie charts no longer disappear when redrawn after calling setData().
+ (reported by zengge1984 and pareeohnos, issues #810 and #945)
+
+ - Added a work-around for the problem where points with a lineWidth of zero
+ still showed up with a visible line. (reported by SalvoSav, issue #842,
+ patch by Jamie Hamel-Smith, pull request #937)
+
+ - Pie charts now accept values in string form, like other plot types.
+ (reported by laerdal.no, issue #534)
+
+ - Avoid rounding errors in the threshold plugin.
+ (reported by jerikojerk, issue #895)
+
+ - Fixed an error when using the navigate plugin with jQuery 1.9.x or later.
+ (reported by Paolo Valleri, issue #964)
+
+ - Fixed inconsistencies between the highlight and unhighlight functions.
+ (reported by djamshed, issue #987)
+
+ - Fixed recalculation of tickSize and tickDecimals on calls to setupGrid.
+ (patch by thecountofzero, pull request #861, issues #860, #1000)
+
+
+## Flot 0.7 ##
+
+### API changes ###
+
+Multiple axes support. Code using dual axes should be changed from using
+x2axis/y2axis in the options to using an array (although backwards-
+compatibility hooks are in place). For instance,
+
+```js
+{
+ xaxis: { ... }, x2axis: { ... },
+ yaxis: { ... }, y2axis: { ... }
+}
+```
+
+becomes
+
+```js
+{
+ xaxes: [ { ... }, { ... } ],
+ yaxes: [ { ... }, { ... } ]
+}
+```
+
+Note that if you're just using one axis, continue to use the xaxis/yaxis
+directly (it now sets the default settings for the arrays). Plugins touching
+the axes must be ported to take the extra axes into account, check the source
+to see some examples.
+
+A related change is that the visibility of axes is now auto-detected. So if
+you were relying on an axis to show up even without any data in the chart, you
+now need to set the axis "show" option explicitly.
+
+"tickColor" on the grid options is now deprecated in favour of a corresponding
+option on the axes, so:
+
+```js
+{ grid: { tickColor: "#000" }}
+```
+
+becomes
+
+```js
+{ xaxis: { tickColor: "#000"}, yaxis: { tickColor: "#000"} }
+```
+
+But if you just configure a base color Flot will now autogenerate a tick color
+by adding transparency. Backwards-compatibility hooks are in place.
+
+Final note: now that IE 9 is coming out with canvas support, you may want to
+adapt the excanvas include to skip loading it in IE 9 (the examples have been
+adapted thanks to Ryley Breiddal). An alternative to excanvas using Flash has
+also surfaced, if your graphs are slow in IE, you may want to give it a spin:
+
+ http://code.google.com/p/flashcanvas/
+
+### Changes ###
+
+ - Support for specifying a bottom for each point for line charts when filling
+ them, this means that an arbitrary bottom can be used instead of just the x
+ axis. (based on patches patiently provided by Roman V. Prikhodchenko)
+
+ - New fillbetween plugin that can compute a bottom for a series from another
+ series, useful for filling areas between lines.
+
+ See new example percentiles.html for a use case.
+
+ - More predictable handling of gaps for the stacking plugin, now all
+ undefined ranges are skipped.
+
+ - Stacking plugin can stack horizontal bar charts.
+
+ - Navigate plugin now redraws the plot while panning instead of only after
+ the fact. (raised by lastthemy, issue 235)
+
+ Can be disabled by setting the pan.frameRate option to null.
+
+ - Date formatter now accepts %0m and %0d to get a zero-padded month or day.
+ (issue raised by Maximillian Dornseif)
+
+ - Revamped internals to support an unlimited number of axes, not just dual.
+ (sponsored by Flight Data Services, www.flightdataservices.com)
+
+ - New setting on axes, "tickLength", to control the size of ticks or turn
+ them off without turning off the labels.
+
+ - Axis labels are now put in container divs with classes, for instance labels
+ in the x axes can be reached via ".xAxis .tickLabel".
+
+ - Support for setting the color of an axis. (sponsored by Flight Data
+ Services, www.flightdataservices.com)
+
+ - Tick color is now auto-generated as the base color with some transparency,
+ unless you override it.
+
+ - Support for aligning ticks in the axes with "alignTicksWithAxis" to ensure
+ that they appear next to each other rather than in between, at the expense
+ of possibly awkward tick steps. (sponsored by Flight Data Services,
+ www.flightdataservices.com)
+
+ - Support for customizing the point type through a callback when plotting
+ points and new symbol plugin with some predefined point types. (sponsored
+ by Utility Data Corporation)
+
+ - Resize plugin for automatically redrawing when the placeholder changes
+ size, e.g. on window resizes. (sponsored by Novus Partners)
+
+ A resize() method has been added to plot object facilitate this.
+
+ - Support Infinity/-Infinity for plotting asymptotes by hacking it into
+ +/-Number.MAX_VALUE. (reported by rabaea.mircea)
+
+ - Support for restricting navigate plugin to not pan/zoom an axis. (based on
+ patch by kkaefer)
+
+ - Support for providing the drag cursor for the navigate plugin as an option.
+ (based on patch by Kelly T. Moore)
+
+ - Options for controlling whether an axis is shown or not (suggestion by Timo
+ Tuominen) and whether to reserve space for it even if it isn't shown.
+
+ - New attribute $.plot.version with the Flot version as a string.
+
+ - The version comment is now included in the minified jquery.flot.min.js.
+
+ - New options.grid.minBorderMargin for adjusting the minimum margin provided
+ around the border (based on patch by corani, issue 188).
+
+ - Refactor replot behaviour so Flot tries to reuse the existing canvas,
+ adding shutdown() methods to the plot. (based on patch by Ryley Breiddal,
+ issue 269)
+
+ This prevents a memory leak in Chrome and hopefully makes replotting faster
+ for those who are using $.plot instead of .setData()/.draw(). Also update
+ jQuery to 1.5.1 to prevent IE leaks fixed in jQuery.
+
+ - New real-time line chart example.
+
+ - New hooks: drawSeries, shutdown.
+
+### Bug fixes ###
+
+ - Fixed problem with findNearbyItem and bars on top of each other. (reported
+ by ragingchikn, issue 242)
+
+ - Fixed problem with ticks and the border. (based on patch from
+ ultimatehustler69, issue 236)
+
+ - Fixed problem with plugins adding options to the series objects.
+
+ - Fixed a problem introduced in 0.6 with specifying a gradient with:
+
+ ```{brightness: x, opacity: y }```
+
+ - Don't use $.browser.msie, check for getContext on the created canvas element
+ instead and try to use excanvas if it's not found.
+
+ Fixes IE 9 compatibility.
+
+ - highlight(s, index) was looking up the point in the original s.data instead
+ of in the computed datapoints array, which breaks with plugins that modify
+ the datapoints, such as the stacking plugin. (reported by curlypaul924,
+ issue 316)
+
+ - More robust handling of axis from data passed in from getData(). (reported)
+ by Morgan)
+
+ - Fixed problem with turning off bar outline. (fix by Jordi Castells,
+ issue 253)
+
+ - Check the selection passed into setSelection in the selection
+ plugin, to guard against errors when synchronizing plots (fix by Lau
+ Bech Lauritzen).
+
+ - Fix bug in crosshair code with mouseout resetting the crosshair even
+ if it is locked (fix by Lau Bech Lauritzen and Banko Adam).
+
+ - Fix bug with points plotting using line width from lines rather than
+ points.
+
+ - Fix bug with passing non-array 0 data (for plugins that don't expect
+ arrays, patch by vpapp1).
+
+ - Fix errors in JSON in examples so they work with jQuery 1.4.2
+ (fix reported by honestbleeps, issue 357).
+
+ - Fix bug with tooltip in interacting.html, this makes the tooltip
+ much smoother (fix by bdkahn). Fix related bug inside highlighting
+ handler in Flot.
+
+ - Use closure trick to make inline colorhelpers plugin respect
+ jQuery.noConflict(true), renaming the global jQuery object (reported
+ by Nick Stielau).
+
+ - Listen for mouseleave events and fire a plothover event with empty
+ item when it occurs to drop highlights when the mouse leaves the
+ plot (reported by by outspirit).
+
+ - Fix bug with using aboveData with a background (reported by
+ amitayd).
+
+ - Fix possible excanvas leak (report and suggested fix by tom9729).
+
+ - Fix bug with backwards compatibility for shadowSize = 0 (report and
+ suggested fix by aspinak).
+
+ - Adapt examples to skip loading excanvas (fix by Ryley Breiddal).
+
+ - Fix bug that prevent a simple f(x) = -x transform from working
+ correctly (fix by Mike, issue 263).
+
+ - Fix bug in restoring cursor in navigate plugin (reported by Matteo
+ Gattanini, issue 395).
+
+ - Fix bug in picking items when transform/inverseTransform is in use
+ (reported by Ofri Raviv, and patches and analysis by Jan and Tom
+ Paton, issue 334 and 467).
+
+ - Fix problem with unaligned ticks and hover/click events caused by
+ padding on the placeholder by hardcoding the placeholder padding to
+ 0 (reported by adityadineshsaxena, Matt Sommer, Daniel Atos and some
+ other people, issue 301).
+
+ - Update colorhelpers plugin to avoid dying when trying to parse an
+ invalid string (reported by cadavor, issue 483).
+
+
+
+## Flot 0.6 ##
+
+### API changes ###
+
+Selection support has been moved to a plugin. Thus if you're passing
+selection: { mode: something }, you MUST include the file
+jquery.flot.selection.js after jquery.flot.js. This reduces the size of
+base Flot and makes it easier to customize the selection as well as
+improving code clarity. The change is based on a patch from andershol.
+
+In the global options specified in the $.plot command, "lines", "points",
+"bars" and "shadowSize" have been moved to a sub-object called "series":
+
+```js
+$.plot(placeholder, data, { lines: { show: true }})
+```
+
+should be changed to
+
+```js
+ $.plot(placeholder, data, { series: { lines: { show: true }}})
+```
+
+All future series-specific options will go into this sub-object to
+simplify plugin writing. Backward-compatibility code is in place, so
+old code should not break.
+
+"plothover" no longer provides the original data point, but instead a
+normalized one, since there may be no corresponding original point.
+
+Due to a bug in previous versions of jQuery, you now need at least
+jQuery 1.2.6. But if you can, try jQuery 1.3.2 as it got some improvements
+in event handling speed.
+
+## Changes ##
+
+ - Added support for disabling interactivity for specific data series.
+ (request from Ronald Schouten and Steve Upton)
+
+ - Flot now calls $() on the placeholder and optional legend container passed
+ in so you can specify DOM elements or CSS expressions to make it easier to
+ use Flot with libraries like Prototype or Mootools or through raw JSON from
+ Ajax responses.
+
+ - A new "plotselecting" event is now emitted while the user is making a
+ selection.
+
+ - The "plothover" event is now emitted immediately instead of at most 10
+ times per second, you'll have to put in a setTimeout yourself if you're
+ doing something really expensive on this event.
+
+ - The built-in date formatter can now be accessed as $.plot.formatDate(...)
+ (suggestion by Matt Manela) and even replaced.
+
+ - Added "borderColor" option to the grid. (patches from Amaury Chamayou and
+ Mike R. Williamson)
+
+ - Added support for gradient backgrounds for the grid. (based on patch from
+ Amaury Chamayou, issue 90)
+
+ The "setting options" example provides a demonstration.
+
+ - Gradient bars. (suggestion by stefpet)
+
+ - Added a "plotunselected" event which is triggered when the selection is
+ removed, see "selection" example. (suggestion by Meda Ugo)
+
+ - The option legend.margin can now specify horizontal and vertical margins
+ independently. (suggestion by someone who's annoyed)
+
+ - Data passed into Flot is now copied to a new canonical format to enable
+ further processing before it hits the drawing routines. As a side-effect,
+ this should make Flot more robust in the face of bad data. (issue 112)
+
+ - Step-wise charting: line charts have a new option "steps" that when set to
+ true connects the points with horizontal/vertical steps instead of diagonal
+ lines.
+
+ - The legend labelFormatter now passes the series in addition to just the
+ label. (suggestion by Vincent Lemeltier)
+
+ - Horizontal bars (based on patch by Jason LeBrun).
+
+ - Support for partial bars by specifying a third coordinate, i.e. they don't
+ have to start from the axis. This can be used to make stacked bars.
+
+ - New option to disable the (grid.show).
+
+ - Added pointOffset method for converting a point in data space to an offset
+ within the placeholder.
+
+ - Plugin system: register an init method in the $.flot.plugins array to get
+ started, see PLUGINS.txt for details on how to write plugins (it's easy).
+ There are also some extra methods to enable access to internal state.
+
+ - Hooks: you can register functions that are called while Flot is crunching
+ the data and doing the plot. This can be used to modify Flot without
+ changing the source, useful for writing plugins. Some hooks are defined,
+ more are likely to come.
+
+ - Threshold plugin: you can set a threshold and a color, and the data points
+ below that threshold will then get the color. Useful for marking data
+ below 0, for instance.
+
+ - Stack plugin: you can specify a stack key for each series to have them
+ summed. This is useful for drawing additive/cumulative graphs with bars and
+ (currently unfilled) lines.
+
+ - Crosshairs plugin: trace the mouse position on the axes, enable with
+ crosshair: { mode: "x"} (see the new tracking example for a use).
+
+ - Image plugin: plot prerendered images.
+
+ - Navigation plugin for panning and zooming a plot.
+
+ - More configurable grid.
+
+ - Axis transformation support, useful for non-linear plots, e.g. log axes and
+ compressed time axes (like omitting weekends).
+
+ - Support for twelve-hour date formatting (patch by Forrest Aldridge).
+
+ - The color parsing code in Flot has been cleaned up and split out so it's
+ now available as a separate jQuery plugin. It's included inline in the Flot
+ source to make dependency managing easier. This also makes it really easy
+ to use the color helpers in Flot plugins.
+
+## Bug fixes ##
+
+ - Fixed two corner-case bugs when drawing filled curves. (report and analysis
+ by Joshua Varner)
+
+ - Fix auto-adjustment code when setting min to 0 for an axis where the
+ dataset is completely flat on that axis. (report by chovy)
+
+ - Fixed a bug with passing in data from getData to setData when the secondary
+ axes are used. (reported by nperelman, issue 65)
+
+ - Fixed so that it is possible to turn lines off when no other chart type is
+ shown (based on problem reported by Glenn Vanderburg), and fixed so that
+ setting lineWidth to 0 also hides the shadow. (based on problem reported by
+ Sergio Nunes)
+
+ - Updated mousemove position expression to the latest from jQuery. (reported
+ by meyuchas)
+
+ - Use CSS borders instead of background in legend. (issues 25 and 45)
+
+ - Explicitly convert axis min/max to numbers.
+
+ - Fixed a bug with drawing marking lines with different colors. (reported by
+ Khurram)
+
+ - Fixed a bug with returning y2 values in the selection event. (fix by
+ exists, issue 75)
+
+ - Only set position relative on placeholder if it hasn't already a position
+ different from static. (reported by kyberneticist, issue 95)
+
+ - Don't round markings to prevent sub-pixel problems. (reported by
+ Dan Lipsitt)
+
+ - Make the grid border act similarly to a regular CSS border, i.e. prevent
+ it from overlapping the plot itself. This also fixes a problem with anti-
+ aliasing when the width is 1 pixel. (reported by Anthony Ettinger)
+
+ - Imported version 3 of excanvas and fixed two issues with the newer version.
+ Hopefully, this will make Flot work with IE8. (nudge by Fabien Menager,
+ further analysis by Booink, issue 133)
+
+ - Changed the shadow code for lines to hopefully look a bit better with
+ vertical lines.
+
+ - Round tick positions to avoid possible problems with fractions. (suggestion
+ by Fred, issue 130)
+
+ - Made the heuristic for determining how many ticks to aim for a bit smarter.
+
+ - Fix for uneven axis margins (report and patch by Paul Kienzle) and snapping
+ to ticks. (report and patch by lifthrasiir)
+
+ - Fixed bug with slicing in findNearbyItems. (patch by zollman)
+
+ - Make heuristic for x axis label widths more dynamic. (patch by
+ rickinhethuis)
+
+ - Make sure points on top take precedence when finding nearby points when
+ hovering. (reported by didroe, issue 224)
+
+
+
+## Flot 0.5 ##
+
+Timestamps are now in UTC. Also "selected" event -> becomes "plotselected"
+with new data, the parameters for setSelection are now different (but
+backwards compatibility hooks are in place), coloredAreas becomes markings
+with a new interface (but backwards compatibility hooks are in place).
+
+### API changes ###
+
+Timestamps in time mode are now displayed according to UTC instead of the time
+zone of the visitor. This affects the way the timestamps should be input;
+you'll probably have to offset the timestamps according to your local time
+zone. It also affects any custom date handling code (which basically now
+should use the equivalent UTC date mehods, e.g. .setUTCMonth() instead of
+.setMonth().
+
+Markings, previously coloredAreas, are now specified as ranges on the axes,
+like ```{ xaxis: { from: 0, to: 10 }}```. Furthermore with markings you can
+now draw horizontal/vertical lines by setting from and to to the same
+coordinate. (idea from line support patch by by Ryan Funduk)
+
+Interactivity: added a new "plothover" event and this and the "plotclick"
+event now returns the closest data item (based on patch by /david, patch by
+Mark Byers for bar support). See the revamped "interacting with the data"
+example for some hints on what you can do.
+
+Highlighting: you can now highlight points and datapoints are autohighlighted
+when you hover over them (if hovering is turned on).
+
+Support for dual axis has been added (based on patch by someone who's annoyed
+and /david). For each data series you can specify which axes it belongs to,
+and there are two more axes, x2axis and y2axis, to customize. This affects the
+"selected" event which has been renamed to "plotselected" and spews out
+```{ xaxis: { from: -10, to: 20 } ... },``` setSelection in which the
+parameters are on a new form (backwards compatible hooks are in place so old
+code shouldn't break) and markings (formerly coloredAreas).
+
+## Changes ##
+
+ - Added support for specifying the size of tick labels (axis.labelWidth,
+ axis.labelHeight). Useful for specifying a max label size to keep multiple
+ plots aligned.
+
+ - The "fill" option can now be a number that specifies the opacity of the
+ fill.
+
+ - You can now specify a coordinate as null (like [2, null]) and Flot will
+ take the other coordinate into account when scaling the axes. (based on
+ patch by joebno)
+
+ - New option for bars "align". Set it to "center" to center the bars on the
+ value they represent.
+
+ - setSelection now takes a second parameter which you can use to prevent the
+ method from firing the "plotselected" handler.
+
+ - Improved the handling of axis auto-scaling with bars.
+
+## Bug fixes ##
+
+ - Fixed a bug in calculating spacing around the plot. (reported by
+ timothytoe)
+
+ - Fixed a bug in finding max values for all-negative data sets.
+
+ - Prevent the possibility of eternal looping in tick calculations.
+
+ - Fixed a bug when borderWidth is set to 0. (reported by Rob/sanchothefat)
+
+ - Fixed a bug with drawing bars extending below 0. (reported by James Hewitt,
+ patch by Ryan Funduk).
+
+ - Fixed a bug with line widths of bars. (reported by MikeM)
+
+ - Fixed a bug with 'nw' and 'sw' legend positions.
+
+ - Fixed a bug with multi-line x-axis tick labels. (reported by Luca Ciano,
+ IE-fix help by Savage Zhang)
+
+ - Using the "container" option in legend now overwrites the container element
+ instead of just appending to it, fixing the infinite legend bug. (reported
+ by several people, fix by Brad Dewey)
+
+
+
+## Flot 0.4 ##
+
+### API changes ###
+
+Deprecated axis.noTicks in favor of just specifying the number as axis.ticks.
+So ```xaxis: { noTicks: 10 }``` becomes ```xaxis: { ticks: 10 }```.
+
+Time series support. Specify axis.mode: "time", put in Javascript timestamps
+as data, and Flot will automatically spit out sensible ticks. Take a look at
+the two new examples. The format can be customized with axis.timeformat and
+axis.monthNames, or if that fails with axis.tickFormatter.
+
+Support for colored background areas via grid.coloredAreas. Specify an array
+of { x1, y1, x2, y2 } objects or a function that returns these given
+{ xmin, xmax, ymin, ymax }.
+
+More members on the plot object (report by Chris Davies and others).
+"getData" for inspecting the assigned settings on data series (e.g. color) and
+"setData", "setupGrid" and "draw" for updating the contents without a total
+replot.
+
+The default number of ticks to aim for is now dependent on the size of the
+plot in pixels. Support for customizing tick interval sizes directly with
+axis.minTickSize and axis.tickSize.
+
+Cleaned up the automatic axis scaling algorithm and fixed how it interacts
+with ticks. Also fixed a couple of tick-related corner case bugs (one reported
+by mainstreetmark, another reported by timothytoe).
+
+The option axis.tickFormatter now takes a function with two parameters, the
+second parameter is an optional object with information about the axis. It has
+min, max, tickDecimals, tickSize.
+
+## Changes ##
+
+ - Added support for segmented lines. (based on patch from Michael MacDonald)
+
+ - Added support for ignoring null and bad values. (suggestion from Nick
+ Konidaris and joshwaihi)
+
+ - Added support for changing the border width. (thanks to joebno and safoo)
+
+ - Label colors can be changed via CSS by selecting the tickLabel class.
+
+## Bug fixes ##
+
+ - Fixed a bug in handling single-item bar series. (reported by Emil Filipov)
+
+ - Fixed erratic behaviour when interacting with the plot with IE 7. (reported
+ by Lau Bech Lauritzen).
+
+ - Prevent IE/Safari text selection when selecting stuff on the canvas.
+
+
+
+## Flot 0.3 ##
+
+This is mostly a quick-fix release because jquery.js wasn't included in the
+previous zip/tarball.
+
+## Changes ##
+
+ - Include jquery.js in the zip/tarball.
+
+ - Support clicking on the plot. Turn it on with grid: { clickable: true },
+ then you get a "plotclick" event on the graph placeholder with the position
+ in units of the plot.
+
+## Bug fixes ##
+
+ - Fixed a bug in dealing with data where min = max. (thanks to Michael
+ Messinides)
+
+
+
+## Flot 0.2 ##
+
+The API should now be fully documented.
+
+### API changes ###
+
+Moved labelMargin option to grid from x/yaxis.
+
+## Changes ##
+
+ - Added support for putting a background behind the default legend. The
+ default is the partly transparent background color. Added backgroundColor
+ and backgroundOpacity to the legend options to control this.
+
+ - The ticks options can now be a callback function that takes one parameter,
+ an object with the attributes min and max. The function should return a
+ ticks array.
+
+ - Added labelFormatter option in legend, useful for turning the legend
+ labels into links.
+
+ - Reduced the size of the code. (patch by Guy Fraser)
+
+
+
+## Flot 0.1 ##
+
+First public release.
diff --git a/misc/flot/PLUGINS.md b/misc/flot/PLUGINS.md
new file mode 100644
index 0000000..b5bf300
--- /dev/null
+++ b/misc/flot/PLUGINS.md
@@ -0,0 +1,143 @@
+## Writing plugins ##
+
+All you need to do to make a new plugin is creating an init function
+and a set of options (if needed), stuffing it into an object and
+putting it in the $.plot.plugins array. For example:
+
+```js
+function myCoolPluginInit(plot) {
+ plot.coolstring = "Hello!";
+};
+
+$.plot.plugins.push({ init: myCoolPluginInit, options: { ... } });
+
+// if $.plot is called, it will return a plot object with the
+// attribute "coolstring"
+```
+
+Now, given that the plugin might run in many different places, it's
+a good idea to avoid leaking names. The usual trick here is wrap the
+above lines in an anonymous function which is called immediately, like
+this: (function () { inner code ... })(). To make it even more robust
+in case $ is not bound to jQuery but some other Javascript library, we
+can write it as
+
+```js
+(function ($) {
+ // plugin definition
+ // ...
+})(jQuery);
+```
+
+There's a complete example below, but you should also check out the
+plugins bundled with Flot.
+
+
+## Complete example ##
+
+Here is a simple debug plugin which alerts each of the series in the
+plot. It has a single option that control whether it is enabled and
+how much info to output:
+
+```js
+(function ($) {
+ function init(plot) {
+ var debugLevel = 1;
+
+ function checkDebugEnabled(plot, options) {
+ if (options.debug) {
+ debugLevel = options.debug;
+ plot.hooks.processDatapoints.push(alertSeries);
+ }
+ }
+
+ function alertSeries(plot, series, datapoints) {
+ var msg = "series " + series.label;
+ if (debugLevel > 1) {
+ msg += " with " + series.data.length + " points";
+ alert(msg);
+ }
+ }
+
+ plot.hooks.processOptions.push(checkDebugEnabled);
+ }
+
+ var options = { debug: 0 };
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: "simpledebug",
+ version: "0.1"
+ });
+})(jQuery);
+```
+
+We also define "name" and "version". It's not used by Flot, but might
+be helpful for other plugins in resolving dependencies.
+
+Put the above in a file named "jquery.flot.debug.js", include it in an
+HTML page and then it can be used with:
+
+```js
+ $.plot($("#placeholder"), [...], { debug: 2 });
+```
+
+This simple plugin illustrates a couple of points:
+
+ - It uses the anonymous function trick to avoid name pollution.
+ - It can be enabled/disabled through an option.
+ - Variables in the init function can be used to store plot-specific
+ state between the hooks.
+
+The two last points are important because there may be multiple plots
+on the same page, and you'd want to make sure they are not mixed up.
+
+
+## Shutting down a plugin ##
+
+Each plot object has a shutdown hook which is run when plot.shutdown()
+is called. This usually mostly happens in case another plot is made on
+top of an existing one.
+
+The purpose of the hook is to give you a chance to unbind any event
+handlers you've registered and remove any extra DOM things you've
+inserted.
+
+The problem with event handlers is that you can have registered a
+handler which is run in some point in the future, e.g. with
+setTimeout(). Meanwhile, the plot may have been shutdown and removed,
+but because your event handler is still referencing it, it can't be
+garbage collected yet, and worse, if your handler eventually runs, it
+may overwrite stuff on a completely different plot.
+
+
+## Some hints on the options ##
+
+Plugins should always support appropriate options to enable/disable
+them because the plugin user may have several plots on the same page
+where only one should use the plugin. In most cases it's probably a
+good idea if the plugin is turned off rather than on per default, just
+like most of the powerful features in Flot.
+
+If the plugin needs options that are specific to each series, like the
+points or lines options in core Flot, you can put them in "series" in
+the options object, e.g.
+
+```js
+var options = {
+ series: {
+ downsample: {
+ algorithm: null,
+ maxpoints: 1000
+ }
+ }
+}
+```
+
+Then they will be copied by Flot into each series, providing default
+values in case none are specified.
+
+Think hard and long about naming the options. These names are going to
+be public API, and code is going to depend on them if the plugin is
+successful.
diff --git a/misc/flot/README.md b/misc/flot/README.md
new file mode 100644
index 0000000..a8f7064
--- /dev/null
+++ b/misc/flot/README.md
@@ -0,0 +1,110 @@
+# Flot [![Build status](https://travis-ci.org/flot/flot.png)](https://travis-ci.org/flot/flot)
+
+## About ##
+
+Flot is a Javascript plotting library for jQuery.
+Read more at the website: <http://www.flotcharts.org/>
+
+Take a look at the the examples in examples/index.html; they should give a good
+impression of what Flot can do, and the source code of the examples is probably
+the fastest way to learn how to use Flot.
+
+
+## Installation ##
+
+Just include the Javascript file after you've included jQuery.
+
+Generally, all browsers that support the HTML5 canvas tag are
+supported.
+
+For support for Internet Explorer < 9, you can use [Excanvas]
+[excanvas], a canvas emulator; this is used in the examples bundled
+with Flot. You just include the excanvas script like this:
+
+```html
+<!--[if lte IE 8]><script language="javascript" type="text/javascript" src="excanvas.min.js"></script><![endif]-->
+```
+
+If it's not working on your development IE 6.0, check that it has
+support for VML which Excanvas is relying on. It appears that some
+stripped down versions used for test environments on virtual machines
+lack the VML support.
+
+You can also try using [Flashcanvas][flashcanvas], which uses Flash to
+do the emulation. Although Flash can be a bit slower to load than VML,
+if you've got a lot of points, the Flash version can be much faster
+overall. Flot contains some wrapper code for activating Excanvas which
+Flashcanvas is compatible with.
+
+You need at least jQuery 1.2.6, but try at least 1.3.2 for interactive
+charts because of performance improvements in event handling.
+
+
+## Basic usage ##
+
+Create a placeholder div to put the graph in:
+
+```html
+<div id="placeholder"></div>
+```
+
+You need to set the width and height of this div, otherwise the plot
+library doesn't know how to scale the graph. You can do it inline like
+this:
+
+```html
+<div id="placeholder" style="width:600px;height:300px"></div>
+```
+
+You can also do it with an external stylesheet. Make sure that the
+placeholder isn't within something with a display:none CSS property -
+in that case, Flot has trouble measuring label dimensions which
+results in garbled looks and might have trouble measuring the
+placeholder dimensions which is fatal (it'll throw an exception).
+
+Then when the div is ready in the DOM, which is usually on document
+ready, run the plot function:
+
+```js
+$.plot($("#placeholder"), data, options);
+```
+
+Here, data is an array of data series and options is an object with
+settings if you want to customize the plot. Take a look at the
+examples for some ideas of what to put in or look at the
+[API reference](API.md). Here's a quick example that'll draw a line
+from (0, 0) to (1, 1):
+
+```js
+$.plot($("#placeholder"), [ [[0, 0], [1, 1]] ], { yaxis: { max: 1 } });
+```
+
+The plot function immediately draws the chart and then returns a plot
+object with a couple of methods.
+
+
+## What's with the name? ##
+
+First: it's pronounced with a short o, like "plot". Not like "flawed".
+
+So "Flot" rhymes with "plot".
+
+And if you look up "flot" in a Danish-to-English dictionary, some of
+the words that come up are "good-looking", "attractive", "stylish",
+"smart", "impressive", "extravagant". One of the main goals with Flot
+is pretty looks.
+
+
+## Notes about the examples ##
+
+In order to have a useful, functional example of time-series plots using time
+zones, date.js from [timezone-js][timezone-js] (released under the Apache 2.0
+license) and the [Olson][olson] time zone database (released to the public
+domain) have been included in the examples directory. They are used in
+examples/axes-time-zones/index.html.
+
+
+[excanvas]: http://code.google.com/p/explorercanvas/
+[flashcanvas]: http://code.google.com/p/flashcanvas/
+[timezone-js]: https://github.com/mde/timezone-js
+[olson]: http://ftp.iana.org/time-zones
diff --git a/misc/flot/examples/.DS_Store b/misc/flot/examples/.DS_Store
new file mode 100644
index 0000000..1d67e9a
--- /dev/null
+++ b/misc/flot/examples/.DS_Store
Binary files differ
diff --git a/misc/flot/examples/ajax/data-eu-gdp-growth-1.json b/misc/flot/examples/ajax/data-eu-gdp-growth-1.json
new file mode 100644
index 0000000..51952cf
--- /dev/null
+++ b/misc/flot/examples/ajax/data-eu-gdp-growth-1.json
@@ -0,0 +1,4 @@
+{
+ "label": "Europe (EU27)",
+ "data": [[1999, 3.0], [2000, 3.9]]
+}
diff --git a/misc/flot/examples/ajax/data-eu-gdp-growth-2.json b/misc/flot/examples/ajax/data-eu-gdp-growth-2.json
new file mode 100644
index 0000000..82004d6
--- /dev/null
+++ b/misc/flot/examples/ajax/data-eu-gdp-growth-2.json
@@ -0,0 +1,4 @@
+{
+ "label": "Europe (EU27)",
+ "data": [[1999, 3.0], [2000, 3.9], [2001, 2.0], [2002, 1.2]]
+}
diff --git a/misc/flot/examples/ajax/data-eu-gdp-growth-3.json b/misc/flot/examples/ajax/data-eu-gdp-growth-3.json
new file mode 100644
index 0000000..8684479
--- /dev/null
+++ b/misc/flot/examples/ajax/data-eu-gdp-growth-3.json
@@ -0,0 +1,4 @@
+{
+ "label": "Europe (EU27)",
+ "data": [[1999, 3.0], [2000, 3.9], [2001, 2.0], [2002, 1.2], [2003, 1.3], [2004, 2.5]]
+}
diff --git a/misc/flot/examples/ajax/data-eu-gdp-growth-4.json b/misc/flot/examples/ajax/data-eu-gdp-growth-4.json
new file mode 100644
index 0000000..b363578
--- /dev/null
+++ b/misc/flot/examples/ajax/data-eu-gdp-growth-4.json
@@ -0,0 +1,4 @@
+{
+ "label": "Europe (EU27)",
+ "data": [[1999, 3.0], [2000, 3.9], [2001, 2.0], [2002, 1.2], [2003, 1.3], [2004, 2.5], [2005, 2.0], [2006, 3.1]]
+}
diff --git a/misc/flot/examples/ajax/data-eu-gdp-growth-5.json b/misc/flot/examples/ajax/data-eu-gdp-growth-5.json
new file mode 100644
index 0000000..a7e1e13
--- /dev/null
+++ b/misc/flot/examples/ajax/data-eu-gdp-growth-5.json
@@ -0,0 +1,4 @@
+{
+ "label": "Europe (EU27)",
+ "data": [[1999, 3.0], [2000, 3.9], [2001, 2.0], [2002, 1.2], [2003, 1.3], [2004, 2.5], [2005, 2.0], [2006, 3.1], [2007, 2.9], [2008, 0.9]]
+}
diff --git a/misc/flot/examples/ajax/data-eu-gdp-growth.json b/misc/flot/examples/ajax/data-eu-gdp-growth.json
new file mode 100644
index 0000000..a7e1e13
--- /dev/null
+++ b/misc/flot/examples/ajax/data-eu-gdp-growth.json
@@ -0,0 +1,4 @@
+{
+ "label": "Europe (EU27)",
+ "data": [[1999, 3.0], [2000, 3.9], [2001, 2.0], [2002, 1.2], [2003, 1.3], [2004, 2.5], [2005, 2.0], [2006, 3.1], [2007, 2.9], [2008, 0.9]]
+}
diff --git a/misc/flot/examples/ajax/data-japan-gdp-growth.json b/misc/flot/examples/ajax/data-japan-gdp-growth.json
new file mode 100644
index 0000000..855477c
--- /dev/null
+++ b/misc/flot/examples/ajax/data-japan-gdp-growth.json
@@ -0,0 +1,4 @@
+{
+ "label": "Japan",
+ "data": [[1999, -0.1], [2000, 2.9], [2001, 0.2], [2002, 0.3], [2003, 1.4], [2004, 2.7], [2005, 1.9], [2006, 2.0], [2007, 2.3], [2008, -0.7]]
+}
diff --git a/misc/flot/examples/ajax/data-usa-gdp-growth.json b/misc/flot/examples/ajax/data-usa-gdp-growth.json
new file mode 100644
index 0000000..33f66c6
--- /dev/null
+++ b/misc/flot/examples/ajax/data-usa-gdp-growth.json
@@ -0,0 +1,4 @@
+{
+ "label": "USA",
+ "data": [[1999, 4.4], [2000, 3.7], [2001, 0.8], [2002, 1.6], [2003, 2.5], [2004, 3.6], [2005, 2.9], [2006, 2.8], [2007, 2.0], [2008, 1.1]]
+}
diff --git a/misc/flot/examples/ajax/index.html b/misc/flot/examples/ajax/index.html
new file mode 100644
index 0000000..2f0532e
--- /dev/null
+++ b/misc/flot/examples/ajax/index.html
@@ -0,0 +1,173 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: AJAX</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var options = {
+ lines: {
+ show: true
+ },
+ points: {
+ show: true
+ },
+ xaxis: {
+ tickDecimals: 0,
+ tickSize: 1
+ }
+ };
+
+ var data = [];
+
+ $.plot("#placeholder", data, options);
+
+ // Fetch one series, adding to what we already have
+
+ var alreadyFetched = {};
+
+ $("button.fetchSeries").click(function () {
+
+ var button = $(this);
+
+ // Find the URL in the link right next to us, then fetch the data
+
+ var dataurl = button.siblings("a").attr("href");
+
+ function onDataReceived(series) {
+
+ // Extract the first coordinate pair; jQuery has parsed it, so
+ // the data is now just an ordinary JavaScript object
+
+ var firstcoordinate = "(" + series.data[0][0] + ", " + series.data[0][1] + ")";
+ button.siblings("span").text("Fetched " + series.label + ", first point: " + firstcoordinate);
+
+ // Push the new data onto our existing data array
+
+ if (!alreadyFetched[series.label]) {
+ alreadyFetched[series.label] = true;
+ data.push(series);
+ }
+
+ $.plot("#placeholder", data, options);
+ }
+
+ $.ajax({
+ url: dataurl,
+ type: "GET",
+ dataType: "json",
+ success: onDataReceived
+ });
+ });
+
+ // Initiate a recurring data update
+
+ $("button.dataUpdate").click(function () {
+
+ data = [];
+ alreadyFetched = {};
+
+ $.plot("#placeholder", data, options);
+
+ var iteration = 0;
+
+ function fetchData() {
+
+ ++iteration;
+
+ function onDataReceived(series) {
+
+ // Load all the data in one pass; if we only got partial
+ // data we could merge it with what we already have.
+
+ data = [ series ];
+ $.plot("#placeholder", data, options);
+ }
+
+ // Normally we call the same URL - a script connected to a
+ // database - but in this case we only have static example
+ // files, so we need to modify the URL.
+
+ $.ajax({
+ url: "data-eu-gdp-growth-" + iteration + ".json",
+ type: "GET",
+ dataType: "json",
+ success: onDataReceived
+ });
+
+ if (iteration < 5) {
+ setTimeout(fetchData, 1000);
+ } else {
+ data = [];
+ alreadyFetched = {};
+ }
+ }
+
+ setTimeout(fetchData, 1000);
+ });
+
+ // Load the first series by default, so we don't have an empty plot
+
+ $("button.fetchSeries:first").click();
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>AJAX</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>Example of loading data dynamically with AJAX. Percentage change in GDP (source: <a href="http://epp.eurostat.ec.europa.eu/tgm/table.do?tab=table&init=1&plugin=1&language=en&pcode=tsieb020">Eurostat</a>). Click the buttons below:</p>
+
+ <p>The data is fetched over HTTP, in this case directly from text files. Usually the URL would point to some web server handler (e.g. a PHP page or Java/.NET/Python/Ruby on Rails handler) that extracts it from a database and serializes it to JSON.</p>
+
+ <p>
+ <button class="fetchSeries">First dataset</button>
+ [ <a href="data-eu-gdp-growth.json">see data</a> ]
+ <span></span>
+ </p>
+
+ <p>
+ <button class="fetchSeries">Second dataset</button>
+ [ <a href="data-japan-gdp-growth.json">see data</a> ]
+ <span></span>
+ </p>
+
+ <p>
+ <button class="fetchSeries">Third dataset</button>
+ [ <a href="data-usa-gdp-growth.json">see data</a> ]
+ <span></span>
+ </p>
+
+ <p>If you combine AJAX with setTimeout, you can poll the server for new data.</p>
+
+ <p>
+ <button class="dataUpdate">Poll for data</button>
+ </p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/annotating/index.html b/misc/flot/examples/annotating/index.html
new file mode 100644
index 0000000..3b4cb1b
--- /dev/null
+++ b/misc/flot/examples/annotating/index.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Adding Annotations</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var d1 = [];
+ for (var i = 0; i < 20; ++i) {
+ d1.push([i, Math.sin(i)]);
+ }
+
+ var data = [{ data: d1, label: "Pressure", color: "#333" }];
+
+ var markings = [
+ { color: "#f6f6f6", yaxis: { from: 1 } },
+ { color: "#f6f6f6", yaxis: { to: -1 } },
+ { color: "#000", lineWidth: 1, xaxis: { from: 2, to: 2 } },
+ { color: "#000", lineWidth: 1, xaxis: { from: 8, to: 8 } }
+ ];
+
+ var placeholder = $("#placeholder");
+
+ var plot = $.plot(placeholder, data, {
+ bars: { show: true, barWidth: 0.5, fill: 0.9 },
+ xaxis: { ticks: [], autoscaleMargin: 0.02 },
+ yaxis: { min: -2, max: 2 },
+ grid: { markings: markings }
+ });
+
+ var o = plot.pointOffset({ x: 2, y: -1.2});
+
+ // Append it to the placeholder that Flot already uses for positioning
+
+ placeholder.append("<div style='position:absolute;left:" + (o.left + 4) + "px;top:" + o.top + "px;color:#666;font-size:smaller'>Warming up</div>");
+
+ o = plot.pointOffset({ x: 8, y: -1.2});
+ placeholder.append("<div style='position:absolute;left:" + (o.left + 4) + "px;top:" + o.top + "px;color:#666;font-size:smaller'>Actual measurements</div>");
+
+ // Draw a little arrow on top of the last label to demonstrate canvas
+ // drawing
+
+ var ctx = plot.getCanvas().getContext("2d");
+ ctx.beginPath();
+ o.left += 4;
+ ctx.moveTo(o.left, o.top);
+ ctx.lineTo(o.left, o.top - 10);
+ ctx.lineTo(o.left + 10, o.top - 5);
+ ctx.lineTo(o.left, o.top);
+ ctx.fillStyle = "#000";
+ ctx.fill();
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Adding Annotations</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>Flot has support for simple background decorations such as lines and rectangles. They can be useful for marking up certain areas. You can easily add any HTML you need with standard DOM manipulation, e.g. for labels. For drawing custom shapes there is also direct access to the canvas.</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/axes-interacting/index.html b/misc/flot/examples/axes-interacting/index.html
new file mode 100644
index 0000000..1e11ea4
--- /dev/null
+++ b/misc/flot/examples/axes-interacting/index.html
@@ -0,0 +1,97 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Interacting with axes</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ function generate(start, end, fn) {
+ var res = [];
+ for (var i = 0; i <= 100; ++i) {
+ var x = start + i / 100 * (end - start);
+ res.push([x, fn(x)]);
+ }
+ return res;
+ }
+
+ var data = [
+ { data: generate(0, 10, function (x) { return Math.sqrt(x);}), xaxis: 1, yaxis:1 },
+ { data: generate(0, 10, function (x) { return Math.sin(x);}), xaxis: 1, yaxis:2 },
+ { data: generate(0, 10, function (x) { return Math.cos(x);}), xaxis: 1, yaxis:3 },
+ { data: generate(2, 10, function (x) { return Math.tan(x);}), xaxis: 2, yaxis: 4 }
+ ];
+
+ var plot = $.plot("#placeholder", data, {
+ xaxes: [
+ { position: 'bottom' },
+ { position: 'top'}
+ ],
+ yaxes: [
+ { position: 'left' },
+ { position: 'left' },
+ { position: 'right' },
+ { position: 'left' }
+ ]
+ });
+
+ // Create a div for each axis
+
+ $.each(plot.getAxes(), function (i, axis) {
+ if (!axis.show)
+ return;
+
+ var box = axis.box;
+
+ $("<div class='axisTarget' style='position:absolute; left:" + box.left + "px; top:" + box.top + "px; width:" + box.width + "px; height:" + box.height + "px'></div>")
+ .data("axis.direction", axis.direction)
+ .data("axis.n", axis.n)
+ .css({ backgroundColor: "#f00", opacity: 0, cursor: "pointer" })
+ .appendTo(plot.getPlaceholder())
+ .hover(
+ function () { $(this).css({ opacity: 0.10 }) },
+ function () { $(this).css({ opacity: 0 }) }
+ )
+ .click(function () {
+ $("#click").text("You clicked the " + axis.direction + axis.n + "axis!")
+ });
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Interacting with axes</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>With multiple axes, you sometimes need to interact with them. A simple way to do this is to draw the plot, deduce the axis placements and insert a couple of divs on top to catch events.</p>
+
+ <p>Try clicking an axis.</p>
+
+ <p id="click"></p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/axes-multiple/index.html b/misc/flot/examples/axes-multiple/index.html
new file mode 100644
index 0000000..cc31d88
--- /dev/null
+++ b/misc/flot/examples/axes-multiple/index.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Multiple Axes</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.time.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var oilprices = [[1167692400000,61.05], [1167778800000,58.32], [1167865200000,57.35], [1167951600000,56.31], [1168210800000,55.55], [1168297200000,55.64], [1168383600000,54.02], [1168470000000,51.88], [1168556400000,52.99], [1168815600000,52.99], [1168902000000,51.21], [1168988400000,52.24], [1169074800000,50.48], [1169161200000,51.99], [1169420400000,51.13], [1169506800000,55.04], [1169593200000,55.37], [1169679600000,54.23], [1169766000000,55.42], [1170025200000,54.01], [1170111600000,56.97], [1170198000000,58.14], [1170284400000,58.14], [1170370800000,59.02], [1170630000000,58.74], [1170716400000,58.88], [1170802800000,57.71], [1170889200000,59.71], [1170975600000,59.89], [1171234800000,57.81], [1171321200000,59.06], [1171407600000,58.00], [1171494000000,57.99], [1171580400000,59.39], [1171839600000,59.39], [1171926000000,58.07], [1172012400000,60.07], [1172098800000,61.14], [1172444400000,61.39], [1172530800000,61.46], [1172617200000,61.79], [1172703600000,62.00], [1172790000000,60.07], [1173135600000,60.69], [1173222000000,61.82], [1173308400000,60.05], [1173654000000,58.91], [1173740400000,57.93], [1173826800000,58.16], [1173913200000,57.55], [1173999600000,57.11], [1174258800000,56.59], [1174345200000,59.61], [1174518000000,61.69], [1174604400000,62.28], [1174860000000,62.91], [1174946400000,62.93], [1175032800000,64.03], [1175119200000,66.03], [1175205600000,65.87], [1175464800000,64.64], [1175637600000,64.38], [1175724000000,64.28], [1175810400000,64.28], [1176069600000,61.51], [1176156000000,61.89], [1176242400000,62.01], [1176328800000,63.85], [1176415200000,63.63], [1176674400000,63.61], [1176760800000,63.10], [1176847200000,63.13], [1176933600000,61.83], [1177020000000,63.38], [1177279200000,64.58], [1177452000000,65.84], [1177538400000,65.06], [1177624800000,66.46], [1177884000000,64.40], [1178056800000,63.68], [1178143200000,63.19], [1178229600000,61.93], [1178488800000,61.47], [1178575200000,61.55], [1178748000000,61.81], [1178834400000,62.37], [1179093600000,62.46], [1179180000000,63.17], [1179266400000,62.55], [1179352800000,64.94], [1179698400000,66.27], [1179784800000,65.50], [1179871200000,65.77], [1179957600000,64.18], [1180044000000,65.20], [1180389600000,63.15], [1180476000000,63.49], [1180562400000,65.08], [1180908000000,66.30], [1180994400000,65.96], [1181167200000,66.93], [1181253600000,65.98], [1181599200000,65.35], [1181685600000,66.26], [1181858400000,68.00], [1182117600000,69.09], [1182204000000,69.10], [1182290400000,68.19], [1182376800000,68.19], [1182463200000,69.14], [1182722400000,68.19], [1182808800000,67.77], [1182895200000,68.97], [1182981600000,69.57], [1183068000000,70.68], [1183327200000,71.09], [1183413600000,70.92], [1183586400000,71.81], [1183672800000,72.81], [1183932000000,72.19], [1184018400000,72.56], [1184191200000,72.50], [1184277600000,74.15], [1184623200000,75.05], [1184796000000,75.92], [1184882400000,75.57], [1185141600000,74.89], [1185228000000,73.56], [1185314400000,75.57], [1185400800000,74.95], [1185487200000,76.83], [1185832800000,78.21], [1185919200000,76.53], [1186005600000,76.86], [1186092000000,76.00], [1186437600000,71.59], [1186696800000,71.47], [1186956000000,71.62], [1187042400000,71.00], [1187301600000,71.98], [1187560800000,71.12], [1187647200000,69.47], [1187733600000,69.26], [1187820000000,69.83], [1187906400000,71.09], [1188165600000,71.73], [1188338400000,73.36], [1188511200000,74.04], [1188856800000,76.30], [1189116000000,77.49], [1189461600000,78.23], [1189548000000,79.91], [1189634400000,80.09], [1189720800000,79.10], [1189980000000,80.57], [1190066400000,81.93], [1190239200000,83.32], [1190325600000,81.62], [1190584800000,80.95], [1190671200000,79.53], [1190757600000,80.30], [1190844000000,82.88], [1190930400000,81.66], [1191189600000,80.24], [1191276000000,80.05], [1191362400000,79.94], [1191448800000,81.44], [1191535200000,81.22], [1191794400000,79.02], [1191880800000,80.26], [1191967200000,80.30], [1192053600000,83.08], [1192140000000,83.69], [1192399200000,86.13], [1192485600000,87.61], [1192572000000,87.40], [1192658400000,89.47], [1192744800000,88.60], [1193004000000,87.56], [1193090400000,87.56], [1193176800000,87.10], [1193263200000,91.86], [1193612400000,93.53], [1193698800000,94.53], [1193871600000,95.93], [1194217200000,93.98], [1194303600000,96.37], [1194476400000,95.46], [1194562800000,96.32], [1195081200000,93.43], [1195167600000,95.10], [1195426800000,94.64], [1195513200000,95.10], [1196031600000,97.70], [1196118000000,94.42], [1196204400000,90.62], [1196290800000,91.01], [1196377200000,88.71], [1196636400000,88.32], [1196809200000,90.23], [1196982000000,88.28], [1197241200000,87.86], [1197327600000,90.02], [1197414000000,92.25], [1197586800000,90.63], [1197846000000,90.63], [1197932400000,90.49], [1198018800000,91.24], [1198105200000,91.06], [1198191600000,90.49], [1198710000000,96.62], [1198796400000,96.00], [1199142000000,99.62], [1199314800000,99.18], [1199401200000,95.09], [1199660400000,96.33], [1199833200000,95.67], [1200351600000,91.90], [1200438000000,90.84], [1200524400000,90.13], [1200610800000,90.57], [1200956400000,89.21], [1201042800000,86.99], [1201129200000,89.85], [1201474800000,90.99], [1201561200000,91.64], [1201647600000,92.33], [1201734000000,91.75], [1202079600000,90.02], [1202166000000,88.41], [1202252400000,87.14], [1202338800000,88.11], [1202425200000,91.77], [1202770800000,92.78], [1202857200000,93.27], [1202943600000,95.46], [1203030000000,95.46], [1203289200000,101.74], [1203462000000,98.81], [1203894000000,100.88], [1204066800000,99.64], [1204153200000,102.59], [1204239600000,101.84], [1204498800000,99.52], [1204585200000,99.52], [1204671600000,104.52], [1204758000000,105.47], [1204844400000,105.15], [1205103600000,108.75], [1205276400000,109.92], [1205362800000,110.33], [1205449200000,110.21], [1205708400000,105.68], [1205967600000,101.84], [1206313200000,100.86], [1206399600000,101.22], [1206486000000,105.90], [1206572400000,107.58], [1206658800000,105.62], [1206914400000,101.58], [1207000800000,100.98], [1207173600000,103.83], [1207260000000,106.23], [1207605600000,108.50], [1207778400000,110.11], [1207864800000,110.14], [1208210400000,113.79], [1208296800000,114.93], [1208383200000,114.86], [1208728800000,117.48], [1208815200000,118.30], [1208988000000,116.06], [1209074400000,118.52], [1209333600000,118.75], [1209420000000,113.46], [1209592800000,112.52], [1210024800000,121.84], [1210111200000,123.53], [1210197600000,123.69], [1210543200000,124.23], [1210629600000,125.80], [1210716000000,126.29], [1211148000000,127.05], [1211320800000,129.07], [1211493600000,132.19], [1211839200000,128.85], [1212357600000,127.76], [1212703200000,138.54], [1212962400000,136.80], [1213135200000,136.38], [1213308000000,134.86], [1213653600000,134.01], [1213740000000,136.68], [1213912800000,135.65], [1214172000000,134.62], [1214258400000,134.62], [1214344800000,134.62], [1214431200000,139.64], [1214517600000,140.21], [1214776800000,140.00], [1214863200000,140.97], [1214949600000,143.57], [1215036000000,145.29], [1215381600000,141.37], [1215468000000,136.04], [1215727200000,146.40], [1215986400000,145.18], [1216072800000,138.74], [1216159200000,134.60], [1216245600000,129.29], [1216332000000,130.65], [1216677600000,127.95], [1216850400000,127.95], [1217282400000,122.19], [1217455200000,124.08], [1217541600000,125.10], [1217800800000,121.41], [1217887200000,119.17], [1217973600000,118.58], [1218060000000,120.02], [1218405600000,114.45], [1218492000000,113.01], [1218578400000,116.00], [1218751200000,113.77], [1219010400000,112.87], [1219096800000,114.53], [1219269600000,114.98], [1219356000000,114.98], [1219701600000,116.27], [1219788000000,118.15], [1219874400000,115.59], [1219960800000,115.46], [1220306400000,109.71], [1220392800000,109.35], [1220565600000,106.23], [1220824800000,106.34]];
+
+ var exchangerates = [[1167606000000,0.7580], [1167692400000,0.7580], [1167778800000,0.75470], [1167865200000,0.75490], [1167951600000,0.76130], [1168038000000,0.76550], [1168124400000,0.76930], [1168210800000,0.76940], [1168297200000,0.76880], [1168383600000,0.76780], [1168470000000,0.77080], [1168556400000,0.77270], [1168642800000,0.77490], [1168729200000,0.77410], [1168815600000,0.77410], [1168902000000,0.77320], [1168988400000,0.77270], [1169074800000,0.77370], [1169161200000,0.77240], [1169247600000,0.77120], [1169334000000,0.7720], [1169420400000,0.77210], [1169506800000,0.77170], [1169593200000,0.77040], [1169679600000,0.7690], [1169766000000,0.77110], [1169852400000,0.7740], [1169938800000,0.77450], [1170025200000,0.77450], [1170111600000,0.7740], [1170198000000,0.77160], [1170284400000,0.77130], [1170370800000,0.76780], [1170457200000,0.76880], [1170543600000,0.77180], [1170630000000,0.77180], [1170716400000,0.77280], [1170802800000,0.77290], [1170889200000,0.76980], [1170975600000,0.76850], [1171062000000,0.76810], [1171148400000,0.7690], [1171234800000,0.7690], [1171321200000,0.76980], [1171407600000,0.76990], [1171494000000,0.76510], [1171580400000,0.76130], [1171666800000,0.76160], [1171753200000,0.76140], [1171839600000,0.76140], [1171926000000,0.76070], [1172012400000,0.76020], [1172098800000,0.76110], [1172185200000,0.76220], [1172271600000,0.76150], [1172358000000,0.75980], [1172444400000,0.75980], [1172530800000,0.75920], [1172617200000,0.75730], [1172703600000,0.75660], [1172790000000,0.75670], [1172876400000,0.75910], [1172962800000,0.75820], [1173049200000,0.75850], [1173135600000,0.76130], [1173222000000,0.76310], [1173308400000,0.76150], [1173394800000,0.760], [1173481200000,0.76130], [1173567600000,0.76270], [1173654000000,0.76270], [1173740400000,0.76080], [1173826800000,0.75830], [1173913200000,0.75750], [1173999600000,0.75620], [1174086000000,0.7520], [1174172400000,0.75120], [1174258800000,0.75120], [1174345200000,0.75170], [1174431600000,0.7520], [1174518000000,0.75110], [1174604400000,0.7480], [1174690800000,0.75090], [1174777200000,0.75310], [1174860000000,0.75310], [1174946400000,0.75270], [1175032800000,0.74980], [1175119200000,0.74930], [1175205600000,0.75040], [1175292000000,0.750], [1175378400000,0.74910], [1175464800000,0.74910], [1175551200000,0.74850], [1175637600000,0.74840], [1175724000000,0.74920], [1175810400000,0.74710], [1175896800000,0.74590], [1175983200000,0.74770], [1176069600000,0.74770], [1176156000000,0.74830], [1176242400000,0.74580], [1176328800000,0.74480], [1176415200000,0.7430], [1176501600000,0.73990], [1176588000000,0.73950], [1176674400000,0.73950], [1176760800000,0.73780], [1176847200000,0.73820], [1176933600000,0.73620], [1177020000000,0.73550], [1177106400000,0.73480], [1177192800000,0.73610], [1177279200000,0.73610], [1177365600000,0.73650], [1177452000000,0.73620], [1177538400000,0.73310], [1177624800000,0.73390], [1177711200000,0.73440], [1177797600000,0.73270], [1177884000000,0.73270], [1177970400000,0.73360], [1178056800000,0.73330], [1178143200000,0.73590], [1178229600000,0.73590], [1178316000000,0.73720], [1178402400000,0.7360], [1178488800000,0.7360], [1178575200000,0.7350], [1178661600000,0.73650], [1178748000000,0.73840], [1178834400000,0.73950], [1178920800000,0.74130], [1179007200000,0.73970], [1179093600000,0.73960], [1179180000000,0.73850], [1179266400000,0.73780], [1179352800000,0.73660], [1179439200000,0.740], [1179525600000,0.74110], [1179612000000,0.74060], [1179698400000,0.74050], [1179784800000,0.74140], [1179871200000,0.74310], [1179957600000,0.74310], [1180044000000,0.74380], [1180130400000,0.74430], [1180216800000,0.74430], [1180303200000,0.74430], [1180389600000,0.74340], [1180476000000,0.74290], [1180562400000,0.74420], [1180648800000,0.7440], [1180735200000,0.74390], [1180821600000,0.74370], [1180908000000,0.74370], [1180994400000,0.74290], [1181080800000,0.74030], [1181167200000,0.73990], [1181253600000,0.74180], [1181340000000,0.74680], [1181426400000,0.7480], [1181512800000,0.7480], [1181599200000,0.7490], [1181685600000,0.74940], [1181772000000,0.75220], [1181858400000,0.75150], [1181944800000,0.75020], [1182031200000,0.74720], [1182117600000,0.74720], [1182204000000,0.74620], [1182290400000,0.74550], [1182376800000,0.74490], [1182463200000,0.74670], [1182549600000,0.74580], [1182636000000,0.74270], [1182722400000,0.74270], [1182808800000,0.7430], [1182895200000,0.74290], [1182981600000,0.7440], [1183068000000,0.7430], [1183154400000,0.74220], [1183240800000,0.73880], [1183327200000,0.73880], [1183413600000,0.73690], [1183500000000,0.73450], [1183586400000,0.73450], [1183672800000,0.73450], [1183759200000,0.73520], [1183845600000,0.73410], [1183932000000,0.73410], [1184018400000,0.7340], [1184104800000,0.73240], [1184191200000,0.72720], [1184277600000,0.72640], [1184364000000,0.72550], [1184450400000,0.72580], [1184536800000,0.72580], [1184623200000,0.72560], [1184709600000,0.72570], [1184796000000,0.72470], [1184882400000,0.72430], [1184968800000,0.72440], [1185055200000,0.72350], [1185141600000,0.72350], [1185228000000,0.72350], [1185314400000,0.72350], [1185400800000,0.72620], [1185487200000,0.72880], [1185573600000,0.73010], [1185660000000,0.73370], [1185746400000,0.73370], [1185832800000,0.73240], [1185919200000,0.72970], [1186005600000,0.73170], [1186092000000,0.73150], [1186178400000,0.72880], [1186264800000,0.72630], [1186351200000,0.72630], [1186437600000,0.72420], [1186524000000,0.72530], [1186610400000,0.72640], [1186696800000,0.7270], [1186783200000,0.73120], [1186869600000,0.73050], [1186956000000,0.73050], [1187042400000,0.73180], [1187128800000,0.73580], [1187215200000,0.74090], [1187301600000,0.74540], [1187388000000,0.74370], [1187474400000,0.74240], [1187560800000,0.74240], [1187647200000,0.74150], [1187733600000,0.74190], [1187820000000,0.74140], [1187906400000,0.73770], [1187992800000,0.73550], [1188079200000,0.73150], [1188165600000,0.73150], [1188252000000,0.7320], [1188338400000,0.73320], [1188424800000,0.73460], [1188511200000,0.73280], [1188597600000,0.73230], [1188684000000,0.7340], [1188770400000,0.7340], [1188856800000,0.73360], [1188943200000,0.73510], [1189029600000,0.73460], [1189116000000,0.73210], [1189202400000,0.72940], [1189288800000,0.72660], [1189375200000,0.72660], [1189461600000,0.72540], [1189548000000,0.72420], [1189634400000,0.72130], [1189720800000,0.71970], [1189807200000,0.72090], [1189893600000,0.7210], [1189980000000,0.7210], [1190066400000,0.7210], [1190152800000,0.72090], [1190239200000,0.71590], [1190325600000,0.71330], [1190412000000,0.71050], [1190498400000,0.70990], [1190584800000,0.70990], [1190671200000,0.70930], [1190757600000,0.70930], [1190844000000,0.70760], [1190930400000,0.7070], [1191016800000,0.70490], [1191103200000,0.70120], [1191189600000,0.70110], [1191276000000,0.70190], [1191362400000,0.70460], [1191448800000,0.70630], [1191535200000,0.70890], [1191621600000,0.70770], [1191708000000,0.70770], [1191794400000,0.70770], [1191880800000,0.70910], [1191967200000,0.71180], [1192053600000,0.70790], [1192140000000,0.70530], [1192226400000,0.7050], [1192312800000,0.70550], [1192399200000,0.70550], [1192485600000,0.70450], [1192572000000,0.70510], [1192658400000,0.70510], [1192744800000,0.70170], [1192831200000,0.70], [1192917600000,0.69950], [1193004000000,0.69940], [1193090400000,0.70140], [1193176800000,0.70360], [1193263200000,0.70210], [1193349600000,0.70020], [1193436000000,0.69670], [1193522400000,0.6950], [1193612400000,0.6950], [1193698800000,0.69390], [1193785200000,0.6940], [1193871600000,0.69220], [1193958000000,0.69190], [1194044400000,0.69140], [1194130800000,0.68940], [1194217200000,0.68910], [1194303600000,0.69040], [1194390000000,0.6890], [1194476400000,0.68340], [1194562800000,0.68230], [1194649200000,0.68070], [1194735600000,0.68150], [1194822000000,0.68150], [1194908400000,0.68470], [1194994800000,0.68590], [1195081200000,0.68220], [1195167600000,0.68270], [1195254000000,0.68370], [1195340400000,0.68230], [1195426800000,0.68220], [1195513200000,0.68220], [1195599600000,0.67920], [1195686000000,0.67460], [1195772400000,0.67350], [1195858800000,0.67310], [1195945200000,0.67420], [1196031600000,0.67440], [1196118000000,0.67390], [1196204400000,0.67310], [1196290800000,0.67610], [1196377200000,0.67610], [1196463600000,0.67850], [1196550000000,0.68180], [1196636400000,0.68360], [1196722800000,0.68230], [1196809200000,0.68050], [1196895600000,0.67930], [1196982000000,0.68490], [1197068400000,0.68330], [1197154800000,0.68250], [1197241200000,0.68250], [1197327600000,0.68160], [1197414000000,0.67990], [1197500400000,0.68130], [1197586800000,0.68090], [1197673200000,0.68680], [1197759600000,0.69330], [1197846000000,0.69330], [1197932400000,0.69450], [1198018800000,0.69440], [1198105200000,0.69460], [1198191600000,0.69640], [1198278000000,0.69650], [1198364400000,0.69560], [1198450800000,0.69560], [1198537200000,0.6950], [1198623600000,0.69480], [1198710000000,0.69280], [1198796400000,0.68870], [1198882800000,0.68240], [1198969200000,0.67940], [1199055600000,0.67940], [1199142000000,0.68030], [1199228400000,0.68550], [1199314800000,0.68240], [1199401200000,0.67910], [1199487600000,0.67830], [1199574000000,0.67850], [1199660400000,0.67850], [1199746800000,0.67970], [1199833200000,0.680], [1199919600000,0.68030], [1200006000000,0.68050], [1200092400000,0.6760], [1200178800000,0.6770], [1200265200000,0.6770], [1200351600000,0.67360], [1200438000000,0.67260], [1200524400000,0.67640], [1200610800000,0.68210], [1200697200000,0.68310], [1200783600000,0.68420], [1200870000000,0.68420], [1200956400000,0.68870], [1201042800000,0.69030], [1201129200000,0.68480], [1201215600000,0.68240], [1201302000000,0.67880], [1201388400000,0.68140], [1201474800000,0.68140], [1201561200000,0.67970], [1201647600000,0.67690], [1201734000000,0.67650], [1201820400000,0.67330], [1201906800000,0.67290], [1201993200000,0.67580], [1202079600000,0.67580], [1202166000000,0.6750], [1202252400000,0.6780], [1202338800000,0.68330], [1202425200000,0.68560], [1202511600000,0.69030], [1202598000000,0.68960], [1202684400000,0.68960], [1202770800000,0.68820], [1202857200000,0.68790], [1202943600000,0.68620], [1203030000000,0.68520], [1203116400000,0.68230], [1203202800000,0.68130], [1203289200000,0.68130], [1203375600000,0.68220], [1203462000000,0.68020], [1203548400000,0.68020], [1203634800000,0.67840], [1203721200000,0.67480], [1203807600000,0.67470], [1203894000000,0.67470], [1203980400000,0.67480], [1204066800000,0.67330], [1204153200000,0.6650], [1204239600000,0.66110], [1204326000000,0.65830], [1204412400000,0.6590], [1204498800000,0.6590], [1204585200000,0.65810], [1204671600000,0.65780], [1204758000000,0.65740], [1204844400000,0.65320], [1204930800000,0.65020], [1205017200000,0.65140], [1205103600000,0.65140], [1205190000000,0.65070], [1205276400000,0.6510], [1205362800000,0.64890], [1205449200000,0.64240], [1205535600000,0.64060], [1205622000000,0.63820], [1205708400000,0.63820], [1205794800000,0.63410], [1205881200000,0.63440], [1205967600000,0.63780], [1206054000000,0.64390], [1206140400000,0.64780], [1206226800000,0.64810], [1206313200000,0.64810], [1206399600000,0.64940], [1206486000000,0.64380], [1206572400000,0.63770], [1206658800000,0.63290], [1206745200000,0.63360], [1206831600000,0.63330], [1206914400000,0.63330], [1207000800000,0.6330], [1207087200000,0.63710], [1207173600000,0.64030], [1207260000000,0.63960], [1207346400000,0.63640], [1207432800000,0.63560], [1207519200000,0.63560], [1207605600000,0.63680], [1207692000000,0.63570], [1207778400000,0.63540], [1207864800000,0.6320], [1207951200000,0.63320], [1208037600000,0.63280], [1208124000000,0.63310], [1208210400000,0.63420], [1208296800000,0.63210], [1208383200000,0.63020], [1208469600000,0.62780], [1208556000000,0.63080], [1208642400000,0.63240], [1208728800000,0.63240], [1208815200000,0.63070], [1208901600000,0.62770], [1208988000000,0.62690], [1209074400000,0.63350], [1209160800000,0.63920], [1209247200000,0.640], [1209333600000,0.64010], [1209420000000,0.63960], [1209506400000,0.64070], [1209592800000,0.64230], [1209679200000,0.64290], [1209765600000,0.64720], [1209852000000,0.64850], [1209938400000,0.64860], [1210024800000,0.64670], [1210111200000,0.64440], [1210197600000,0.64670], [1210284000000,0.65090], [1210370400000,0.64780], [1210456800000,0.64610], [1210543200000,0.64610], [1210629600000,0.64680], [1210716000000,0.64490], [1210802400000,0.6470], [1210888800000,0.64610], [1210975200000,0.64520], [1211061600000,0.64220], [1211148000000,0.64220], [1211234400000,0.64250], [1211320800000,0.64140], [1211407200000,0.63660], [1211493600000,0.63460], [1211580000000,0.6350], [1211666400000,0.63460], [1211752800000,0.63460], [1211839200000,0.63430], [1211925600000,0.63460], [1212012000000,0.63790], [1212098400000,0.64160], [1212184800000,0.64420], [1212271200000,0.64310], [1212357600000,0.64310], [1212444000000,0.64350], [1212530400000,0.6440], [1212616800000,0.64730], [1212703200000,0.64690], [1212789600000,0.63860], [1212876000000,0.63560], [1212962400000,0.6340], [1213048800000,0.63460], [1213135200000,0.6430], [1213221600000,0.64520], [1213308000000,0.64670], [1213394400000,0.65060], [1213480800000,0.65040], [1213567200000,0.65030], [1213653600000,0.64810], [1213740000000,0.64510], [1213826400000,0.6450], [1213912800000,0.64410], [1213999200000,0.64140], [1214085600000,0.64090], [1214172000000,0.64090], [1214258400000,0.64280], [1214344800000,0.64310], [1214431200000,0.64180], [1214517600000,0.63710], [1214604000000,0.63490], [1214690400000,0.63330], [1214776800000,0.63340], [1214863200000,0.63380], [1214949600000,0.63420], [1215036000000,0.6320], [1215122400000,0.63180], [1215208800000,0.6370], [1215295200000,0.63680], [1215381600000,0.63680], [1215468000000,0.63830], [1215554400000,0.63710], [1215640800000,0.63710], [1215727200000,0.63550], [1215813600000,0.6320], [1215900000000,0.62770], [1215986400000,0.62760], [1216072800000,0.62910], [1216159200000,0.62740], [1216245600000,0.62930], [1216332000000,0.63110], [1216418400000,0.6310], [1216504800000,0.63120], [1216591200000,0.63120], [1216677600000,0.63040], [1216764000000,0.62940], [1216850400000,0.63480], [1216936800000,0.63780], [1217023200000,0.63680], [1217109600000,0.63680], [1217196000000,0.63680], [1217282400000,0.6360], [1217368800000,0.6370], [1217455200000,0.64180], [1217541600000,0.64110], [1217628000000,0.64350], [1217714400000,0.64270], [1217800800000,0.64270], [1217887200000,0.64190], [1217973600000,0.64460], [1218060000000,0.64680], [1218146400000,0.64870], [1218232800000,0.65940], [1218319200000,0.66660], [1218405600000,0.66660], [1218492000000,0.66780], [1218578400000,0.67120], [1218664800000,0.67050], [1218751200000,0.67180], [1218837600000,0.67840], [1218924000000,0.68110], [1219010400000,0.68110], [1219096800000,0.67940], [1219183200000,0.68040], [1219269600000,0.67810], [1219356000000,0.67560], [1219442400000,0.67350], [1219528800000,0.67630], [1219615200000,0.67620], [1219701600000,0.67770], [1219788000000,0.68150], [1219874400000,0.68020], [1219960800000,0.6780], [1220047200000,0.67960], [1220133600000,0.68170], [1220220000000,0.68170], [1220306400000,0.68320], [1220392800000,0.68770], [1220479200000,0.69120], [1220565600000,0.69140], [1220652000000,0.70090], [1220738400000,0.70120], [1220824800000,0.7010], [1220911200000,0.70050]];
+
+ function euroFormatter(v, axis) {
+ return v.toFixed(axis.tickDecimals) + "€";
+ }
+
+ function doPlot(position) {
+ $.plot("#placeholder", [
+ { data: oilprices, label: "Oil price ($)" },
+ { data: exchangerates, label: "USD/EUR exchange rate", yaxis: 2 }
+ ], {
+ xaxes: [ { mode: "time" } ],
+ yaxes: [ { min: 0 }, {
+ // align if we are to the right
+ alignTicksWithAxis: position == "right" ? 1 : null,
+ position: position,
+ tickFormatter: euroFormatter
+ } ],
+ legend: { position: "sw" }
+ });
+ }
+
+ doPlot("right");
+
+ $("button").click(function () {
+ doPlot($(this).text());
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Multiple axes</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>Multiple axis support showing the raw oil price in US $/barrel of crude oil vs. the exchange rate from US $ to €.</p>
+
+ <p>As illustrated, you can put in multiple axes if you need to. For each data series, simply specify the axis number. In the options, you can then configure where you want the extra axes to appear.</p>
+
+ <p>Position axis <button>left</button> or <button>right</button>.</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/axes-time-zones/date.js b/misc/flot/examples/axes-time-zones/date.js
new file mode 100644
index 0000000..2899dda
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/date.js
@@ -0,0 +1,893 @@
+// -----
+// The `timezoneJS.Date` object gives you full-blown timezone support, independent from the timezone set on the end-user's machine running the browser. It uses the Olson zoneinfo files for its timezone data.
+//
+// The constructor function and setter methods use proxy JavaScript Date objects behind the scenes, so you can use strings like '10/22/2006' with the constructor. You also get the same sensible wraparound behavior with numeric parameters (like setting a value of 14 for the month wraps around to the next March).
+//
+// The other significant difference from the built-in JavaScript Date is that `timezoneJS.Date` also has named properties that store the values of year, month, date, etc., so it can be directly serialized to JSON and used for data transfer.
+
+/*
+ * Copyright 2010 Matthew Eernisse (mde@fleegix.org)
+ * and Open Source Applications Foundation
+ *
+ * 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.
+ *
+ * Credits: Ideas included from incomplete JS implementation of Olson
+ * parser, "XMLDAte" by Philippe Goetz (philippe.goetz@wanadoo.fr)
+ *
+ * Contributions:
+ * Jan Niehusmann
+ * Ricky Romero
+ * Preston Hunt (prestonhunt@gmail.com)
+ * Dov. B Katz (dov.katz@morganstanley.com)
+ * Peter Bergström (pbergstr@mac.com)
+ * Long Ho
+ */
+(function () {
+ // Standard initialization stuff to make sure the library is
+ // usable on both client and server (node) side.
+
+ var root = this;
+
+ var timezoneJS;
+ if (typeof exports !== 'undefined') {
+ timezoneJS = exports;
+ } else {
+ timezoneJS = root.timezoneJS = {};
+ }
+
+ timezoneJS.VERSION = '1.0.0';
+
+ // Grab the ajax library from global context.
+ // This can be jQuery, Zepto or fleegix.
+ // You can also specify your own transport mechanism by declaring
+ // `timezoneJS.timezone.transport` to a `function`. More details will follow
+ var $ = root.$ || root.jQuery || root.Zepto
+ , fleegix = root.fleegix
+ // Declare constant list of days and months. Unfortunately this doesn't leave room for i18n due to the Olson data being in English itself
+ , DAYS = timezoneJS.Days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
+ , MONTHS = timezoneJS.Months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
+ , SHORT_MONTHS = {}
+ , SHORT_DAYS = {}
+ , EXACT_DATE_TIME = {}
+ , TZ_REGEXP = new RegExp('^[a-zA-Z]+/');
+
+ //`{ "Jan": 0, "Feb": 1, "Mar": 2, "Apr": 3, "May": 4, "Jun": 5, "Jul": 6, "Aug": 7, "Sep": 8, "Oct": 9, "Nov": 10, "Dec": 11 }`
+ for (var i = 0; i < MONTHS.length; i++) {
+ SHORT_MONTHS[MONTHS[i].substr(0, 3)] = i;
+ }
+
+ //`{ "Sun": 0, "Mon": 1, "Tue": 2, "Wed": 3, "Thu": 4, "Fri": 5, "Sat": 6 }`
+ for (i = 0; i < DAYS.length; i++) {
+ SHORT_DAYS[DAYS[i].substr(0, 3)] = i;
+ }
+
+
+ //Handle array indexOf in IE
+ if (!Array.prototype.indexOf) {
+ Array.prototype.indexOf = function (el) {
+ for (var i = 0; i < this.length; i++ ) {
+ if (el === this[i]) return i;
+ }
+ return -1;
+ }
+ }
+
+ // Format a number to the length = digits. For ex:
+ //
+ // `_fixWidth(2, 2) = '02'`
+ //
+ // `_fixWidth(1998, 2) = '98'`
+ //
+ // This is used to pad numbers in converting date to string in ISO standard.
+ var _fixWidth = function (number, digits) {
+ if (typeof number !== "number") { throw "not a number: " + number; }
+ var s = number.toString();
+ if (number.length > digits) {
+ return number.substr(number.length - digits, number.length);
+ }
+ while (s.length < digits) {
+ s = '0' + s;
+ }
+ return s;
+ };
+
+ // Abstraction layer for different transport layers, including fleegix/jQuery/Zepto
+ //
+ // Object `opts` include
+ //
+ // - `url`: url to ajax query
+ //
+ // - `async`: true for asynchronous, false otherwise. If false, return value will be response from URL. This is true by default
+ //
+ // - `success`: success callback function
+ //
+ // - `error`: error callback function
+ // Returns response from URL if async is false, otherwise the AJAX request object itself
+ var _transport = function (opts) {
+ if ((!fleegix || typeof fleegix.xhr === 'undefined') && (!$ || typeof $.ajax === 'undefined')) {
+ throw new Error('Please use the Fleegix.js XHR module, jQuery ajax, Zepto ajax, or define your own transport mechanism for downloading zone files.');
+ }
+ if (!opts) return;
+ if (!opts.url) throw new Error ('URL must be specified');
+ if (!('async' in opts)) opts.async = true;
+ if (!opts.async) {
+ return fleegix && fleegix.xhr
+ ? fleegix.xhr.doReq({ url: opts.url, async: false })
+ : $.ajax({ url : opts.url, async : false }).responseText;
+ }
+ return fleegix && fleegix.xhr
+ ? fleegix.xhr.send({
+ url : opts.url,
+ method : 'get',
+ handleSuccess : opts.success,
+ handleErr : opts.error
+ })
+ : $.ajax({
+ url : opts.url,
+ dataType: 'text',
+ method : 'GET',
+ error : opts.error,
+ success : opts.success
+ });
+ };
+
+ // Constructor, which is similar to that of the native Date object itself
+ timezoneJS.Date = function () {
+ var args = Array.prototype.slice.apply(arguments)
+ , dt = null
+ , tz = null
+ , arr = [];
+
+
+ //We support several different constructors, including all the ones from `Date` object
+ // with a timezone string at the end.
+ //
+ //- `[tz]`: Returns object with time in `tz` specified.
+ //
+ // - `utcMillis`, `[tz]`: Return object with UTC time = `utcMillis`, in `tz`.
+ //
+ // - `Date`, `[tz]`: Returns object with UTC time = `Date.getTime()`, in `tz`.
+ //
+ // - `year, month, [date,] [hours,] [minutes,] [seconds,] [millis,] [tz]: Same as `Date` object
+ // with tz.
+ //
+ // - `Array`: Can be any combo of the above.
+ //
+ //If 1st argument is an array, we can use it as a list of arguments itself
+ if (Object.prototype.toString.call(args[0]) === '[object Array]') {
+ args = args[0];
+ }
+ if (typeof args[args.length - 1] === 'string' && TZ_REGEXP.test(args[args.length - 1])) {
+ tz = args.pop();
+ }
+ switch (args.length) {
+ case 0:
+ dt = new Date();
+ break;
+ case 1:
+ dt = new Date(args[0]);
+ break;
+ default:
+ for (var i = 0; i < 7; i++) {
+ arr[i] = args[i] || 0;
+ }
+ dt = new Date(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5], arr[6]);
+ break;
+ }
+
+ this._useCache = false;
+ this._tzInfo = {};
+ this._day = 0;
+ this.year = 0;
+ this.month = 0;
+ this.date = 0;
+ this.hours = 0;
+ this.minutes = 0;
+ this.seconds = 0;
+ this.milliseconds = 0;
+ this.timezone = tz || null;
+ //Tricky part:
+ // For the cases where there are 1/2 arguments: `timezoneJS.Date(millis, [tz])` and `timezoneJS.Date(Date, [tz])`. The
+ // Date `dt` created should be in UTC. Thus the way I detect such cases is to determine if `arr` is not populated & `tz`
+ // is specified. Because if `tz` is not specified, `dt` can be in local time.
+ if (arr.length) {
+ this.setFromDateObjProxy(dt);
+ } else {
+ this.setFromTimeProxy(dt.getTime(), tz);
+ }
+ };
+
+ // Implements most of the native Date object
+ timezoneJS.Date.prototype = {
+ getDate: function () { return this.date; },
+ getDay: function () { return this._day; },
+ getFullYear: function () { return this.year; },
+ getMonth: function () { return this.month; },
+ getYear: function () { return this.year; },
+ getHours: function () { return this.hours; },
+ getMilliseconds: function () { return this.milliseconds; },
+ getMinutes: function () { return this.minutes; },
+ getSeconds: function () { return this.seconds; },
+ getUTCDate: function () { return this.getUTCDateProxy().getUTCDate(); },
+ getUTCDay: function () { return this.getUTCDateProxy().getUTCDay(); },
+ getUTCFullYear: function () { return this.getUTCDateProxy().getUTCFullYear(); },
+ getUTCHours: function () { return this.getUTCDateProxy().getUTCHours(); },
+ getUTCMilliseconds: function () { return this.getUTCDateProxy().getUTCMilliseconds(); },
+ getUTCMinutes: function () { return this.getUTCDateProxy().getUTCMinutes(); },
+ getUTCMonth: function () { return this.getUTCDateProxy().getUTCMonth(); },
+ getUTCSeconds: function () { return this.getUTCDateProxy().getUTCSeconds(); },
+ // Time adjusted to user-specified timezone
+ getTime: function () {
+ return this._timeProxy + (this.getTimezoneOffset() * 60 * 1000);
+ },
+ getTimezone: function () { return this.timezone; },
+ getTimezoneOffset: function () { return this.getTimezoneInfo().tzOffset; },
+ getTimezoneAbbreviation: function () { return this.getTimezoneInfo().tzAbbr; },
+ getTimezoneInfo: function () {
+ if (this._useCache) return this._tzInfo;
+ var res;
+ // If timezone is specified, get the correct timezone info based on the Date given
+ if (this.timezone) {
+ res = this.timezone === 'Etc/UTC' || this.timezone === 'Etc/GMT'
+ ? { tzOffset: 0, tzAbbr: 'UTC' }
+ : timezoneJS.timezone.getTzInfo(this._timeProxy, this.timezone);
+ }
+ // If no timezone was specified, use the local browser offset
+ else {
+ res = { tzOffset: this.getLocalOffset(), tzAbbr: null };
+ }
+ this._tzInfo = res;
+ this._useCache = true;
+ return res
+ },
+ getUTCDateProxy: function () {
+ var dt = new Date(this._timeProxy);
+ dt.setUTCMinutes(dt.getUTCMinutes() + this.getTimezoneOffset());
+ return dt;
+ },
+ setDate: function (n) { this.setAttribute('date', n); },
+ setFullYear: function (n) { this.setAttribute('year', n); },
+ setMonth: function (n) { this.setAttribute('month', n); },
+ setYear: function (n) { this.setUTCAttribute('year', n); },
+ setHours: function (n) { this.setAttribute('hours', n); },
+ setMilliseconds: function (n) { this.setAttribute('milliseconds', n); },
+ setMinutes: function (n) { this.setAttribute('minutes', n); },
+ setSeconds: function (n) { this.setAttribute('seconds', n); },
+ setTime: function (n) {
+ if (isNaN(n)) { throw new Error('Units must be a number.'); }
+ this.setFromTimeProxy(n, this.timezone);
+ },
+ setUTCDate: function (n) { this.setUTCAttribute('date', n); },
+ setUTCFullYear: function (n) { this.setUTCAttribute('year', n); },
+ setUTCHours: function (n) { this.setUTCAttribute('hours', n); },
+ setUTCMilliseconds: function (n) { this.setUTCAttribute('milliseconds', n); },
+ setUTCMinutes: function (n) { this.setUTCAttribute('minutes', n); },
+ setUTCMonth: function (n) { this.setUTCAttribute('month', n); },
+ setUTCSeconds: function (n) { this.setUTCAttribute('seconds', n); },
+ setFromDateObjProxy: function (dt) {
+ this.year = dt.getFullYear();
+ this.month = dt.getMonth();
+ this.date = dt.getDate();
+ this.hours = dt.getHours();
+ this.minutes = dt.getMinutes();
+ this.seconds = dt.getSeconds();
+ this.milliseconds = dt.getMilliseconds();
+ this._day = dt.getDay();
+ this._dateProxy = dt;
+ this._timeProxy = Date.UTC(this.year, this.month, this.date, this.hours, this.minutes, this.seconds, this.milliseconds);
+ this._useCache = false;
+ },
+ setFromTimeProxy: function (utcMillis, tz) {
+ var dt = new Date(utcMillis);
+ var tzOffset;
+ tzOffset = tz ? timezoneJS.timezone.getTzInfo(dt, tz).tzOffset : dt.getTimezoneOffset();
+ dt.setTime(utcMillis + (dt.getTimezoneOffset() - tzOffset) * 60000);
+ this.setFromDateObjProxy(dt);
+ },
+ setAttribute: function (unit, n) {
+ if (isNaN(n)) { throw new Error('Units must be a number.'); }
+ var dt = this._dateProxy;
+ var meth = unit === 'year' ? 'FullYear' : unit.substr(0, 1).toUpperCase() + unit.substr(1);
+ dt['set' + meth](n);
+ this.setFromDateObjProxy(dt);
+ },
+ setUTCAttribute: function (unit, n) {
+ if (isNaN(n)) { throw new Error('Units must be a number.'); }
+ var meth = unit === 'year' ? 'FullYear' : unit.substr(0, 1).toUpperCase() + unit.substr(1);
+ var dt = this.getUTCDateProxy();
+ dt['setUTC' + meth](n);
+ dt.setUTCMinutes(dt.getUTCMinutes() - this.getTimezoneOffset());
+ this.setFromTimeProxy(dt.getTime() + this.getTimezoneOffset() * 60000, this.timezone);
+ },
+ setTimezone: function (tz) {
+ var previousOffset = this.getTimezoneInfo().tzOffset;
+ this.timezone = tz;
+ this._useCache = false;
+ // Set UTC minutes offsets by the delta of the two timezones
+ this.setUTCMinutes(this.getUTCMinutes() - this.getTimezoneInfo().tzOffset + previousOffset);
+ },
+ removeTimezone: function () {
+ this.timezone = null;
+ this._useCache = false;
+ },
+ valueOf: function () { return this.getTime(); },
+ clone: function () {
+ return this.timezone ? new timezoneJS.Date(this.getTime(), this.timezone) : new timezoneJS.Date(this.getTime());
+ },
+ toGMTString: function () { return this.toString('EEE, dd MMM yyyy HH:mm:ss Z', 'Etc/GMT'); },
+ toLocaleString: function () {},
+ toLocaleDateString: function () {},
+ toLocaleTimeString: function () {},
+ toSource: function () {},
+ toISOString: function () { return this.toString('yyyy-MM-ddTHH:mm:ss.SSS', 'Etc/UTC') + 'Z'; },
+ toJSON: function () { return this.toISOString(); },
+ // Allows different format following ISO8601 format:
+ toString: function (format, tz) {
+ // Default format is the same as toISOString
+ if (!format) format = 'yyyy-MM-dd HH:mm:ss';
+ var result = format;
+ var tzInfo = tz ? timezoneJS.timezone.getTzInfo(this.getTime(), tz) : this.getTimezoneInfo();
+ var _this = this;
+ // If timezone is specified, get a clone of the current Date object and modify it
+ if (tz) {
+ _this = this.clone();
+ _this.setTimezone(tz);
+ }
+ var hours = _this.getHours();
+ return result
+ // fix the same characters in Month names
+ .replace(/a+/g, function () { return 'k'; })
+ // `y`: year
+ .replace(/y+/g, function (token) { return _fixWidth(_this.getFullYear(), token.length); })
+ // `d`: date
+ .replace(/d+/g, function (token) { return _fixWidth(_this.getDate(), token.length); })
+ // `m`: minute
+ .replace(/m+/g, function (token) { return _fixWidth(_this.getMinutes(), token.length); })
+ // `s`: second
+ .replace(/s+/g, function (token) { return _fixWidth(_this.getSeconds(), token.length); })
+ // `S`: millisecond
+ .replace(/S+/g, function (token) { return _fixWidth(_this.getMilliseconds(), token.length); })
+ // `M`: month. Note: `MM` will be the numeric representation (e.g February is 02) but `MMM` will be text representation (e.g February is Feb)
+ .replace(/M+/g, function (token) {
+ var _month = _this.getMonth(),
+ _len = token.length;
+ if (_len > 3) {
+ return timezoneJS.Months[_month];
+ } else if (_len > 2) {
+ return timezoneJS.Months[_month].substring(0, _len);
+ }
+ return _fixWidth(_month + 1, _len);
+ })
+ // `k`: AM/PM
+ .replace(/k+/g, function () {
+ if (hours >= 12) {
+ if (hours > 12) {
+ hours -= 12;
+ }
+ return 'PM';
+ }
+ return 'AM';
+ })
+ // `H`: hour
+ .replace(/H+/g, function (token) { return _fixWidth(hours, token.length); })
+ // `E`: day
+ .replace(/E+/g, function (token) { return DAYS[_this.getDay()].substring(0, token.length); })
+ // `Z`: timezone abbreviation
+ .replace(/Z+/gi, function () { return tzInfo.tzAbbr; });
+ },
+ toUTCString: function () { return this.toGMTString(); },
+ civilToJulianDayNumber: function (y, m, d) {
+ var a;
+ // Adjust for zero-based JS-style array
+ m++;
+ if (m > 12) {
+ a = parseInt(m/12, 10);
+ m = m % 12;
+ y += a;
+ }
+ if (m <= 2) {
+ y -= 1;
+ m += 12;
+ }
+ a = Math.floor(y / 100);
+ var b = 2 - a + Math.floor(a / 4)
+ , jDt = Math.floor(365.25 * (y + 4716)) + Math.floor(30.6001 * (m + 1)) + d + b - 1524;
+ return jDt;
+ },
+ getLocalOffset: function () {
+ return this._dateProxy.getTimezoneOffset();
+ }
+ };
+
+
+ timezoneJS.timezone = new function () {
+ var _this = this
+ , regionMap = {'Etc':'etcetera','EST':'northamerica','MST':'northamerica','HST':'northamerica','EST5EDT':'northamerica','CST6CDT':'northamerica','MST7MDT':'northamerica','PST8PDT':'northamerica','America':'northamerica','Pacific':'australasia','Atlantic':'europe','Africa':'africa','Indian':'africa','Antarctica':'antarctica','Asia':'asia','Australia':'australasia','Europe':'europe','WET':'europe','CET':'europe','MET':'europe','EET':'europe'}
+ , regionExceptions = {'Pacific/Honolulu':'northamerica','Atlantic/Bermuda':'northamerica','Atlantic/Cape_Verde':'africa','Atlantic/St_Helena':'africa','Indian/Kerguelen':'antarctica','Indian/Chagos':'asia','Indian/Maldives':'asia','Indian/Christmas':'australasia','Indian/Cocos':'australasia','America/Danmarkshavn':'europe','America/Scoresbysund':'europe','America/Godthab':'europe','America/Thule':'europe','Asia/Yekaterinburg':'europe','Asia/Omsk':'europe','Asia/Novosibirsk':'europe','Asia/Krasnoyarsk':'europe','Asia/Irkutsk':'europe','Asia/Yakutsk':'europe','Asia/Vladivostok':'europe','Asia/Sakhalin':'europe','Asia/Magadan':'europe','Asia/Kamchatka':'europe','Asia/Anadyr':'europe','Africa/Ceuta':'europe','America/Argentina/Buenos_Aires':'southamerica','America/Argentina/Cordoba':'southamerica','America/Argentina/Tucuman':'southamerica','America/Argentina/La_Rioja':'southamerica','America/Argentina/San_Juan':'southamerica','America/Argentina/Jujuy':'southamerica','America/Argentina/Catamarca':'southamerica','America/Argentina/Mendoza':'southamerica','America/Argentina/Rio_Gallegos':'southamerica','America/Argentina/Ushuaia':'southamerica','America/Aruba':'southamerica','America/La_Paz':'southamerica','America/Noronha':'southamerica','America/Belem':'southamerica','America/Fortaleza':'southamerica','America/Recife':'southamerica','America/Araguaina':'southamerica','America/Maceio':'southamerica','America/Bahia':'southamerica','America/Sao_Paulo':'southamerica','America/Campo_Grande':'southamerica','America/Cuiaba':'southamerica','America/Porto_Velho':'southamerica','America/Boa_Vista':'southamerica','America/Manaus':'southamerica','America/Eirunepe':'southamerica','America/Rio_Branco':'southamerica','America/Santiago':'southamerica','Pacific/Easter':'southamerica','America/Bogota':'southamerica','America/Curacao':'southamerica','America/Guayaquil':'southamerica','Pacific/Galapagos':'southamerica','Atlantic/Stanley':'southamerica','America/Cayenne':'southamerica','America/Guyana':'southamerica','America/Asuncion':'southamerica','America/Lima':'southamerica','Atlantic/South_Georgia':'southamerica','America/Paramaribo':'southamerica','America/Port_of_Spain':'southamerica','America/Montevideo':'southamerica','America/Caracas':'southamerica'};
+ function invalidTZError(t) { throw new Error('Timezone "' + t + '" is either incorrect, or not loaded in the timezone registry.'); }
+ function builtInLoadZoneFile(fileName, opts) {
+ var url = _this.zoneFileBasePath + '/' + fileName;
+ return !opts || !opts.async
+ ? _this.parseZones(_this.transport({ url : url, async : false }))
+ : _this.transport({
+ async: true,
+ url : url,
+ success : function (str) {
+ if (_this.parseZones(str) && typeof opts.callback === 'function') {
+ opts.callback();
+ }
+ return true;
+ },
+ error : function () {
+ throw new Error('Error retrieving "' + url + '" zoneinfo files');
+ }
+ });
+ }
+ function getRegionForTimezone(tz) {
+ var exc = regionExceptions[tz]
+ , reg
+ , ret;
+ if (exc) return exc;
+ reg = tz.split('/')[0];
+ ret = regionMap[reg];
+ // If there's nothing listed in the main regions for this TZ, check the 'backward' links
+ if (ret) return ret;
+ var link = _this.zones[tz];
+ if (typeof link === 'string') {
+ return getRegionForTimezone(link);
+ }
+ // Backward-compat file hasn't loaded yet, try looking in there
+ if (!_this.loadedZones.backward) {
+ // This is for obvious legacy zones (e.g., Iceland) that don't even have a prefix like "America/" that look like normal zones
+ _this.loadZoneFile('backward');
+ return getRegionForTimezone(tz);
+ }
+ invalidTZError(tz);
+ }
+ function parseTimeString(str) {
+ var pat = /(\d+)(?::0*(\d*))?(?::0*(\d*))?([wsugz])?$/;
+ var hms = str.match(pat);
+ hms[1] = parseInt(hms[1], 10);
+ hms[2] = hms[2] ? parseInt(hms[2], 10) : 0;
+ hms[3] = hms[3] ? parseInt(hms[3], 10) : 0;
+
+ return hms;
+ }
+ function processZone(z) {
+ if (!z[3]) { return; }
+ var yea = parseInt(z[3], 10);
+ var mon = 11;
+ var dat = 31;
+ if (z[4]) {
+ mon = SHORT_MONTHS[z[4].substr(0, 3)];
+ dat = parseInt(z[5], 10) || 1;
+ }
+ var string = z[6] ? z[6] : '00:00:00'
+ , t = parseTimeString(string);
+ return [yea, mon, dat, t[1], t[2], t[3]];
+ }
+ function getZone(dt, tz) {
+ var utcMillis = typeof dt === 'number' ? dt : new Date(dt).getTime();
+ var t = tz;
+ var zoneList = _this.zones[t];
+ // Follow links to get to an actual zone
+ while (typeof zoneList === "string") {
+ t = zoneList;
+ zoneList = _this.zones[t];
+ }
+ if (!zoneList) {
+ // Backward-compat file hasn't loaded yet, try looking in there
+ if (!_this.loadedZones.backward) {
+ //This is for backward entries like "America/Fort_Wayne" that
+ // getRegionForTimezone *thinks* it has a region file and zone
+ // for (e.g., America => 'northamerica'), but in reality it's a
+ // legacy zone we need the backward file for.
+ _this.loadZoneFile('backward');
+ return getZone(dt, tz);
+ }
+ invalidTZError(t);
+ }
+ if (zoneList.length === 0) {
+ throw new Error('No Zone found for "' + tz + '" on ' + dt);
+ }
+ //Do backwards lookup since most use cases deal with newer dates.
+ for (var i = zoneList.length - 1; i >= 0; i--) {
+ var z = zoneList[i];
+ if (z[3] && utcMillis > z[3]) break;
+ }
+ return zoneList[i+1];
+ }
+ function getBasicOffset(time) {
+ var off = parseTimeString(time)
+ , adj = time.indexOf('-') === 0 ? -1 : 1;
+ off = adj * (((off[1] * 60 + off[2]) * 60 + off[3]) * 1000);
+ return off/60/1000;
+ }
+
+ //if isUTC is true, date is given in UTC, otherwise it's given
+ // in local time (ie. date.getUTC*() returns local time components)
+ function getRule(dt, zone, isUTC) {
+ var date = typeof dt === 'number' ? new Date(dt) : dt;
+ var ruleset = zone[1];
+ var basicOffset = zone[0];
+
+ //Convert a date to UTC. Depending on the 'type' parameter, the date
+ // parameter may be:
+ //
+ // - `u`, `g`, `z`: already UTC (no adjustment).
+ //
+ // - `s`: standard time (adjust for time zone offset but not for DST)
+ //
+ // - `w`: wall clock time (adjust for both time zone and DST offset).
+ //
+ // DST adjustment is done using the rule given as third argument.
+ var convertDateToUTC = function (date, type, rule) {
+ var offset = 0;
+
+ if (type === 'u' || type === 'g' || type === 'z') { // UTC
+ offset = 0;
+ } else if (type === 's') { // Standard Time
+ offset = basicOffset;
+ } else if (type === 'w' || !type) { // Wall Clock Time
+ offset = getAdjustedOffset(basicOffset, rule);
+ } else {
+ throw("unknown type " + type);
+ }
+ offset *= 60 * 1000; // to millis
+
+ return new Date(date.getTime() + offset);
+ };
+
+ //Step 1: Find applicable rules for this year.
+ //
+ //Step 2: Sort the rules by effective date.
+ //
+ //Step 3: Check requested date to see if a rule has yet taken effect this year. If not,
+ //
+ //Step 4: Get the rules for the previous year. If there isn't an applicable rule for last year, then
+ // there probably is no current time offset since they seem to explicitly turn off the offset
+ // when someone stops observing DST.
+ //
+ // FIXME if this is not the case and we'll walk all the way back (ugh).
+ //
+ //Step 5: Sort the rules by effective date.
+ //Step 6: Apply the most recent rule before the current time.
+ var convertRuleToExactDateAndTime = function (yearAndRule, prevRule) {
+ var year = yearAndRule[0]
+ , rule = yearAndRule[1];
+ // Assume that the rule applies to the year of the given date.
+
+ var hms = rule[5];
+ var effectiveDate;
+
+ if (!EXACT_DATE_TIME[year])
+ EXACT_DATE_TIME[year] = {};
+
+ // Result for given parameters is already stored
+ if (EXACT_DATE_TIME[year][rule])
+ effectiveDate = EXACT_DATE_TIME[year][rule];
+ else {
+ //If we have a specific date, use that!
+ if (!isNaN(rule[4])) {
+ effectiveDate = new Date(Date.UTC(year, SHORT_MONTHS[rule[3]], rule[4], hms[1], hms[2], hms[3], 0));
+ }
+ //Let's hunt for the date.
+ else {
+ var targetDay
+ , operator;
+ //Example: `lastThu`
+ if (rule[4].substr(0, 4) === "last") {
+ // Start at the last day of the month and work backward.
+ effectiveDate = new Date(Date.UTC(year, SHORT_MONTHS[rule[3]] + 1, 1, hms[1] - 24, hms[2], hms[3], 0));
+ targetDay = SHORT_DAYS[rule[4].substr(4, 3)];
+ operator = "<=";
+ }
+ //Example: `Sun>=15`
+ else {
+ //Start at the specified date.
+ effectiveDate = new Date(Date.UTC(year, SHORT_MONTHS[rule[3]], rule[4].substr(5), hms[1], hms[2], hms[3], 0));
+ targetDay = SHORT_DAYS[rule[4].substr(0, 3)];
+ operator = rule[4].substr(3, 2);
+ }
+ var ourDay = effectiveDate.getUTCDay();
+ //Go forwards.
+ if (operator === ">=") {
+ effectiveDate.setUTCDate(effectiveDate.getUTCDate() + (targetDay - ourDay + ((targetDay < ourDay) ? 7 : 0)));
+ }
+ //Go backwards. Looking for the last of a certain day, or operator is "<=" (less likely).
+ else {
+ effectiveDate.setUTCDate(effectiveDate.getUTCDate() + (targetDay - ourDay - ((targetDay > ourDay) ? 7 : 0)));
+ }
+ }
+ EXACT_DATE_TIME[year][rule] = effectiveDate;
+ }
+
+
+ //If previous rule is given, correct for the fact that the starting time of the current
+ // rule may be specified in local time.
+ if (prevRule) {
+ effectiveDate = convertDateToUTC(effectiveDate, hms[4], prevRule);
+ }
+ return effectiveDate;
+ };
+
+ var findApplicableRules = function (year, ruleset) {
+ var applicableRules = [];
+ for (var i = 0; ruleset && i < ruleset.length; i++) {
+ //Exclude future rules.
+ if (ruleset[i][0] <= year &&
+ (
+ // Date is in a set range.
+ ruleset[i][1] >= year ||
+ // Date is in an "only" year.
+ (ruleset[i][0] === year && ruleset[i][1] === "only") ||
+ //We're in a range from the start year to infinity.
+ ruleset[i][1] === "max"
+ )
+ ) {
+ //It's completely okay to have any number of matches here.
+ // Normally we should only see two, but that doesn't preclude other numbers of matches.
+ // These matches are applicable to this year.
+ applicableRules.push([year, ruleset[i]]);
+ }
+ }
+ return applicableRules;
+ };
+
+ var compareDates = function (a, b, prev) {
+ var year, rule;
+ if (a.constructor !== Date) {
+ year = a[0];
+ rule = a[1];
+ a = (!prev && EXACT_DATE_TIME[year] && EXACT_DATE_TIME[year][rule])
+ ? EXACT_DATE_TIME[year][rule]
+ : convertRuleToExactDateAndTime(a, prev);
+ } else if (prev) {
+ a = convertDateToUTC(a, isUTC ? 'u' : 'w', prev);
+ }
+ if (b.constructor !== Date) {
+ year = b[0];
+ rule = b[1];
+ b = (!prev && EXACT_DATE_TIME[year] && EXACT_DATE_TIME[year][rule]) ? EXACT_DATE_TIME[year][rule]
+ : convertRuleToExactDateAndTime(b, prev);
+ } else if (prev) {
+ b = convertDateToUTC(b, isUTC ? 'u' : 'w', prev);
+ }
+ a = Number(a);
+ b = Number(b);
+ return a - b;
+ };
+
+ var year = date.getUTCFullYear();
+ var applicableRules;
+
+ applicableRules = findApplicableRules(year, _this.rules[ruleset]);
+ applicableRules.push(date);
+ //While sorting, the time zone in which the rule starting time is specified
+ // is ignored. This is ok as long as the timespan between two DST changes is
+ // larger than the DST offset, which is probably always true.
+ // As the given date may indeed be close to a DST change, it may get sorted
+ // to a wrong position (off by one), which is corrected below.
+ applicableRules.sort(compareDates);
+
+ //If there are not enough past DST rules...
+ if (applicableRules.indexOf(date) < 2) {
+ applicableRules = applicableRules.concat(findApplicableRules(year-1, _this.rules[ruleset]));
+ applicableRules.sort(compareDates);
+ }
+ var pinpoint = applicableRules.indexOf(date);
+ if (pinpoint > 1 && compareDates(date, applicableRules[pinpoint-1], applicableRules[pinpoint-2][1]) < 0) {
+ //The previous rule does not really apply, take the one before that.
+ return applicableRules[pinpoint - 2][1];
+ } else if (pinpoint > 0 && pinpoint < applicableRules.length - 1 && compareDates(date, applicableRules[pinpoint+1], applicableRules[pinpoint-1][1]) > 0) {
+
+ //The next rule does already apply, take that one.
+ return applicableRules[pinpoint + 1][1];
+ } else if (pinpoint === 0) {
+ //No applicable rule found in this and in previous year.
+ return null;
+ }
+ return applicableRules[pinpoint - 1][1];
+ }
+ function getAdjustedOffset(off, rule) {
+ return -Math.ceil(rule[6] - off);
+ }
+ function getAbbreviation(zone, rule) {
+ var res;
+ var base = zone[2];
+ if (base.indexOf('%s') > -1) {
+ var repl;
+ if (rule) {
+ repl = rule[7] === '-' ? '' : rule[7];
+ }
+ //FIXME: Right now just falling back to Standard --
+ // apparently ought to use the last valid rule,
+ // although in practice that always ought to be Standard
+ else {
+ repl = 'S';
+ }
+ res = base.replace('%s', repl);
+ }
+ else if (base.indexOf('/') > -1) {
+ //Chose one of two alternative strings.
+ res = base.split("/", 2)[rule[6] ? 1 : 0];
+ } else {
+ res = base;
+ }
+ return res;
+ }
+
+ this.zoneFileBasePath;
+ this.zoneFiles = ['africa', 'antarctica', 'asia', 'australasia', 'backward', 'etcetera', 'europe', 'northamerica', 'pacificnew', 'southamerica'];
+ this.loadingSchemes = {
+ PRELOAD_ALL: 'preloadAll',
+ LAZY_LOAD: 'lazyLoad',
+ MANUAL_LOAD: 'manualLoad'
+ };
+ this.loadingScheme = this.loadingSchemes.LAZY_LOAD;
+ this.loadedZones = {};
+ this.zones = {};
+ this.rules = {};
+
+ this.init = function (o) {
+ var opts = { async: true }
+ , def = this.defaultZoneFile = this.loadingScheme === this.loadingSchemes.PRELOAD_ALL
+ ? this.zoneFiles
+ : 'northamerica'
+ , done = 0
+ , callbackFn;
+ //Override default with any passed-in opts
+ for (var p in o) {
+ opts[p] = o[p];
+ }
+ if (typeof def === 'string') {
+ return this.loadZoneFile(def, opts);
+ }
+ //Wraps callback function in another one that makes
+ // sure all files have been loaded.
+ callbackFn = opts.callback;
+ opts.callback = function () {
+ done++;
+ (done === def.length) && typeof callbackFn === 'function' && callbackFn();
+ };
+ for (var i = 0; i < def.length; i++) {
+ this.loadZoneFile(def[i], opts);
+ }
+ };
+
+ //Get the zone files via XHR -- if the sync flag
+ // is set to true, it's being called by the lazy-loading
+ // mechanism, so the result needs to be returned inline.
+ this.loadZoneFile = function (fileName, opts) {
+ if (typeof this.zoneFileBasePath === 'undefined') {
+ throw new Error('Please define a base path to your zone file directory -- timezoneJS.timezone.zoneFileBasePath.');
+ }
+ //Ignore already loaded zones.
+ if (this.loadedZones[fileName]) {
+ return;
+ }
+ this.loadedZones[fileName] = true;
+ return builtInLoadZoneFile(fileName, opts);
+ };
+ this.loadZoneJSONData = function (url, sync) {
+ var processData = function (data) {
+ data = eval('('+ data +')');
+ for (var z in data.zones) {
+ _this.zones[z] = data.zones[z];
+ }
+ for (var r in data.rules) {
+ _this.rules[r] = data.rules[r];
+ }
+ };
+ return sync
+ ? processData(_this.transport({ url : url, async : false }))
+ : _this.transport({ url : url, success : processData });
+ };
+ this.loadZoneDataFromObject = function (data) {
+ if (!data) { return; }
+ for (var z in data.zones) {
+ _this.zones[z] = data.zones[z];
+ }
+ for (var r in data.rules) {
+ _this.rules[r] = data.rules[r];
+ }
+ };
+ this.getAllZones = function () {
+ var arr = [];
+ for (var z in this.zones) { arr.push(z); }
+ return arr.sort();
+ };
+ this.parseZones = function (str) {
+ var lines = str.split('\n')
+ , arr = []
+ , chunk = ''
+ , l
+ , zone = null
+ , rule = null;
+ for (var i = 0; i < lines.length; i++) {
+ l = lines[i];
+ if (l.match(/^\s/)) {
+ l = "Zone " + zone + l;
+ }
+ l = l.split("#")[0];
+ if (l.length > 3) {
+ arr = l.split(/\s+/);
+ chunk = arr.shift();
+ //Ignore Leap.
+ switch (chunk) {
+ case 'Zone':
+ zone = arr.shift();
+ if (!_this.zones[zone]) {
+ _this.zones[zone] = [];
+ }
+ if (arr.length < 3) break;
+ //Process zone right here and replace 3rd element with the processed array.
+ arr.splice(3, arr.length, processZone(arr));
+ if (arr[3]) arr[3] = Date.UTC.apply(null, arr[3]);
+ arr[0] = -getBasicOffset(arr[0]);
+ _this.zones[zone].push(arr);
+ break;
+ case 'Rule':
+ rule = arr.shift();
+ if (!_this.rules[rule]) {
+ _this.rules[rule] = [];
+ }
+ //Parse int FROM year and TO year
+ arr[0] = parseInt(arr[0], 10);
+ arr[1] = parseInt(arr[1], 10) || arr[1];
+ //Parse time string AT
+ arr[5] = parseTimeString(arr[5]);
+ //Parse offset SAVE
+ arr[6] = getBasicOffset(arr[6]);
+ _this.rules[rule].push(arr);
+ break;
+ case 'Link':
+ //No zones for these should already exist.
+ if (_this.zones[arr[1]]) {
+ throw new Error('Error with Link ' + arr[1] + '. Cannot create link of a preexisted zone.');
+ }
+ //Create the link.
+ _this.zones[arr[1]] = arr[0];
+ break;
+ }
+ }
+ }
+ return true;
+ };
+ //Expose transport mechanism and allow overwrite.
+ this.transport = _transport;
+ this.getTzInfo = function (dt, tz, isUTC) {
+ //Lazy-load any zones not yet loaded.
+ if (this.loadingScheme === this.loadingSchemes.LAZY_LOAD) {
+ //Get the correct region for the zone.
+ var zoneFile = getRegionForTimezone(tz);
+ if (!zoneFile) {
+ throw new Error('Not a valid timezone ID.');
+ }
+ if (!this.loadedZones[zoneFile]) {
+ //Get the file and parse it -- use synchronous XHR.
+ this.loadZoneFile(zoneFile);
+ }
+ }
+ var z = getZone(dt, tz);
+ var off = z[0];
+ //See if the offset needs adjustment.
+ var rule = getRule(dt, z, isUTC);
+ if (rule) {
+ off = getAdjustedOffset(off, rule);
+ }
+ var abbr = getAbbreviation(z, rule);
+ return { tzOffset: off, tzAbbr: abbr };
+ };
+ };
+}).call(this);
diff --git a/misc/flot/examples/axes-time-zones/index.html b/misc/flot/examples/axes-time-zones/index.html
new file mode 100644
index 0000000..5444241
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/index.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Time zones</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.time.js"></script>
+ <script language="javascript" type="text/javascript" src="date.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ timezoneJS.timezone.zoneFileBasePath = "tz";
+ timezoneJS.timezone.defaultZoneFile = [];
+ timezoneJS.timezone.init({async: false});
+
+ var d = [
+ [Date.UTC(2011, 2, 12, 14, 0, 0), 28],
+ [Date.UTC(2011, 2, 12, 15, 0, 0), 27],
+ [Date.UTC(2011, 2, 12, 16, 0, 0), 25],
+ [Date.UTC(2011, 2, 12, 17, 0, 0), 19],
+ [Date.UTC(2011, 2, 12, 18, 0, 0), 16],
+ [Date.UTC(2011, 2, 12, 19, 0, 0), 14],
+ [Date.UTC(2011, 2, 12, 20, 0, 0), 11],
+ [Date.UTC(2011, 2, 12, 21, 0, 0), 9],
+ [Date.UTC(2011, 2, 12, 22, 0, 0), 7.5],
+ [Date.UTC(2011, 2, 12, 23, 0, 0), 6],
+ [Date.UTC(2011, 2, 13, 0, 0, 0), 5],
+ [Date.UTC(2011, 2, 13, 1, 0, 0), 6],
+ [Date.UTC(2011, 2, 13, 2, 0, 0), 7.5],
+ [Date.UTC(2011, 2, 13, 3, 0, 0), 9],
+ [Date.UTC(2011, 2, 13, 4, 0, 0), 11],
+ [Date.UTC(2011, 2, 13, 5, 0, 0), 14],
+ [Date.UTC(2011, 2, 13, 6, 0, 0), 16],
+ [Date.UTC(2011, 2, 13, 7, 0, 0), 19],
+ [Date.UTC(2011, 2, 13, 8, 0, 0), 25],
+ [Date.UTC(2011, 2, 13, 9, 0, 0), 27],
+ [Date.UTC(2011, 2, 13, 10, 0, 0), 28],
+ [Date.UTC(2011, 2, 13, 11, 0, 0), 29],
+ [Date.UTC(2011, 2, 13, 12, 0, 0), 29.5],
+ [Date.UTC(2011, 2, 13, 13, 0, 0), 29],
+ [Date.UTC(2011, 2, 13, 14, 0, 0), 28],
+ [Date.UTC(2011, 2, 13, 15, 0, 0), 27],
+ [Date.UTC(2011, 2, 13, 16, 0, 0), 25],
+ [Date.UTC(2011, 2, 13, 17, 0, 0), 19],
+ [Date.UTC(2011, 2, 13, 18, 0, 0), 16],
+ [Date.UTC(2011, 2, 13, 19, 0, 0), 14],
+ [Date.UTC(2011, 2, 13, 20, 0, 0), 11],
+ [Date.UTC(2011, 2, 13, 21, 0, 0), 9],
+ [Date.UTC(2011, 2, 13, 22, 0, 0), 7.5],
+ [Date.UTC(2011, 2, 13, 23, 0, 0), 6]
+ ];
+
+ var plot = $.plot("#placeholderUTC", [d], {
+ xaxis: {
+ mode: "time"
+ }
+ });
+
+ var plot = $.plot("#placeholderLocal", [d], {
+ xaxis: {
+ mode: "time",
+ timezone: "browser"
+ }
+ });
+
+ var plot = $.plot("#placeholderChicago", [d], {
+ xaxis: {
+ mode: "time",
+ timezone: "America/Chicago"
+ }
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Time zones</h2>
+ </div>
+
+ <div id="content">
+
+ <h3>UTC</h3>
+ <div class="demo-container" style="height: 300px;">
+ <div id="placeholderUTC" class="demo-placeholder"></div>
+ </div>
+
+ <h3>Browser</h3>
+ <div class="demo-container" style="height: 300px;">
+ <div id="placeholderLocal" class="demo-placeholder"></div>
+ </div>
+
+ <h3>Chicago</h3>
+ <div class="demo-container" style="height: 300px;">
+ <div id="placeholderChicago" class="demo-placeholder"></div>
+ </div>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/axes-time-zones/tz/africa b/misc/flot/examples/axes-time-zones/tz/africa
new file mode 100644
index 0000000..54c7a1e
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/africa
@@ -0,0 +1,1181 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# This data is by no means authoritative; if you think you know better,
+# go ahead and edit the file (and please send any changes to
+# tz@iana.org for general use in the future).
+
+# From Paul Eggert (2006-03-22):
+#
+# A good source for time zone historical data outside the U.S. is
+# Thomas G. Shanks and Rique Pottenger, The International Atlas (6th edition),
+# San Diego: ACS Publications, Inc. (2003).
+#
+# Gwillim Law writes that a good source
+# for recent time zone data is the International Air Transport
+# Association's Standard Schedules Information Manual (IATA SSIM),
+# published semiannually. Law sent in several helpful summaries
+# of the IATA's data after 1990.
+#
+# Except where otherwise noted, Shanks & Pottenger is the source for
+# entries through 1990, and IATA SSIM is the source for entries afterwards.
+#
+# Another source occasionally used is Edward W. Whitman, World Time Differences,
+# Whitman Publishing Co, 2 Niagara Av, Ealing, London (undated), which
+# I found in the UCLA library.
+#
+# A reliable and entertaining source about time zones is
+# Derek Howse, Greenwich time and longitude, Philip Wilson Publishers (1997).
+#
+# Previous editions of this database used WAT, CAT, SAT, and EAT
+# for +0:00 through +3:00, respectively,
+# but Mark R V Murray reports that
+# `SAST' is the official abbreviation for +2:00 in the country of South Africa,
+# `CAT' is commonly used for +2:00 in countries north of South Africa, and
+# `WAT' is probably the best name for +1:00, as the common phrase for
+# the area that includes Nigeria is ``West Africa''.
+# He has heard of ``Western Sahara Time'' for +0:00 but can find no reference.
+#
+# To make things confusing, `WAT' seems to have been used for -1:00 long ago;
+# I'd guess that this was because people needed _some_ name for -1:00,
+# and at the time, far west Africa was the only major land area in -1:00.
+# This usage is now obsolete, as the last use of -1:00 on the African
+# mainland seems to have been 1976 in Western Sahara.
+#
+# To summarize, the following abbreviations seem to have some currency:
+# -1:00 WAT West Africa Time (no longer used)
+# 0:00 GMT Greenwich Mean Time
+# 2:00 CAT Central Africa Time
+# 2:00 SAST South Africa Standard Time
+# and Murray suggests the following abbreviation:
+# 1:00 WAT West Africa Time
+# I realize that this leads to `WAT' being used for both -1:00 and 1:00
+# for times before 1976, but this is the best I can think of
+# until we get more information.
+#
+# I invented the following abbreviations; corrections are welcome!
+# 2:00 WAST West Africa Summer Time
+# 2:30 BEAT British East Africa Time (no longer used)
+# 2:45 BEAUT British East Africa Unified Time (no longer used)
+# 3:00 CAST Central Africa Summer Time (no longer used)
+# 3:00 SAST South Africa Summer Time (no longer used)
+# 3:00 EAT East Africa Time
+# 4:00 EAST East Africa Summer Time (no longer used)
+
+# Algeria
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Algeria 1916 only - Jun 14 23:00s 1:00 S
+Rule Algeria 1916 1919 - Oct Sun>=1 23:00s 0 -
+Rule Algeria 1917 only - Mar 24 23:00s 1:00 S
+Rule Algeria 1918 only - Mar 9 23:00s 1:00 S
+Rule Algeria 1919 only - Mar 1 23:00s 1:00 S
+Rule Algeria 1920 only - Feb 14 23:00s 1:00 S
+Rule Algeria 1920 only - Oct 23 23:00s 0 -
+Rule Algeria 1921 only - Mar 14 23:00s 1:00 S
+Rule Algeria 1921 only - Jun 21 23:00s 0 -
+Rule Algeria 1939 only - Sep 11 23:00s 1:00 S
+Rule Algeria 1939 only - Nov 19 1:00 0 -
+Rule Algeria 1944 1945 - Apr Mon>=1 2:00 1:00 S
+Rule Algeria 1944 only - Oct 8 2:00 0 -
+Rule Algeria 1945 only - Sep 16 1:00 0 -
+Rule Algeria 1971 only - Apr 25 23:00s 1:00 S
+Rule Algeria 1971 only - Sep 26 23:00s 0 -
+Rule Algeria 1977 only - May 6 0:00 1:00 S
+Rule Algeria 1977 only - Oct 21 0:00 0 -
+Rule Algeria 1978 only - Mar 24 1:00 1:00 S
+Rule Algeria 1978 only - Sep 22 3:00 0 -
+Rule Algeria 1980 only - Apr 25 0:00 1:00 S
+Rule Algeria 1980 only - Oct 31 2:00 0 -
+# Shanks & Pottenger give 0:09:20 for Paris Mean Time; go with Howse's
+# more precise 0:09:21.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Algiers 0:12:12 - LMT 1891 Mar 15 0:01
+ 0:09:21 - PMT 1911 Mar 11 # Paris Mean Time
+ 0:00 Algeria WE%sT 1940 Feb 25 2:00
+ 1:00 Algeria CE%sT 1946 Oct 7
+ 0:00 - WET 1956 Jan 29
+ 1:00 - CET 1963 Apr 14
+ 0:00 Algeria WE%sT 1977 Oct 21
+ 1:00 Algeria CE%sT 1979 Oct 26
+ 0:00 Algeria WE%sT 1981 May
+ 1:00 - CET
+
+# Angola
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Luanda 0:52:56 - LMT 1892
+ 0:52:04 - AOT 1911 May 26 # Angola Time
+ 1:00 - WAT
+
+# Benin
+# Whitman says they switched to 1:00 in 1946, not 1934;
+# go with Shanks & Pottenger.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Porto-Novo 0:10:28 - LMT 1912
+ 0:00 - GMT 1934 Feb 26
+ 1:00 - WAT
+
+# Botswana
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Gaborone 1:43:40 - LMT 1885
+ 2:00 - CAT 1943 Sep 19 2:00
+ 2:00 1:00 CAST 1944 Mar 19 2:00
+ 2:00 - CAT
+
+# Burkina Faso
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Ouagadougou -0:06:04 - LMT 1912
+ 0:00 - GMT
+
+# Burundi
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Bujumbura 1:57:28 - LMT 1890
+ 2:00 - CAT
+
+# Cameroon
+# Whitman says they switched to 1:00 in 1920; go with Shanks & Pottenger.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Douala 0:38:48 - LMT 1912
+ 1:00 - WAT
+
+# Cape Verde
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Atlantic/Cape_Verde -1:34:04 - LMT 1907 # Praia
+ -2:00 - CVT 1942 Sep
+ -2:00 1:00 CVST 1945 Oct 15
+ -2:00 - CVT 1975 Nov 25 2:00
+ -1:00 - CVT
+
+# Central African Republic
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Bangui 1:14:20 - LMT 1912
+ 1:00 - WAT
+
+# Chad
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Ndjamena 1:00:12 - LMT 1912
+ 1:00 - WAT 1979 Oct 14
+ 1:00 1:00 WAST 1980 Mar 8
+ 1:00 - WAT
+
+# Comoros
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Indian/Comoro 2:53:04 - LMT 1911 Jul # Moroni, Gran Comoro
+ 3:00 - EAT
+
+# Democratic Republic of Congo
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Kinshasa 1:01:12 - LMT 1897 Nov 9
+ 1:00 - WAT
+Zone Africa/Lubumbashi 1:49:52 - LMT 1897 Nov 9
+ 2:00 - CAT
+
+# Republic of the Congo
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Brazzaville 1:01:08 - LMT 1912
+ 1:00 - WAT
+
+# Cote D'Ivoire
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Abidjan -0:16:08 - LMT 1912
+ 0:00 - GMT
+
+# Djibouti
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Djibouti 2:52:36 - LMT 1911 Jul
+ 3:00 - EAT
+
+###############################################################################
+
+# Egypt
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Egypt 1940 only - Jul 15 0:00 1:00 S
+Rule Egypt 1940 only - Oct 1 0:00 0 -
+Rule Egypt 1941 only - Apr 15 0:00 1:00 S
+Rule Egypt 1941 only - Sep 16 0:00 0 -
+Rule Egypt 1942 1944 - Apr 1 0:00 1:00 S
+Rule Egypt 1942 only - Oct 27 0:00 0 -
+Rule Egypt 1943 1945 - Nov 1 0:00 0 -
+Rule Egypt 1945 only - Apr 16 0:00 1:00 S
+Rule Egypt 1957 only - May 10 0:00 1:00 S
+Rule Egypt 1957 1958 - Oct 1 0:00 0 -
+Rule Egypt 1958 only - May 1 0:00 1:00 S
+Rule Egypt 1959 1981 - May 1 1:00 1:00 S
+Rule Egypt 1959 1965 - Sep 30 3:00 0 -
+Rule Egypt 1966 1994 - Oct 1 3:00 0 -
+Rule Egypt 1982 only - Jul 25 1:00 1:00 S
+Rule Egypt 1983 only - Jul 12 1:00 1:00 S
+Rule Egypt 1984 1988 - May 1 1:00 1:00 S
+Rule Egypt 1989 only - May 6 1:00 1:00 S
+Rule Egypt 1990 1994 - May 1 1:00 1:00 S
+# IATA (after 1990) says transitions are at 0:00.
+# Go with IATA starting in 1995, except correct 1995 entry from 09-30 to 09-29.
+
+# From Alexander Krivenyshev (2011-04-20):
+# "...Egypt's interim cabinet decided on Wednesday to cancel daylight
+# saving time after a poll posted on its website showed the majority of
+# Egyptians would approve the cancellation."
+#
+# Egypt to cancel daylight saving time
+# <a href="http://www.almasryalyoum.com/en/node/407168">
+# http://www.almasryalyoum.com/en/node/407168
+# </a>
+# or
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_egypt04.html">
+# http://www.worldtimezone.com/dst_news/dst_news_egypt04.html
+# </a>
+Rule Egypt 1995 2010 - Apr lastFri 0:00s 1:00 S
+Rule Egypt 1995 2005 - Sep lastThu 23:00s 0 -
+# From Steffen Thorsen (2006-09-19):
+# The Egyptian Gazette, issue 41,090 (2006-09-18), page 1, reports:
+# Egypt will turn back clocks by one hour at the midnight of Thursday
+# after observing the daylight saving time since May.
+# http://news.gom.com.eg/gazette/pdf/2006/09/18/01.pdf
+Rule Egypt 2006 only - Sep 21 23:00s 0 -
+# From Dirk Losch (2007-08-14):
+# I received a mail from an airline which says that the daylight
+# saving time in Egypt will end in the night of 2007-09-06 to 2007-09-07.
+# From Jesper Norgaard Welen (2007-08-15): [The following agree:]
+# http://www.nentjes.info/Bill/bill5.htm
+# http://www.timeanddate.com/worldclock/city.html?n=53
+# From Steffen Thorsen (2007-09-04): The official information...:
+# http://www.sis.gov.eg/En/EgyptOnline/Miscellaneous/000002/0207000000000000001580.htm
+Rule Egypt 2007 only - Sep Thu>=1 23:00s 0 -
+# From Abdelrahman Hassan (2007-09-06):
+# Due to the Hijri (lunar Islamic calendar) year being 11 days shorter
+# than the year of the Gregorian calendar, Ramadan shifts earlier each
+# year. This year it will be observed September 13 (September is quite
+# hot in Egypt), and the idea is to make fasting easier for workers by
+# shifting business hours one hour out of daytime heat. Consequently,
+# unless discontinued, next DST may end Thursday 28 August 2008.
+# From Paul Eggert (2007-08-17):
+# For lack of better info, assume the new rule is last Thursday in August.
+
+# From Petr Machata (2009-04-06):
+# The following appeared in Red Hat bugzilla[1] (edited):
+#
+# > $ zdump -v /usr/share/zoneinfo/Africa/Cairo | grep 2009
+# > /usr/share/zoneinfo/Africa/Cairo Thu Apr 23 21:59:59 2009 UTC = Thu =
+# Apr 23
+# > 23:59:59 2009 EET isdst=0 gmtoff=7200
+# > /usr/share/zoneinfo/Africa/Cairo Thu Apr 23 22:00:00 2009 UTC = Fri =
+# Apr 24
+# > 01:00:00 2009 EEST isdst=1 gmtoff=10800
+# > /usr/share/zoneinfo/Africa/Cairo Thu Aug 27 20:59:59 2009 UTC = Thu =
+# Aug 27
+# > 23:59:59 2009 EEST isdst=1 gmtoff=10800
+# > /usr/share/zoneinfo/Africa/Cairo Thu Aug 27 21:00:00 2009 UTC = Thu =
+# Aug 27
+# > 23:00:00 2009 EET isdst=0 gmtoff=7200
+#
+# > end date should be Thu Sep 24 2009 (Last Thursday in September at 23:59=
+# :59)
+# > http://support.microsoft.com/kb/958729/
+#
+# timeanddate[2] and another site I've found[3] also support that.
+#
+# [1] <a href="https://bugzilla.redhat.com/show_bug.cgi?id=492263">
+# https://bugzilla.redhat.com/show_bug.cgi?id=492263
+# </a>
+# [2] <a href="http://www.timeanddate.com/worldclock/clockchange.html?n=53">
+# http://www.timeanddate.com/worldclock/clockchange.html?n=53
+# </a>
+# [3] <a href="http://wwp.greenwichmeantime.com/time-zone/africa/egypt/">
+# http://wwp.greenwichmeantime.com/time-zone/africa/egypt/
+# </a>
+
+# From Arthur David Olson (2009-04-20):
+# In 2009 (and for the next several years), Ramadan ends before the fourth
+# Thursday in September; Egypt is expected to revert to the last Thursday
+# in September.
+
+# From Steffen Thorsen (2009-08-11):
+# We have been able to confirm the August change with the Egyptian Cabinet
+# Information and Decision Support Center:
+# <a href="http://www.timeanddate.com/news/time/egypt-dst-ends-2009.html">
+# http://www.timeanddate.com/news/time/egypt-dst-ends-2009.html
+# </a>
+#
+# The Middle East News Agency
+# <a href="http://www.mena.org.eg/index.aspx">
+# http://www.mena.org.eg/index.aspx
+# </a>
+# also reports "Egypt starts winter time on August 21"
+# today in article numbered "71, 11/08/2009 12:25 GMT."
+# Only the title above is available without a subscription to their service,
+# and can be found by searching for "winter" in their search engine
+# (at least today).
+
+# From Alexander Krivenyshev (2010-07-20):
+# According to News from Egypt - Al-Masry Al-Youm Egypt's cabinet has
+# decided that Daylight Saving Time will not be used in Egypt during
+# Ramadan.
+#
+# Arabic translation:
+# "Clocks to go back during Ramadan--and then forward again"
+# <a href="http://www.almasryalyoum.com/en/news/clocks-go-back-during-ramadan-and-then-forward-again">
+# http://www.almasryalyoum.com/en/news/clocks-go-back-during-ramadan-and-then-forward-again
+# </a>
+# or
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_egypt02.html">
+# http://www.worldtimezone.com/dst_news/dst_news_egypt02.html
+# </a>
+
+Rule Egypt 2008 only - Aug lastThu 23:00s 0 -
+Rule Egypt 2009 only - Aug 20 23:00s 0 -
+Rule Egypt 2010 only - Aug 11 0:00 0 -
+Rule Egypt 2010 only - Sep 10 0:00 1:00 S
+Rule Egypt 2010 only - Sep lastThu 23:00s 0 -
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Cairo 2:05:00 - LMT 1900 Oct
+ 2:00 Egypt EE%sT
+
+# Equatorial Guinea
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Malabo 0:35:08 - LMT 1912
+ 0:00 - GMT 1963 Dec 15
+ 1:00 - WAT
+
+# Eritrea
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Asmara 2:35:32 - LMT 1870
+ 2:35:32 - AMT 1890 # Asmara Mean Time
+ 2:35:20 - ADMT 1936 May 5 # Adis Dera MT
+ 3:00 - EAT
+
+# Ethiopia
+# From Paul Eggert (2006-03-22):
+# Shanks & Pottenger write that Ethiopia had six narrowly-spaced time zones
+# between 1870 and 1890, and that they merged to 38E50 (2:35:20) in 1890.
+# We'll guess that 38E50 is for Adis Dera.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Addis_Ababa 2:34:48 - LMT 1870
+ 2:35:20 - ADMT 1936 May 5 # Adis Dera MT
+ 3:00 - EAT
+
+# Gabon
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Libreville 0:37:48 - LMT 1912
+ 1:00 - WAT
+
+# Gambia
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Banjul -1:06:36 - LMT 1912
+ -1:06:36 - BMT 1935 # Banjul Mean Time
+ -1:00 - WAT 1964
+ 0:00 - GMT
+
+# Ghana
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+# Whitman says DST was observed from 1931 to ``the present'';
+# go with Shanks & Pottenger.
+Rule Ghana 1936 1942 - Sep 1 0:00 0:20 GHST
+Rule Ghana 1936 1942 - Dec 31 0:00 0 GMT
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Accra -0:00:52 - LMT 1918
+ 0:00 Ghana %s
+
+# Guinea
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Conakry -0:54:52 - LMT 1912
+ 0:00 - GMT 1934 Feb 26
+ -1:00 - WAT 1960
+ 0:00 - GMT
+
+# Guinea-Bissau
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Bissau -1:02:20 - LMT 1911 May 26
+ -1:00 - WAT 1975
+ 0:00 - GMT
+
+# Kenya
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Nairobi 2:27:16 - LMT 1928 Jul
+ 3:00 - EAT 1930
+ 2:30 - BEAT 1940
+ 2:45 - BEAUT 1960
+ 3:00 - EAT
+
+# Lesotho
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Maseru 1:50:00 - LMT 1903 Mar
+ 2:00 - SAST 1943 Sep 19 2:00
+ 2:00 1:00 SAST 1944 Mar 19 2:00
+ 2:00 - SAST
+
+# Liberia
+# From Paul Eggert (2006-03-22):
+# In 1972 Liberia was the last country to switch
+# from a UTC offset that was not a multiple of 15 or 20 minutes.
+# Howse reports that it was in honor of their president's birthday.
+# Shank & Pottenger report the date as May 1, whereas Howse reports Jan;
+# go with Shanks & Pottenger.
+# For Liberia before 1972, Shanks & Pottenger report -0:44, whereas Howse and
+# Whitman each report -0:44:30; go with the more precise figure.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Monrovia -0:43:08 - LMT 1882
+ -0:43:08 - MMT 1919 Mar # Monrovia Mean Time
+ -0:44:30 - LRT 1972 May # Liberia Time
+ 0:00 - GMT
+
+###############################################################################
+
+# Libya
+
+# From Even Scharning (2012-11-10):
+# Libya set their time one hour back at 02:00 on Saturday November 10.
+# http://www.libyaherald.com/2012/11/04/clocks-to-go-back-an-hour-on-saturday/
+# Here is an official source [in Arabic]: http://ls.ly/fb6Yc
+#
+# Steffen Thorsen forwarded a translation (2012-11-10) in
+# http://mm.icann.org/pipermail/tz/2012-November/018451.html
+#
+# From Tim Parenti (2012-11-11):
+# Treat the 2012-11-10 change as a zone change from UTC+2 to UTC+1.
+# The DST rules planned for 2013 and onward roughly mirror those of Europe
+# (either two days before them or five days after them, so as to fall on
+# lastFri instead of lastSun).
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Libya 1951 only - Oct 14 2:00 1:00 S
+Rule Libya 1952 only - Jan 1 0:00 0 -
+Rule Libya 1953 only - Oct 9 2:00 1:00 S
+Rule Libya 1954 only - Jan 1 0:00 0 -
+Rule Libya 1955 only - Sep 30 0:00 1:00 S
+Rule Libya 1956 only - Jan 1 0:00 0 -
+Rule Libya 1982 1984 - Apr 1 0:00 1:00 S
+Rule Libya 1982 1985 - Oct 1 0:00 0 -
+Rule Libya 1985 only - Apr 6 0:00 1:00 S
+Rule Libya 1986 only - Apr 4 0:00 1:00 S
+Rule Libya 1986 only - Oct 3 0:00 0 -
+Rule Libya 1987 1989 - Apr 1 0:00 1:00 S
+Rule Libya 1987 1989 - Oct 1 0:00 0 -
+Rule Libya 1997 only - Apr 4 0:00 1:00 S
+Rule Libya 1997 only - Oct 4 0:00 0 -
+Rule Libya 2013 max - Mar lastFri 1:00 1:00 S
+Rule Libya 2013 max - Oct lastFri 2:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Tripoli 0:52:44 - LMT 1920
+ 1:00 Libya CE%sT 1959
+ 2:00 - EET 1982
+ 1:00 Libya CE%sT 1990 May 4
+# The 1996 and 1997 entries are from Shanks & Pottenger;
+# the IATA SSIM data contain some obvious errors.
+ 2:00 - EET 1996 Sep 30
+ 1:00 Libya CE%sT 1997 Oct 4
+ 2:00 - EET 2012 Nov 10 2:00
+ 1:00 Libya CE%sT
+
+# Madagascar
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Indian/Antananarivo 3:10:04 - LMT 1911 Jul
+ 3:00 - EAT 1954 Feb 27 23:00s
+ 3:00 1:00 EAST 1954 May 29 23:00s
+ 3:00 - EAT
+
+# Malawi
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Blantyre 2:20:00 - LMT 1903 Mar
+ 2:00 - CAT
+
+# Mali
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Bamako -0:32:00 - LMT 1912
+ 0:00 - GMT 1934 Feb 26
+ -1:00 - WAT 1960 Jun 20
+ 0:00 - GMT
+
+# Mauritania
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Nouakchott -1:03:48 - LMT 1912
+ 0:00 - GMT 1934 Feb 26
+ -1:00 - WAT 1960 Nov 28
+ 0:00 - GMT
+
+# Mauritius
+
+# From Steffen Thorsen (2008-06-25):
+# Mauritius plans to observe DST from 2008-11-01 to 2009-03-31 on a trial
+# basis....
+# It seems that Mauritius observed daylight saving time from 1982-10-10 to
+# 1983-03-20 as well, but that was not successful....
+# http://www.timeanddate.com/news/time/mauritius-daylight-saving-time.html
+
+# From Alex Krivenyshev (2008-06-25):
+# http://economicdevelopment.gov.mu/portal/site/Mainhomepage/menuitem.a42b24128104d9845dabddd154508a0c/?content_id=0a7cee8b5d69a110VgnVCM1000000a04a8c0RCRD
+
+# From Arthur David Olson (2008-06-30):
+# The www.timeanddate.com article cited by Steffen Thorsen notes that "A
+# final decision has yet to be made on the times that daylight saving
+# would begin and end on these dates." As a place holder, use midnight.
+
+# From Paul Eggert (2008-06-30):
+# Follow Thorsen on DST in 1982/1983, instead of Shanks & Pottenger.
+
+# From Steffen Thorsen (2008-07-10):
+# According to
+# <a href="http://www.lexpress.mu/display_article.php?news_id=111216">
+# http://www.lexpress.mu/display_article.php?news_id=111216
+# </a>
+# (in French), Mauritius will start and end their DST a few days earlier
+# than previously announced (2008-11-01 to 2009-03-31). The new start
+# date is 2008-10-26 at 02:00 and the new end date is 2009-03-27 (no time
+# given, but it is probably at either 2 or 3 wall clock time).
+#
+# A little strange though, since the article says that they moved the date
+# to align itself with Europe and USA which also change time on that date,
+# but that means they have not paid attention to what happened in
+# USA/Canada last year (DST ends first Sunday in November). I also wonder
+# why that they end on a Friday, instead of aligning with Europe which
+# changes two days later.
+
+# From Alex Krivenyshev (2008-07-11):
+# Seems that English language article "The revival of daylight saving
+# time: Energy conservation?"-# No. 16578 (07/11/2008) was originally
+# published on Monday, June 30, 2008...
+#
+# I guess that article in French "Le gouvernement avance l'introduction
+# de l'heure d'ete" stating that DST in Mauritius starting on October 26
+# and ending on March 27, 2009 is the most recent one.
+# ...
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_mauritius02.html">
+# http://www.worldtimezone.com/dst_news/dst_news_mauritius02.html
+# </a>
+
+# From Riad M. Hossen Ally (2008-08-03):
+# The Government of Mauritius weblink
+# <a href="http://www.gov.mu/portal/site/pmosite/menuitem.4ca0efdee47462e7440a600248a521ca/?content_id=4728ca68b2a5b110VgnVCM1000000a04a8c0RCRD">
+# http://www.gov.mu/portal/site/pmosite/menuitem.4ca0efdee47462e7440a600248a521ca/?content_id=4728ca68b2a5b110VgnVCM1000000a04a8c0RCRD
+# </a>
+# Cabinet Decision of July 18th, 2008 states as follows:
+#
+# 4. ...Cabinet has agreed to the introduction into the National Assembly
+# of the Time Bill which provides for the introduction of summer time in
+# Mauritius. The summer time period which will be of one hour ahead of
+# the standard time, will be aligned with that in Europe and the United
+# States of America. It will start at two o'clock in the morning on the
+# last Sunday of October and will end at two o'clock in the morning on
+# the last Sunday of March the following year. The summer time for the
+# year 2008 - 2009 will, therefore, be effective as from 26 October 2008
+# and end on 29 March 2009.
+
+# From Ed Maste (2008-10-07):
+# THE TIME BILL (No. XXVII of 2008) Explanatory Memorandum states the
+# beginning / ending of summer time is 2 o'clock standard time in the
+# morning of the last Sunday of October / last Sunday of March.
+# <a href="http://www.gov.mu/portal/goc/assemblysite/file/bill2708.pdf">
+# http://www.gov.mu/portal/goc/assemblysite/file/bill2708.pdf
+# </a>
+
+# From Steffen Thorsen (2009-06-05):
+# According to several sources, Mauritius will not continue to observe
+# DST the coming summer...
+#
+# Some sources, in French:
+# <a href="http://www.defimedia.info/news/946/Rashid-Beebeejaun-:-%C2%AB-L%E2%80%99heure-d%E2%80%99%C3%A9t%C3%A9-ne-sera-pas-appliqu%C3%A9e-cette-ann%C3%A9e-%C2%BB">
+# http://www.defimedia.info/news/946/Rashid-Beebeejaun-:-%C2%AB-L%E2%80%99heure-d%E2%80%99%C3%A9t%C3%A9-ne-sera-pas-appliqu%C3%A9e-cette-ann%C3%A9e-%C2%BB
+# </a>
+# <a href="http://lexpress.mu/Story/3398~Beebeejaun---Les-objectifs-d-%C3%A9conomie-d-%C3%A9nergie-de-l-heure-d-%C3%A9t%C3%A9-ont-%C3%A9t%C3%A9-atteints-">
+# http://lexpress.mu/Story/3398~Beebeejaun---Les-objectifs-d-%C3%A9conomie-d-%C3%A9nergie-de-l-heure-d-%C3%A9t%C3%A9-ont-%C3%A9t%C3%A9-atteints-
+# </a>
+#
+# Our wrap-up:
+# <a href="http://www.timeanddate.com/news/time/mauritius-dst-will-not-repeat.html">
+# http://www.timeanddate.com/news/time/mauritius-dst-will-not-repeat.html
+# </a>
+
+# From Arthur David Olson (2009-07-11):
+# The "mauritius-dst-will-not-repeat" wrapup includes this:
+# "The trial ended on March 29, 2009, when the clocks moved back by one hour
+# at 2am (or 02:00) local time..."
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Mauritius 1982 only - Oct 10 0:00 1:00 S
+Rule Mauritius 1983 only - Mar 21 0:00 0 -
+Rule Mauritius 2008 only - Oct lastSun 2:00 1:00 S
+Rule Mauritius 2009 only - Mar lastSun 2:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Indian/Mauritius 3:50:00 - LMT 1907 # Port Louis
+ 4:00 Mauritius MU%sT # Mauritius Time
+# Agalega Is, Rodriguez
+# no information; probably like Indian/Mauritius
+
+# Mayotte
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Indian/Mayotte 3:00:56 - LMT 1911 Jul # Mamoutzou
+ 3:00 - EAT
+
+# Morocco
+# See the `europe' file for Spanish Morocco (Africa/Ceuta).
+
+# From Alex Krivenyshev (2008-05-09):
+# Here is an article that Morocco plan to introduce Daylight Saving Time between
+# 1 June, 2008 and 27 September, 2008.
+#
+# "... Morocco is to save energy by adjusting its clock during summer so it will
+# be one hour ahead of GMT between 1 June and 27 September, according to
+# Communication Minister and Gov ernment Spokesman, Khalid Naciri...."
+#
+# <a href="http://www.worldtimezone.net/dst_news/dst_news_morocco01.html">
+# http://www.worldtimezone.net/dst_news/dst_news_morocco01.html
+# </a>
+# OR
+# <a href="http://en.afrik.com/news11892.html">
+# http://en.afrik.com/news11892.html
+# </a>
+
+# From Alex Krivenyshev (2008-05-09):
+# The Morocco time change can be confirmed on Morocco web site Maghreb Arabe Presse:
+# <a href="http://www.map.ma/eng/sections/box3/morocco_shifts_to_da/view">
+# http://www.map.ma/eng/sections/box3/morocco_shifts_to_da/view
+# </a>
+#
+# Morocco shifts to daylight time on June 1st through September 27, Govt.
+# spokesman.
+
+# From Patrice Scattolin (2008-05-09):
+# According to this article:
+# <a href="http://www.avmaroc.com/actualite/heure-dete-comment-a127896.html">
+# http://www.avmaroc.com/actualite/heure-dete-comment-a127896.html
+# </a>
+# (and republished here:
+# <a href="http://www.actu.ma/heure-dete-comment_i127896_0.html">
+# http://www.actu.ma/heure-dete-comment_i127896_0.html
+# </a>
+# )
+# the changes occurs at midnight:
+#
+# saturday night may 31st at midnight (which in french is to be
+# intrepreted as the night between saturday and sunday)
+# sunday night the 28th at midnight
+#
+# Seeing that the 28th is monday, I am guessing that she intends to say
+# the midnight of the 28th which is the midnight between sunday and
+# monday, which jives with other sources that say that it's inclusive
+# june1st to sept 27th.
+#
+# The decision was taken by decree *2-08-224 *but I can't find the decree
+# published on the web.
+#
+# It's also confirmed here:
+# <a href="http://www.maroc.ma/NR/exeres/FACF141F-D910-44B0-B7FA-6E03733425D1.htm">
+# http://www.maroc.ma/NR/exeres/FACF141F-D910-44B0-B7FA-6E03733425D1.htm
+# </a>
+# on a government portal as being between june 1st and sept 27th (not yet
+# posted in english).
+#
+# The following google query will generate many relevant hits:
+# <a href="http://www.google.com/search?hl=en&q=Conseil+de+gouvernement+maroc+heure+avance&btnG=Search">
+# http://www.google.com/search?hl=en&q=Conseil+de+gouvernement+maroc+heure+avance&btnG=Search
+# </a>
+
+# From Alex Krivenyshev (2008-05-09):
+# Is Western Sahara (part which administrated by Morocco) going to follow
+# Morocco DST changes? Any information? What about other part of
+# Western Sahara - under administration of POLISARIO Front (also named
+# SADR Saharawi Arab Democratic Republic)?
+
+# From Arthur David Olson (2008-05-09):
+# XXX--guess that it is only Morocco for now; guess only 2008 for now.
+
+# From Steffen Thorsen (2008-08-27):
+# Morocco will change the clocks back on the midnight between August 31
+# and September 1. They originally planned to observe DST to near the end
+# of September:
+#
+# One article about it (in French):
+# <a href="http://www.menara.ma/fr/Actualites/Maroc/Societe/ci.retour_a_l_heure_gmt_a_partir_du_dimanche_31_aout_a_minuit_officiel_.default">
+# http://www.menara.ma/fr/Actualites/Maroc/Societe/ci.retour_a_l_heure_gmt_a_partir_du_dimanche_31_aout_a_minuit_officiel_.default
+# </a>
+#
+# We have some further details posted here:
+# <a href="http://www.timeanddate.com/news/time/morocco-ends-dst-early-2008.html">
+# http://www.timeanddate.com/news/time/morocco-ends-dst-early-2008.html
+# </a>
+
+# From Steffen Thorsen (2009-03-17):
+# Morocco will observe DST from 2009-06-01 00:00 to 2009-08-21 00:00 according
+# to many sources, such as
+# <a href="http://news.marweb.com/morocco/entertainment/morocco-daylight-saving.html">
+# http://news.marweb.com/morocco/entertainment/morocco-daylight-saving.html
+# </a>
+# <a href="http://www.medi1sat.ma/fr/depeche.aspx?idp=2312">
+# http://www.medi1sat.ma/fr/depeche.aspx?idp=2312
+# </a>
+# (French)
+#
+# Our summary:
+# <a href="http://www.timeanddate.com/news/time/morocco-starts-dst-2009.html">
+# http://www.timeanddate.com/news/time/morocco-starts-dst-2009.html
+# </a>
+
+# From Alexander Krivenyshev (2009-03-17):
+# Here is a link to official document from Royaume du Maroc Premier Ministre,
+# Ministere de la Modernisation des Secteurs Publics
+#
+# Under Article 1 of Royal Decree No. 455-67 of Act 23 safar 1387 (2 june 1967)
+# concerning the amendment of the legal time, the Ministry of Modernization of
+# Public Sectors announced that the official time in the Kingdom will be
+# advanced 60 minutes from Sunday 31 May 2009 at midnight.
+#
+# <a href="http://www.mmsp.gov.ma/francais/Actualites_fr/PDF_Actualites_Fr/HeureEte_FR.pdf">
+# http://www.mmsp.gov.ma/francais/Actualites_fr/PDF_Actualites_Fr/HeureEte_FR.pdf
+# </a>
+#
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_morocco03.html">
+# http://www.worldtimezone.com/dst_news/dst_news_morocco03.html
+# </a>
+
+# From Steffen Thorsen (2010-04-13):
+# Several news media in Morocco report that the Ministry of Modernization
+# of Public Sectors has announced that Morocco will have DST from
+# 2010-05-02 to 2010-08-08.
+#
+# Example:
+# <a href="http://www.lavieeco.com/actualites/4099-le-maroc-passera-a-l-heure-d-ete-gmt1-le-2-mai.html">
+# http://www.lavieeco.com/actualites/4099-le-maroc-passera-a-l-heure-d-ete-gmt1-le-2-mai.html
+# </a>
+# (French)
+# Our page:
+# <a href="http://www.timeanddate.com/news/time/morocco-starts-dst-2010.html">
+# http://www.timeanddate.com/news/time/morocco-starts-dst-2010.html
+# </a>
+
+# From Dan Abitol (2011-03-30):
+# ...Rules for Africa/Casablanca are the following (24h format)
+# The 3rd april 2011 at 00:00:00, [it] will be 3rd april 1:00:00
+# The 31th july 2011 at 00:59:59, [it] will be 31th July 00:00:00
+# ...Official links of change in morocco
+# The change was broadcast on the FM Radio
+# I ve called ANRT (telecom regulations in Morocco) at
+# +212.537.71.84.00
+# <a href="http://www.anrt.net.ma/fr/">
+# http://www.anrt.net.ma/fr/
+# </a>
+# They said that
+# <a href="http://www.map.ma/fr/sections/accueil/l_heure_legale_au_ma/view">
+# http://www.map.ma/fr/sections/accueil/l_heure_legale_au_ma/view
+# </a>
+# is the official publication to look at.
+# They said that the decision was already taken.
+#
+# More articles in the press
+# <a href="http://www.yabiladi.com/articles/details/5058/secret-l-heure-d-ete-maroc-lev">
+# http://www.yabiladi.com/articles/details/5058/secret-l-heure-d-ete-maroc-lev
+# </a>
+# e.html
+# <a href="http://www.lematin.ma/Actualite/Express/Article.asp?id=148923">
+# http://www.lematin.ma/Actualite/Express/Article.asp?id=148923
+# </a>
+# <a href="http://www.lavieeco.com/actualite/Le-Maroc-passe-sur-GMT%2B1-a-partir-de-dim">
+# http://www.lavieeco.com/actualite/Le-Maroc-passe-sur-GMT%2B1-a-partir-de-dim
+# anche-prochain-5538.html
+# </a>
+
+# From Petr Machata (2011-03-30):
+# They have it written in English here:
+# <a href="http://www.map.ma/eng/sections/home/morocco_to_spring_fo/view">
+# http://www.map.ma/eng/sections/home/morocco_to_spring_fo/view
+# </a>
+#
+# It says there that "Morocco will resume its standard time on July 31,
+# 2011 at midnight." Now they don't say whether they mean midnight of
+# wall clock time (i.e. 11pm UTC), but that's what I would assume. It has
+# also been like that in the past.
+
+# From Alexander Krivenyshev (2012-03-09):
+# According to Infom&eacute;diaire web site from Morocco (infomediaire.ma),
+# on March 9, 2012, (in French) Heure l&eacute;gale:
+# Le Maroc adopte officiellement l'heure d'&eacute;t&eacute;
+# <a href="http://www.infomediaire.ma/news/maroc/heure-l%C3%A9gale-le-maroc-adopte-officiellement-lheure-d%C3%A9t%C3%A9">
+# http://www.infomediaire.ma/news/maroc/heure-l%C3%A9gale-le-maroc-adopte-officiellement-lheure-d%C3%A9t%C3%A9
+# </a>
+# Governing Council adopted draft decree, that Morocco DST starts on
+# the last Sunday of March (March 25, 2012) and ends on
+# last Sunday of September (September 30, 2012)
+# except the month of Ramadan.
+# or (brief)
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_morocco06.html">
+# http://www.worldtimezone.com/dst_news/dst_news_morocco06.html
+# </a>
+
+# From Arthur David Olson (2012-03-10):
+# The infomediaire.ma source indicates that the system is to be in
+# effect every year. It gives 03H00 as the "fall back" time of day;
+# it lacks a "spring forward" time of day; assume 2:00 XXX.
+# Wait on specifying the Ramadan exception for details about
+# start date, start time of day, end date, and end time of day XXX.
+
+# From Christophe Tropamer (2012-03-16):
+# Seen Morocco change again:
+# <a href="http://www.le2uminutes.com/actualite.php">
+# http://www.le2uminutes.com/actualite.php
+# </a>
+# "...&agrave; partir du dernier dimance d'avril et non fins mars,
+# comme annonc&eacute; pr&eacute;c&eacute;demment."
+
+# From Milamber Space Network (2012-07-17):
+# The official return to GMT is announced by the Moroccan government:
+# <a href="http://www.mmsp.gov.ma/fr/actualites.aspx?id=288">
+# http://www.mmsp.gov.ma/fr/actualites.aspx?id=288 [in French]
+# </a>
+#
+# Google translation, lightly edited:
+# Back to the standard time of the Kingdom (GMT)
+# Pursuant to Decree No. 2-12-126 issued on 26 Jumada (I) 1433 (April 18,
+# 2012) and in accordance with the order of Mr. President of the
+# Government No. 3-47-12 issued on 24 Sha'ban (11 July 2012), the Ministry
+# of Public Service and Administration Modernization announces the return
+# of the legal time of the Kingdom (GMT) from Friday, July 20, 2012 until
+# Monday, August 20, 2012. So the time will be delayed by 60 minutes from
+# 3:00 am Friday, July 20, 2012 and will again be advanced by 60 minutes
+# August 20, 2012 from 2:00 am.
+
+# RULE NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+
+Rule Morocco 1939 only - Sep 12 0:00 1:00 S
+Rule Morocco 1939 only - Nov 19 0:00 0 -
+Rule Morocco 1940 only - Feb 25 0:00 1:00 S
+Rule Morocco 1945 only - Nov 18 0:00 0 -
+Rule Morocco 1950 only - Jun 11 0:00 1:00 S
+Rule Morocco 1950 only - Oct 29 0:00 0 -
+Rule Morocco 1967 only - Jun 3 12:00 1:00 S
+Rule Morocco 1967 only - Oct 1 0:00 0 -
+Rule Morocco 1974 only - Jun 24 0:00 1:00 S
+Rule Morocco 1974 only - Sep 1 0:00 0 -
+Rule Morocco 1976 1977 - May 1 0:00 1:00 S
+Rule Morocco 1976 only - Aug 1 0:00 0 -
+Rule Morocco 1977 only - Sep 28 0:00 0 -
+Rule Morocco 1978 only - Jun 1 0:00 1:00 S
+Rule Morocco 1978 only - Aug 4 0:00 0 -
+Rule Morocco 2008 only - Jun 1 0:00 1:00 S
+Rule Morocco 2008 only - Sep 1 0:00 0 -
+Rule Morocco 2009 only - Jun 1 0:00 1:00 S
+Rule Morocco 2009 only - Aug 21 0:00 0 -
+Rule Morocco 2010 only - May 2 0:00 1:00 S
+Rule Morocco 2010 only - Aug 8 0:00 0 -
+Rule Morocco 2011 only - Apr 3 0:00 1:00 S
+Rule Morocco 2011 only - Jul 31 0 0 -
+Rule Morocco 2012 max - Apr lastSun 2:00 1:00 S
+Rule Morocco 2012 max - Sep lastSun 3:00 0 -
+Rule Morocco 2012 only - Jul 20 3:00 0 -
+Rule Morocco 2012 only - Aug 20 2:00 1:00 S
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Casablanca -0:30:20 - LMT 1913 Oct 26
+ 0:00 Morocco WE%sT 1984 Mar 16
+ 1:00 - CET 1986
+ 0:00 Morocco WE%sT
+# Western Sahara
+Zone Africa/El_Aaiun -0:52:48 - LMT 1934 Jan
+ -1:00 - WAT 1976 Apr 14
+ 0:00 - WET
+
+# Mozambique
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Maputo 2:10:20 - LMT 1903 Mar
+ 2:00 - CAT
+
+# Namibia
+# The 1994-04-03 transition is from Shanks & Pottenger.
+# Shanks & Pottenger report no DST after 1998-04; go with IATA.
+
+# From Petronella Sibeene (2007-03-30) in
+# <http://allafrica.com/stories/200703300178.html>:
+# While the entire country changes its time, Katima Mulilo and other
+# settlements in Caprivi unofficially will not because the sun there
+# rises and sets earlier compared to other regions. Chief of
+# Forecasting Riaan van Zyl explained that the far eastern parts of
+# the country are close to 40 minutes earlier in sunrise than the rest
+# of the country.
+#
+# From Paul Eggert (2007-03-31):
+# Apparently the Caprivi Strip informally observes Botswana time, but
+# we have no details. In the meantime people there can use Africa/Gaborone.
+
+# RULE NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Namibia 1994 max - Sep Sun>=1 2:00 1:00 S
+Rule Namibia 1995 max - Apr Sun>=1 2:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Windhoek 1:08:24 - LMT 1892 Feb 8
+ 1:30 - SWAT 1903 Mar # SW Africa Time
+ 2:00 - SAST 1942 Sep 20 2:00
+ 2:00 1:00 SAST 1943 Mar 21 2:00
+ 2:00 - SAST 1990 Mar 21 # independence
+ 2:00 - CAT 1994 Apr 3
+ 1:00 Namibia WA%sT
+
+# Niger
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Niamey 0:08:28 - LMT 1912
+ -1:00 - WAT 1934 Feb 26
+ 0:00 - GMT 1960
+ 1:00 - WAT
+
+# Nigeria
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Lagos 0:13:36 - LMT 1919 Sep
+ 1:00 - WAT
+
+# Reunion
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Indian/Reunion 3:41:52 - LMT 1911 Jun # Saint-Denis
+ 4:00 - RET # Reunion Time
+#
+# Scattered Islands (Iles Eparses) administered from Reunion are as follows.
+# The following information about them is taken from
+# Iles Eparses (www.outre-mer.gouv.fr/domtom/ile.htm, 1997-07-22, in French;
+# no longer available as of 1999-08-17).
+# We have no info about their time zone histories.
+#
+# Bassas da India - uninhabited
+# Europa Island - inhabited from 1905 to 1910 by two families
+# Glorioso Is - inhabited until at least 1958
+# Juan de Nova - uninhabited
+# Tromelin - inhabited until at least 1958
+
+# Rwanda
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Kigali 2:00:16 - LMT 1935 Jun
+ 2:00 - CAT
+
+# St Helena
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Atlantic/St_Helena -0:22:48 - LMT 1890 # Jamestown
+ -0:22:48 - JMT 1951 # Jamestown Mean Time
+ 0:00 - GMT
+# The other parts of the St Helena territory are similar:
+# Tristan da Cunha: on GMT, say Whitman and the CIA
+# Ascension: on GMT, says usno1995 and the CIA
+# Gough (scientific station since 1955; sealers wintered previously):
+# on GMT, says the CIA
+# Inaccessible, Nightingale: no information, but probably GMT
+
+# Sao Tome and Principe
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Sao_Tome 0:26:56 - LMT 1884
+ -0:36:32 - LMT 1912 # Lisbon Mean Time
+ 0:00 - GMT
+
+# Senegal
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Dakar -1:09:44 - LMT 1912
+ -1:00 - WAT 1941 Jun
+ 0:00 - GMT
+
+# Seychelles
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Indian/Mahe 3:41:48 - LMT 1906 Jun # Victoria
+ 4:00 - SCT # Seychelles Time
+# From Paul Eggert (2001-05-30):
+# Aldabra, Farquhar, and Desroches, originally dependencies of the
+# Seychelles, were transferred to the British Indian Ocean Territory
+# in 1965 and returned to Seychelles control in 1976. We don't know
+# whether this affected their time zone, so omit this for now.
+# Possibly the islands were uninhabited.
+
+# Sierra Leone
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+# Whitman gives Mar 31 - Aug 31 for 1931 on; go with Shanks & Pottenger.
+Rule SL 1935 1942 - Jun 1 0:00 0:40 SLST
+Rule SL 1935 1942 - Oct 1 0:00 0 WAT
+Rule SL 1957 1962 - Jun 1 0:00 1:00 SLST
+Rule SL 1957 1962 - Sep 1 0:00 0 GMT
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Freetown -0:53:00 - LMT 1882
+ -0:53:00 - FMT 1913 Jun # Freetown Mean Time
+ -1:00 SL %s 1957
+ 0:00 SL %s
+
+# Somalia
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Mogadishu 3:01:28 - LMT 1893 Nov
+ 3:00 - EAT 1931
+ 2:30 - BEAT 1957
+ 3:00 - EAT
+
+# South Africa
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule SA 1942 1943 - Sep Sun>=15 2:00 1:00 -
+Rule SA 1943 1944 - Mar Sun>=15 2:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Johannesburg 1:52:00 - LMT 1892 Feb 8
+ 1:30 - SAST 1903 Mar
+ 2:00 SA SAST
+# Marion and Prince Edward Is
+# scientific station since 1947
+# no information
+
+# Sudan
+#
+# From <a href="http://www.sunanews.net/sn13jane.html">
+# Sudan News Agency (2000-01-13)
+# </a>, also reported by Michael De Beukelaer-Dossche via Steffen Thorsen:
+# Clocks will be moved ahead for 60 minutes all over the Sudan as of noon
+# Saturday.... This was announced Thursday by Caretaker State Minister for
+# Manpower Abdul-Rahman Nur-Eddin.
+#
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Sudan 1970 only - May 1 0:00 1:00 S
+Rule Sudan 1970 1985 - Oct 15 0:00 0 -
+Rule Sudan 1971 only - Apr 30 0:00 1:00 S
+Rule Sudan 1972 1985 - Apr lastSun 0:00 1:00 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Khartoum 2:10:08 - LMT 1931
+ 2:00 Sudan CA%sT 2000 Jan 15 12:00
+ 3:00 - EAT
+
+# South Sudan
+Zone Africa/Juba 2:06:24 - LMT 1931
+ 2:00 Sudan CA%sT 2000 Jan 15 12:00
+ 3:00 - EAT
+
+# Swaziland
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Mbabane 2:04:24 - LMT 1903 Mar
+ 2:00 - SAST
+
+# Tanzania
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Dar_es_Salaam 2:37:08 - LMT 1931
+ 3:00 - EAT 1948
+ 2:45 - BEAUT 1961
+ 3:00 - EAT
+
+# Togo
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Lome 0:04:52 - LMT 1893
+ 0:00 - GMT
+
+# Tunisia
+
+# From Gwillim Law (2005-04-30):
+# My correspondent, Risto Nykanen, has alerted me to another adoption of DST,
+# this time in Tunisia. According to Yahoo France News
+# <http://fr.news.yahoo.com/050426/5/4dumk.html>, in a story attributed to AP
+# and dated 2005-04-26, "Tunisia has decided to advance its official time by
+# one hour, starting on Sunday, May 1. Henceforth, Tunisian time will be
+# UTC+2 instead of UTC+1. The change will take place at 23:00 UTC next
+# Saturday." (My translation)
+#
+# From Oscar van Vlijmen (2005-05-02):
+# LaPresse, the first national daily newspaper ...
+# <http://www.lapresse.tn/archives/archives280405/actualites/lheure.html>
+# ... DST for 2005: on: Sun May 1 0h standard time, off: Fri Sept. 30,
+# 1h standard time.
+#
+# From Atef Loukil (2006-03-28):
+# The daylight saving time will be the same each year:
+# Beginning : the last Sunday of March at 02:00
+# Ending : the last Sunday of October at 03:00 ...
+# http://www.tap.info.tn/en/index.php?option=com_content&task=view&id=1188&Itemid=50
+
+# From Steffen Thorsen (2009-03-16):
+# According to several news sources, Tunisia will not observe DST this year.
+# (Arabic)
+# <a href="http://www.elbashayer.com/?page=viewn&nid=42546">
+# http://www.elbashayer.com/?page=viewn&nid=42546
+# </a>
+# <a href="http://www.babnet.net/kiwidetail-15295.asp">
+# http://www.babnet.net/kiwidetail-15295.asp
+# </a>
+#
+# We have also confirmed this with the US embassy in Tunisia.
+# We have a wrap-up about this on the following page:
+# <a href="http://www.timeanddate.com/news/time/tunisia-cancels-dst-2009.html">
+# http://www.timeanddate.com/news/time/tunisia-cancels-dst-2009.html
+# </a>
+
+# From Alexander Krivenyshev (2009-03-17):
+# Here is a link to Tunis Afrique Presse News Agency
+#
+# Standard time to be kept the whole year long (tap.info.tn):
+#
+# (in English)
+# <a href="http://www.tap.info.tn/en/index.php?option=com_content&task=view&id=26813&Itemid=157">
+# http://www.tap.info.tn/en/index.php?option=com_content&task=view&id=26813&Itemid=157
+# </a>
+#
+# (in Arabic)
+# <a href="http://www.tap.info.tn/ar/index.php?option=com_content&task=view&id=61240&Itemid=1">
+# http://www.tap.info.tn/ar/index.php?option=com_content&task=view&id=61240&Itemid=1
+# </a>
+
+# From Arthur David Olson (2009--3-18):
+# The Tunis Afrique Presse News Agency notice contains this: "This measure is due to the fact
+# that the fasting month of ramadan coincides with the period concerned by summer time.
+# Therefore, the standard time will be kept unchanged the whole year long."
+# So foregoing DST seems to be an exception (albeit one that may be repeated in the future).
+
+# From Alexander Krivenyshev (2010-03-27):
+# According to some news reports Tunis confirmed not to use DST in 2010
+#
+# (translation):
+# "The Tunisian government has decided to abandon DST, which was scheduled on
+# Sunday...
+# Tunisian authorities had suspended the DST for the first time last year also
+# coincided with the month of Ramadan..."
+#
+# (in Arabic)
+# <a href="http://www.moheet.com/show_news.aspx?nid=358861&pg=1">
+# http://www.moheet.com/show_news.aspx?nid=358861&pg=1
+# <a href="http://www.almadenahnews.com/newss/news.php?c=118&id=38036">
+# http://www.almadenahnews.com/newss/news.php?c=118&id=38036
+# or
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_tunis02.html">
+# http://www.worldtimezone.com/dst_news/dst_news_tunis02.html
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Tunisia 1939 only - Apr 15 23:00s 1:00 S
+Rule Tunisia 1939 only - Nov 18 23:00s 0 -
+Rule Tunisia 1940 only - Feb 25 23:00s 1:00 S
+Rule Tunisia 1941 only - Oct 6 0:00 0 -
+Rule Tunisia 1942 only - Mar 9 0:00 1:00 S
+Rule Tunisia 1942 only - Nov 2 3:00 0 -
+Rule Tunisia 1943 only - Mar 29 2:00 1:00 S
+Rule Tunisia 1943 only - Apr 17 2:00 0 -
+Rule Tunisia 1943 only - Apr 25 2:00 1:00 S
+Rule Tunisia 1943 only - Oct 4 2:00 0 -
+Rule Tunisia 1944 1945 - Apr Mon>=1 2:00 1:00 S
+Rule Tunisia 1944 only - Oct 8 0:00 0 -
+Rule Tunisia 1945 only - Sep 16 0:00 0 -
+Rule Tunisia 1977 only - Apr 30 0:00s 1:00 S
+Rule Tunisia 1977 only - Sep 24 0:00s 0 -
+Rule Tunisia 1978 only - May 1 0:00s 1:00 S
+Rule Tunisia 1978 only - Oct 1 0:00s 0 -
+Rule Tunisia 1988 only - Jun 1 0:00s 1:00 S
+Rule Tunisia 1988 1990 - Sep lastSun 0:00s 0 -
+Rule Tunisia 1989 only - Mar 26 0:00s 1:00 S
+Rule Tunisia 1990 only - May 1 0:00s 1:00 S
+Rule Tunisia 2005 only - May 1 0:00s 1:00 S
+Rule Tunisia 2005 only - Sep 30 1:00s 0 -
+Rule Tunisia 2006 2008 - Mar lastSun 2:00s 1:00 S
+Rule Tunisia 2006 2008 - Oct lastSun 2:00s 0 -
+
+# Shanks & Pottenger give 0:09:20 for Paris Mean Time; go with Howse's
+# more precise 0:09:21.
+# Shanks & Pottenger say the 1911 switch was on Mar 9; go with Howse's Mar 11.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Tunis 0:40:44 - LMT 1881 May 12
+ 0:09:21 - PMT 1911 Mar 11 # Paris Mean Time
+ 1:00 Tunisia CE%sT
+
+# Uganda
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Kampala 2:09:40 - LMT 1928 Jul
+ 3:00 - EAT 1930
+ 2:30 - BEAT 1948
+ 2:45 - BEAUT 1957
+ 3:00 - EAT
+
+# Zambia
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Lusaka 1:53:08 - LMT 1903 Mar
+ 2:00 - CAT
+
+# Zimbabwe
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Africa/Harare 2:04:12 - LMT 1903 Mar
+ 2:00 - CAT
diff --git a/misc/flot/examples/axes-time-zones/tz/antarctica b/misc/flot/examples/axes-time-zones/tz/antarctica
new file mode 100644
index 0000000..f55cbde
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/antarctica
@@ -0,0 +1,413 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# From Paul Eggert (1999-11-15):
+# To keep things manageable, we list only locations occupied year-round; see
+# <a href="http://www.comnap.aq/comnap/comnap.nsf/P/Stations/">
+# COMNAP - Stations and Bases
+# </a>
+# and
+# <a href="http://www.spri.cam.ac.uk/bob/periant.htm">
+# Summary of the Peri-Antarctic Islands (1998-07-23)
+# </a>
+# for information.
+# Unless otherwise specified, we have no time zone information.
+#
+# Except for the French entries,
+# I made up all time zone abbreviations mentioned here; corrections welcome!
+# FORMAT is `zzz' and GMTOFF is 0 for locations while uninhabited.
+
+# These rules are stolen from the `southamerica' file.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule ArgAQ 1964 1966 - Mar 1 0:00 0 -
+Rule ArgAQ 1964 1966 - Oct 15 0:00 1:00 S
+Rule ArgAQ 1967 only - Apr 2 0:00 0 -
+Rule ArgAQ 1967 1968 - Oct Sun>=1 0:00 1:00 S
+Rule ArgAQ 1968 1969 - Apr Sun>=1 0:00 0 -
+Rule ArgAQ 1974 only - Jan 23 0:00 1:00 S
+Rule ArgAQ 1974 only - May 1 0:00 0 -
+Rule ChileAQ 1972 1986 - Mar Sun>=9 3:00u 0 -
+Rule ChileAQ 1974 1987 - Oct Sun>=9 4:00u 1:00 S
+Rule ChileAQ 1987 only - Apr 12 3:00u 0 -
+Rule ChileAQ 1988 1989 - Mar Sun>=9 3:00u 0 -
+Rule ChileAQ 1988 only - Oct Sun>=1 4:00u 1:00 S
+Rule ChileAQ 1989 only - Oct Sun>=9 4:00u 1:00 S
+Rule ChileAQ 1990 only - Mar 18 3:00u 0 -
+Rule ChileAQ 1990 only - Sep 16 4:00u 1:00 S
+Rule ChileAQ 1991 1996 - Mar Sun>=9 3:00u 0 -
+Rule ChileAQ 1991 1997 - Oct Sun>=9 4:00u 1:00 S
+Rule ChileAQ 1997 only - Mar 30 3:00u 0 -
+Rule ChileAQ 1998 only - Mar Sun>=9 3:00u 0 -
+Rule ChileAQ 1998 only - Sep 27 4:00u 1:00 S
+Rule ChileAQ 1999 only - Apr 4 3:00u 0 -
+Rule ChileAQ 1999 2010 - Oct Sun>=9 4:00u 1:00 S
+Rule ChileAQ 2000 2007 - Mar Sun>=9 3:00u 0 -
+# N.B.: the end of March 29 in Chile is March 30 in Universal time,
+# which is used below in specifying the transition.
+Rule ChileAQ 2008 only - Mar 30 3:00u 0 -
+Rule ChileAQ 2009 only - Mar Sun>=9 3:00u 0 -
+Rule ChileAQ 2010 only - Apr Sun>=1 3:00u 0 -
+Rule ChileAQ 2011 only - May Sun>=2 3:00u 0 -
+Rule ChileAQ 2011 only - Aug Sun>=16 4:00u 1:00 S
+Rule ChileAQ 2012 only - Apr Sun>=23 3:00u 0 -
+Rule ChileAQ 2012 only - Sep Sun>=2 4:00u 1:00 S
+Rule ChileAQ 2013 max - Mar Sun>=9 3:00u 0 -
+Rule ChileAQ 2013 max - Oct Sun>=9 4:00u 1:00 S
+
+# These rules are stolen from the `australasia' file.
+Rule AusAQ 1917 only - Jan 1 0:01 1:00 -
+Rule AusAQ 1917 only - Mar 25 2:00 0 -
+Rule AusAQ 1942 only - Jan 1 2:00 1:00 -
+Rule AusAQ 1942 only - Mar 29 2:00 0 -
+Rule AusAQ 1942 only - Sep 27 2:00 1:00 -
+Rule AusAQ 1943 1944 - Mar lastSun 2:00 0 -
+Rule AusAQ 1943 only - Oct 3 2:00 1:00 -
+Rule ATAQ 1967 only - Oct Sun>=1 2:00s 1:00 -
+Rule ATAQ 1968 only - Mar lastSun 2:00s 0 -
+Rule ATAQ 1968 1985 - Oct lastSun 2:00s 1:00 -
+Rule ATAQ 1969 1971 - Mar Sun>=8 2:00s 0 -
+Rule ATAQ 1972 only - Feb lastSun 2:00s 0 -
+Rule ATAQ 1973 1981 - Mar Sun>=1 2:00s 0 -
+Rule ATAQ 1982 1983 - Mar lastSun 2:00s 0 -
+Rule ATAQ 1984 1986 - Mar Sun>=1 2:00s 0 -
+Rule ATAQ 1986 only - Oct Sun>=15 2:00s 1:00 -
+Rule ATAQ 1987 1990 - Mar Sun>=15 2:00s 0 -
+Rule ATAQ 1987 only - Oct Sun>=22 2:00s 1:00 -
+Rule ATAQ 1988 1990 - Oct lastSun 2:00s 1:00 -
+Rule ATAQ 1991 1999 - Oct Sun>=1 2:00s 1:00 -
+Rule ATAQ 1991 2005 - Mar lastSun 2:00s 0 -
+Rule ATAQ 2000 only - Aug lastSun 2:00s 1:00 -
+Rule ATAQ 2001 max - Oct Sun>=1 2:00s 1:00 -
+Rule ATAQ 2006 only - Apr Sun>=1 2:00s 0 -
+Rule ATAQ 2007 only - Mar lastSun 2:00s 0 -
+Rule ATAQ 2008 max - Apr Sun>=1 2:00s 0 -
+
+# Argentina - year-round bases
+# Belgrano II, Confin Coast, -770227-0343737, since 1972-02-05
+# Esperanza, San Martin Land, -6323-05659, since 1952-12-17
+# Jubany, Potter Peninsula, King George Island, -6414-0602320, since 1982-01
+# Marambio, Seymour I, -6414-05637, since 1969-10-29
+# Orcadas, Laurie I, -6016-04444, since 1904-02-22
+# San Martin, Debenham I, -6807-06708, since 1951-03-21
+# (except 1960-03 / 1976-03-21)
+
+# Australia - territories
+# Heard Island, McDonald Islands (uninhabited)
+# previously sealers and scientific personnel wintered
+# <a href="http://web.archive.org/web/20021204222245/http://www.dstc.qut.edu.au/DST/marg/daylight.html">
+# Margaret Turner reports
+# </a> (1999-09-30) that they're UTC+5, with no DST;
+# presumably this is when they have visitors.
+#
+# year-round bases
+# Casey, Bailey Peninsula, -6617+11032, since 1969
+# Davis, Vestfold Hills, -6835+07759, since 1957-01-13
+# (except 1964-11 - 1969-02)
+# Mawson, Holme Bay, -6736+06253, since 1954-02-13
+
+# From Steffen Thorsen (2009-03-11):
+# Three Australian stations in Antarctica have changed their time zone:
+# Casey moved from UTC+8 to UTC+11
+# Davis moved from UTC+7 to UTC+5
+# Mawson moved from UTC+6 to UTC+5
+# The changes occurred on 2009-10-18 at 02:00 (local times).
+#
+# Government source: (Australian Antarctic Division)
+# <a href="http://www.aad.gov.au/default.asp?casid=37079">
+# http://www.aad.gov.au/default.asp?casid=37079
+# </a>
+#
+# We have more background information here:
+# <a href="http://www.timeanddate.com/news/time/antarctica-new-times.html">
+# http://www.timeanddate.com/news/time/antarctica-new-times.html
+# </a>
+
+# From Steffen Thorsen (2010-03-10):
+# We got these changes from the Australian Antarctic Division:
+# - Macquarie Island will stay on UTC+11 for winter and therefore not
+# switch back from daylight savings time when other parts of Australia do
+# on 4 April.
+#
+# - Casey station reverted to its normal time of UTC+8 on 5 March 2010.
+# The change to UTC+11 is being considered as a regular summer thing but
+# has not been decided yet.
+#
+# - Davis station will revert to its normal time of UTC+7 at 10 March 2010
+# 20:00 UTC.
+#
+# - Mawson station stays on UTC+5.
+#
+# In addition to the Rule changes for Casey/Davis, it means that Macquarie
+# will no longer be like Hobart and will have to have its own Zone created.
+#
+# Background:
+# <a href="http://www.timeanddate.com/news/time/antartica-time-changes-2010.html">
+# http://www.timeanddate.com/news/time/antartica-time-changes-2010.html
+# </a>
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Antarctica/Casey 0 - zzz 1969
+ 8:00 - WST 2009 Oct 18 2:00
+ # Western (Aus) Standard Time
+ 11:00 - CAST 2010 Mar 5 2:00
+ # Casey Time
+ 8:00 - WST 2011 Oct 28 2:00
+ 11:00 - CAST 2012 Feb 21 17:00u
+ 8:00 - WST
+Zone Antarctica/Davis 0 - zzz 1957 Jan 13
+ 7:00 - DAVT 1964 Nov # Davis Time
+ 0 - zzz 1969 Feb
+ 7:00 - DAVT 2009 Oct 18 2:00
+ 5:00 - DAVT 2010 Mar 10 20:00u
+ 7:00 - DAVT 2011 Oct 28 2:00
+ 5:00 - DAVT 2012 Feb 21 20:00u
+ 7:00 - DAVT
+Zone Antarctica/Mawson 0 - zzz 1954 Feb 13
+ 6:00 - MAWT 2009 Oct 18 2:00
+ # Mawson Time
+ 5:00 - MAWT
+Zone Antarctica/Macquarie 0 - zzz 1911
+ 10:00 - EST 1916 Oct 1 2:00
+ 10:00 1:00 EST 1917 Feb
+ 10:00 AusAQ EST 1967
+ 10:00 ATAQ EST 2010 Apr 4 3:00
+ 11:00 - MIST # Macquarie Island Time
+# References:
+# <a href="http://www.antdiv.gov.au/aad/exop/sfo/casey/casey_aws.html">
+# Casey Weather (1998-02-26)
+# </a>
+# <a href="http://www.antdiv.gov.au/aad/exop/sfo/davis/video.html">
+# Davis Station, Antarctica (1998-02-26)
+# </a>
+# <a href="http://www.antdiv.gov.au/aad/exop/sfo/mawson/video.html">
+# Mawson Station, Antarctica (1998-02-25)
+# </a>
+
+# Brazil - year-round base
+# Comandante Ferraz, King George Island, -6205+05824, since 1983/4
+
+# Chile - year-round bases and towns
+# Escudero, South Shetland Is, -621157-0585735, since 1994
+# Presidente Eduadro Frei, King George Island, -6214-05848, since 1969-03-07
+# General Bernardo O'Higgins, Antarctic Peninsula, -6319-05704, since 1948-02
+# Capitan Arturo Prat, -6230-05941
+# Villa Las Estrellas (a town), around the Frei base, since 1984-04-09
+# These locations have always used Santiago time; use TZ='America/Santiago'.
+
+# China - year-round bases
+# Great Wall, King George Island, -6213-05858, since 1985-02-20
+# Zhongshan, Larsemann Hills, Prydz Bay, -6922+07623, since 1989-02-26
+
+# France - year-round bases
+#
+# From Antoine Leca (1997-01-20):
+# Time data are from Nicole Pailleau at the IFRTP
+# (French Institute for Polar Research and Technology).
+# She confirms that French Southern Territories and Terre Adelie bases
+# don't observe daylight saving time, even if Terre Adelie supplies came
+# from Tasmania.
+#
+# French Southern Territories with year-round inhabitants
+#
+# Martin-de-Vivies Base, Amsterdam Island, -374105+0773155, since 1950
+# Alfred-Faure Base, Crozet Islands, -462551+0515152, since 1964
+# Port-aux-Francais, Kerguelen Islands, -492110+0701303, since 1951;
+# whaling & sealing station operated 1908/1914, 1920/1929, and 1951/1956
+#
+# St Paul Island - near Amsterdam, uninhabited
+# fishing stations operated variously 1819/1931
+#
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Indian/Kerguelen 0 - zzz 1950 # Port-aux-Francais
+ 5:00 - TFT # ISO code TF Time
+#
+# year-round base in the main continent
+# Dumont-d'Urville, Ile des Petrels, -6640+14001, since 1956-11
+#
+# Another base at Port-Martin, 50km east, began operation in 1947.
+# It was destroyed by fire on 1952-01-14.
+#
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Antarctica/DumontDUrville 0 - zzz 1947
+ 10:00 - PMT 1952 Jan 14 # Port-Martin Time
+ 0 - zzz 1956 Nov
+ 10:00 - DDUT # Dumont-d'Urville Time
+# Reference:
+# <a href="http://en.wikipedia.org/wiki/Dumont_d'Urville_Station">
+# Dumont d'Urville Station (2005-12-05)
+# </a>
+
+# Germany - year-round base
+# Georg von Neumayer, -7039-00815
+
+# India - year-round base
+# Dakshin Gangotri, -7005+01200
+
+# Japan - year-round bases
+# Dome Fuji, -7719+03942
+# Syowa, -690022+0393524
+#
+# From Hideyuki Suzuki (1999-02-06):
+# In all Japanese stations, +0300 is used as the standard time.
+#
+# Syowa station, which is the first antarctic station of Japan,
+# was established on 1957-01-29. Since Syowa station is still the main
+# station of Japan, it's appropriate for the principal location.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Antarctica/Syowa 0 - zzz 1957 Jan 29
+ 3:00 - SYOT # Syowa Time
+# See:
+# <a href="http://www.nipr.ac.jp/english/ara01.html">
+# NIPR Antarctic Research Activities (1999-08-17)
+# </a>
+
+# S Korea - year-round base
+# King Sejong, King George Island, -6213-05847, since 1988
+
+# New Zealand - claims
+# Balleny Islands (never inhabited)
+# Scott Island (never inhabited)
+#
+# year-round base
+# Scott, Ross Island, since 1957-01, is like Antarctica/McMurdo.
+#
+# These rules for New Zealand are stolen from the `australasia' file.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule NZAQ 1974 only - Nov 3 2:00s 1:00 D
+Rule NZAQ 1975 1988 - Oct lastSun 2:00s 1:00 D
+Rule NZAQ 1989 only - Oct 8 2:00s 1:00 D
+Rule NZAQ 1990 2006 - Oct Sun>=1 2:00s 1:00 D
+Rule NZAQ 1975 only - Feb 23 2:00s 0 S
+Rule NZAQ 1976 1989 - Mar Sun>=1 2:00s 0 S
+Rule NZAQ 1990 2007 - Mar Sun>=15 2:00s 0 S
+Rule NZAQ 2007 max - Sep lastSun 2:00s 1:00 D
+Rule NZAQ 2008 max - Apr Sun>=1 2:00s 0 S
+
+# Norway - territories
+# Bouvet (never inhabited)
+#
+# claims
+# Peter I Island (never inhabited)
+
+# Poland - year-round base
+# Arctowski, King George Island, -620945-0582745, since 1977
+
+# Russia - year-round bases
+# Bellingshausen, King George Island, -621159-0585337, since 1968-02-22
+# Mirny, Davis coast, -6633+09301, since 1956-02
+# Molodezhnaya, Alasheyev Bay, -6740+04551,
+# year-round from 1962-02 to 1999-07-01
+# Novolazarevskaya, Queen Maud Land, -7046+01150,
+# year-round from 1960/61 to 1992
+
+# Vostok, since 1957-12-16, temporarily closed 1994-02/1994-11
+# <a href="http://quest.arc.nasa.gov/antarctica/QA/computers/Directions,Time,ZIP">
+# From Craig Mundell (1994-12-15)</a>:
+# Vostok, which is one of the Russian stations, is set on the same
+# time as Moscow, Russia.
+#
+# From Lee Hotz (2001-03-08):
+# I queried the folks at Columbia who spent the summer at Vostok and this is
+# what they had to say about time there:
+# ``in the US Camp (East Camp) we have been on New Zealand (McMurdo)
+# time, which is 12 hours ahead of GMT. The Russian Station Vostok was
+# 6 hours behind that (although only 2 miles away, i.e. 6 hours ahead
+# of GMT). This is a time zone I think two hours east of Moscow. The
+# natural time zone is in between the two: 8 hours ahead of GMT.''
+#
+# From Paul Eggert (2001-05-04):
+# This seems to be hopelessly confusing, so I asked Lee Hotz about it
+# in person. He said that some Antartic locations set their local
+# time so that noon is the warmest part of the day, and that this
+# changes during the year and does not necessarily correspond to mean
+# solar noon. So the Vostok time might have been whatever the clocks
+# happened to be during their visit. So we still don't really know what time
+# it is at Vostok. But we'll guess UTC+6.
+#
+Zone Antarctica/Vostok 0 - zzz 1957 Dec 16
+ 6:00 - VOST # Vostok time
+
+# S Africa - year-round bases
+# Marion Island, -4653+03752
+# Sanae, -7141-00250
+
+# UK
+#
+# British Antarctic Territories (BAT) claims
+# South Orkney Islands
+# scientific station from 1903
+# whaling station at Signy I 1920/1926
+# South Shetland Islands
+#
+# year-round bases
+# Bird Island, South Georgia, -5400-03803, since 1983
+# Deception Island, -6259-06034, whaling station 1912/1931,
+# scientific station 1943/1967,
+# previously sealers and a scientific expedition wintered by accident,
+# and a garrison was deployed briefly
+# Halley, Coates Land, -7535-02604, since 1956-01-06
+# Halley is on a moving ice shelf and is periodically relocated
+# so that it is never more than 10km from its nominal location.
+# Rothera, Adelaide Island, -6734-6808, since 1976-12-01
+#
+# From Paul Eggert (2002-10-22)
+# <http://webexhibits.org/daylightsaving/g.html> says Rothera is -03 all year.
+#
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Antarctica/Rothera 0 - zzz 1976 Dec 1
+ -3:00 - ROTT # Rothera time
+
+# Uruguay - year round base
+# Artigas, King George Island, -621104-0585107
+
+# USA - year-round bases
+#
+# Palmer, Anvers Island, since 1965 (moved 2 miles in 1968)
+#
+# From Ethan Dicks (1996-10-06):
+# It keeps the same time as Punta Arenas, Chile, because, just like us
+# and the South Pole, that's the other end of their supply line....
+# I verified with someone who was there that since 1980,
+# Palmer has followed Chile. Prior to that, before the Falklands War,
+# Palmer used to be supplied from Argentina.
+#
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Antarctica/Palmer 0 - zzz 1965
+ -4:00 ArgAQ AR%sT 1969 Oct 5
+ -3:00 ArgAQ AR%sT 1982 May
+ -4:00 ChileAQ CL%sT
+#
+#
+# McMurdo, Ross Island, since 1955-12
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Antarctica/McMurdo 0 - zzz 1956
+ 12:00 NZAQ NZ%sT
+#
+# Amundsen-Scott, South Pole, continuously occupied since 1956-11-20
+#
+# From Paul Eggert (1996-09-03):
+# Normally it wouldn't have a separate entry, since it's like the
+# larger Antarctica/McMurdo since 1970, but it's too famous to omit.
+#
+# From Chris Carrier (1996-06-27):
+# Siple, the first commander of the South Pole station,
+# stated that he would have liked to have kept GMT at the station,
+# but that he found it more convenient to keep GMT+12
+# as supplies for the station were coming from McMurdo Sound,
+# which was on GMT+12 because New Zealand was on GMT+12 all year
+# at that time (1957). (Source: Siple's book 90 degrees SOUTH.)
+#
+# From Susan Smith
+# http://www.cybertours.com/whs/pole10.html
+# (1995-11-13 16:24:56 +1300, no longer available):
+# We use the same time as McMurdo does.
+# And they use the same time as Christchurch, NZ does....
+# One last quirk about South Pole time.
+# All the electric clocks are usually wrong.
+# Something about the generators running at 60.1hertz or something
+# makes all of the clocks run fast. So every couple of days,
+# we have to go around and set them back 5 minutes or so.
+# Maybe if we let them run fast all of the time, we'd get to leave here sooner!!
+#
+Link Antarctica/McMurdo Antarctica/South_Pole
diff --git a/misc/flot/examples/axes-time-zones/tz/asia b/misc/flot/examples/axes-time-zones/tz/asia
new file mode 100644
index 0000000..d5562c8
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/asia
@@ -0,0 +1,2717 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# This data is by no means authoritative; if you think you know better,
+# go ahead and edit the file (and please send any changes to
+# tz@iana.org for general use in the future).
+
+# From Paul Eggert (2006-03-22):
+#
+# A good source for time zone historical data outside the U.S. is
+# Thomas G. Shanks and Rique Pottenger, The International Atlas (6th edition),
+# San Diego: ACS Publications, Inc. (2003).
+#
+# Gwillim Law writes that a good source
+# for recent time zone data is the International Air Transport
+# Association's Standard Schedules Information Manual (IATA SSIM),
+# published semiannually. Law sent in several helpful summaries
+# of the IATA's data after 1990.
+#
+# Except where otherwise noted, Shanks & Pottenger is the source for
+# entries through 1990, and IATA SSIM is the source for entries afterwards.
+#
+# Another source occasionally used is Edward W. Whitman, World Time Differences,
+# Whitman Publishing Co, 2 Niagara Av, Ealing, London (undated), which
+# I found in the UCLA library.
+#
+# A reliable and entertaining source about time zones is
+# Derek Howse, Greenwich time and longitude, Philip Wilson Publishers (1997).
+#
+# I invented the abbreviations marked `*' in the following table;
+# the rest are from earlier versions of this file, or from other sources.
+# Corrections are welcome!
+# std dst
+# LMT Local Mean Time
+# 2:00 EET EEST Eastern European Time
+# 2:00 IST IDT Israel
+# 3:00 AST ADT Arabia*
+# 3:30 IRST IRDT Iran
+# 4:00 GST Gulf*
+# 5:30 IST India
+# 7:00 ICT Indochina*
+# 7:00 WIT west Indonesia
+# 8:00 CIT central Indonesia
+# 8:00 CST China
+# 9:00 CJT Central Japanese Time (1896/1937)*
+# 9:00 EIT east Indonesia
+# 9:00 JST JDT Japan
+# 9:00 KST KDT Korea
+# 9:30 CST (Australian) Central Standard Time
+#
+# See the `europe' file for Russia and Turkey in Asia.
+
+# From Guy Harris:
+# Incorporates data for Singapore from Robert Elz' asia 1.1, as well as
+# additional information from Tom Yap, Sun Microsystems Intercontinental
+# Technical Support (including a page from the Official Airline Guide -
+# Worldwide Edition). The names for time zones are guesses.
+
+###############################################################################
+
+# These rules are stolen from the `europe' file.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule EUAsia 1981 max - Mar lastSun 1:00u 1:00 S
+Rule EUAsia 1979 1995 - Sep lastSun 1:00u 0 -
+Rule EUAsia 1996 max - Oct lastSun 1:00u 0 -
+Rule E-EurAsia 1981 max - Mar lastSun 0:00 1:00 S
+Rule E-EurAsia 1979 1995 - Sep lastSun 0:00 0 -
+Rule E-EurAsia 1996 max - Oct lastSun 0:00 0 -
+Rule RussiaAsia 1981 1984 - Apr 1 0:00 1:00 S
+Rule RussiaAsia 1981 1983 - Oct 1 0:00 0 -
+Rule RussiaAsia 1984 1991 - Sep lastSun 2:00s 0 -
+Rule RussiaAsia 1985 1991 - Mar lastSun 2:00s 1:00 S
+Rule RussiaAsia 1992 only - Mar lastSat 23:00 1:00 S
+Rule RussiaAsia 1992 only - Sep lastSat 23:00 0 -
+Rule RussiaAsia 1993 max - Mar lastSun 2:00s 1:00 S
+Rule RussiaAsia 1993 1995 - Sep lastSun 2:00s 0 -
+Rule RussiaAsia 1996 max - Oct lastSun 2:00s 0 -
+
+# Afghanistan
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Kabul 4:36:48 - LMT 1890
+ 4:00 - AFT 1945
+ 4:30 - AFT
+
+# Armenia
+# From Paul Eggert (2006-03-22):
+# Shanks & Pottenger have Yerevan switching to 3:00 (with Russian DST)
+# in spring 1991, then to 4:00 with no DST in fall 1995, then
+# readopting Russian DST in 1997. Go with Shanks & Pottenger, even
+# when they disagree with others. Edgar Der-Danieliantz
+# reported (1996-05-04) that Yerevan probably wouldn't use DST
+# in 1996, though it did use DST in 1995. IATA SSIM (1991/1998) reports that
+# Armenia switched from 3:00 to 4:00 in 1998 and observed DST after 1991,
+# but started switching at 3:00s in 1998.
+
+# From Arthur David Olson (2011-06-15):
+# While Russia abandoned DST in 2011, Armenia may choose to
+# follow Russia's "old" rules.
+
+# From Alexander Krivenyshev (2012-02-10):
+# According to News Armenia, on Feb 9, 2012,
+# http://newsarmenia.ru/society/20120209/42609695.html
+#
+# The Armenia National Assembly adopted final reading of Amendments to the
+# Law "On procedure of calculation time on the territory of the Republic of
+# Armenia" according to which Armenia [is] abolishing Daylight Saving Time.
+# or
+# (brief)
+# http://www.worldtimezone.com/dst_news/dst_news_armenia03.html
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Yerevan 2:58:00 - LMT 1924 May 2
+ 3:00 - YERT 1957 Mar # Yerevan Time
+ 4:00 RussiaAsia YER%sT 1991 Mar 31 2:00s
+ 3:00 1:00 YERST 1991 Sep 23 # independence
+ 3:00 RussiaAsia AM%sT 1995 Sep 24 2:00s
+ 4:00 - AMT 1997
+ 4:00 RussiaAsia AM%sT 2012 Mar 25 2:00s
+ 4:00 - AMT
+
+# Azerbaijan
+# From Rustam Aliyev of the Azerbaijan Internet Forum (2005-10-23):
+# According to the resolution of Cabinet of Ministers, 1997
+# Resolution available at: http://aif.az/docs/daylight_res.pdf
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Azer 1997 max - Mar lastSun 4:00 1:00 S
+Rule Azer 1997 max - Oct lastSun 5:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Baku 3:19:24 - LMT 1924 May 2
+ 3:00 - BAKT 1957 Mar # Baku Time
+ 4:00 RussiaAsia BAK%sT 1991 Mar 31 2:00s
+ 3:00 1:00 BAKST 1991 Aug 30 # independence
+ 3:00 RussiaAsia AZ%sT 1992 Sep lastSat 23:00
+ 4:00 - AZT 1996 # Azerbaijan time
+ 4:00 EUAsia AZ%sT 1997
+ 4:00 Azer AZ%sT
+
+# Bahrain
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Bahrain 3:22:20 - LMT 1920 # Al Manamah
+ 4:00 - GST 1972 Jun
+ 3:00 - AST
+
+# Bangladesh
+# From Alexander Krivenyshev (2009-05-13):
+# According to newspaper Asian Tribune (May 6, 2009) Bangladesh may introduce
+# Daylight Saving Time from June 16 to Sept 30
+#
+# Bangladesh to introduce daylight saving time likely from June 16
+# <a href="http://www.asiantribune.com/?q=node/17288">
+# http://www.asiantribune.com/?q=node/17288
+# </a>
+# or
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_bangladesh02.html">
+# http://www.worldtimezone.com/dst_news/dst_news_bangladesh02.html
+# </a>
+#
+# "... Bangladesh government has decided to switch daylight saving time from
+# June
+# 16 till September 30 in a bid to ensure maximum use of daylight to cope with
+# crippling power crisis. "
+#
+# The switch will remain in effect from June 16 to Sept 30 (2009) but if
+# implemented the next year, it will come in force from April 1, 2010
+
+# From Steffen Thorsen (2009-06-02):
+# They have finally decided now, but changed the start date to midnight between
+# the 19th and 20th, and they have not set the end date yet.
+#
+# Some sources:
+# <a href="http://in.reuters.com/article/southAsiaNews/idINIndia-40017620090601">
+# http://in.reuters.com/article/southAsiaNews/idINIndia-40017620090601
+# </a>
+# <a href="http://bdnews24.com/details.php?id=85889&cid=2">
+# http://bdnews24.com/details.php?id=85889&cid=2
+# </a>
+#
+# Our wrap-up:
+# <a href="http://www.timeanddate.com/news/time/bangladesh-daylight-saving-2009.html">
+# http://www.timeanddate.com/news/time/bangladesh-daylight-saving-2009.html
+# </a>
+
+# From A. N. M. Kamrus Saadat (2009-06-15):
+# Finally we've got the official mail regarding DST start time where DST start
+# time is mentioned as Jun 19 2009, 23:00 from BTRC (Bangladesh
+# Telecommunication Regulatory Commission).
+#
+# No DST end date has been announced yet.
+
+# From Alexander Krivenyshev (2009-09-25):
+# Bangladesh won't go back to Standard Time from October 1, 2009,
+# instead it will continue DST measure till the cabinet makes a fresh decision.
+#
+# Following report by same newspaper-"The Daily Star Friday":
+# "DST change awaits cabinet decision-Clock won't go back by 1-hr from Oct 1"
+# <a href="http://www.thedailystar.net/newDesign/news-details.php?nid=107021">
+# http://www.thedailystar.net/newDesign/news-details.php?nid=107021
+# </a>
+# or
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_bangladesh04.html">
+# http://www.worldtimezone.com/dst_news/dst_news_bangladesh04.html
+# </a>
+
+# From Steffen Thorsen (2009-10-13):
+# IANS (Indo-Asian News Service) now reports:
+# Bangladesh has decided that the clock advanced by an hour to make
+# maximum use of daylight hours as an energy saving measure would
+# "continue for an indefinite period."
+#
+# One of many places where it is published:
+# <a href="http://www.thaindian.com/newsportal/business/bangladesh-to-continue-indefinitely-with-advanced-time_100259987.html">
+# http://www.thaindian.com/newsportal/business/bangladesh-to-continue-indefinitely-with-advanced-time_100259987.html
+# </a>
+
+# From Alexander Krivenyshev (2009-12-24):
+# According to Bangladesh newspaper "The Daily Star,"
+# Bangladesh will change its clock back to Standard Time on Dec 31, 2009.
+#
+# Clock goes back 1-hr on Dec 31 night.
+# <a href="http://www.thedailystar.net/newDesign/news-details.php?nid=119228">
+# http://www.thedailystar.net/newDesign/news-details.php?nid=119228
+# </a>
+# and
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_bangladesh05.html">
+# http://www.worldtimezone.com/dst_news/dst_news_bangladesh05.html
+# </a>
+#
+# "...The government yesterday decided to put the clock back by one hour
+# on December 31 midnight and the new time will continue until March 31,
+# 2010 midnight. The decision came at a cabinet meeting at the Prime
+# Minister's Office last night..."
+
+# From Alexander Krivenyshev (2010-03-22):
+# According to Bangladesh newspaper "The Daily Star,"
+# Cabinet cancels Daylight Saving Time
+# <a href="http://www.thedailystar.net/newDesign/latest_news.php?nid=22817">
+# http://www.thedailystar.net/newDesign/latest_news.php?nid=22817
+# </a>
+# or
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_bangladesh06.html">
+# http://www.worldtimezone.com/dst_news/dst_news_bangladesh06.html
+# </a>
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Dhaka 2009 only - Jun 19 23:00 1:00 S
+Rule Dhaka 2009 only - Dec 31 23:59 0 -
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Dhaka 6:01:40 - LMT 1890
+ 5:53:20 - HMT 1941 Oct # Howrah Mean Time?
+ 6:30 - BURT 1942 May 15 # Burma Time
+ 5:30 - IST 1942 Sep
+ 6:30 - BURT 1951 Sep 30
+ 6:00 - DACT 1971 Mar 26 # Dacca Time
+ 6:00 - BDT 2009
+ 6:00 Dhaka BD%sT
+
+# Bhutan
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Thimphu 5:58:36 - LMT 1947 Aug 15 # or Thimbu
+ 5:30 - IST 1987 Oct
+ 6:00 - BTT # Bhutan Time
+
+# British Indian Ocean Territory
+# Whitman and the 1995 CIA time zone map say 5:00, but the
+# 1997 and later maps say 6:00. Assume the switch occurred in 1996.
+# We have no information as to when standard time was introduced;
+# assume it occurred in 1907, the same year as Mauritius (which
+# then contained the Chagos Archipelago).
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Indian/Chagos 4:49:40 - LMT 1907
+ 5:00 - IOT 1996 # BIOT Time
+ 6:00 - IOT
+
+# Brunei
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Brunei 7:39:40 - LMT 1926 Mar # Bandar Seri Begawan
+ 7:30 - BNT 1933
+ 8:00 - BNT
+
+# Burma / Myanmar
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Rangoon 6:24:40 - LMT 1880 # or Yangon
+ 6:24:36 - RMT 1920 # Rangoon Mean Time?
+ 6:30 - BURT 1942 May # Burma Time
+ 9:00 - JST 1945 May 3
+ 6:30 - MMT # Myanmar Time
+
+# Cambodia
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Phnom_Penh 6:59:40 - LMT 1906 Jun 9
+ 7:06:20 - SMT 1911 Mar 11 0:01 # Saigon MT?
+ 7:00 - ICT 1912 May
+ 8:00 - ICT 1931 May
+ 7:00 - ICT
+
+# China
+
+# From Guy Harris:
+# People's Republic of China. Yes, they really have only one time zone.
+
+# From Bob Devine (1988-01-28):
+# No they don't. See TIME mag, 1986-02-17 p.52. Even though
+# China is across 4 physical time zones, before Feb 1, 1986 only the
+# Peking (Bejing) time zone was recognized. Since that date, China
+# has two of 'em -- Peking's and Urumqi (named after the capital of
+# the Xinjiang Uyghur Autonomous Region). I don't know about DST for it.
+#
+# . . .I just deleted the DST table and this editor makes it too
+# painful to suck in another copy.. So, here is what I have for
+# DST start/end dates for Peking's time zone (info from AP):
+#
+# 1986 May 4 - Sept 14
+# 1987 mid-April - ??
+
+# From U. S. Naval Observatory (1989-01-19):
+# CHINA 8 H AHEAD OF UTC ALL OF CHINA, INCL TAIWAN
+# CHINA 9 H AHEAD OF UTC APR 17 - SEP 10
+
+# From Paul Eggert (2006-03-22):
+# Shanks & Pottenger write that China (except for Hong Kong and Macau)
+# has had a single time zone since 1980 May 1, observing summer DST
+# from 1986 through 1991; this contradicts Devine's
+# note about Time magazine, though apparently _something_ happened in 1986.
+# Go with Shanks & Pottenger for now. I made up names for the other
+# pre-1980 time zones.
+
+# From Shanks & Pottenger:
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Shang 1940 only - Jun 3 0:00 1:00 D
+Rule Shang 1940 1941 - Oct 1 0:00 0 S
+Rule Shang 1941 only - Mar 16 0:00 1:00 D
+Rule PRC 1986 only - May 4 0:00 1:00 D
+Rule PRC 1986 1991 - Sep Sun>=11 0:00 0 S
+Rule PRC 1987 1991 - Apr Sun>=10 0:00 1:00 D
+
+# From Anthony Fok (2001-12-20):
+# BTW, I did some research on-line and found some info regarding these five
+# historic timezones from some Taiwan websites. And yes, there are official
+# Chinese names for these locales (before 1949).
+#
+# From Jesper Norgaard Welen (2006-07-14):
+# I have investigated the timezones around 1970 on the
+# http://www.astro.com/atlas site [with provinces and county
+# boundaries summarized below].... A few other exceptions were two
+# counties on the Sichuan side of the Xizang-Sichuan border,
+# counties Dege and Baiyu which lies on the Sichuan side and are
+# therefore supposed to be GMT+7, Xizang region being GMT+6, but Dege
+# county is GMT+8 according to astro.com while Baiyu county is GMT+6
+# (could be true), for the moment I am assuming that those two
+# counties are mistakes in the astro.com data.
+
+# From Paul Eggert (2008-02-11):
+# I just now checked Google News for western news sources that talk
+# about China's single time zone, and couldn't find anything before 1986
+# talking about China being in one time zone. (That article was: Jim
+# Mann, "A clumsy embrace for another western custom: China on daylight
+# time--sort of", Los Angeles Times, 1986-05-05. By the way, this
+# article confirms the tz database's data claiming that China began
+# observing daylight saving time in 1986.
+#
+# From Thomas S. Mullaney (2008-02-11):
+# I think you're combining two subjects that need to treated
+# separately: daylight savings (which, you're correct, wasn't
+# implemented until the 1980s) and the unified time zone centered near
+# Beijing (which was implemented in 1949). Briefly, there was also a
+# "Lhasa Time" in Tibet and "Urumqi Time" in Xinjiang. The first was
+# ceased, and the second eventually recognized (again, in the 1980s).
+#
+# From Paul Eggert (2008-06-30):
+# There seems to be a good chance China switched to a single time zone in 1949
+# rather than in 1980 as Shanks & Pottenger have it, but we don't have a
+# reliable documentary source saying so yet, so for now we still go with
+# Shanks & Pottenger.
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+# Changbai Time ("Long-white Time", Long-white = Heilongjiang area)
+# Heilongjiang (except Mohe county), Jilin
+Zone Asia/Harbin 8:26:44 - LMT 1928 # or Haerbin
+ 8:30 - CHAT 1932 Mar # Changbai Time
+ 8:00 - CST 1940
+ 9:00 - CHAT 1966 May
+ 8:30 - CHAT 1980 May
+ 8:00 PRC C%sT
+# Zhongyuan Time ("Central plain Time")
+# most of China
+Zone Asia/Shanghai 8:05:52 - LMT 1928
+ 8:00 Shang C%sT 1949
+ 8:00 PRC C%sT
+# Long-shu Time (probably due to Long and Shu being two names of that area)
+# Guangxi, Guizhou, Hainan, Ningxia, Sichuan, Shaanxi, and Yunnan;
+# most of Gansu; west Inner Mongolia; west Qinghai; and the Guangdong
+# counties Deqing, Enping, Kaiping, Luoding, Taishan, Xinxing,
+# Yangchun, Yangjiang, Yu'nan, and Yunfu.
+Zone Asia/Chongqing 7:06:20 - LMT 1928 # or Chungking
+ 7:00 - LONT 1980 May # Long-shu Time
+ 8:00 PRC C%sT
+# Xin-zang Time ("Xinjiang-Tibet Time")
+# The Gansu counties Aksay, Anxi, Dunhuang, Subei; west Qinghai;
+# the Guangdong counties Xuwen, Haikang, Suixi, Lianjiang,
+# Zhanjiang, Wuchuan, Huazhou, Gaozhou, Maoming, Dianbai, and Xinyi;
+# east Tibet, including Lhasa, Chamdo, Shigaise, Jimsar, Shawan and Hutubi;
+# east Xinjiang, including Urumqi, Turpan, Karamay, Korla, Minfeng, Jinghe,
+# Wusu, Qiemo, Xinyan, Wulanwusu, Jinghe, Yumin, Tacheng, Tuoli, Emin,
+# Shihezi, Changji, Yanqi, Heshuo, Tuokexun, Tulufan, Shanshan, Hami,
+# Fukang, Kuitun, Kumukuli, Miquan, Qitai, and Turfan.
+Zone Asia/Urumqi 5:50:20 - LMT 1928 # or Urumchi
+ 6:00 - URUT 1980 May # Urumqi Time
+ 8:00 PRC C%sT
+# Kunlun Time
+# West Tibet, including Pulan, Aheqi, Shufu, Shule;
+# West Xinjiang, including Aksu, Atushi, Yining, Hetian, Cele, Luopu, Nileke,
+# Zhaosu, Tekesi, Gongliu, Chabuchaer, Huocheng, Bole, Pishan, Suiding,
+# and Yarkand.
+
+# From Luther Ma (2009-10-17):
+# Almost all (>99.9%) ethnic Chinese (properly ethnic Han) living in
+# Xinjiang use Chinese Standard Time. Some are aware of Xinjiang time,
+# but have no need of it. All planes, trains, and schools function on
+# what is called "Beijing time." When Han make an appointment in Chinese
+# they implicitly use Beijing time.
+#
+# On the other hand, ethnic Uyghurs, who make up about half the
+# population of Xinjiang, typically use "Xinjiang time" which is two
+# hours behind Beijing time, or UTC +0600. The government of the Xinjiang
+# Uyghur Autonomous Region, (XAUR, or just Xinjiang for short) as well as
+# local governments such as the Urumqi city government use both times in
+# publications, referring to what is popularly called Xinjiang time as
+# "Urumqi time." When Uyghurs make an appointment in the Uyghur language
+# they almost invariably use Xinjiang time.
+#
+# (Their ethnic Han compatriots would typically have no clue of its
+# widespread use, however, because so extremely few of them are fluent in
+# Uyghur, comparable to the number of Anglo-Americans fluent in Navajo.)
+#
+# (...As with the rest of China there was a brief interval ending in 1990
+# or 1991 when summer time was in use. The confusion was severe, with
+# the province not having dual times but four times in use at the same
+# time. Some areas remained on standard Xinjiang time or Beijing time and
+# others moving their clocks ahead.)
+#
+# ...an example of an official website using of Urumqi time.
+#
+# The first few lines of the Google translation of
+# <a href="http://www.fjysgl.gov.cn/show.aspx?id=2379&cid=39">
+# http://www.fjysgl.gov.cn/show.aspx?id=2379&cid=39
+# </a>
+# (retrieved 2009-10-13)
+# > Urumqi fire seven people are missing the alleged losses of at least
+# > 500 million yuan
+# >
+# > (Reporter Dong Liu) the day before 20:20 or so (Urumqi Time 18:20),
+# > Urumqi City Department of International Plaza Luther Qiantang River
+# > burst fire. As of yesterday, 18:30, Urumqi City Fire officers and men
+# > have worked continuously for 22 hours...
+
+# From Luther Ma (2009-11-19):
+# With the risk of being redundant to previous answers these are the most common
+# English "transliterations" (w/o using non-English symbols):
+#
+# 1. Wulumuqi...
+# 2. Kashi...
+# 3. Urumqi...
+# 4. Kashgar...
+# ...
+# 5. It seems that Uyghurs in Urumqi has been using Xinjiang since at least the
+# 1960's. I know of one Han, now over 50, who grew up in the surrounding
+# countryside and used Xinjiang time as a child.
+#
+# 6. Likewise for Kashgar and the rest of south Xinjiang I don't know of any
+# start date for Xinjiang time.
+#
+# Without having access to local historical records, nor the ability to legally
+# publish them, I would go with October 1, 1949, when Xinjiang became the Uyghur
+# Autonomous Region under the PRC. (Before that Uyghurs, of course, would also
+# not be using Beijing time, but some local time.)
+
+Zone Asia/Kashgar 5:03:56 - LMT 1928 # or Kashi or Kaxgar
+ 5:30 - KAST 1940 # Kashgar Time
+ 5:00 - KAST 1980 May
+ 8:00 PRC C%sT
+
+
+# From Lee Yiu Chung (2009-10-24):
+# I found there are some mistakes for the...DST rule for Hong
+# Kong. [According] to the DST record from Hong Kong Observatory (actually,
+# it is not [an] observatory, but the official meteorological agency of HK,
+# and also serves as the official timing agency), there are some missing
+# and incorrect rules. Although the exact switch over time is missing, I
+# think 3:30 is correct. The official DST record for Hong Kong can be
+# obtained from
+# <a href="http://www.hko.gov.hk/gts/time/Summertime.htm">
+# http://www.hko.gov.hk/gts/time/Summertime.htm
+# </a>.
+
+# From Arthur David Olson (2009-10-28):
+# Here are the dates given at
+# <a href="http://www.hko.gov.hk/gts/time/Summertime.htm">
+# http://www.hko.gov.hk/gts/time/Summertime.htm
+# </a>
+# as of 2009-10-28:
+# Year Period
+# 1941 1 Apr to 30 Sep
+# 1942 Whole year
+# 1943 Whole year
+# 1944 Whole year
+# 1945 Whole year
+# 1946 20 Apr to 1 Dec
+# 1947 13 Apr to 30 Dec
+# 1948 2 May to 31 Oct
+# 1949 3 Apr to 30 Oct
+# 1950 2 Apr to 29 Oct
+# 1951 1 Apr to 28 Oct
+# 1952 6 Apr to 25 Oct
+# 1953 5 Apr to 1 Nov
+# 1954 21 Mar to 31 Oct
+# 1955 20 Mar to 6 Nov
+# 1956 18 Mar to 4 Nov
+# 1957 24 Mar to 3 Nov
+# 1958 23 Mar to 2 Nov
+# 1959 22 Mar to 1 Nov
+# 1960 20 Mar to 6 Nov
+# 1961 19 Mar to 5 Nov
+# 1962 18 Mar to 4 Nov
+# 1963 24 Mar to 3 Nov
+# 1964 22 Mar to 1 Nov
+# 1965 18 Apr to 17 Oct
+# 1966 17 Apr to 16 Oct
+# 1967 16 Apr to 22 Oct
+# 1968 21 Apr to 20 Oct
+# 1969 20 Apr to 19 Oct
+# 1970 19 Apr to 18 Oct
+# 1971 18 Apr to 17 Oct
+# 1972 16 Apr to 22 Oct
+# 1973 22 Apr to 21 Oct
+# 1973/74 30 Dec 73 to 20 Oct 74
+# 1975 20 Apr to 19 Oct
+# 1976 18 Apr to 17 Oct
+# 1977 Nil
+# 1978 Nil
+# 1979 13 May to 21 Oct
+# 1980 to Now Nil
+# The page does not give start or end times of day.
+# The page does not give a start date for 1942.
+# The page does not givw an end date for 1945.
+# The Japanese occupation of Hong Kong began on 1941-12-25.
+# The Japanese surrender of Hong Kong was signed 1945-09-15.
+# For lack of anything better, use start of those days as the transition times.
+
+# Hong Kong (Xianggang)
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule HK 1941 only - Apr 1 3:30 1:00 S
+Rule HK 1941 only - Sep 30 3:30 0 -
+Rule HK 1946 only - Apr 20 3:30 1:00 S
+Rule HK 1946 only - Dec 1 3:30 0 -
+Rule HK 1947 only - Apr 13 3:30 1:00 S
+Rule HK 1947 only - Dec 30 3:30 0 -
+Rule HK 1948 only - May 2 3:30 1:00 S
+Rule HK 1948 1951 - Oct lastSun 3:30 0 -
+Rule HK 1952 only - Oct 25 3:30 0 -
+Rule HK 1949 1953 - Apr Sun>=1 3:30 1:00 S
+Rule HK 1953 only - Nov 1 3:30 0 -
+Rule HK 1954 1964 - Mar Sun>=18 3:30 1:00 S
+Rule HK 1954 only - Oct 31 3:30 0 -
+Rule HK 1955 1964 - Nov Sun>=1 3:30 0 -
+Rule HK 1965 1976 - Apr Sun>=16 3:30 1:00 S
+Rule HK 1965 1976 - Oct Sun>=16 3:30 0 -
+Rule HK 1973 only - Dec 30 3:30 1:00 S
+Rule HK 1979 only - May Sun>=8 3:30 1:00 S
+Rule HK 1979 only - Oct Sun>=16 3:30 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Hong_Kong 7:36:36 - LMT 1904 Oct 30
+ 8:00 HK HK%sT 1941 Dec 25
+ 9:00 - JST 1945 Sep 15
+ 8:00 HK HK%sT
+
+###############################################################################
+
+# Taiwan
+
+# Shanks & Pottenger write that Taiwan observed DST during 1945, when it
+# was still controlled by Japan. This is hard to believe, but we don't
+# have any other information.
+
+# From smallufo (2010-04-03):
+# According to Taiwan's CWB,
+# <a href="http://www.cwb.gov.tw/V6/astronomy/cdata/summert.htm">
+# http://www.cwb.gov.tw/V6/astronomy/cdata/summert.htm
+# </a>
+# Taipei has DST in 1979 between July 1st and Sep 30.
+
+# From Arthur David Olson (2010-04-07):
+# Here's Google's translation of the table at the bottom of the "summert.htm" page:
+# Decade Name Start and end date
+# Republic of China 34 years to 40 years (AD 1945-1951 years) Summer Time May 1 to September 30
+# 41 years of the Republic of China (AD 1952) Daylight Saving Time March 1 to October 31
+# Republic of China 42 years to 43 years (AD 1953-1954 years) Daylight Saving Time April 1 to October 31
+# In the 44 years to 45 years (AD 1955-1956 years) Daylight Saving Time April 1 to September 30
+# Republic of China 46 years to 48 years (AD 1957-1959) Summer Time April 1 to September 30
+# Republic of China 49 years to 50 years (AD 1960-1961) Summer Time June 1 to September 30
+# Republic of China 51 years to 62 years (AD 1962-1973 years) Stop Summer Time
+# Republic of China 63 years to 64 years (1974-1975 AD) Daylight Saving Time April 1 to September 30
+# Republic of China 65 years to 67 years (1976-1978 AD) Stop Daylight Saving Time
+# Republic of China 68 years (AD 1979) Daylight Saving Time July 1 to September 30
+# Republic of China since 69 years (AD 1980) Stop Daylight Saving Time
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Taiwan 1945 1951 - May 1 0:00 1:00 D
+Rule Taiwan 1945 1951 - Oct 1 0:00 0 S
+Rule Taiwan 1952 only - Mar 1 0:00 1:00 D
+Rule Taiwan 1952 1954 - Nov 1 0:00 0 S
+Rule Taiwan 1953 1959 - Apr 1 0:00 1:00 D
+Rule Taiwan 1955 1961 - Oct 1 0:00 0 S
+Rule Taiwan 1960 1961 - Jun 1 0:00 1:00 D
+Rule Taiwan 1974 1975 - Apr 1 0:00 1:00 D
+Rule Taiwan 1974 1975 - Oct 1 0:00 0 S
+Rule Taiwan 1979 only - Jun 30 0:00 1:00 D
+Rule Taiwan 1979 only - Sep 30 0:00 0 S
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Taipei 8:06:00 - LMT 1896 # or Taibei or T'ai-pei
+ 8:00 Taiwan C%sT
+
+# Macau (Macao, Aomen)
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Macau 1961 1962 - Mar Sun>=16 3:30 1:00 S
+Rule Macau 1961 1964 - Nov Sun>=1 3:30 0 -
+Rule Macau 1963 only - Mar Sun>=16 0:00 1:00 S
+Rule Macau 1964 only - Mar Sun>=16 3:30 1:00 S
+Rule Macau 1965 only - Mar Sun>=16 0:00 1:00 S
+Rule Macau 1965 only - Oct 31 0:00 0 -
+Rule Macau 1966 1971 - Apr Sun>=16 3:30 1:00 S
+Rule Macau 1966 1971 - Oct Sun>=16 3:30 0 -
+Rule Macau 1972 1974 - Apr Sun>=15 0:00 1:00 S
+Rule Macau 1972 1973 - Oct Sun>=15 0:00 0 -
+Rule Macau 1974 1977 - Oct Sun>=15 3:30 0 -
+Rule Macau 1975 1977 - Apr Sun>=15 3:30 1:00 S
+Rule Macau 1978 1980 - Apr Sun>=15 0:00 1:00 S
+Rule Macau 1978 1980 - Oct Sun>=15 0:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Macau 7:34:20 - LMT 1912
+ 8:00 Macau MO%sT 1999 Dec 20 # return to China
+ 8:00 PRC C%sT
+
+
+###############################################################################
+
+# Cyprus
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Cyprus 1975 only - Apr 13 0:00 1:00 S
+Rule Cyprus 1975 only - Oct 12 0:00 0 -
+Rule Cyprus 1976 only - May 15 0:00 1:00 S
+Rule Cyprus 1976 only - Oct 11 0:00 0 -
+Rule Cyprus 1977 1980 - Apr Sun>=1 0:00 1:00 S
+Rule Cyprus 1977 only - Sep 25 0:00 0 -
+Rule Cyprus 1978 only - Oct 2 0:00 0 -
+Rule Cyprus 1979 1997 - Sep lastSun 0:00 0 -
+Rule Cyprus 1981 1998 - Mar lastSun 0:00 1:00 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Nicosia 2:13:28 - LMT 1921 Nov 14
+ 2:00 Cyprus EE%sT 1998 Sep
+ 2:00 EUAsia EE%sT
+# IATA SSIM (1998-09) has Cyprus using EU rules for the first time.
+
+# Classically, Cyprus belongs to Asia; e.g. see Herodotus, Histories, I.72.
+# However, for various reasons many users expect to find it under Europe.
+Link Asia/Nicosia Europe/Nicosia
+
+# Georgia
+# From Paul Eggert (1994-11-19):
+# Today's _Economist_ (p 60) reports that Georgia moved its clocks forward
+# an hour recently, due to a law proposed by Zurab Murvanidze,
+# an MP who went on a hunger strike for 11 days to force discussion about it!
+# We have no details, but we'll guess they didn't move the clocks back in fall.
+#
+# From Mathew Englander, quoting AP (1996-10-23 13:05-04):
+# Instead of putting back clocks at the end of October, Georgia
+# will stay on daylight savings time this winter to save energy,
+# President Eduard Shevardnadze decreed Wednesday.
+#
+# From the BBC via Joseph S. Myers (2004-06-27):
+#
+# Georgia moved closer to Western Europe on Sunday... The former Soviet
+# republic has changed its time zone back to that of Moscow. As a result it
+# is now just four hours ahead of Greenwich Mean Time, rather than five hours
+# ahead. The switch was decreed by the pro-Western president of Georgia,
+# Mikhail Saakashvili, who said the change was partly prompted by the process
+# of integration into Europe.
+
+# From Teimuraz Abashidze (2005-11-07):
+# Government of Georgia ... decided to NOT CHANGE daylight savings time on
+# [Oct.] 30, as it was done before during last more than 10 years.
+# Currently, we are in fact GMT +4:00, as before 30 October it was GMT
+# +3:00.... The problem is, there is NO FORMAL LAW or governmental document
+# about it. As far as I can find, I was told, that there is no document,
+# because we just DIDN'T ISSUE document about switching to winter time....
+# I don't know what can be done, especially knowing that some years ago our
+# DST rules where changed THREE TIMES during one month.
+
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Tbilisi 2:59:16 - LMT 1880
+ 2:59:16 - TBMT 1924 May 2 # Tbilisi Mean Time
+ 3:00 - TBIT 1957 Mar # Tbilisi Time
+ 4:00 RussiaAsia TBI%sT 1991 Mar 31 2:00s
+ 3:00 1:00 TBIST 1991 Apr 9 # independence
+ 3:00 RussiaAsia GE%sT 1992 # Georgia Time
+ 3:00 E-EurAsia GE%sT 1994 Sep lastSun
+ 4:00 E-EurAsia GE%sT 1996 Oct lastSun
+ 4:00 1:00 GEST 1997 Mar lastSun
+ 4:00 E-EurAsia GE%sT 2004 Jun 27
+ 3:00 RussiaAsia GE%sT 2005 Mar lastSun 2:00
+ 4:00 - GET
+
+# East Timor
+
+# See Indonesia for the 1945 transition.
+
+# From Joao Carrascalao, brother of the former governor of East Timor, in
+# <a href="http://etan.org/et99c/december/26-31/30ETMAY.htm">
+# East Timor may be late for its millennium
+# </a> (1999-12-26/31):
+# Portugal tried to change the time forward in 1974 because the sun
+# rises too early but the suggestion raised a lot of problems with the
+# Timorese and I still don't think it would work today because it
+# conflicts with their way of life.
+
+# From Paul Eggert (2000-12-04):
+# We don't have any record of the above attempt.
+# Most likely our records are incomplete, but we have no better data.
+
+# <a href="http://www.hri.org/news/world/undh/last/00-08-16.undh.html">
+# From Manoel de Almeida e Silva, Deputy Spokesman for the UN Secretary-General
+# (2000-08-16)</a>:
+# The Cabinet of the East Timor Transition Administration decided
+# today to advance East Timor's time by one hour. The time change,
+# which will be permanent, with no seasonal adjustment, will happen at
+# midnight on Saturday, September 16.
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Dili 8:22:20 - LMT 1912
+ 8:00 - TLT 1942 Feb 21 23:00 # E Timor Time
+ 9:00 - JST 1945 Sep 23
+ 9:00 - TLT 1976 May 3
+ 8:00 - CIT 2000 Sep 17 00:00
+ 9:00 - TLT
+
+# India
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Kolkata 5:53:28 - LMT 1880 # Kolkata
+ 5:53:20 - HMT 1941 Oct # Howrah Mean Time?
+ 6:30 - BURT 1942 May 15 # Burma Time
+ 5:30 - IST 1942 Sep
+ 5:30 1:00 IST 1945 Oct 15
+ 5:30 - IST
+# The following are like Asia/Kolkata:
+# Andaman Is
+# Lakshadweep (Laccadive, Minicoy and Amindivi Is)
+# Nicobar Is
+
+# Indonesia
+#
+# From Gwillim Law (2001-05-28), overriding Shanks & Pottenger:
+# <http://www.sumatera-inc.com/go_to_invest/about_indonesia.asp#standtime>
+# says that Indonesia's time zones changed on 1988-01-01. Looking at some
+# time zone maps, I think that must refer to Western Borneo (Kalimantan Barat
+# and Kalimantan Tengah) switching from UTC+8 to UTC+7.
+#
+# From Paul Eggert (2007-03-10):
+# Here is another correction to Shanks & Pottenger.
+# JohnTWB writes that Japanese forces did not surrender control in
+# Indonesia until 1945-09-01 00:00 at the earliest (in Jakarta) and
+# other formal surrender ceremonies were September 9, 11, and 13, plus
+# September 12 for the regional surrender to Mountbatten in Singapore.
+# These would be the earliest possible times for a change.
+# Regimes horaires pour le monde entier, by Henri Le Corre, (Editions
+# Traditionnelles, 1987, Paris) says that Java and Madura switched
+# from JST to UTC+07:30 on 1945-09-23, and gives 1944-09-01 for Jayapura
+# (Hollandia). For now, assume all Indonesian locations other than Jayapura
+# switched on 1945-09-23.
+#
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Jakarta 7:07:12 - LMT 1867 Aug 10
+# Shanks & Pottenger say the next transition was at 1924 Jan 1 0:13,
+# but this must be a typo.
+ 7:07:12 - JMT 1923 Dec 31 23:47:12 # Jakarta
+ 7:20 - JAVT 1932 Nov # Java Time
+ 7:30 - WIT 1942 Mar 23
+ 9:00 - JST 1945 Sep 23
+ 7:30 - WIT 1948 May
+ 8:00 - WIT 1950 May
+ 7:30 - WIT 1964
+ 7:00 - WIT
+Zone Asia/Pontianak 7:17:20 - LMT 1908 May
+ 7:17:20 - PMT 1932 Nov # Pontianak MT
+ 7:30 - WIT 1942 Jan 29
+ 9:00 - JST 1945 Sep 23
+ 7:30 - WIT 1948 May
+ 8:00 - WIT 1950 May
+ 7:30 - WIT 1964
+ 8:00 - CIT 1988 Jan 1
+ 7:00 - WIT
+Zone Asia/Makassar 7:57:36 - LMT 1920
+ 7:57:36 - MMT 1932 Nov # Macassar MT
+ 8:00 - CIT 1942 Feb 9
+ 9:00 - JST 1945 Sep 23
+ 8:00 - CIT
+Zone Asia/Jayapura 9:22:48 - LMT 1932 Nov
+ 9:00 - EIT 1944 Sep 1
+ 9:30 - CST 1964
+ 9:00 - EIT
+
+# Iran
+
+# From Roozbeh Pournader (2003-03-15):
+# This is an English translation of what I just found (originally in Persian).
+# The Gregorian dates in brackets are mine:
+#
+# Official Newspaper No. 13548-1370/6/25 [1991-09-16]
+# No. 16760/T233 H 1370/6/10 [1991-09-01]
+#
+# The Rule About Change of the Official Time of the Country
+#
+# The Board of Ministers, in the meeting dated 1370/5/23 [1991-08-14],
+# based on the suggestion number 2221/D dated 1370/4/22 [1991-07-13]
+# of the Country's Organization for Official and Employment Affairs,
+# and referring to the law for equating the working hours of workers
+# and officers in the whole country dated 1359/4/23 [1980-07-14], and
+# for synchronizing the official times of the country, agreed that:
+#
+# The official time of the country will should move forward one hour
+# at the 24[:00] hours of the first day of Farvardin and should return
+# to its previous state at the 24[:00] hours of the 30th day of
+# Shahrivar.
+#
+# First Deputy to the President - Hassan Habibi
+#
+# From personal experience, that agrees with what has been followed
+# for at least the last 5 years. Before that, for a few years, the
+# date used was the first Thursday night of Farvardin and the last
+# Thursday night of Shahrivar, but I can't give exact dates....
+# I have also changed the abbreviations to what is considered correct
+# here in Iran, IRST for regular time and IRDT for daylight saving time.
+#
+# From Roozbeh Pournader (2005-04-05):
+# The text of the Iranian law, in effect since 1925, clearly mentions
+# that the true solar year is the measure, and there is no arithmetic
+# leap year calculation involved. There has never been any serious
+# plan to change that law....
+#
+# From Paul Eggert (2006-03-22):
+# Go with Shanks & Pottenger before Sept. 1991, and with Pournader thereafter.
+# I used Ed Reingold's cal-persia in GNU Emacs 21.2 to check Persian dates,
+# stopping after 2037 when 32-bit time_t's overflow.
+# That cal-persia used Birashk's approximation, which disagrees with the solar
+# calendar predictions for the year 2025, so I corrected those dates by hand.
+#
+# From Oscar van Vlijmen (2005-03-30), writing about future
+# discrepancies between cal-persia and the Iranian calendar:
+# For 2091 solar-longitude-after yields 2091-03-20 08:40:07.7 UT for
+# the vernal equinox and that gets so close to 12:00 some local
+# Iranian time that the definition of the correct location needs to be
+# known exactly, amongst other factors. 2157 is even closer:
+# 2157-03-20 08:37:15.5 UT. But the Gregorian year 2025 should give
+# no interpretation problem whatsoever. By the way, another instant
+# in the near future where there will be a discrepancy between
+# arithmetical and astronomical Iranian calendars will be in 2058:
+# vernal equinox on 2058-03-20 09:03:05.9 UT. The Java version of
+# Reingold's/Dershowitz' calculator gives correctly the Gregorian date
+# 2058-03-21 for 1 Farvardin 1437 (astronomical).
+#
+# From Steffen Thorsen (2006-03-22):
+# Several of my users have reported that Iran will not observe DST anymore:
+# http://www.irna.ir/en/news/view/line-17/0603193812164948.htm
+#
+# From Reuters (2007-09-16), with a heads-up from Jesper Norgaard Welen:
+# ... the Guardian Council ... approved a law on Sunday to re-introduce
+# daylight saving time ...
+# http://uk.reuters.com/article/oilRpt/idUKBLA65048420070916
+#
+# From Roozbeh Pournader (2007-11-05):
+# This is quoted from Official Gazette of the Islamic Republic of
+# Iran, Volume 63, Number 18242, dated Tuesday 1386/6/24
+# [2007-10-16]. I am doing the best translation I can:...
+# The official time of the country will be moved forward for one hour
+# on the 24 hours of the first day of the month of Farvardin and will
+# be changed back to its previous state on the 24 hours of the
+# thirtieth day of Shahrivar.
+#
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Iran 1978 1980 - Mar 21 0:00 1:00 D
+Rule Iran 1978 only - Oct 21 0:00 0 S
+Rule Iran 1979 only - Sep 19 0:00 0 S
+Rule Iran 1980 only - Sep 23 0:00 0 S
+Rule Iran 1991 only - May 3 0:00 1:00 D
+Rule Iran 1992 1995 - Mar 22 0:00 1:00 D
+Rule Iran 1991 1995 - Sep 22 0:00 0 S
+Rule Iran 1996 only - Mar 21 0:00 1:00 D
+Rule Iran 1996 only - Sep 21 0:00 0 S
+Rule Iran 1997 1999 - Mar 22 0:00 1:00 D
+Rule Iran 1997 1999 - Sep 22 0:00 0 S
+Rule Iran 2000 only - Mar 21 0:00 1:00 D
+Rule Iran 2000 only - Sep 21 0:00 0 S
+Rule Iran 2001 2003 - Mar 22 0:00 1:00 D
+Rule Iran 2001 2003 - Sep 22 0:00 0 S
+Rule Iran 2004 only - Mar 21 0:00 1:00 D
+Rule Iran 2004 only - Sep 21 0:00 0 S
+Rule Iran 2005 only - Mar 22 0:00 1:00 D
+Rule Iran 2005 only - Sep 22 0:00 0 S
+Rule Iran 2008 only - Mar 21 0:00 1:00 D
+Rule Iran 2008 only - Sep 21 0:00 0 S
+Rule Iran 2009 2011 - Mar 22 0:00 1:00 D
+Rule Iran 2009 2011 - Sep 22 0:00 0 S
+Rule Iran 2012 only - Mar 21 0:00 1:00 D
+Rule Iran 2012 only - Sep 21 0:00 0 S
+Rule Iran 2013 2015 - Mar 22 0:00 1:00 D
+Rule Iran 2013 2015 - Sep 22 0:00 0 S
+Rule Iran 2016 only - Mar 21 0:00 1:00 D
+Rule Iran 2016 only - Sep 21 0:00 0 S
+Rule Iran 2017 2019 - Mar 22 0:00 1:00 D
+Rule Iran 2017 2019 - Sep 22 0:00 0 S
+Rule Iran 2020 only - Mar 21 0:00 1:00 D
+Rule Iran 2020 only - Sep 21 0:00 0 S
+Rule Iran 2021 2023 - Mar 22 0:00 1:00 D
+Rule Iran 2021 2023 - Sep 22 0:00 0 S
+Rule Iran 2024 only - Mar 21 0:00 1:00 D
+Rule Iran 2024 only - Sep 21 0:00 0 S
+Rule Iran 2025 2027 - Mar 22 0:00 1:00 D
+Rule Iran 2025 2027 - Sep 22 0:00 0 S
+Rule Iran 2028 2029 - Mar 21 0:00 1:00 D
+Rule Iran 2028 2029 - Sep 21 0:00 0 S
+Rule Iran 2030 2031 - Mar 22 0:00 1:00 D
+Rule Iran 2030 2031 - Sep 22 0:00 0 S
+Rule Iran 2032 2033 - Mar 21 0:00 1:00 D
+Rule Iran 2032 2033 - Sep 21 0:00 0 S
+Rule Iran 2034 2035 - Mar 22 0:00 1:00 D
+Rule Iran 2034 2035 - Sep 22 0:00 0 S
+Rule Iran 2036 2037 - Mar 21 0:00 1:00 D
+Rule Iran 2036 2037 - Sep 21 0:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Tehran 3:25:44 - LMT 1916
+ 3:25:44 - TMT 1946 # Tehran Mean Time
+ 3:30 - IRST 1977 Nov
+ 4:00 Iran IR%sT 1979
+ 3:30 Iran IR%sT
+
+
+# Iraq
+#
+# From Jonathan Lennox (2000-06-12):
+# An article in this week's Economist ("Inside the Saddam-free zone", p. 50 in
+# the U.S. edition) on the Iraqi Kurds contains a paragraph:
+# "The three northern provinces ... switched their clocks this spring and
+# are an hour ahead of Baghdad."
+#
+# But Rives McDow (2000-06-18) quotes a contact in Iraqi-Kurdistan as follows:
+# In the past, some Kurdish nationalists, as a protest to the Iraqi
+# Government, did not adhere to daylight saving time. They referred
+# to daylight saving as Saddam time. But, as of today, the time zone
+# in Iraqi-Kurdistan is on standard time with Baghdad, Iraq.
+#
+# So we'll ignore the Economist's claim.
+
+# From Steffen Thorsen (2008-03-10):
+# The cabinet in Iraq abolished DST last week, according to the following
+# news sources (in Arabic):
+# <a href="http://www.aljeeran.net/wesima_articles/news-20080305-98602.html">
+# http://www.aljeeran.net/wesima_articles/news-20080305-98602.html
+# </a>
+# <a href="http://www.aswataliraq.info/look/article.tpl?id=2047&IdLanguage=17&IdPublication=4&NrArticle=71743&NrIssue=1&NrSection=10">
+# http://www.aswataliraq.info/look/article.tpl?id=2047&IdLanguage=17&IdPublication=4&NrArticle=71743&NrIssue=1&NrSection=10
+# </a>
+#
+# We have published a short article in English about the change:
+# <a href="http://www.timeanddate.com/news/time/iraq-dumps-daylight-saving.html">
+# http://www.timeanddate.com/news/time/iraq-dumps-daylight-saving.html
+# </a>
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Iraq 1982 only - May 1 0:00 1:00 D
+Rule Iraq 1982 1984 - Oct 1 0:00 0 S
+Rule Iraq 1983 only - Mar 31 0:00 1:00 D
+Rule Iraq 1984 1985 - Apr 1 0:00 1:00 D
+Rule Iraq 1985 1990 - Sep lastSun 1:00s 0 S
+Rule Iraq 1986 1990 - Mar lastSun 1:00s 1:00 D
+# IATA SSIM (1991/1996) says Apr 1 12:01am UTC; guess the `:01' is a typo.
+# Shanks & Pottenger say Iraq did not observe DST 1992/1997; ignore this.
+#
+Rule Iraq 1991 2007 - Apr 1 3:00s 1:00 D
+Rule Iraq 1991 2007 - Oct 1 3:00s 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Baghdad 2:57:40 - LMT 1890
+ 2:57:36 - BMT 1918 # Baghdad Mean Time?
+ 3:00 - AST 1982 May
+ 3:00 Iraq A%sT
+
+
+###############################################################################
+
+# Israel
+
+# From Ephraim Silverberg (2001-01-11):
+#
+# I coined "IST/IDT" circa 1988. Until then there were three
+# different abbreviations in use:
+#
+# JST Jerusalem Standard Time [Danny Braniss, Hebrew University]
+# IZT Israel Zonal (sic) Time [Prof. Haim Papo, Technion]
+# EEST Eastern Europe Standard Time [used by almost everyone else]
+#
+# Since timezones should be called by country and not capital cities,
+# I ruled out JST. As Israel is in Asia Minor and not Eastern Europe,
+# EEST was equally unacceptable. Since "zonal" was not compatible with
+# any other timezone abbreviation, I felt that 'IST' was the way to go
+# and, indeed, it has received almost universal acceptance in timezone
+# settings in Israeli computers.
+#
+# In any case, I am happy to share timezone abbreviations with India,
+# high on my favorite-country list (and not only because my wife's
+# family is from India).
+
+# From Shanks & Pottenger:
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Zion 1940 only - Jun 1 0:00 1:00 D
+Rule Zion 1942 1944 - Nov 1 0:00 0 S
+Rule Zion 1943 only - Apr 1 2:00 1:00 D
+Rule Zion 1944 only - Apr 1 0:00 1:00 D
+Rule Zion 1945 only - Apr 16 0:00 1:00 D
+Rule Zion 1945 only - Nov 1 2:00 0 S
+Rule Zion 1946 only - Apr 16 2:00 1:00 D
+Rule Zion 1946 only - Nov 1 0:00 0 S
+Rule Zion 1948 only - May 23 0:00 2:00 DD
+Rule Zion 1948 only - Sep 1 0:00 1:00 D
+Rule Zion 1948 1949 - Nov 1 2:00 0 S
+Rule Zion 1949 only - May 1 0:00 1:00 D
+Rule Zion 1950 only - Apr 16 0:00 1:00 D
+Rule Zion 1950 only - Sep 15 3:00 0 S
+Rule Zion 1951 only - Apr 1 0:00 1:00 D
+Rule Zion 1951 only - Nov 11 3:00 0 S
+Rule Zion 1952 only - Apr 20 2:00 1:00 D
+Rule Zion 1952 only - Oct 19 3:00 0 S
+Rule Zion 1953 only - Apr 12 2:00 1:00 D
+Rule Zion 1953 only - Sep 13 3:00 0 S
+Rule Zion 1954 only - Jun 13 0:00 1:00 D
+Rule Zion 1954 only - Sep 12 0:00 0 S
+Rule Zion 1955 only - Jun 11 2:00 1:00 D
+Rule Zion 1955 only - Sep 11 0:00 0 S
+Rule Zion 1956 only - Jun 3 0:00 1:00 D
+Rule Zion 1956 only - Sep 30 3:00 0 S
+Rule Zion 1957 only - Apr 29 2:00 1:00 D
+Rule Zion 1957 only - Sep 22 0:00 0 S
+Rule Zion 1974 only - Jul 7 0:00 1:00 D
+Rule Zion 1974 only - Oct 13 0:00 0 S
+Rule Zion 1975 only - Apr 20 0:00 1:00 D
+Rule Zion 1975 only - Aug 31 0:00 0 S
+Rule Zion 1985 only - Apr 14 0:00 1:00 D
+Rule Zion 1985 only - Sep 15 0:00 0 S
+Rule Zion 1986 only - May 18 0:00 1:00 D
+Rule Zion 1986 only - Sep 7 0:00 0 S
+Rule Zion 1987 only - Apr 15 0:00 1:00 D
+Rule Zion 1987 only - Sep 13 0:00 0 S
+Rule Zion 1988 only - Apr 9 0:00 1:00 D
+Rule Zion 1988 only - Sep 3 0:00 0 S
+
+# From Ephraim Silverberg
+# (1997-03-04, 1998-03-16, 1998-12-28, 2000-01-17, 2000-07-25, 2004-12-22,
+# and 2005-02-17):
+
+# According to the Office of the Secretary General of the Ministry of
+# Interior, there is NO set rule for Daylight-Savings/Standard time changes.
+# One thing is entrenched in law, however: that there must be at least 150
+# days of daylight savings time annually. From 1993-1998, the change to
+# daylight savings time was on a Friday morning from midnight IST to
+# 1 a.m IDT; up until 1998, the change back to standard time was on a
+# Saturday night from midnight daylight savings time to 11 p.m. standard
+# time. 1996 is an exception to this rule where the change back to standard
+# time took place on Sunday night instead of Saturday night to avoid
+# conflicts with the Jewish New Year. In 1999, the change to
+# daylight savings time was still on a Friday morning but from
+# 2 a.m. IST to 3 a.m. IDT; furthermore, the change back to standard time
+# was also on a Friday morning from 2 a.m. IDT to 1 a.m. IST for
+# 1999 only. In the year 2000, the change to daylight savings time was
+# similar to 1999, but although the change back will be on a Friday, it
+# will take place from 1 a.m. IDT to midnight IST. Starting in 2001, all
+# changes to/from will take place at 1 a.m. old time, but now there is no
+# rule as to what day of the week it will take place in as the start date
+# (except in 2003) is the night after the Passover Seder (i.e. the eve
+# of the 16th of Nisan in the lunar Hebrew calendar) and the end date
+# (except in 2002) is three nights before Yom Kippur [Day of Atonement]
+# (the eve of the 7th of Tishrei in the lunar Hebrew calendar).
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Zion 1989 only - Apr 30 0:00 1:00 D
+Rule Zion 1989 only - Sep 3 0:00 0 S
+Rule Zion 1990 only - Mar 25 0:00 1:00 D
+Rule Zion 1990 only - Aug 26 0:00 0 S
+Rule Zion 1991 only - Mar 24 0:00 1:00 D
+Rule Zion 1991 only - Sep 1 0:00 0 S
+Rule Zion 1992 only - Mar 29 0:00 1:00 D
+Rule Zion 1992 only - Sep 6 0:00 0 S
+Rule Zion 1993 only - Apr 2 0:00 1:00 D
+Rule Zion 1993 only - Sep 5 0:00 0 S
+
+# The dates for 1994-1995 were obtained from Office of the Spokeswoman for the
+# Ministry of Interior, Jerusalem, Israel. The spokeswoman can be reached by
+# calling the office directly at 972-2-6701447 or 972-2-6701448.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Zion 1994 only - Apr 1 0:00 1:00 D
+Rule Zion 1994 only - Aug 28 0:00 0 S
+Rule Zion 1995 only - Mar 31 0:00 1:00 D
+Rule Zion 1995 only - Sep 3 0:00 0 S
+
+# The dates for 1996 were determined by the Minister of Interior of the
+# time, Haim Ramon. The official announcement regarding 1996-1998
+# (with the dates for 1997-1998 no longer being relevant) can be viewed at:
+#
+# ftp://ftp.cs.huji.ac.il/pub/tz/announcements/1996-1998.ramon.ps.gz
+#
+# The dates for 1997-1998 were altered by his successor, Rabbi Eli Suissa.
+#
+# The official announcements for the years 1997-1999 can be viewed at:
+#
+# ftp://ftp.cs.huji.ac.il/pub/tz/announcements/YYYY.ps.gz
+#
+# where YYYY is the relevant year.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Zion 1996 only - Mar 15 0:00 1:00 D
+Rule Zion 1996 only - Sep 16 0:00 0 S
+Rule Zion 1997 only - Mar 21 0:00 1:00 D
+Rule Zion 1997 only - Sep 14 0:00 0 S
+Rule Zion 1998 only - Mar 20 0:00 1:00 D
+Rule Zion 1998 only - Sep 6 0:00 0 S
+Rule Zion 1999 only - Apr 2 2:00 1:00 D
+Rule Zion 1999 only - Sep 3 2:00 0 S
+
+# The Knesset Interior Committee has changed the dates for 2000 for
+# the third time in just over a year and have set new dates for the
+# years 2001-2004 as well.
+#
+# The official announcement for the start date of 2000 can be viewed at:
+#
+# ftp://ftp.cs.huji.ac.il/pub/tz/announcements/2000-start.ps.gz
+#
+# The official announcement for the end date of 2000 and the dates
+# for the years 2001-2004 can be viewed at:
+#
+# ftp://ftp.cs.huji.ac.il/pub/tz/announcements/2000-2004.ps.gz
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Zion 2000 only - Apr 14 2:00 1:00 D
+Rule Zion 2000 only - Oct 6 1:00 0 S
+Rule Zion 2001 only - Apr 9 1:00 1:00 D
+Rule Zion 2001 only - Sep 24 1:00 0 S
+Rule Zion 2002 only - Mar 29 1:00 1:00 D
+Rule Zion 2002 only - Oct 7 1:00 0 S
+Rule Zion 2003 only - Mar 28 1:00 1:00 D
+Rule Zion 2003 only - Oct 3 1:00 0 S
+Rule Zion 2004 only - Apr 7 1:00 1:00 D
+Rule Zion 2004 only - Sep 22 1:00 0 S
+
+# The proposed law agreed upon by the Knesset Interior Committee on
+# 2005-02-14 is that, for 2005 and beyond, DST starts at 02:00 the
+# last Friday before April 2nd (i.e. the last Friday in March or April
+# 1st itself if it falls on a Friday) and ends at 02:00 on the Saturday
+# night _before_ the fast of Yom Kippur.
+#
+# Those who can read Hebrew can view the announcement at:
+#
+# ftp://ftp.cs.huji.ac.il/pub/tz/announcements/2005+beyond.ps
+
+# From Paul Eggert (2012-10-26):
+# I used Ephraim Silverberg's dst-israel.el program
+# <ftp://ftp.cs.huji.ac.il/pub/tz/software/dst-israel.el> (2005-02-20)
+# along with Ed Reingold's cal-hebrew in GNU Emacs 21.4,
+# to generate the transitions from 2005 through 2012.
+# (I replaced "lastFri" with "Fri>=26" by hand.)
+# The spring transitions all correspond to the following Rule:
+#
+# Rule Zion 2005 2012 - Mar Fri>=26 2:00 1:00 D
+#
+# but older zic implementations (e.g., Solaris 8) do not support
+# "Fri>=26" to mean April 1 in years like 2005, so for now we list the
+# springtime transitions explicitly.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Zion 2005 only - Apr 1 2:00 1:00 D
+Rule Zion 2005 only - Oct 9 2:00 0 S
+Rule Zion 2006 2010 - Mar Fri>=26 2:00 1:00 D
+Rule Zion 2006 only - Oct 1 2:00 0 S
+Rule Zion 2007 only - Sep 16 2:00 0 S
+Rule Zion 2008 only - Oct 5 2:00 0 S
+Rule Zion 2009 only - Sep 27 2:00 0 S
+Rule Zion 2010 only - Sep 12 2:00 0 S
+Rule Zion 2011 only - Apr 1 2:00 1:00 D
+Rule Zion 2011 only - Oct 2 2:00 0 S
+Rule Zion 2012 only - Mar Fri>=26 2:00 1:00 D
+Rule Zion 2012 only - Sep 23 2:00 0 S
+
+# From Ephraim Silverberg (2012-10-18):
+# Yesterday, the Interior Ministry Committee, after more than a year
+# past, approved sending the proposed June 2011 changes to the Time
+# Decree Law back to the Knesset for second and third (final) votes
+# before the upcoming elections on Jan. 22, 2013. Hence, although the
+# changes are not yet law, they are expected to be so before February 2013.
+#
+# As of 2013, DST starts at 02:00 on the Friday before the last Sunday in March.
+# DST ends at 02:00 on the first Sunday after October 1, unless it occurs on the
+# second day of the Jewish Rosh Hashana holiday, in which case DST ends a day
+# later (i.e. at 02:00 the first Monday after October 2).
+# [Rosh Hashana holidays are factored in until 2100.]
+
+# From Ephraim Silverberg (2012-11-05):
+# The Knesset passed today (in second and final readings) the amendment to the
+# Time Decree Law making the changes ... law.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Zion 2013 max - Mar Fri>=23 2:00 1:00 D
+Rule Zion 2013 2026 - Oct Sun>=2 2:00 0 S
+Rule Zion 2027 only - Oct Mon>=3 2:00 0 S
+Rule Zion 2028 max - Oct Sun>=2 2:00 0 S
+# The following rules are commented out for now, as they break older
+# versions of zic that support only signed 32-bit timestamps, i.e.,
+# through 2038-01-19 03:14:07 UTC.
+#Rule Zion 2028 2053 - Oct Sun>=2 2:00 0 S
+#Rule Zion 2054 only - Oct Mon>=3 2:00 0 S
+#Rule Zion 2055 2080 - Oct Sun>=2 2:00 0 S
+#Rule Zion 2081 only - Oct Mon>=3 2:00 0 S
+#Rule Zion 2082 max - Oct Sun>=2 2:00 0 S
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Jerusalem 2:20:56 - LMT 1880
+ 2:20:40 - JMT 1918 # Jerusalem Mean Time?
+ 2:00 Zion I%sT
+
+
+
+###############################################################################
+
+# Japan
+
+# `9:00' and `JST' is from Guy Harris.
+
+# From Paul Eggert (1995-03-06):
+# Today's _Asahi Evening News_ (page 4) reports that Japan had
+# daylight saving between 1948 and 1951, but ``the system was discontinued
+# because the public believed it would lead to longer working hours.''
+
+# From Mayumi Negishi in the 2005-08-10 Japan Times
+# <http://www.japantimes.co.jp/cgi-bin/getarticle.pl5?nn20050810f2.htm>:
+# Occupation authorities imposed daylight-saving time on Japan on
+# [1948-05-01].... But lack of prior debate and the execution of
+# daylight-saving time just three days after the bill was passed generated
+# deep hatred of the concept.... The Diet unceremoniously passed a bill to
+# dump the unpopular system in October 1951, less than a month after the San
+# Francisco Peace Treaty was signed. (A government poll in 1951 showed 53%
+# of the Japanese wanted to scrap daylight-saving time, as opposed to 30% who
+# wanted to keep it.)
+
+# From Paul Eggert (2006-03-22):
+# Shanks & Pottenger write that DST in Japan during those years was as follows:
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Japan 1948 only - May Sun>=1 2:00 1:00 D
+Rule Japan 1948 1951 - Sep Sat>=8 2:00 0 S
+Rule Japan 1949 only - Apr Sun>=1 2:00 1:00 D
+Rule Japan 1950 1951 - May Sun>=1 2:00 1:00 D
+# but the only locations using it (for birth certificates, presumably, since
+# their audience is astrologers) were US military bases. For now, assume
+# that for most purposes daylight-saving time was observed; otherwise, what
+# would have been the point of the 1951 poll?
+
+# From Hideyuki Suzuki (1998-11-09):
+# 'Tokyo' usually stands for the former location of Tokyo Astronomical
+# Observatory: E 139 44' 40".90 (9h 18m 58s.727), N 35 39' 16".0.
+# This data is from 'Rika Nenpyou (Chronological Scientific Tables) 1996'
+# edited by National Astronomical Observatory of Japan....
+# JST (Japan Standard Time) has been used since 1888-01-01 00:00 (JST).
+# The law is enacted on 1886-07-07.
+
+# From Hideyuki Suzuki (1998-11-16):
+# The ordinance No. 51 (1886) established "standard time" in Japan,
+# which stands for the time on E 135 degree.
+# In the ordinance No. 167 (1895), "standard time" was renamed to "central
+# standard time". And the same ordinance also established "western standard
+# time", which stands for the time on E 120 degree.... But "western standard
+# time" was abolished in the ordinance No. 529 (1937). In the ordinance No.
+# 167, there is no mention regarding for what place western standard time is
+# standard....
+#
+# I wrote "ordinance" above, but I don't know how to translate.
+# In Japanese it's "chokurei", which means ordinance from emperor.
+
+# Shanks & Pottenger claim JST in use since 1896, and that a few
+# places (e.g. Ishigaki) use +0800; go with Suzuki. Guess that all
+# ordinances took effect on Jan 1.
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Tokyo 9:18:59 - LMT 1887 Dec 31 15:00u
+ 9:00 - JST 1896
+ 9:00 - CJT 1938
+ 9:00 Japan J%sT
+# Since 1938, all Japanese possessions have been like Asia/Tokyo.
+
+# Jordan
+#
+# From <a href="http://star.arabia.com/990701/JO9.html">
+# Jordan Week (1999-07-01) </a> via Steffen Thorsen (1999-09-09):
+# Clocks in Jordan were forwarded one hour on Wednesday at midnight,
+# in accordance with the government's decision to implement summer time
+# all year round.
+#
+# From <a href="http://star.arabia.com/990930/JO9.html">
+# Jordan Week (1999-09-30) </a> via Steffen Thorsen (1999-11-09):
+# Winter time starts today Thursday, 30 September. Clocks will be turned back
+# by one hour. This is the latest government decision and it's final!
+# The decision was taken because of the increase in working hours in
+# government's departments from six to seven hours.
+#
+# From Paul Eggert (2005-11-22):
+# Starting 2003 transitions are from Steffen Thorsen's web site timeanddate.com.
+#
+# From Steffen Thorsen (2005-11-23):
+# For Jordan I have received multiple independent user reports every year
+# about DST end dates, as the end-rule is different every year.
+#
+# From Steffen Thorsen (2006-10-01), after a heads-up from Hilal Malawi:
+# http://www.petranews.gov.jo/nepras/2006/Sep/05/4000.htm
+# "Jordan will switch to winter time on Friday, October 27".
+#
+
+# From Phil Pizzey (2009-04-02):
+# ...I think I may have spotted an error in the timezone data for
+# Jordan.
+# The current (2009d) asia file shows Jordan going to daylight
+# saving
+# time on the last Thursday in March.
+#
+# Rule Jordan 2000 max - Mar lastThu 0:00s 1:00 S
+#
+# However timeanddate.com, which I usually find reliable, shows Jordan
+# going to daylight saving time on the last Friday in March since 2002.
+# Please see
+# <a href="http://www.timeanddate.com/worldclock/timezone.html?n=11">
+# http://www.timeanddate.com/worldclock/timezone.html?n=11
+# </a>
+
+# From Steffen Thorsen (2009-04-02):
+# This single one might be good enough, (2009-03-24, Arabic):
+# <a href="http://petra.gov.jo/Artical.aspx?Lng=2&Section=8&Artical=95279">
+# http://petra.gov.jo/Artical.aspx?Lng=2&Section=8&Artical=95279
+# </a>
+#
+# Google's translation:
+#
+# > The Council of Ministers decided in 2002 to adopt the principle of timely
+# > submission of the summer at 60 minutes as of midnight on the last Thursday
+# > of the month of March of each year.
+#
+# So - this means the midnight between Thursday and Friday since 2002.
+
+# From Arthur David Olson (2009-04-06):
+# We still have Jordan switching to DST on Thursdays in 2000 and 2001.
+
+# From Steffen Thorsen (2012-10-25):
+# Yesterday the government in Jordan announced that they will not
+# switch back to standard time this winter, so the will stay on DST
+# until about the same time next year (at least).
+# http://www.petra.gov.jo/Public_News/Nws_NewsDetails.aspx?NewsID=88950
+#
+# From Paul Eggert (2012-10-25):
+# For now, assume this is just a one-year measure. If it becomes
+# permanent, we should move Jordan from EET to AST effective tomorrow.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Jordan 1973 only - Jun 6 0:00 1:00 S
+Rule Jordan 1973 1975 - Oct 1 0:00 0 -
+Rule Jordan 1974 1977 - May 1 0:00 1:00 S
+Rule Jordan 1976 only - Nov 1 0:00 0 -
+Rule Jordan 1977 only - Oct 1 0:00 0 -
+Rule Jordan 1978 only - Apr 30 0:00 1:00 S
+Rule Jordan 1978 only - Sep 30 0:00 0 -
+Rule Jordan 1985 only - Apr 1 0:00 1:00 S
+Rule Jordan 1985 only - Oct 1 0:00 0 -
+Rule Jordan 1986 1988 - Apr Fri>=1 0:00 1:00 S
+Rule Jordan 1986 1990 - Oct Fri>=1 0:00 0 -
+Rule Jordan 1989 only - May 8 0:00 1:00 S
+Rule Jordan 1990 only - Apr 27 0:00 1:00 S
+Rule Jordan 1991 only - Apr 17 0:00 1:00 S
+Rule Jordan 1991 only - Sep 27 0:00 0 -
+Rule Jordan 1992 only - Apr 10 0:00 1:00 S
+Rule Jordan 1992 1993 - Oct Fri>=1 0:00 0 -
+Rule Jordan 1993 1998 - Apr Fri>=1 0:00 1:00 S
+Rule Jordan 1994 only - Sep Fri>=15 0:00 0 -
+Rule Jordan 1995 1998 - Sep Fri>=15 0:00s 0 -
+Rule Jordan 1999 only - Jul 1 0:00s 1:00 S
+Rule Jordan 1999 2002 - Sep lastFri 0:00s 0 -
+Rule Jordan 2000 2001 - Mar lastThu 0:00s 1:00 S
+Rule Jordan 2002 max - Mar lastThu 24:00 1:00 S
+Rule Jordan 2003 only - Oct 24 0:00s 0 -
+Rule Jordan 2004 only - Oct 15 0:00s 0 -
+Rule Jordan 2005 only - Sep lastFri 0:00s 0 -
+Rule Jordan 2006 2011 - Oct lastFri 0:00s 0 -
+Rule Jordan 2013 max - Oct lastFri 0:00s 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Amman 2:23:44 - LMT 1931
+ 2:00 Jordan EE%sT
+
+
+# Kazakhstan
+
+# From Paul Eggert (1996-11-22):
+# Andrew Evtichov (1996-04-13) writes that Kazakhstan
+# stayed in sync with Moscow after 1990, and that Aqtobe (formerly Aktyubinsk)
+# and Aqtau (formerly Shevchenko) are the largest cities in their zones.
+# Guess that Aqtau and Aqtobe diverged in 1995, since that's the first time
+# IATA SSIM mentions a third time zone in Kazakhstan.
+
+# From Paul Eggert (2006-03-22):
+# German Iofis, ELSI, Almaty (2001-10-09) reports that Kazakhstan uses
+# RussiaAsia rules, instead of switching at 00:00 as the IATA has it.
+# Go with Shanks & Pottenger, who have them always using RussiaAsia rules.
+# Also go with the following claims of Shanks & Pottenger:
+#
+# - Kazakhstan did not observe DST in 1991.
+# - Qyzylorda switched from +5:00 to +6:00 on 1992-01-19 02:00.
+# - Oral switched from +5:00 to +4:00 in spring 1989.
+
+# <a href="http://www.kazsociety.org.uk/news/2005/03/30.htm">
+# From Kazakhstan Embassy's News Bulletin #11 (2005-03-21):
+# </a>
+# The Government of Kazakhstan passed a resolution March 15 abolishing
+# daylight saving time citing lack of economic benefits and health
+# complications coupled with a decrease in productivity.
+#
+# From Branislav Kojic (in Astana) via Gwillim Law (2005-06-28):
+# ... what happened was that the former Kazakhstan Eastern time zone
+# was "blended" with the Central zone. Therefore, Kazakhstan now has
+# two time zones, and difference between them is one hour. The zone
+# closer to UTC is the former Western zone (probably still called the
+# same), encompassing four provinces in the west: Aqtobe, Atyrau,
+# Mangghystau, and West Kazakhstan. The other zone encompasses
+# everything else.... I guess that would make Kazakhstan time zones
+# de jure UTC+5 and UTC+6 respectively.
+
+#
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+#
+# Almaty (formerly Alma-Ata), representing most locations in Kazakhstan
+Zone Asia/Almaty 5:07:48 - LMT 1924 May 2 # or Alma-Ata
+ 5:00 - ALMT 1930 Jun 21 # Alma-Ata Time
+ 6:00 RussiaAsia ALM%sT 1991
+ 6:00 - ALMT 1992
+ 6:00 RussiaAsia ALM%sT 2005 Mar 15
+ 6:00 - ALMT
+# Qyzylorda (aka Kyzylorda, Kizilorda, Kzyl-Orda, etc.)
+Zone Asia/Qyzylorda 4:21:52 - LMT 1924 May 2
+ 4:00 - KIZT 1930 Jun 21 # Kizilorda Time
+ 5:00 - KIZT 1981 Apr 1
+ 5:00 1:00 KIZST 1981 Oct 1
+ 6:00 - KIZT 1982 Apr 1
+ 5:00 RussiaAsia KIZ%sT 1991
+ 5:00 - KIZT 1991 Dec 16 # independence
+ 5:00 - QYZT 1992 Jan 19 2:00
+ 6:00 RussiaAsia QYZ%sT 2005 Mar 15
+ 6:00 - QYZT
+# Aqtobe (aka Aktobe, formerly Akt'ubinsk)
+Zone Asia/Aqtobe 3:48:40 - LMT 1924 May 2
+ 4:00 - AKTT 1930 Jun 21 # Aktyubinsk Time
+ 5:00 - AKTT 1981 Apr 1
+ 5:00 1:00 AKTST 1981 Oct 1
+ 6:00 - AKTT 1982 Apr 1
+ 5:00 RussiaAsia AKT%sT 1991
+ 5:00 - AKTT 1991 Dec 16 # independence
+ 5:00 RussiaAsia AQT%sT 2005 Mar 15 # Aqtobe Time
+ 5:00 - AQTT
+# Mangghystau
+# Aqtau was not founded until 1963, but it represents an inhabited region,
+# so include time stamps before 1963.
+Zone Asia/Aqtau 3:21:04 - LMT 1924 May 2
+ 4:00 - FORT 1930 Jun 21 # Fort Shevchenko T
+ 5:00 - FORT 1963
+ 5:00 - SHET 1981 Oct 1 # Shevchenko Time
+ 6:00 - SHET 1982 Apr 1
+ 5:00 RussiaAsia SHE%sT 1991
+ 5:00 - SHET 1991 Dec 16 # independence
+ 5:00 RussiaAsia AQT%sT 1995 Mar lastSun 2:00 # Aqtau Time
+ 4:00 RussiaAsia AQT%sT 2005 Mar 15
+ 5:00 - AQTT
+# West Kazakhstan
+Zone Asia/Oral 3:25:24 - LMT 1924 May 2 # or Ural'sk
+ 4:00 - URAT 1930 Jun 21 # Ural'sk time
+ 5:00 - URAT 1981 Apr 1
+ 5:00 1:00 URAST 1981 Oct 1
+ 6:00 - URAT 1982 Apr 1
+ 5:00 RussiaAsia URA%sT 1989 Mar 26 2:00
+ 4:00 RussiaAsia URA%sT 1991
+ 4:00 - URAT 1991 Dec 16 # independence
+ 4:00 RussiaAsia ORA%sT 2005 Mar 15 # Oral Time
+ 5:00 - ORAT
+
+# Kyrgyzstan (Kirgizstan)
+# Transitions through 1991 are from Shanks & Pottenger.
+
+# From Paul Eggert (2005-08-15):
+# According to an article dated today in the Kyrgyzstan Development Gateway
+# <http://eng.gateway.kg/cgi-bin/page.pl?id=1&story_name=doc9979.shtml>
+# Kyrgyzstan is canceling the daylight saving time system. I take the article
+# to mean that they will leave their clocks at 6 hours ahead of UTC.
+# From Malik Abdugaliev (2005-09-21):
+# Our government cancels daylight saving time 6th of August 2005.
+# From 2005-08-12 our GMT-offset is +6, w/o any daylight saving.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Kyrgyz 1992 1996 - Apr Sun>=7 0:00s 1:00 S
+Rule Kyrgyz 1992 1996 - Sep lastSun 0:00 0 -
+Rule Kyrgyz 1997 2005 - Mar lastSun 2:30 1:00 S
+Rule Kyrgyz 1997 2004 - Oct lastSun 2:30 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Bishkek 4:58:24 - LMT 1924 May 2
+ 5:00 - FRUT 1930 Jun 21 # Frunze Time
+ 6:00 RussiaAsia FRU%sT 1991 Mar 31 2:00s
+ 5:00 1:00 FRUST 1991 Aug 31 2:00 # independence
+ 5:00 Kyrgyz KG%sT 2005 Aug 12 # Kyrgyzstan Time
+ 6:00 - KGT
+
+###############################################################################
+
+# Korea (North and South)
+
+# From Annie I. Bang (2006-07-10) in
+# <http://www.koreaherald.co.kr/SITE/data/html_dir/2006/07/10/200607100012.asp>:
+# The Ministry of Commerce, Industry and Energy has already
+# commissioned a research project [to reintroduce DST] and has said
+# the system may begin as early as 2008.... Korea ran a daylight
+# saving program from 1949-61 but stopped it during the 1950-53 Korean War.
+
+# From Shanks & Pottenger:
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule ROK 1960 only - May 15 0:00 1:00 D
+Rule ROK 1960 only - Sep 13 0:00 0 S
+Rule ROK 1987 1988 - May Sun>=8 0:00 1:00 D
+Rule ROK 1987 1988 - Oct Sun>=8 0:00 0 S
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Seoul 8:27:52 - LMT 1890
+ 8:30 - KST 1904 Dec
+ 9:00 - KST 1928
+ 8:30 - KST 1932
+ 9:00 - KST 1954 Mar 21
+ 8:00 ROK K%sT 1961 Aug 10
+ 8:30 - KST 1968 Oct
+ 9:00 ROK K%sT
+Zone Asia/Pyongyang 8:23:00 - LMT 1890
+ 8:30 - KST 1904 Dec
+ 9:00 - KST 1928
+ 8:30 - KST 1932
+ 9:00 - KST 1954 Mar 21
+ 8:00 - KST 1961 Aug 10
+ 9:00 - KST
+
+###############################################################################
+
+# Kuwait
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+# From the Arab Times (2007-03-14):
+# The Civil Service Commission (CSC) has approved a proposal forwarded
+# by MP Ahmad Baqer on implementing the daylight saving time (DST) in
+# Kuwait starting from April until the end of Sept this year, reports Al-Anba.
+# <http://www.arabtimesonline.com/arabtimes/kuwait/Viewdet.asp?ID=9950>.
+# From Paul Eggert (2007-03-29):
+# We don't know the details, or whether the approval means it'll happen,
+# so for now we assume no DST.
+Zone Asia/Kuwait 3:11:56 - LMT 1950
+ 3:00 - AST
+
+# Laos
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Vientiane 6:50:24 - LMT 1906 Jun 9 # or Viangchan
+ 7:06:20 - SMT 1911 Mar 11 0:01 # Saigon MT?
+ 7:00 - ICT 1912 May
+ 8:00 - ICT 1931 May
+ 7:00 - ICT
+
+# Lebanon
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Lebanon 1920 only - Mar 28 0:00 1:00 S
+Rule Lebanon 1920 only - Oct 25 0:00 0 -
+Rule Lebanon 1921 only - Apr 3 0:00 1:00 S
+Rule Lebanon 1921 only - Oct 3 0:00 0 -
+Rule Lebanon 1922 only - Mar 26 0:00 1:00 S
+Rule Lebanon 1922 only - Oct 8 0:00 0 -
+Rule Lebanon 1923 only - Apr 22 0:00 1:00 S
+Rule Lebanon 1923 only - Sep 16 0:00 0 -
+Rule Lebanon 1957 1961 - May 1 0:00 1:00 S
+Rule Lebanon 1957 1961 - Oct 1 0:00 0 -
+Rule Lebanon 1972 only - Jun 22 0:00 1:00 S
+Rule Lebanon 1972 1977 - Oct 1 0:00 0 -
+Rule Lebanon 1973 1977 - May 1 0:00 1:00 S
+Rule Lebanon 1978 only - Apr 30 0:00 1:00 S
+Rule Lebanon 1978 only - Sep 30 0:00 0 -
+Rule Lebanon 1984 1987 - May 1 0:00 1:00 S
+Rule Lebanon 1984 1991 - Oct 16 0:00 0 -
+Rule Lebanon 1988 only - Jun 1 0:00 1:00 S
+Rule Lebanon 1989 only - May 10 0:00 1:00 S
+Rule Lebanon 1990 1992 - May 1 0:00 1:00 S
+Rule Lebanon 1992 only - Oct 4 0:00 0 -
+Rule Lebanon 1993 max - Mar lastSun 0:00 1:00 S
+Rule Lebanon 1993 1998 - Sep lastSun 0:00 0 -
+Rule Lebanon 1999 max - Oct lastSun 0:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Beirut 2:22:00 - LMT 1880
+ 2:00 Lebanon EE%sT
+
+# Malaysia
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule NBorneo 1935 1941 - Sep 14 0:00 0:20 TS # one-Third Summer
+Rule NBorneo 1935 1941 - Dec 14 0:00 0 -
+#
+# peninsular Malaysia
+# The data here are taken from Mok Ly Yng (2003-10-30)
+# <http://www.math.nus.edu.sg/aslaksen/teaching/timezone.html>.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Kuala_Lumpur 6:46:46 - LMT 1901 Jan 1
+ 6:55:25 - SMT 1905 Jun 1 # Singapore M.T.
+ 7:00 - MALT 1933 Jan 1 # Malaya Time
+ 7:00 0:20 MALST 1936 Jan 1
+ 7:20 - MALT 1941 Sep 1
+ 7:30 - MALT 1942 Feb 16
+ 9:00 - JST 1945 Sep 12
+ 7:30 - MALT 1982 Jan 1
+ 8:00 - MYT # Malaysia Time
+# Sabah & Sarawak
+# From Paul Eggert (2006-03-22):
+# The data here are mostly from Shanks & Pottenger, but the 1942, 1945 and 1982
+# transition dates are from Mok Ly Yng.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Kuching 7:21:20 - LMT 1926 Mar
+ 7:30 - BORT 1933 # Borneo Time
+ 8:00 NBorneo BOR%sT 1942 Feb 16
+ 9:00 - JST 1945 Sep 12
+ 8:00 - BORT 1982 Jan 1
+ 8:00 - MYT
+
+# Maldives
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Indian/Maldives 4:54:00 - LMT 1880 # Male
+ 4:54:00 - MMT 1960 # Male Mean Time
+ 5:00 - MVT # Maldives Time
+
+# Mongolia
+
+# Shanks & Pottenger say that Mongolia has three time zones, but
+# usno1995 and the CIA map Standard Time Zones of the World (2005-03)
+# both say that it has just one.
+
+# From Oscar van Vlijmen (1999-12-11):
+# <a href="http://www.mongoliatourism.gov.mn/general.htm">
+# General Information Mongolia
+# </a> (1999-09)
+# "Time: Mongolia has two time zones. Three westernmost provinces of
+# Bayan-Ulgii, Uvs, and Hovd are one hour earlier than the capital city, and
+# the rest of the country follows the Ulaanbaatar time, which is UTC/GMT plus
+# eight hours."
+
+# From Rives McDow (1999-12-13):
+# Mongolia discontinued the use of daylight savings time in 1999; 1998
+# being the last year it was implemented. The dates of implementation I am
+# unsure of, but most probably it was similar to Russia, except for the time
+# of implementation may have been different....
+# Some maps in the past have indicated that there was an additional time
+# zone in the eastern part of Mongolia, including the provinces of Dornod,
+# Suhbaatar, and possibly Khentij.
+
+# From Paul Eggert (1999-12-15):
+# Naming and spelling is tricky in Mongolia.
+# We'll use Hovd (also spelled Chovd and Khovd) to represent the west zone;
+# the capital of the Hovd province is sometimes called Hovd, sometimes Dund-Us,
+# and sometimes Jirgalanta (with variant spellings), but the name Hovd
+# is good enough for our purposes.
+
+# From Rives McDow (2001-05-13):
+# In addition to Mongolia starting daylight savings as reported earlier
+# (adopted DST on 2001-04-27 02:00 local time, ending 2001-09-28),
+# there are three time zones.
+#
+# Provinces [at 7:00]: Bayan-ulgii, Uvs, Khovd, Zavkhan, Govi-Altai
+# Provinces [at 8:00]: Khovsgol, Bulgan, Arkhangai, Khentii, Tov,
+# Bayankhongor, Ovorkhangai, Dundgovi, Dornogovi, Omnogovi
+# Provinces [at 9:00]: Dornod, Sukhbaatar
+#
+# [The province of Selenge is omitted from the above lists.]
+
+# From Ganbold Ts., Ulaanbaatar (2004-04-17):
+# Daylight saving occurs at 02:00 local time last Saturday of March.
+# It will change back to normal at 02:00 local time last Saturday of
+# September.... As I remember this rule was changed in 2001.
+#
+# From Paul Eggert (2004-04-17):
+# For now, assume Rives McDow's informant got confused about Friday vs
+# Saturday, and that his 2001 dates should have 1 added to them.
+
+# From Paul Eggert (2005-07-26):
+# We have wildly conflicting information about Mongolia's time zones.
+# Bill Bonnet (2005-05-19) reports that the US Embassy in Ulaanbaatar says
+# there is only one time zone and that DST is observed, citing Microsoft
+# Windows XP as the source. Risto Nykanen (2005-05-16) reports that
+# travelmongolia.org says there are two time zones (UTC+7, UTC+8) with no DST.
+# Oscar van Vlijmen (2005-05-20) reports that the Mongolian Embassy in
+# Washington, DC says there are two time zones, with DST observed.
+# He also found
+# <http://ubpost.mongolnews.mn/index.php?subaction=showcomments&id=1111634894&archive=&start_from=&ucat=1&>
+# which also says that there is DST, and which has a comment by "Toddius"
+# (2005-03-31 06:05 +0700) saying "Mongolia actually has 3.5 time zones.
+# The West (OLGII) is +7 GMT, most of the country is ULAT is +8 GMT
+# and some Eastern provinces are +9 GMT but Sukhbaatar Aimag is SUHK +8.5 GMT.
+# The SUKH timezone is new this year, it is one of the few things the
+# parliament passed during the tumultuous winter session."
+# For now, let's ignore this information, until we have more confirmation.
+
+# From Ganbold Ts. (2007-02-26):
+# Parliament of Mongolia has just changed the daylight-saving rule in February.
+# They decided not to adopt daylight-saving time....
+# http://www.mongolnews.mn/index.php?module=unuudur&sec=view&id=15742
+
+# From Deborah Goldsmith (2008-03-30):
+# We received a bug report claiming that the tz database UTC offset for
+# Asia/Choibalsan (GMT+09:00) is incorrect, and that it should be GMT
+# +08:00 instead. Different sources appear to disagree with the tz
+# database on this, e.g.:
+#
+# <a href="http://www.timeanddate.com/worldclock/city.html?n=1026">
+# http://www.timeanddate.com/worldclock/city.html?n=1026
+# </a>
+# <a href="http://www.worldtimeserver.com/current_time_in_MN.aspx">
+# http://www.worldtimeserver.com/current_time_in_MN.aspx
+# </a>
+#
+# both say GMT+08:00.
+
+# From Steffen Thorsen (2008-03-31):
+# eznis airways, which operates several domestic flights, has a flight
+# schedule here:
+# <a href="http://www.eznis.com/Container.jsp?id=112">
+# http://www.eznis.com/Container.jsp?id=112
+# </a>
+# (click the English flag for English)
+#
+# There it appears that flights between Choibalsan and Ulaanbatar arrive
+# about 1:35 - 1:50 hours later in local clock time, no matter the
+# direction, while Ulaanbaatar-Khvod takes 2 hours in the Eastern
+# direction and 3:35 back, which indicates that Ulaanbatar and Khvod are
+# in different time zones (like we know about), while Choibalsan and
+# Ulaanbatar are in the same time zone (correction needed).
+
+# From Arthur David Olson (2008-05-19):
+# Assume that Choibalsan is indeed offset by 8:00.
+# XXX--in the absence of better information, assume that transition
+# was at the start of 2008-03-31 (the day of Steffen Thorsen's report);
+# this is almost surely wrong.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Mongol 1983 1984 - Apr 1 0:00 1:00 S
+Rule Mongol 1983 only - Oct 1 0:00 0 -
+# Shanks & Pottenger and IATA SSIM say 1990s switches occurred at 00:00,
+# but McDow says the 2001 switches occurred at 02:00. Also, IATA SSIM
+# (1996-09) says 1996-10-25. Go with Shanks & Pottenger through 1998.
+#
+# Shanks & Pottenger say that the Sept. 1984 through Sept. 1990 switches
+# in Choibalsan (more precisely, in Dornod and Sukhbaatar) took place
+# at 02:00 standard time, not at 00:00 local time as in the rest of
+# the country. That would be odd, and possibly is a result of their
+# correction of 02:00 (in the previous edition) not being done correctly
+# in the latest edition; so ignore it for now.
+
+Rule Mongol 1985 1998 - Mar lastSun 0:00 1:00 S
+Rule Mongol 1984 1998 - Sep lastSun 0:00 0 -
+# IATA SSIM (1999-09) says Mongolia no longer observes DST.
+Rule Mongol 2001 only - Apr lastSat 2:00 1:00 S
+Rule Mongol 2001 2006 - Sep lastSat 2:00 0 -
+Rule Mongol 2002 2006 - Mar lastSat 2:00 1:00 S
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+# Hovd, a.k.a. Chovd, Dund-Us, Dzhargalant, Khovd, Jirgalanta
+Zone Asia/Hovd 6:06:36 - LMT 1905 Aug
+ 6:00 - HOVT 1978 # Hovd Time
+ 7:00 Mongol HOV%sT
+# Ulaanbaatar, a.k.a. Ulan Bataar, Ulan Bator, Urga
+Zone Asia/Ulaanbaatar 7:07:32 - LMT 1905 Aug
+ 7:00 - ULAT 1978 # Ulaanbaatar Time
+ 8:00 Mongol ULA%sT
+# Choibalsan, a.k.a. Bajan Tuemen, Bajan Tumen, Chojbalsan,
+# Choybalsan, Sanbejse, Tchoibalsan
+Zone Asia/Choibalsan 7:38:00 - LMT 1905 Aug
+ 7:00 - ULAT 1978
+ 8:00 - ULAT 1983 Apr
+ 9:00 Mongol CHO%sT 2008 Mar 31 # Choibalsan Time
+ 8:00 Mongol CHO%sT
+
+# Nepal
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Kathmandu 5:41:16 - LMT 1920
+ 5:30 - IST 1986
+ 5:45 - NPT # Nepal Time
+
+# Oman
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Muscat 3:54:20 - LMT 1920
+ 4:00 - GST
+
+# Pakistan
+
+# From Rives McDow (2002-03-13):
+# I have been advised that Pakistan has decided to adopt dst on a
+# TRIAL basis for one year, starting 00:01 local time on April 7, 2002
+# and ending at 00:01 local time October 6, 2002. This is what I was
+# told, but I believe that the actual time of change may be 00:00; the
+# 00:01 was to make it clear which day it was on.
+
+# From Paul Eggert (2002-03-15):
+# Jesper Norgaard found this URL:
+# http://www.pak.gov.pk/public/news/app/app06_dec.htm
+# (dated 2001-12-06) which says that the Cabinet adopted a scheme "to
+# advance the clocks by one hour on the night between the first
+# Saturday and Sunday of April and revert to the original position on
+# 15th October each year". This agrees with McDow's 04-07 at 00:00,
+# but disagrees about the October transition, and makes it sound like
+# it's not on a trial basis. Also, the "between the first Saturday
+# and Sunday of April" phrase, if taken literally, means that the
+# transition takes place at 00:00 on the first Sunday on or after 04-02.
+
+# From Paul Eggert (2003-02-09):
+# DAWN <http://www.dawn.com/2002/10/06/top13.htm> reported on 2002-10-05
+# that 2002 DST ended that day at midnight. Go with McDow for now.
+
+# From Steffen Thorsen (2003-03-14):
+# According to http://www.dawn.com/2003/03/07/top15.htm
+# there will be no DST in Pakistan this year:
+#
+# ISLAMABAD, March 6: Information and Media Development Minister Sheikh
+# Rashid Ahmed on Thursday said the cabinet had reversed a previous
+# decision to advance clocks by one hour in summer and put them back by
+# one hour in winter with the aim of saving light hours and energy.
+#
+# The minister told a news conference that the experiment had rather
+# shown 8 per cent higher consumption of electricity.
+
+# From Alex Krivenyshev (2008-05-15):
+#
+# Here is an article that Pakistan plan to introduce Daylight Saving Time
+# on June 1, 2008 for 3 months.
+#
+# "... The federal cabinet on Wednesday announced a new conservation plan to help
+# reduce load shedding by approving the closure of commercial centres at 9pm and
+# moving clocks forward by one hour for the next three months.
+# ...."
+#
+# <a href="http://www.worldtimezone.net/dst_news/dst_news_pakistan01.html">
+# http://www.worldtimezone.net/dst_news/dst_news_pakistan01.html
+# </a>
+# OR
+# <a href="http://www.dailytimes.com.pk/default.asp?page=2008%5C05%5C15%5Cstory_15-5-2008_pg1_4">
+# http://www.dailytimes.com.pk/default.asp?page=2008%5C05%5C15%5Cstory_15-5-2008_pg1_4
+# </a>
+
+# From Arthur David Olson (2008-05-19):
+# XXX--midnight transitions is a guess; 2008 only is a guess.
+
+# From Alexander Krivenyshev (2008-08-28):
+# Pakistan government has decided to keep the watches one-hour advanced
+# for another 2 months--plan to return to Standard Time on October 31
+# instead of August 31.
+#
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_pakistan02.html">
+# http://www.worldtimezone.com/dst_news/dst_news_pakistan02.html
+# </a>
+# OR
+# <a href="http://dailymailnews.com/200808/28/news/dmbrn03.html">
+# http://dailymailnews.com/200808/28/news/dmbrn03.html
+# </a>
+
+# From Alexander Krivenyshev (2009-04-08):
+# Based on previous media reports that "... proposed plan to
+# advance clocks by one hour from May 1 will cause disturbance
+# to the working schedules rather than bringing discipline in
+# official working."
+# <a href="http://www.thenews.com.pk/daily_detail.asp?id=171280">
+# http://www.thenews.com.pk/daily_detail.asp?id=171280
+# </a>
+#
+# recent news that instead of May 2009 - Pakistan plan to
+# introduce DST from April 15, 2009
+#
+# FYI: Associated Press Of Pakistan
+# April 08, 2009
+# Cabinet okays proposal to advance clocks by one hour from April 15
+# <a href="http://www.app.com.pk/en_/index.php?option=com_content&task=view&id=73043&Itemid=1">
+# http://www.app.com.pk/en_/index.php?option=com_content&task=view&id=73043&Itemid=1
+# </a>
+#
+# or
+#
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_pakistan05.html">
+# http://www.worldtimezone.com/dst_news/dst_news_pakistan05.html
+# </a>
+#
+# ....
+# The Federal Cabinet on Wednesday approved the proposal to
+# advance clocks in the country by one hour from April 15 to
+# conserve energy"
+
+# From Steffen Thorsen (2009-09-17):
+# "The News International," Pakistan reports that: "The Federal
+# Government has decided to restore the previous time by moving the
+# clocks backward by one hour from October 1. A formal announcement to
+# this effect will be made after the Prime Minister grants approval in
+# this regard."
+# <a href="http://www.thenews.com.pk/updates.asp?id=87168">
+# http://www.thenews.com.pk/updates.asp?id=87168
+# </a>
+
+# From Alexander Krivenyshev (2009-09-28):
+# According to Associated Press Of Pakistan, it is confirmed that
+# Pakistan clocks across the country would be turned back by an hour from October
+# 1, 2009.
+#
+# "Clocks to go back one hour from 1 Oct"
+# <a href="http://www.app.com.pk/en_/index.php?option=com_content&task=view&id=86715&Itemid=2">
+# http://www.app.com.pk/en_/index.php?option=com_content&task=view&id=86715&Itemid=2
+# </a>
+# or
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_pakistan07.htm">
+# http://www.worldtimezone.com/dst_news/dst_news_pakistan07.htm
+# </a>
+
+# From Steffen Thorsen (2009-09-29):
+# Alexander Krivenyshev wrote:
+# > According to Associated Press Of Pakistan, it is confirmed that
+# > Pakistan clocks across the country would be turned back by an hour from October
+# > 1, 2009.
+#
+# Now they seem to have changed their mind, November 1 is the new date:
+# <a href="http://www.thenews.com.pk/top_story_detail.asp?Id=24742">
+# http://www.thenews.com.pk/top_story_detail.asp?Id=24742
+# </a>
+# "The country's clocks will be reversed by one hour on November 1.
+# Officials of Federal Ministry for Interior told this to Geo News on
+# Monday."
+#
+# And more importantly, it seems that these dates will be kept every year:
+# "It has now been decided that clocks will be wound forward by one hour
+# on April 15 and reversed by an hour on November 1 every year without
+# obtaining prior approval, the officials added."
+#
+# We have confirmed this year's end date with both with the Ministry of
+# Water and Power and the Pakistan Electric Power Company:
+# <a href="http://www.timeanddate.com/news/time/pakistan-ends-dst09.html">
+# http://www.timeanddate.com/news/time/pakistan-ends-dst09.html
+# </a>
+
+# From Christoph Goehre (2009-10-01):
+# [T]he German Consulate General in Karachi reported me today that Pakistan
+# will go back to standard time on 1st of November.
+
+# From Steffen Thorsen (2010-03-26):
+# Steffen Thorsen wrote:
+# > On Thursday (2010-03-25) it was announced that DST would start in
+# > Pakistan on 2010-04-01.
+# >
+# > Then today, the president said that they might have to revert the
+# > decision if it is not supported by the parliament. So at the time
+# > being, it seems unclear if DST will be actually observed or not - but
+# > April 1 could be a more likely date than April 15.
+# Now, it seems that the decision to not observe DST in final:
+#
+# "Govt Withdraws Plan To Advance Clocks"
+# <a href="http://www.apakistannews.com/govt-withdraws-plan-to-advance-clocks-172041">
+# http://www.apakistannews.com/govt-withdraws-plan-to-advance-clocks-172041
+# </a>
+#
+# "People laud PM's announcement to end DST"
+# <a href="http://www.app.com.pk/en_/index.php?option=com_content&task=view&id=99374&Itemid=2">
+# http://www.app.com.pk/en_/index.php?option=com_content&task=view&id=99374&Itemid=2
+# </a>
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Pakistan 2002 only - Apr Sun>=2 0:01 1:00 S
+Rule Pakistan 2002 only - Oct Sun>=2 0:01 0 -
+Rule Pakistan 2008 only - Jun 1 0:00 1:00 S
+Rule Pakistan 2008 only - Nov 1 0:00 0 -
+Rule Pakistan 2009 only - Apr 15 0:00 1:00 S
+Rule Pakistan 2009 only - Nov 1 0:00 0 -
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Karachi 4:28:12 - LMT 1907
+ 5:30 - IST 1942 Sep
+ 5:30 1:00 IST 1945 Oct 15
+ 5:30 - IST 1951 Sep 30
+ 5:00 - KART 1971 Mar 26 # Karachi Time
+ 5:00 Pakistan PK%sT # Pakistan Time
+
+# Palestine
+
+# From Amos Shapir (1998-02-15):
+#
+# From 1917 until 1948-05-15, all of Palestine, including the parts now
+# known as the Gaza Strip and the West Bank, was under British rule.
+# Therefore the rules given for Israel for that period, apply there too...
+#
+# The Gaza Strip was under Egyptian rule between 1948-05-15 until 1967-06-05
+# (except a short occupation by Israel from 1956-11 till 1957-03, but no
+# time zone was affected then). It was never formally annexed to Egypt,
+# though.
+#
+# The rest of Palestine was under Jordanian rule at that time, formally
+# annexed in 1950 as the West Bank (and the word "Trans" was dropped from
+# the country's previous name of "the Hashemite Kingdom of the
+# Trans-Jordan"). So the rules for Jordan for that time apply. Major
+# towns in that area are Nablus (Shchem), El-Halil (Hebron), Ramallah, and
+# East Jerusalem.
+#
+# Both areas were occupied by Israel in June 1967, but not annexed (except
+# for East Jerusalem). They were on Israel time since then; there might
+# have been a Military Governor's order about time zones, but I'm not aware
+# of any (such orders may have been issued semi-annually whenever summer
+# time was in effect, but maybe the legal aspect of time was just neglected).
+#
+# The Palestinian Authority was established in 1993, and got hold of most
+# towns in the West Bank and Gaza by 1995. I know that in order to
+# demonstrate...independence, they have been switching to
+# summer time and back on a different schedule than Israel's, but I don't
+# know when this was started, or what algorithm is used (most likely the
+# Jordanian one).
+#
+# To summarize, the table should probably look something like that:
+#
+# Area \ when | 1918-1947 | 1948-1967 | 1967-1995 | 1996-
+# ------------+-----------+-----------+-----------+-----------
+# Israel | Zion | Zion | Zion | Zion
+# West bank | Zion | Jordan | Zion | Jordan
+# Gaza | Zion | Egypt | Zion | Jordan
+#
+# I guess more info may be available from the PA's web page (if/when they
+# have one).
+
+# From Paul Eggert (2006-03-22):
+# Shanks & Pottenger write that Gaza did not observe DST until 1957, but go
+# with Shapir and assume that it observed DST from 1940 through 1947,
+# and that it used Jordanian rules starting in 1996.
+# We don't yet need a separate entry for the West Bank, since
+# the only differences between it and Gaza that we know about
+# occurred before our cutoff date of 1970.
+# However, as we get more information, we may need to add entries
+# for parts of the West Bank as they transitioned from Israel's rules
+# to Palestine's rules.
+
+# From IINS News Service - Israel - 1998-03-23 10:38:07 Israel time,
+# forwarded by Ephraim Silverberg:
+#
+# Despite the fact that Israel changed over to daylight savings time
+# last week, the PLO Authority (PA) has decided not to turn its clocks
+# one-hour forward at this time. As a sign of independence from Israeli rule,
+# the PA has decided to implement DST in April.
+
+# From Paul Eggert (1999-09-20):
+# Daoud Kuttab writes in
+# <a href="http://www.jpost.com/com/Archive/22.Apr.1999/Opinion/Article-2.html">
+# Holiday havoc
+# </a> (Jerusalem Post, 1999-04-22) that
+# the Palestinian National Authority changed to DST on 1999-04-15.
+# I vaguely recall that they switch back in October (sorry, forgot the source).
+# For now, let's assume that the spring switch was at 24:00,
+# and that they switch at 0:00 on the 3rd Fridays of April and October.
+
+# From Paul Eggert (2005-11-22):
+# Starting 2004 transitions are from Steffen Thorsen's web site timeanddate.com.
+
+# From Steffen Thorsen (2005-11-23):
+# A user from Gaza reported that Gaza made the change early because of
+# the Ramadan. Next year Ramadan will be even earlier, so I think
+# there is a good chance next year's end date will be around two weeks
+# earlier--the same goes for Jordan.
+
+# From Steffen Thorsen (2006-08-17):
+# I was informed by a user in Bethlehem that in Bethlehem it started the
+# same day as Israel, and after checking with other users in the area, I
+# was informed that they started DST one day after Israel. I was not
+# able to find any authoritative sources at the time, nor details if
+# Gaza changed as well, but presumed Gaza to follow the same rules as
+# the West Bank.
+
+# From Steffen Thorsen (2006-09-26):
+# according to the Palestine News Network (2006-09-19):
+# http://english.pnn.ps/index.php?option=com_content&task=view&id=596&Itemid=5
+# > The Council of Ministers announced that this year its winter schedule
+# > will begin early, as of midnight Thursday. It is also time to turn
+# > back the clocks for winter. Friday will begin an hour late this week.
+# I guess it is likely that next year's date will be moved as well,
+# because of the Ramadan.
+
+# From Jesper Norgaard Welen (2007-09-18):
+# According to Steffen Thorsen's web site the Gaza Strip and the rest of the
+# Palestinian territories left DST early on 13.th. of September at 2:00.
+
+# From Paul Eggert (2007-09-20):
+# My understanding is that Gaza and the West Bank disagree even over when
+# the weekend is (Thursday+Friday versus Friday+Saturday), so I'd be a bit
+# surprised if they agreed about DST. But for now, assume they agree.
+# For lack of better information, predict that future changes will be
+# the 2nd Thursday of September at 02:00.
+
+# From Alexander Krivenyshev (2008-08-28):
+# Here is an article, that Mideast running on different clocks at Ramadan.
+#
+# Gaza Strip (as Egypt) ended DST at midnight Thursday (Aug 28, 2008), while
+# the West Bank will end Daylight Saving Time at midnight Sunday (Aug 31, 2008).
+#
+# <a href="http://www.guardian.co.uk/world/feedarticle/7759001">
+# http://www.guardian.co.uk/world/feedarticle/7759001
+# </a>
+# <a href="http://www.abcnews.go.com/International/wireStory?id=5676087">
+# http://www.abcnews.go.com/International/wireStory?id=5676087
+# </a>
+# or
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_gazastrip01.html">
+# http://www.worldtimezone.com/dst_news/dst_news_gazastrip01.html
+# </a>
+
+# From Alexander Krivenyshev (2009-03-26):
+# According to the Palestine News Network (arabic.pnn.ps), Palestinian
+# government decided to start Daylight Time on Thursday night March
+# 26 and continue until the night of 27 September 2009.
+#
+# (in Arabic)
+# <a href="http://arabic.pnn.ps/index.php?option=com_content&task=view&id=50850">
+# http://arabic.pnn.ps/index.php?option=com_content&task=view&id=50850
+# </a>
+#
+# or
+# (English translation)
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_westbank01.html">
+# http://www.worldtimezone.com/dst_news/dst_news_westbank01.html
+# </a>
+
+# From Steffen Thorsen (2009-08-31):
+# Palestine's Council of Ministers announced that they will revert back to
+# winter time on Friday, 2009-09-04.
+#
+# One news source:
+# <a href="http://www.safa.ps/ara/?action=showdetail&seid=4158">
+# http://www.safa.ps/ara/?action=showdetail&seid=4158
+# </a>
+# (Palestinian press agency, Arabic),
+# Google translate: "Decided that the Palestinian government in Ramallah
+# headed by Salam Fayyad, the start of work in time for the winter of
+# 2009, starting on Friday approved the fourth delay Sept. clock sixty
+# minutes per hour as of Friday morning."
+#
+# We are not sure if Gaza will do the same, last year they had a different
+# end date, we will keep this page updated:
+# <a href="http://www.timeanddate.com/news/time/westbank-gaza-dst-2009.html">
+# http://www.timeanddate.com/news/time/westbank-gaza-dst-2009.html
+# </a>
+
+# From Alexander Krivenyshev (2009-09-02):
+# Seems that Gaza Strip will go back to Winter Time same date as West Bank.
+#
+# According to Palestinian Ministry Of Interior, West Bank and Gaza Strip plan
+# to change time back to Standard time on September 4, 2009.
+#
+# "Winter time unite the West Bank and Gaza"
+# (from Palestinian National Authority):
+# <a href="http://www.moi.gov.ps/en/?page=633167343250594025&nid=11505
+# http://www.moi.gov.ps/en/?page=633167343250594025&nid=11505
+# </a>
+# or
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_gazastrip02.html>
+# http://www.worldtimezone.com/dst_news/dst_news_gazastrip02.html
+# </a>
+
+# From Alexander Krivenyshev (2010-03-19):
+# According to Voice of Palestine DST will last for 191 days, from March
+# 26, 2010 till "the last Sunday before the tenth day of Tishri
+# (October), each year" (October 03, 2010?)
+#
+# <a href="http://palvoice.org/forums/showthread.php?t=245697">
+# http://palvoice.org/forums/showthread.php?t=245697
+# </a>
+# (in Arabic)
+# or
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_westbank03.html">
+# http://www.worldtimezone.com/dst_news/dst_news_westbank03.html
+# </a>
+
+# From Steffen Thorsen (2010-03-24):
+# ...Ma'an News Agency reports that Hamas cabinet has decided it will
+# start one day later, at 12:01am. Not sure if they really mean 12:01am or
+# noon though:
+#
+# <a href="http://www.maannews.net/eng/ViewDetails.aspx?ID=271178">
+# http://www.maannews.net/eng/ViewDetails.aspx?ID=271178
+# </a>
+# (Ma'an News Agency)
+# "At 12:01am Friday, clocks in Israel and the West Bank will change to
+# 1:01am, while Gaza clocks will change at 12:01am Saturday morning."
+
+# From Steffen Thorsen (2010-08-11):
+# According to several sources, including
+# <a href="http://www.maannews.net/eng/ViewDetails.aspx?ID=306795">
+# http://www.maannews.net/eng/ViewDetails.aspx?ID=306795
+# </a>
+# the clocks were set back one hour at 2010-08-11 00:00:00 local time in
+# Gaza and the West Bank.
+# Some more background info:
+# <a href="http://www.timeanddate.com/news/time/westbank-gaza-end-dst-2010.html">
+# http://www.timeanddate.com/news/time/westbank-gaza-end-dst-2010.html
+# </a>
+
+# From Steffen Thorsen (2011-08-26):
+# Gaza and the West Bank did go back to standard time in the beginning of
+# August, and will now enter daylight saving time again on 2011-08-30
+# 00:00 (so two periods of DST in 2011). The pause was because of
+# Ramadan.
+#
+# <a href="http://www.maannews.net/eng/ViewDetails.aspx?ID=416217">
+# http://www.maannews.net/eng/ViewDetails.aspx?ID=416217
+# </a>
+# Additional info:
+# <a href="http://www.timeanddate.com/news/time/palestine-dst-2011.html">
+# http://www.timeanddate.com/news/time/palestine-dst-2011.html
+# </a>
+
+# From Alexander Krivenyshev (2011-08-27):
+# According to the article in The Jerusalem Post:
+# "...Earlier this month, the Palestinian government in the West Bank decided to
+# move to standard time for 30 days, during Ramadan. The Palestinians in the
+# Gaza Strip accepted the change and also moved their clocks one hour back.
+# The Hamas government said on Saturday that it won't observe summertime after
+# the Muslim feast of Id al-Fitr, which begins on Tuesday..."
+# ...
+# <a href="http://www.jpost.com/MiddleEast/Article.aspx?id=235650">
+# http://www.jpost.com/MiddleEast/Article.aspx?id=235650
+# </a>
+# or
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_gazastrip05.html">
+# http://www.worldtimezone.com/dst_news/dst_news_gazastrip05.html
+# </a>
+# The rules for Egypt are stolen from the `africa' file.
+
+# From Steffen Thorsen (2011-09-30):
+# West Bank did end Daylight Saving Time this morning/midnight (2011-09-30
+# 00:00).
+# So West Bank and Gaza now have the same time again.
+#
+# Many sources, including:
+# <a href="http://www.maannews.net/eng/ViewDetails.aspx?ID=424808">
+# http://www.maannews.net/eng/ViewDetails.aspx?ID=424808
+# </a>
+
+# From Steffen Thorsen (2012-03-26):
+# Palestinian news sources tell that both Gaza and West Bank will start DST
+# on Friday (Thursday midnight, 2012-03-29 24:00).
+# Some of many sources in Arabic:
+# <a href="http://www.samanews.com/index.php?act=Show&id=122638">
+# http://www.samanews.com/index.php?act=Show&id=122638
+# </a>
+#
+# <a href="http://safa.ps/details/news/74352/%D8%A8%D8%AF%D8%A1-%D8%A7%D9%84%D8%AA%D9%88%D9%82%D9%8A%D8%AA-%D8%A7%D9%84%D8%B5%D9%8A%D9%81%D9%8A-%D8%A8%D8%A7%D9%84%D8%B6%D9%81%D8%A9-%D9%88%D8%BA%D8%B2%D8%A9-%D9%84%D9%8A%D9%84%D8%A9-%D8%A7%D9%84%D8%AC%D9%85%D8%B9%D8%A9.html">
+# http://safa.ps/details/news/74352/%D8%A8%D8%AF%D8%A1-%D8%A7%D9%84%D8%AA%D9%88%D9%82%D9%8A%D8%AA-%D8%A7%D9%84%D8%B5%D9%8A%D9%81%D9%8A-%D8%A8%D8%A7%D9%84%D8%B6%D9%81%D8%A9-%D9%88%D8%BA%D8%B2%D8%A9-%D9%84%D9%8A%D9%84%D8%A9-%D8%A7%D9%84%D8%AC%D9%85%D8%B9%D8%A9.html
+# </a>
+#
+# Our brief summary:
+# <a href="http://www.timeanddate.com/news/time/gaza-west-bank-dst-2012.html">
+# http://www.timeanddate.com/news/time/gaza-west-bank-dst-2012.html
+# </a>
+
+# From Arthur David Olson (2012-03-27):
+# The timeanddate article for 2012 says that "the end date has not yet been
+# announced" and that "Last year, both...paused daylight saving time during...
+# Ramadan. It is not yet known [for] 2012."
+# For now, assume both switch back on the last Friday in September. XXX
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule EgyptAsia 1957 only - May 10 0:00 1:00 S
+Rule EgyptAsia 1957 1958 - Oct 1 0:00 0 -
+Rule EgyptAsia 1958 only - May 1 0:00 1:00 S
+Rule EgyptAsia 1959 1967 - May 1 1:00 1:00 S
+Rule EgyptAsia 1959 1965 - Sep 30 3:00 0 -
+Rule EgyptAsia 1966 only - Oct 1 3:00 0 -
+
+Rule Palestine 1999 2005 - Apr Fri>=15 0:00 1:00 S
+Rule Palestine 1999 2003 - Oct Fri>=15 0:00 0 -
+Rule Palestine 2004 only - Oct 1 1:00 0 -
+Rule Palestine 2005 only - Oct 4 2:00 0 -
+Rule Palestine 2006 2008 - Apr 1 0:00 1:00 S
+Rule Palestine 2006 only - Sep 22 0:00 0 -
+Rule Palestine 2007 only - Sep Thu>=8 2:00 0 -
+Rule Palestine 2008 only - Aug lastFri 0:00 0 -
+Rule Palestine 2009 only - Mar lastFri 0:00 1:00 S
+Rule Palestine 2009 only - Sep Fri>=1 2:00 0 -
+Rule Palestine 2010 only - Mar lastSat 0:01 1:00 S
+Rule Palestine 2010 only - Aug 11 0:00 0 -
+
+# From Arthur David Olson (2011-09-20):
+# 2011 transitions per http://www.timeanddate.com as of 2011-09-20.
+# From Paul Eggert (2012-10-12):
+# 2012 transitions per http://www.timeanddate.com as of 2012-10-12.
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Gaza 2:17:52 - LMT 1900 Oct
+ 2:00 Zion EET 1948 May 15
+ 2:00 EgyptAsia EE%sT 1967 Jun 5
+ 2:00 Zion I%sT 1996
+ 2:00 Jordan EE%sT 1999
+ 2:00 Palestine EE%sT 2011 Apr 2 12:01
+ 2:00 1:00 EEST 2011 Aug 1
+ 2:00 - EET 2012 Mar 30
+ 2:00 1:00 EEST 2012 Sep 21 1:00
+ 2:00 - EET
+
+Zone Asia/Hebron 2:20:23 - LMT 1900 Oct
+ 2:00 Zion EET 1948 May 15
+ 2:00 EgyptAsia EE%sT 1967 Jun 5
+ 2:00 Zion I%sT 1996
+ 2:00 Jordan EE%sT 1999
+ 2:00 Palestine EE%sT 2008 Aug
+ 2:00 1:00 EEST 2008 Sep
+ 2:00 Palestine EE%sT 2011 Apr 1 12:01
+ 2:00 1:00 EEST 2011 Aug 1
+ 2:00 - EET 2011 Aug 30
+ 2:00 1:00 EEST 2011 Sep 30 3:00
+ 2:00 - EET 2012 Mar 30
+ 2:00 1:00 EEST 2012 Sep 21 1:00
+ 2:00 - EET
+
+# Paracel Is
+# no information
+
+# Philippines
+# On 1844-08-16, Narciso Claveria, governor-general of the
+# Philippines, issued a proclamation announcing that 1844-12-30 was to
+# be immediately followed by 1845-01-01. Robert H. van Gent has a
+# transcript of the decree in <http://www.phys.uu.nl/~vgent/idl/idl.htm>.
+# The rest of the data are from Shanks & Pottenger.
+
+# From Paul Eggert (2006-04-25):
+# Tomorrow's Manila Standard reports that the Philippines Department of
+# Trade and Industry is considering adopting DST this June when the
+# rainy season begins. See
+# <http://www.manilastandardtoday.com/?page=politics02_april26_2006>.
+# For now, we'll ignore this, since it's not definite and we lack details.
+#
+# From Jesper Norgaard Welen (2006-04-26):
+# ... claims that Philippines had DST last time in 1990:
+# http://story.philippinetimes.com/p.x/ct/9/id/145be20cc6b121c0/cid/3e5bbccc730d258c/
+# [a story dated 2006-04-25 by Cris Larano of Dow Jones Newswires,
+# but no details]
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Phil 1936 only - Nov 1 0:00 1:00 S
+Rule Phil 1937 only - Feb 1 0:00 0 -
+Rule Phil 1954 only - Apr 12 0:00 1:00 S
+Rule Phil 1954 only - Jul 1 0:00 0 -
+Rule Phil 1978 only - Mar 22 0:00 1:00 S
+Rule Phil 1978 only - Sep 21 0:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Manila -15:56:00 - LMT 1844 Dec 31
+ 8:04:00 - LMT 1899 May 11
+ 8:00 Phil PH%sT 1942 May
+ 9:00 - JST 1944 Nov
+ 8:00 Phil PH%sT
+
+# Qatar
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Qatar 3:26:08 - LMT 1920 # Al Dawhah / Doha
+ 4:00 - GST 1972 Jun
+ 3:00 - AST
+
+# Saudi Arabia
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Riyadh 3:06:52 - LMT 1950
+ 3:00 - AST
+
+# Singapore
+# The data here are taken from Mok Ly Yng (2003-10-30)
+# <http://www.math.nus.edu.sg/aslaksen/teaching/timezone.html>.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Singapore 6:55:25 - LMT 1901 Jan 1
+ 6:55:25 - SMT 1905 Jun 1 # Singapore M.T.
+ 7:00 - MALT 1933 Jan 1 # Malaya Time
+ 7:00 0:20 MALST 1936 Jan 1
+ 7:20 - MALT 1941 Sep 1
+ 7:30 - MALT 1942 Feb 16
+ 9:00 - JST 1945 Sep 12
+ 7:30 - MALT 1965 Aug 9 # independence
+ 7:30 - SGT 1982 Jan 1 # Singapore Time
+ 8:00 - SGT
+
+# Spratly Is
+# no information
+
+# Sri Lanka
+# From Paul Eggert (1996-09-03):
+# "Sri Lanka advances clock by an hour to avoid blackout"
+# (www.virtual-pc.com/lankaweb/news/items/240596-2.html, 1996-05-24,
+# no longer available as of 1999-08-17)
+# reported ``the country's standard time will be put forward by one hour at
+# midnight Friday (1830 GMT) `in the light of the present power crisis'.''
+#
+# From Dharmasiri Senanayake, Sri Lanka Media Minister (1996-10-24), as quoted
+# by Shamindra in
+# <a href="news:54rka5$m5h@mtinsc01-mgt.ops.worldnet.att.net">
+# Daily News - Hot News Section (1996-10-26)
+# </a>:
+# With effect from 12.30 a.m. on 26th October 1996
+# Sri Lanka will be six (06) hours ahead of GMT.
+
+# From Jesper Norgaard Welen (2006-04-14), quoting Sri Lanka News Online
+# <http://news.sinhalaya.com/wmview.php?ArtID=11002> (2006-04-13):
+# 0030 hrs on April 15, 2006 (midnight of April 14, 2006 +30 minutes)
+# at present, become 2400 hours of April 14, 2006 (midnight of April 14, 2006).
+
+# From Peter Apps and Ranga Sirila of Reuters (2006-04-12) in:
+# <http://today.reuters.co.uk/news/newsArticle.aspx?type=scienceNews&storyID=2006-04-12T172228Z_01_COL295762_RTRIDST_0_SCIENCE-SRILANKA-TIME-DC.XML>
+# [The Tamil Tigers] never accepted the original 1996 time change and simply
+# kept their clocks set five and a half hours ahead of Greenwich Mean
+# Time (GMT), in line with neighbor India.
+# From Paul Eggert (2006-04-18):
+# People who live in regions under Tamil control can use [TZ='Asia/Kolkata'],
+# as that zone has agreed with the Tamil areas since our cutoff date of 1970.
+
+# From K Sethu (2006-04-25):
+# I think the abbreviation LKT originated from the world of computers at
+# the time of or subsequent to the time zone changes by SL Government
+# twice in 1996 and probably SL Government or its standardization
+# agencies never declared an abbreviation as a national standard.
+#
+# I recollect before the recent change the government annoucemments
+# mentioning it as simply changing Sri Lanka Standard Time or Sri Lanka
+# Time and no mention was made about the abbreviation.
+#
+# If we look at Sri Lanka Department of Government's "Official News
+# Website of Sri Lanka" ... http://www.news.lk/ we can see that they
+# use SLT as abbreviation in time stamp at the beginning of each news
+# item....
+#
+# Within Sri Lanka I think LKT is well known among computer users and
+# adminsitrators. In my opinion SLT may not be a good choice because the
+# nation's largest telcom / internet operator Sri Lanka Telcom is well
+# known by that abbreviation - simply as SLT (there IP domains are
+# slt.lk and sltnet.lk).
+#
+# But if indeed our government has adopted SLT as standard abbreviation
+# (that we have not known so far) then it is better that it be used for
+# all computers.
+
+# From Paul Eggert (2006-04-25):
+# One possibility is that we wait for a bit for the dust to settle down
+# and then see what people actually say in practice.
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Colombo 5:19:24 - LMT 1880
+ 5:19:32 - MMT 1906 # Moratuwa Mean Time
+ 5:30 - IST 1942 Jan 5
+ 5:30 0:30 IHST 1942 Sep
+ 5:30 1:00 IST 1945 Oct 16 2:00
+ 5:30 - IST 1996 May 25 0:00
+ 6:30 - LKT 1996 Oct 26 0:30
+ 6:00 - LKT 2006 Apr 15 0:30
+ 5:30 - IST
+
+# Syria
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Syria 1920 1923 - Apr Sun>=15 2:00 1:00 S
+Rule Syria 1920 1923 - Oct Sun>=1 2:00 0 -
+Rule Syria 1962 only - Apr 29 2:00 1:00 S
+Rule Syria 1962 only - Oct 1 2:00 0 -
+Rule Syria 1963 1965 - May 1 2:00 1:00 S
+Rule Syria 1963 only - Sep 30 2:00 0 -
+Rule Syria 1964 only - Oct 1 2:00 0 -
+Rule Syria 1965 only - Sep 30 2:00 0 -
+Rule Syria 1966 only - Apr 24 2:00 1:00 S
+Rule Syria 1966 1976 - Oct 1 2:00 0 -
+Rule Syria 1967 1978 - May 1 2:00 1:00 S
+Rule Syria 1977 1978 - Sep 1 2:00 0 -
+Rule Syria 1983 1984 - Apr 9 2:00 1:00 S
+Rule Syria 1983 1984 - Oct 1 2:00 0 -
+Rule Syria 1986 only - Feb 16 2:00 1:00 S
+Rule Syria 1986 only - Oct 9 2:00 0 -
+Rule Syria 1987 only - Mar 1 2:00 1:00 S
+Rule Syria 1987 1988 - Oct 31 2:00 0 -
+Rule Syria 1988 only - Mar 15 2:00 1:00 S
+Rule Syria 1989 only - Mar 31 2:00 1:00 S
+Rule Syria 1989 only - Oct 1 2:00 0 -
+Rule Syria 1990 only - Apr 1 2:00 1:00 S
+Rule Syria 1990 only - Sep 30 2:00 0 -
+Rule Syria 1991 only - Apr 1 0:00 1:00 S
+Rule Syria 1991 1992 - Oct 1 0:00 0 -
+Rule Syria 1992 only - Apr 8 0:00 1:00 S
+Rule Syria 1993 only - Mar 26 0:00 1:00 S
+Rule Syria 1993 only - Sep 25 0:00 0 -
+# IATA SSIM (1998-02) says 1998-04-02;
+# (1998-09) says 1999-03-29 and 1999-09-29; (1999-02) says 1999-04-02,
+# 2000-04-02, and 2001-04-02; (1999-09) says 2000-03-31 and 2001-03-31;
+# (2006) says 2006-03-31 and 2006-09-22;
+# for now ignore all these claims and go with Shanks & Pottenger,
+# except for the 2006-09-22 claim (which seems right for Ramadan).
+Rule Syria 1994 1996 - Apr 1 0:00 1:00 S
+Rule Syria 1994 2005 - Oct 1 0:00 0 -
+Rule Syria 1997 1998 - Mar lastMon 0:00 1:00 S
+Rule Syria 1999 2006 - Apr 1 0:00 1:00 S
+# From Stephen Colebourne (2006-09-18):
+# According to IATA data, Syria will change DST on 21st September [21:00 UTC]
+# this year [only].... This is probably related to Ramadan, like Egypt.
+Rule Syria 2006 only - Sep 22 0:00 0 -
+# From Paul Eggert (2007-03-29):
+# Today the AP reported "Syria will switch to summertime at midnight Thursday."
+# http://www.iht.com/articles/ap/2007/03/29/africa/ME-GEN-Syria-Time-Change.php
+Rule Syria 2007 only - Mar lastFri 0:00 1:00 S
+# From Jesper Norgard (2007-10-27):
+# The sister center ICARDA of my work CIMMYT is confirming that Syria DST will
+# not take place 1.st November at 0:00 o'clock but 1.st November at 24:00 or
+# rather Midnight between Thursday and Friday. This does make more sence than
+# having it between Wednesday and Thursday (two workdays in Syria) since the
+# weekend in Syria is not Saturday and Sunday, but Friday and Saturday. So now
+# it is implemented at midnight of the last workday before weekend...
+#
+# From Steffen Thorsen (2007-10-27):
+# Jesper Norgaard Welen wrote:
+#
+# > "Winter local time in Syria will be observed at midnight of Thursday 1
+# > November 2007, and the clock will be put back 1 hour."
+#
+# I found confirmation on this in this gov.sy-article (Arabic):
+# http://wehda.alwehda.gov.sy/_print_veiw.asp?FileName=12521710520070926111247
+#
+# which using Google's translate tools says:
+# Council of Ministers also approved the commencement of work on
+# identifying the winter time as of Friday, 2/11/2007 where the 60th
+# minute delay at midnight Thursday 1/11/2007.
+Rule Syria 2007 only - Nov Fri>=1 0:00 0 -
+
+# From Stephen Colebourne (2008-03-17):
+# For everyone's info, I saw an IATA time zone change for [Syria] for
+# this month (March 2008) in the last day or so...This is the data IATA
+# are now using:
+# Country Time Standard --- DST Start --- --- DST End --- DST
+# Name Zone Variation Time Date Time Date
+# Variation
+# Syrian Arab
+# Republic SY +0200 2200 03APR08 2100 30SEP08 +0300
+# 2200 02APR09 2100 30SEP09 +0300
+# 2200 01APR10 2100 30SEP10 +0300
+
+# From Arthur David Olson (2008-03-17):
+# Here's a link to English-language coverage by the Syrian Arab News
+# Agency (SANA)...
+# <a href="http://www.sana.sy/eng/21/2008/03/11/165173.htm">
+# http://www.sana.sy/eng/21/2008/03/11/165173.htm
+# </a>...which reads (in part) "The Cabinet approved the suggestion of the
+# Ministry of Electricity to begin daylight savings time on Friday April
+# 4th, advancing clocks one hour ahead on midnight of Thursday April 3rd."
+# Since Syria is two hours east of UTC, the 2200 and 2100 transition times
+# shown above match up with midnight in Syria.
+
+# From Arthur David Olson (2008-03-18):
+# My buest guess at a Syrian rule is "the Friday nearest April 1";
+# coding that involves either using a "Mar Fri>=29" construct that old time zone
+# compilers can't handle or having multiple Rules (a la Israel).
+# For now, use "Apr Fri>=1", and go with IATA on a uniform Sep 30 end.
+
+# From Steffen Thorsen (2008-10-07):
+# Syria has now officially decided to end DST on 2008-11-01 this year,
+# according to the following article in the Syrian Arab News Agency (SANA).
+#
+# The article is in Arabic, and seems to tell that they will go back to
+# winter time on 2008-11-01 at 00:00 local daylight time (delaying/setting
+# clocks back 60 minutes).
+#
+# <a href="http://sana.sy/ara/2/2008/10/07/195459.htm">
+# http://sana.sy/ara/2/2008/10/07/195459.htm
+# </a>
+
+# From Steffen Thorsen (2009-03-19):
+# Syria will start DST on 2009-03-27 00:00 this year according to many sources,
+# two examples:
+#
+# <a href="http://www.sana.sy/eng/21/2009/03/17/217563.htm">
+# http://www.sana.sy/eng/21/2009/03/17/217563.htm
+# </a>
+# (English, Syrian Arab News # Agency)
+# <a href="http://thawra.alwehda.gov.sy/_View_news2.asp?FileName=94459258720090318012209">
+# http://thawra.alwehda.gov.sy/_View_news2.asp?FileName=94459258720090318012209
+# </a>
+# (Arabic, gov-site)
+#
+# We have not found any sources saying anything about when DST ends this year.
+#
+# Our summary
+# <a href="http://www.timeanddate.com/news/time/syria-dst-starts-march-27-2009.html">
+# http://www.timeanddate.com/news/time/syria-dst-starts-march-27-2009.html
+# </a>
+
+# From Steffen Thorsen (2009-10-27):
+# The Syrian Arab News Network on 2009-09-29 reported that Syria will
+# revert back to winter (standard) time on midnight between Thursday
+# 2009-10-29 and Friday 2009-10-30:
+# <a href="http://www.sana.sy/ara/2/2009/09/29/247012.htm">
+# http://www.sana.sy/ara/2/2009/09/29/247012.htm (Arabic)
+# </a>
+
+# From Arthur David Olson (2009-10-28):
+# We'll see if future DST switching times turn out to be end of the last
+# Thursday of the month or the start of the last Friday of the month or
+# something else. For now, use the start of the last Friday.
+
+# From Steffen Thorsen (2010-03-17):
+# The "Syrian News Station" reported on 2010-03-16 that the Council of
+# Ministers has decided that Syria will start DST on midnight Thursday
+# 2010-04-01: (midnight between Thursday and Friday):
+# <a href="http://sns.sy/sns/?path=news/read/11421">
+# http://sns.sy/sns/?path=news/read/11421 (Arabic)
+# </a>
+
+# From Steffen Thorsen (2012-03-26):
+# Today, Syria's government announced that they will start DST early on Friday
+# (00:00). This is a bit earlier than the past two years.
+#
+# From Syrian Arab News Agency, in Arabic:
+# <a href="http://www.sana.sy/ara/2/2012/03/26/408215.htm">
+# http://www.sana.sy/ara/2/2012/03/26/408215.htm
+# </a>
+#
+# Our brief summary:
+# <a href="http://www.timeanddate.com/news/time/syria-dst-2012.html">
+# http://www.timeanddate.com/news/time/syria-dst-2012.html
+# </a>
+
+# From Arthur David Olson (2012-03-27):
+# Assume last Friday in March going forward XXX.
+
+Rule Syria 2008 only - Apr Fri>=1 0:00 1:00 S
+Rule Syria 2008 only - Nov 1 0:00 0 -
+Rule Syria 2009 only - Mar lastFri 0:00 1:00 S
+Rule Syria 2010 2011 - Apr Fri>=1 0:00 1:00 S
+Rule Syria 2012 max - Mar lastFri 0:00 1:00 S
+Rule Syria 2009 max - Oct lastFri 0:00 0 -
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Damascus 2:25:12 - LMT 1920 # Dimashq
+ 2:00 Syria EE%sT
+
+# Tajikistan
+# From Shanks & Pottenger.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Dushanbe 4:35:12 - LMT 1924 May 2
+ 5:00 - DUST 1930 Jun 21 # Dushanbe Time
+ 6:00 RussiaAsia DUS%sT 1991 Mar 31 2:00s
+ 5:00 1:00 DUSST 1991 Sep 9 2:00s
+ 5:00 - TJT # Tajikistan Time
+
+# Thailand
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Bangkok 6:42:04 - LMT 1880
+ 6:42:04 - BMT 1920 Apr # Bangkok Mean Time
+ 7:00 - ICT
+
+# Turkmenistan
+# From Shanks & Pottenger.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Ashgabat 3:53:32 - LMT 1924 May 2 # or Ashkhabad
+ 4:00 - ASHT 1930 Jun 21 # Ashkhabad Time
+ 5:00 RussiaAsia ASH%sT 1991 Mar 31 2:00
+ 4:00 RussiaAsia ASH%sT 1991 Oct 27 # independence
+ 4:00 RussiaAsia TM%sT 1992 Jan 19 2:00
+ 5:00 - TMT
+
+# United Arab Emirates
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Dubai 3:41:12 - LMT 1920
+ 4:00 - GST
+
+# Uzbekistan
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Samarkand 4:27:12 - LMT 1924 May 2
+ 4:00 - SAMT 1930 Jun 21 # Samarkand Time
+ 5:00 - SAMT 1981 Apr 1
+ 5:00 1:00 SAMST 1981 Oct 1
+ 6:00 - TAST 1982 Apr 1 # Tashkent Time
+ 5:00 RussiaAsia SAM%sT 1991 Sep 1 # independence
+ 5:00 RussiaAsia UZ%sT 1992
+ 5:00 - UZT
+Zone Asia/Tashkent 4:37:12 - LMT 1924 May 2
+ 5:00 - TAST 1930 Jun 21 # Tashkent Time
+ 6:00 RussiaAsia TAS%sT 1991 Mar 31 2:00
+ 5:00 RussiaAsia TAS%sT 1991 Sep 1 # independence
+ 5:00 RussiaAsia UZ%sT 1992
+ 5:00 - UZT
+
+# Vietnam
+
+# From Arthur David Olson (2008-03-18):
+# The English-language name of Vietnam's most populous city is "Ho Chi Min City";
+# we use Ho_Chi_Minh below to avoid a name of more than 14 characters.
+
+# From Shanks & Pottenger:
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Ho_Chi_Minh 7:06:40 - LMT 1906 Jun 9
+ 7:06:20 - SMT 1911 Mar 11 0:01 # Saigon MT?
+ 7:00 - ICT 1912 May
+ 8:00 - ICT 1931 May
+ 7:00 - ICT
+
+# Yemen
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Asia/Aden 3:00:48 - LMT 1950
+ 3:00 - AST
diff --git a/misc/flot/examples/axes-time-zones/tz/australasia b/misc/flot/examples/axes-time-zones/tz/australasia
new file mode 100644
index 0000000..bef6f20
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/australasia
@@ -0,0 +1,1719 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# This file also includes Pacific islands.
+
+# Notes are at the end of this file
+
+###############################################################################
+
+# Australia
+
+# Please see the notes below for the controversy about "EST" versus "AEST" etc.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Aus 1917 only - Jan 1 0:01 1:00 -
+Rule Aus 1917 only - Mar 25 2:00 0 -
+Rule Aus 1942 only - Jan 1 2:00 1:00 -
+Rule Aus 1942 only - Mar 29 2:00 0 -
+Rule Aus 1942 only - Sep 27 2:00 1:00 -
+Rule Aus 1943 1944 - Mar lastSun 2:00 0 -
+Rule Aus 1943 only - Oct 3 2:00 1:00 -
+# Go with Whitman and the Australian National Standards Commission, which
+# says W Australia didn't use DST in 1943/1944. Ignore Whitman's claim that
+# 1944/1945 was just like 1943/1944.
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+# Northern Territory
+Zone Australia/Darwin 8:43:20 - LMT 1895 Feb
+ 9:00 - CST 1899 May
+ 9:30 Aus CST
+# Western Australia
+#
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule AW 1974 only - Oct lastSun 2:00s 1:00 -
+Rule AW 1975 only - Mar Sun>=1 2:00s 0 -
+Rule AW 1983 only - Oct lastSun 2:00s 1:00 -
+Rule AW 1984 only - Mar Sun>=1 2:00s 0 -
+Rule AW 1991 only - Nov 17 2:00s 1:00 -
+Rule AW 1992 only - Mar Sun>=1 2:00s 0 -
+Rule AW 2006 only - Dec 3 2:00s 1:00 -
+Rule AW 2007 2009 - Mar lastSun 2:00s 0 -
+Rule AW 2007 2008 - Oct lastSun 2:00s 1:00 -
+Zone Australia/Perth 7:43:24 - LMT 1895 Dec
+ 8:00 Aus WST 1943 Jul
+ 8:00 AW WST
+Zone Australia/Eucla 8:35:28 - LMT 1895 Dec
+ 8:45 Aus CWST 1943 Jul
+ 8:45 AW CWST
+
+# Queensland
+#
+# From Alex Livingston (1996-11-01):
+# I have heard or read more than once that some resort islands off the coast
+# of Queensland chose to keep observing daylight-saving time even after
+# Queensland ceased to.
+#
+# From Paul Eggert (1996-11-22):
+# IATA SSIM (1993-02/1994-09) say that the Holiday Islands (Hayman, Lindeman,
+# Hamilton) observed DST for two years after the rest of Queensland stopped.
+# Hamilton is the largest, but there is also a Hamilton in Victoria,
+# so use Lindeman.
+#
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule AQ 1971 only - Oct lastSun 2:00s 1:00 -
+Rule AQ 1972 only - Feb lastSun 2:00s 0 -
+Rule AQ 1989 1991 - Oct lastSun 2:00s 1:00 -
+Rule AQ 1990 1992 - Mar Sun>=1 2:00s 0 -
+Rule Holiday 1992 1993 - Oct lastSun 2:00s 1:00 -
+Rule Holiday 1993 1994 - Mar Sun>=1 2:00s 0 -
+Zone Australia/Brisbane 10:12:08 - LMT 1895
+ 10:00 Aus EST 1971
+ 10:00 AQ EST
+Zone Australia/Lindeman 9:55:56 - LMT 1895
+ 10:00 Aus EST 1971
+ 10:00 AQ EST 1992 Jul
+ 10:00 Holiday EST
+
+# South Australia
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule AS 1971 1985 - Oct lastSun 2:00s 1:00 -
+Rule AS 1986 only - Oct 19 2:00s 1:00 -
+Rule AS 1987 2007 - Oct lastSun 2:00s 1:00 -
+Rule AS 1972 only - Feb 27 2:00s 0 -
+Rule AS 1973 1985 - Mar Sun>=1 2:00s 0 -
+Rule AS 1986 1990 - Mar Sun>=15 2:00s 0 -
+Rule AS 1991 only - Mar 3 2:00s 0 -
+Rule AS 1992 only - Mar 22 2:00s 0 -
+Rule AS 1993 only - Mar 7 2:00s 0 -
+Rule AS 1994 only - Mar 20 2:00s 0 -
+Rule AS 1995 2005 - Mar lastSun 2:00s 0 -
+Rule AS 2006 only - Apr 2 2:00s 0 -
+Rule AS 2007 only - Mar lastSun 2:00s 0 -
+Rule AS 2008 max - Apr Sun>=1 2:00s 0 -
+Rule AS 2008 max - Oct Sun>=1 2:00s 1:00 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Australia/Adelaide 9:14:20 - LMT 1895 Feb
+ 9:00 - CST 1899 May
+ 9:30 Aus CST 1971
+ 9:30 AS CST
+
+# Tasmania
+#
+# From Paul Eggert (2005-08-16):
+# <http://www.bom.gov.au/climate/averages/tables/dst_times.shtml>
+# says King Island didn't observe DST from WWII until late 1971.
+#
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule AT 1967 only - Oct Sun>=1 2:00s 1:00 -
+Rule AT 1968 only - Mar lastSun 2:00s 0 -
+Rule AT 1968 1985 - Oct lastSun 2:00s 1:00 -
+Rule AT 1969 1971 - Mar Sun>=8 2:00s 0 -
+Rule AT 1972 only - Feb lastSun 2:00s 0 -
+Rule AT 1973 1981 - Mar Sun>=1 2:00s 0 -
+Rule AT 1982 1983 - Mar lastSun 2:00s 0 -
+Rule AT 1984 1986 - Mar Sun>=1 2:00s 0 -
+Rule AT 1986 only - Oct Sun>=15 2:00s 1:00 -
+Rule AT 1987 1990 - Mar Sun>=15 2:00s 0 -
+Rule AT 1987 only - Oct Sun>=22 2:00s 1:00 -
+Rule AT 1988 1990 - Oct lastSun 2:00s 1:00 -
+Rule AT 1991 1999 - Oct Sun>=1 2:00s 1:00 -
+Rule AT 1991 2005 - Mar lastSun 2:00s 0 -
+Rule AT 2000 only - Aug lastSun 2:00s 1:00 -
+Rule AT 2001 max - Oct Sun>=1 2:00s 1:00 -
+Rule AT 2006 only - Apr Sun>=1 2:00s 0 -
+Rule AT 2007 only - Mar lastSun 2:00s 0 -
+Rule AT 2008 max - Apr Sun>=1 2:00s 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Australia/Hobart 9:49:16 - LMT 1895 Sep
+ 10:00 - EST 1916 Oct 1 2:00
+ 10:00 1:00 EST 1917 Feb
+ 10:00 Aus EST 1967
+ 10:00 AT EST
+Zone Australia/Currie 9:35:28 - LMT 1895 Sep
+ 10:00 - EST 1916 Oct 1 2:00
+ 10:00 1:00 EST 1917 Feb
+ 10:00 Aus EST 1971 Jul
+ 10:00 AT EST
+
+# Victoria
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule AV 1971 1985 - Oct lastSun 2:00s 1:00 -
+Rule AV 1972 only - Feb lastSun 2:00s 0 -
+Rule AV 1973 1985 - Mar Sun>=1 2:00s 0 -
+Rule AV 1986 1990 - Mar Sun>=15 2:00s 0 -
+Rule AV 1986 1987 - Oct Sun>=15 2:00s 1:00 -
+Rule AV 1988 1999 - Oct lastSun 2:00s 1:00 -
+Rule AV 1991 1994 - Mar Sun>=1 2:00s 0 -
+Rule AV 1995 2005 - Mar lastSun 2:00s 0 -
+Rule AV 2000 only - Aug lastSun 2:00s 1:00 -
+Rule AV 2001 2007 - Oct lastSun 2:00s 1:00 -
+Rule AV 2006 only - Apr Sun>=1 2:00s 0 -
+Rule AV 2007 only - Mar lastSun 2:00s 0 -
+Rule AV 2008 max - Apr Sun>=1 2:00s 0 -
+Rule AV 2008 max - Oct Sun>=1 2:00s 1:00 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Australia/Melbourne 9:39:52 - LMT 1895 Feb
+ 10:00 Aus EST 1971
+ 10:00 AV EST
+
+# New South Wales
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule AN 1971 1985 - Oct lastSun 2:00s 1:00 -
+Rule AN 1972 only - Feb 27 2:00s 0 -
+Rule AN 1973 1981 - Mar Sun>=1 2:00s 0 -
+Rule AN 1982 only - Apr Sun>=1 2:00s 0 -
+Rule AN 1983 1985 - Mar Sun>=1 2:00s 0 -
+Rule AN 1986 1989 - Mar Sun>=15 2:00s 0 -
+Rule AN 1986 only - Oct 19 2:00s 1:00 -
+Rule AN 1987 1999 - Oct lastSun 2:00s 1:00 -
+Rule AN 1990 1995 - Mar Sun>=1 2:00s 0 -
+Rule AN 1996 2005 - Mar lastSun 2:00s 0 -
+Rule AN 2000 only - Aug lastSun 2:00s 1:00 -
+Rule AN 2001 2007 - Oct lastSun 2:00s 1:00 -
+Rule AN 2006 only - Apr Sun>=1 2:00s 0 -
+Rule AN 2007 only - Mar lastSun 2:00s 0 -
+Rule AN 2008 max - Apr Sun>=1 2:00s 0 -
+Rule AN 2008 max - Oct Sun>=1 2:00s 1:00 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Australia/Sydney 10:04:52 - LMT 1895 Feb
+ 10:00 Aus EST 1971
+ 10:00 AN EST
+Zone Australia/Broken_Hill 9:25:48 - LMT 1895 Feb
+ 10:00 - EST 1896 Aug 23
+ 9:00 - CST 1899 May
+ 9:30 Aus CST 1971
+ 9:30 AN CST 2000
+ 9:30 AS CST
+
+# Lord Howe Island
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule LH 1981 1984 - Oct lastSun 2:00 1:00 -
+Rule LH 1982 1985 - Mar Sun>=1 2:00 0 -
+Rule LH 1985 only - Oct lastSun 2:00 0:30 -
+Rule LH 1986 1989 - Mar Sun>=15 2:00 0 -
+Rule LH 1986 only - Oct 19 2:00 0:30 -
+Rule LH 1987 1999 - Oct lastSun 2:00 0:30 -
+Rule LH 1990 1995 - Mar Sun>=1 2:00 0 -
+Rule LH 1996 2005 - Mar lastSun 2:00 0 -
+Rule LH 2000 only - Aug lastSun 2:00 0:30 -
+Rule LH 2001 2007 - Oct lastSun 2:00 0:30 -
+Rule LH 2006 only - Apr Sun>=1 2:00 0 -
+Rule LH 2007 only - Mar lastSun 2:00 0 -
+Rule LH 2008 max - Apr Sun>=1 2:00 0 -
+Rule LH 2008 max - Oct Sun>=1 2:00 0:30 -
+Zone Australia/Lord_Howe 10:36:20 - LMT 1895 Feb
+ 10:00 - EST 1981 Mar
+ 10:30 LH LHST
+
+# Australian miscellany
+#
+# Ashmore Is, Cartier
+# no indigenous inhabitants; only seasonal caretakers
+# no times are set
+#
+# Coral Sea Is
+# no indigenous inhabitants; only meteorologists
+# no times are set
+#
+# Macquarie
+# permanent occupation (scientific station) since 1948;
+# sealing and penguin oil station operated 1888/1917
+# like Australia/Hobart
+
+# Christmas
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Indian/Christmas 7:02:52 - LMT 1895 Feb
+ 7:00 - CXT # Christmas Island Time
+
+# Cook Is
+# From Shanks & Pottenger:
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Cook 1978 only - Nov 12 0:00 0:30 HS
+Rule Cook 1979 1991 - Mar Sun>=1 0:00 0 -
+Rule Cook 1979 1990 - Oct lastSun 0:00 0:30 HS
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Rarotonga -10:39:04 - LMT 1901 # Avarua
+ -10:30 - CKT 1978 Nov 12 # Cook Is Time
+ -10:00 Cook CK%sT
+
+# Cocos
+# These islands were ruled by the Ross family from about 1830 to 1978.
+# We don't know when standard time was introduced; for now, we guess 1900.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Indian/Cocos 6:27:40 - LMT 1900
+ 6:30 - CCT # Cocos Islands Time
+
+# Fiji
+# From Alexander Krivenyshev (2009-11-10):
+# According to Fiji Broadcasting Corporation, Fiji plans to re-introduce DST
+# from November 29th 2009 to April 25th 2010.
+#
+# "Daylight savings to commence this month"
+# <a href="http://www.radiofiji.com.fj/fullstory.php?id=23719">
+# http://www.radiofiji.com.fj/fullstory.php?id=23719
+# </a>
+# or
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_fiji01.html">
+# http://www.worldtimezone.com/dst_news/dst_news_fiji01.html
+# </a>
+
+# From Steffen Thorsen (2009-11-10):
+# The Fiji Government has posted some more details about the approved
+# amendments:
+# <a href="http://www.fiji.gov.fj/publish/page_16198.shtml">
+# http://www.fiji.gov.fj/publish/page_16198.shtml
+# </a>
+
+# From Steffen Thorsen (2010-03-03):
+# The Cabinet in Fiji has decided to end DST about a month early, on
+# 2010-03-28 at 03:00.
+# The plan is to observe DST again, from 2010-10-24 to sometime in March
+# 2011 (last Sunday a good guess?).
+#
+# Official source:
+# <a href="http://www.fiji.gov.fj/index.php?option=com_content&view=article&id=1096:3310-cabinet-approves-change-in-daylight-savings-dates&catid=49:cabinet-releases&Itemid=166">
+# http://www.fiji.gov.fj/index.php?option=com_content&view=article&id=1096:3310-cabinet-approves-change-in-daylight-savings-dates&catid=49:cabinet-releases&Itemid=166
+# </a>
+#
+# A bit more background info here:
+# <a href="http://www.timeanddate.com/news/time/fiji-dst-ends-march-2010.html">
+# http://www.timeanddate.com/news/time/fiji-dst-ends-march-2010.html
+# </a>
+
+# From Alexander Krivenyshev (2010-10-24):
+# According to Radio Fiji and Fiji Times online, Fiji will end DST 3
+# weeks earlier than expected - on March 6, 2011, not March 27, 2011...
+# Here is confirmation from Government of the Republic of the Fiji Islands,
+# Ministry of Information (fiji.gov.fj) web site:
+# <a href="http://www.fiji.gov.fj/index.php?option=com_content&view=article&id=2608:daylight-savings&catid=71:press-releases&Itemid=155">
+# http://www.fiji.gov.fj/index.php?option=com_content&view=article&id=2608:daylight-savings&catid=71:press-releases&Itemid=155
+# </a>
+# or
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_fiji04.html">
+# http://www.worldtimezone.com/dst_news/dst_news_fiji04.html
+# </a>
+
+# From Steffen Thorsen (2011-10-03):
+# Now the dates have been confirmed, and at least our start date
+# assumption was correct (end date was one week wrong).
+#
+# <a href="http://www.fiji.gov.fj/index.php?option=com_content&view=article&id=4966:daylight-saving-starts-in-fiji&catid=71:press-releases&Itemid=155">
+# www.fiji.gov.fj/index.php?option=com_content&view=article&id=4966:daylight-saving-starts-in-fiji&catid=71:press-releases&Itemid=155
+# </a>
+# which says
+# Members of the public are reminded to change their time to one hour in
+# advance at 2am to 3am on October 23, 2011 and one hour back at 3am to
+# 2am on February 26 next year.
+
+# From Ken Rylander (2011-10-24)
+# Another change to the Fiji DST end date. In the TZ database the end date for
+# Fiji DST 2012, is currently Feb 26. This has been changed to Jan 22.
+#
+# <a href="http://www.fiji.gov.fj/index.php?option=com_content&view=article&id=5017:amendments-to-daylight-savings&catid=71:press-releases&Itemid=155">
+# http://www.fiji.gov.fj/index.php?option=com_content&view=article&id=5017:amendments-to-daylight-savings&catid=71:press-releases&Itemid=155
+# </a>
+# states:
+#
+# The end of daylight saving scheduled initially for the 26th of February 2012
+# has been brought forward to the 22nd of January 2012.
+# The commencement of daylight saving will remain unchanged and start
+# on the 23rd of October, 2011.
+
+# From the Fiji Government Online Portal (2012-08-21) via Steffen Thorsen:
+# The Minister for Labour, Industrial Relations and Employment Mr Jone Usamate
+# today confirmed that Fiji will start daylight savings at 2 am on Sunday 21st
+# October 2012 and end at 3 am on Sunday 20th January 2013.
+# http://www.fiji.gov.fj/index.php?option=com_content&view=article&id=6702&catid=71&Itemid=155
+#
+# From Paul Eggert (2012-08-31):
+# For now, guess a pattern of the penultimate Sundays in October and January.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Fiji 1998 1999 - Nov Sun>=1 2:00 1:00 S
+Rule Fiji 1999 2000 - Feb lastSun 3:00 0 -
+Rule Fiji 2009 only - Nov 29 2:00 1:00 S
+Rule Fiji 2010 only - Mar lastSun 3:00 0 -
+Rule Fiji 2010 max - Oct Sun>=18 2:00 1:00 S
+Rule Fiji 2011 only - Mar Sun>=1 3:00 0 -
+Rule Fiji 2012 max - Jan Sun>=18 3:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Fiji 11:53:40 - LMT 1915 Oct 26 # Suva
+ 12:00 Fiji FJ%sT # Fiji Time
+
+# French Polynesia
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Gambier -8:59:48 - LMT 1912 Oct # Rikitea
+ -9:00 - GAMT # Gambier Time
+Zone Pacific/Marquesas -9:18:00 - LMT 1912 Oct
+ -9:30 - MART # Marquesas Time
+Zone Pacific/Tahiti -9:58:16 - LMT 1912 Oct # Papeete
+ -10:00 - TAHT # Tahiti Time
+# Clipperton (near North America) is administered from French Polynesia;
+# it is uninhabited.
+
+# Guam
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Guam -14:21:00 - LMT 1844 Dec 31
+ 9:39:00 - LMT 1901 # Agana
+ 10:00 - GST 2000 Dec 23 # Guam
+ 10:00 - ChST # Chamorro Standard Time
+
+# Kiribati
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Tarawa 11:32:04 - LMT 1901 # Bairiki
+ 12:00 - GILT # Gilbert Is Time
+Zone Pacific/Enderbury -11:24:20 - LMT 1901
+ -12:00 - PHOT 1979 Oct # Phoenix Is Time
+ -11:00 - PHOT 1995
+ 13:00 - PHOT
+Zone Pacific/Kiritimati -10:29:20 - LMT 1901
+ -10:40 - LINT 1979 Oct # Line Is Time
+ -10:00 - LINT 1995
+ 14:00 - LINT
+
+# N Mariana Is
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Saipan -14:17:00 - LMT 1844 Dec 31
+ 9:43:00 - LMT 1901
+ 9:00 - MPT 1969 Oct # N Mariana Is Time
+ 10:00 - MPT 2000 Dec 23
+ 10:00 - ChST # Chamorro Standard Time
+
+# Marshall Is
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Majuro 11:24:48 - LMT 1901
+ 11:00 - MHT 1969 Oct # Marshall Islands Time
+ 12:00 - MHT
+Zone Pacific/Kwajalein 11:09:20 - LMT 1901
+ 11:00 - MHT 1969 Oct
+ -12:00 - KWAT 1993 Aug 20 # Kwajalein Time
+ 12:00 - MHT
+
+# Micronesia
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Chuuk 10:07:08 - LMT 1901
+ 10:00 - CHUT # Chuuk Time
+Zone Pacific/Pohnpei 10:32:52 - LMT 1901 # Kolonia
+ 11:00 - PONT # Pohnpei Time
+Zone Pacific/Kosrae 10:51:56 - LMT 1901
+ 11:00 - KOST 1969 Oct # Kosrae Time
+ 12:00 - KOST 1999
+ 11:00 - KOST
+
+# Nauru
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Nauru 11:07:40 - LMT 1921 Jan 15 # Uaobe
+ 11:30 - NRT 1942 Mar 15 # Nauru Time
+ 9:00 - JST 1944 Aug 15
+ 11:30 - NRT 1979 May
+ 12:00 - NRT
+
+# New Caledonia
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule NC 1977 1978 - Dec Sun>=1 0:00 1:00 S
+Rule NC 1978 1979 - Feb 27 0:00 0 -
+Rule NC 1996 only - Dec 1 2:00s 1:00 S
+# Shanks & Pottenger say the following was at 2:00; go with IATA.
+Rule NC 1997 only - Mar 2 2:00s 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Noumea 11:05:48 - LMT 1912 Jan 13
+ 11:00 NC NC%sT
+
+
+###############################################################################
+
+# New Zealand
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule NZ 1927 only - Nov 6 2:00 1:00 S
+Rule NZ 1928 only - Mar 4 2:00 0 M
+Rule NZ 1928 1933 - Oct Sun>=8 2:00 0:30 S
+Rule NZ 1929 1933 - Mar Sun>=15 2:00 0 M
+Rule NZ 1934 1940 - Apr lastSun 2:00 0 M
+Rule NZ 1934 1940 - Sep lastSun 2:00 0:30 S
+Rule NZ 1946 only - Jan 1 0:00 0 S
+# Since 1957 Chatham has been 45 minutes ahead of NZ, but there's no
+# convenient notation for this so we must duplicate the Rule lines.
+Rule NZ 1974 only - Nov Sun>=1 2:00s 1:00 D
+Rule Chatham 1974 only - Nov Sun>=1 2:45s 1:00 D
+Rule NZ 1975 only - Feb lastSun 2:00s 0 S
+Rule Chatham 1975 only - Feb lastSun 2:45s 0 S
+Rule NZ 1975 1988 - Oct lastSun 2:00s 1:00 D
+Rule Chatham 1975 1988 - Oct lastSun 2:45s 1:00 D
+Rule NZ 1976 1989 - Mar Sun>=1 2:00s 0 S
+Rule Chatham 1976 1989 - Mar Sun>=1 2:45s 0 S
+Rule NZ 1989 only - Oct Sun>=8 2:00s 1:00 D
+Rule Chatham 1989 only - Oct Sun>=8 2:45s 1:00 D
+Rule NZ 1990 2006 - Oct Sun>=1 2:00s 1:00 D
+Rule Chatham 1990 2006 - Oct Sun>=1 2:45s 1:00 D
+Rule NZ 1990 2007 - Mar Sun>=15 2:00s 0 S
+Rule Chatham 1990 2007 - Mar Sun>=15 2:45s 0 S
+Rule NZ 2007 max - Sep lastSun 2:00s 1:00 D
+Rule Chatham 2007 max - Sep lastSun 2:45s 1:00 D
+Rule NZ 2008 max - Apr Sun>=1 2:00s 0 S
+Rule Chatham 2008 max - Apr Sun>=1 2:45s 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Auckland 11:39:04 - LMT 1868 Nov 2
+ 11:30 NZ NZ%sT 1946 Jan 1
+ 12:00 NZ NZ%sT
+Zone Pacific/Chatham 12:13:48 - LMT 1957 Jan 1
+ 12:45 Chatham CHA%sT
+
+
+# Auckland Is
+# uninhabited; Maori and Moriori, colonial settlers, pastoralists, sealers,
+# and scientific personnel have wintered
+
+# Campbell I
+# minor whaling stations operated 1909/1914
+# scientific station operated 1941/1995;
+# previously whalers, sealers, pastoralists, and scientific personnel wintered
+# was probably like Pacific/Auckland
+
+###############################################################################
+
+
+# Niue
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Niue -11:19:40 - LMT 1901 # Alofi
+ -11:20 - NUT 1951 # Niue Time
+ -11:30 - NUT 1978 Oct 1
+ -11:00 - NUT
+
+# Norfolk
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Norfolk 11:11:52 - LMT 1901 # Kingston
+ 11:12 - NMT 1951 # Norfolk Mean Time
+ 11:30 - NFT # Norfolk Time
+
+# Palau (Belau)
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Palau 8:57:56 - LMT 1901 # Koror
+ 9:00 - PWT # Palau Time
+
+# Papua New Guinea
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Port_Moresby 9:48:40 - LMT 1880
+ 9:48:32 - PMMT 1895 # Port Moresby Mean Time
+ 10:00 - PGT # Papua New Guinea Time
+
+# Pitcairn
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Pitcairn -8:40:20 - LMT 1901 # Adamstown
+ -8:30 - PNT 1998 Apr 27 00:00
+ -8:00 - PST # Pitcairn Standard Time
+
+# American Samoa
+Zone Pacific/Pago_Pago 12:37:12 - LMT 1879 Jul 5
+ -11:22:48 - LMT 1911
+ -11:30 - SAMT 1950 # Samoa Time
+ -11:00 - NST 1967 Apr # N=Nome
+ -11:00 - BST 1983 Nov 30 # B=Bering
+ -11:00 - SST # S=Samoa
+
+# Samoa
+
+# From Steffen Thorsen (2009-10-16):
+# We have been in contact with the government of Samoa again, and received
+# the following info:
+#
+# "Cabinet has now approved Daylight Saving to be effected next year
+# commencing from the last Sunday of September 2010 and conclude first
+# Sunday of April 2011."
+#
+# Background info:
+# <a href="http://www.timeanddate.com/news/time/samoa-dst-plan-2009.html">
+# http://www.timeanddate.com/news/time/samoa-dst-plan-2009.html
+# </a>
+#
+# Samoa's Daylight Saving Time Act 2009 is available here, but does not
+# contain any dates:
+# <a href="http://www.parliament.gov.ws/documents/acts/Daylight%20Saving%20Act%20%202009%20%28English%29%20-%20Final%207-7-091.pdf">
+# http://www.parliament.gov.ws/documents/acts/Daylight%20Saving%20Act%20%202009%20%28English%29%20-%20Final%207-7-091.pdf
+# </a>
+
+# From Laupue Raymond Hughes (2010-10-07):
+# Please see
+# <a href="http://www.mcil.gov.ws">
+# http://www.mcil.gov.ws
+# </a>,
+# the Ministry of Commerce, Industry and Labour (sideframe) "Last Sunday
+# September 2010 (26/09/10) - adjust clocks forward from 12:00 midnight
+# to 01:00am and First Sunday April 2011 (03/04/11) - adjust clocks
+# backwards from 1:00am to 12:00am"
+
+# From Laupue Raymond Hughes (2011-03-07):
+# I believe this will be posted shortly on the website
+# <a href="http://www.mcil.gov.ws">
+# www.mcil.gov.ws
+# </a>
+#
+# PUBLIC NOTICE ON DAYLIGHT SAVING TIME
+#
+# Pursuant to the Daylight Saving Act 2009 and Cabinets decision,
+# businesses and the general public are hereby advised that daylight
+# saving time is on the first Saturday of April 2011 (02/04/11).
+#
+# The public is therefore advised that when the standard time strikes
+# the hour of four oclock (4.00am or 0400 Hours) on the 2nd April 2011,
+# then all instruments used to measure standard time are to be
+# adjusted/changed to three oclock (3:00am or 0300Hrs).
+#
+# Margaret Fruean ACTING CHIEF EXECUTIVE OFFICER MINISTRY OF COMMERCE,
+# INDUSTRY AND LABOUR 28th February 2011
+
+# From David Zuelke (2011-05-09):
+# Subject: Samoa to move timezone from east to west of international date line
+#
+# <a href="http://www.morningstar.co.uk/uk/markets/newsfeeditem.aspx?id=138501958347963">
+# http://www.morningstar.co.uk/uk/markets/newsfeeditem.aspx?id=138501958347963
+# </a>
+
+# From Mark Sim-Smith (2011-08-17):
+# I have been in contact with Leilani Tuala Warren from the Samoa Law
+# Reform Commission, and she has sent me a copy of the Bill that she
+# confirmed has been passed...Most of the sections are about maps rather
+# than the time zone change, but I'll paste the relevant bits below. But
+# the essence is that at midnight 29 Dec (UTC-11 I suppose), Samoa
+# changes from UTC-11 to UTC+13:
+#
+# International Date Line Bill 2011
+#
+# AN ACT to provide for the change to standard time in Samoa and to make
+# consequential amendments to the position of the International Date
+# Line, and for related purposes.
+#
+# BE IT ENACTED by the Legislative Assembly of Samoa in Parliament
+# assembled as follows:
+#
+# 1. Short title and commencement-(1) This Act may be cited as the
+# International Date Line Act 2011. (2) Except for section 5(3) this Act
+# commences at 12 o'clock midnight, on Thursday 29th December 2011. (3)
+# Section 5(3) commences on the date of assent by the Head of State.
+#
+# [snip]
+#
+# 3. Interpretation - [snip] "Samoa standard time" in this Act and any
+# other statute of Samoa which refers to 'Samoa standard time' means the
+# time 13 hours in advance of Co-ordinated Universal Time.
+#
+# 4. Samoa standard time - (1) Upon the commencement of this Act, Samoa
+# standard time shall be set at 13 hours in advance of Co-ordinated
+# Universal Time for the whole of Samoa. (2) All references to Samoa's
+# time zone and to Samoa standard time in Samoa in all legislation and
+# instruments after the commencement of this Act shall be references to
+# Samoa standard time as provided for in this Act. (3) Nothing in this
+# Act affects the provisions of the Daylight Saving Act 2009, except that
+# it defines Samoa standard time....
+
+# From Laupue Raymond Hughes (2011-09-02):
+# <a href="http://www.mcil.gov.ws/mcil_publications.html">
+# http://www.mcil.gov.ws/mcil_publications.html
+# </a>
+#
+# here is the official website publication for Samoa DST and dateline change
+#
+# DST
+# Year End Time Start Time
+# 2011 - - - - - - 24 September 3:00am to 4:00am
+# 2012 01 April 4:00am to 3:00am - - - - - -
+#
+# Dateline Change skip Friday 30th Dec 2011
+# Thursday 29th December 2011 23:59:59 Hours
+# Saturday 31st December 2011 00:00:00 Hours
+#
+# Clarification by Tim Parenti (2012-01-03):
+# Although Samoa has used Daylight Saving Time in the 2010-2011 and 2011-2012
+# seasons, there is not yet any indication that this trend will continue on
+# a regular basis. For now, we have explicitly listed the transitions below.
+#
+# From Nicky (2012-09-10):
+# Daylight Saving Time commences on Sunday 30th September 2012 and
+# ends on Sunday 7th of April 2013.
+#
+# Please find link below for more information.
+# http://www.mcil.gov.ws/mcil_publications.html
+#
+# That publication also includes dates for Summer of 2013/4 as well
+# which give the impression of a pattern in selecting dates for the
+# future, so for now, we will guess this will continue.
+
+# Western Samoa
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule WS 2012 max - Sep lastSun 3:00 1 D
+Rule WS 2012 max - Apr Sun>=1 4:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Apia 12:33:04 - LMT 1879 Jul 5
+ -11:26:56 - LMT 1911
+ -11:30 - SAMT 1950 # Samoa Time
+ -11:00 - WST 2010 Sep 26
+ -11:00 1:00 WSDT 2011 Apr 2 4:00
+ -11:00 - WST 2011 Sep 24 3:00
+ -11:00 1:00 WSDT 2011 Dec 30
+ 13:00 1:00 WSDT 2012 Apr Sun>=1 4:00
+ 13:00 WS WS%sT
+
+# Solomon Is
+# excludes Bougainville, for which see Papua New Guinea
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Guadalcanal 10:39:48 - LMT 1912 Oct # Honiara
+ 11:00 - SBT # Solomon Is Time
+
+# Tokelau Is
+#
+# From Gwillim Law (2011-12-29)
+# A correspondent informed me that Tokelau, like Samoa, will be skipping
+# December 31 this year ...
+#
+# From Steffen Thorsen (2012-07-25)
+# ... we double checked by calling hotels and offices based in Tokelau asking
+# about the time there, and they all told a time that agrees with UTC+13....
+# Shanks says UTC-10 from 1901 [but] ... there is a good chance the change
+# actually was to UTC-11 back then.
+#
+# From Paul Eggert (2012-07-25)
+# A Google Books snippet of Appendix to the Journals of the House of
+# Representatives of New Zealand, Session 1948,
+# <http://books.google.com/books?id=ZaVCAQAAIAAJ>, page 65, says Tokelau
+# was "11 hours slow on G.M.T." Go with Thorsen and assume Shanks & Pottenger
+# are off by an hour starting in 1901.
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Fakaofo -11:24:56 - LMT 1901
+ -11:00 - TKT 2011 Dec 30 # Tokelau Time
+ 13:00 - TKT
+
+# Tonga
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Tonga 1999 only - Oct 7 2:00s 1:00 S
+Rule Tonga 2000 only - Mar 19 2:00s 0 -
+Rule Tonga 2000 2001 - Nov Sun>=1 2:00 1:00 S
+Rule Tonga 2001 2002 - Jan lastSun 2:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Tongatapu 12:19:20 - LMT 1901
+ 12:20 - TOT 1941 # Tonga Time
+ 13:00 - TOT 1999
+ 13:00 Tonga TO%sT
+
+# Tuvalu
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Funafuti 11:56:52 - LMT 1901
+ 12:00 - TVT # Tuvalu Time
+
+
+# US minor outlying islands
+
+# Howland, Baker
+# Howland was mined for guano by American companies 1857-1878 and British
+# 1886-1891; Baker was similar but exact dates are not known.
+# Inhabited by civilians 1935-1942; U.S. military bases 1943-1944;
+# uninhabited thereafter.
+# Howland observed Hawaii Standard Time (UTC-10:30) in 1937;
+# see page 206 of Elgen M. Long and Marie K. Long,
+# Amelia Earhart: the Mystery Solved, Simon & Schuster (2000).
+# So most likely Howland and Baker observed Hawaii Time from 1935
+# until they were abandoned after the war.
+
+# Jarvis
+# Mined for guano by American companies 1857-1879 and British 1883?-1891?.
+# Inhabited by civilians 1935-1942; IGY scientific base 1957-1958;
+# uninhabited thereafter.
+# no information; was probably like Pacific/Kiritimati
+
+# Johnston
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Johnston -10:00 - HST
+
+# Kingman
+# uninhabited
+
+# Midway
+#
+# From Mark Brader (2005-01-23):
+# [Fallacies and Fantasies of Air Transport History, by R.E.G. Davies,
+# published 1994 by Paladwr Press, McLean, VA, USA; ISBN 0-9626483-5-3]
+# reproduced a Pan American Airways timeables from 1936, for their weekly
+# "Orient Express" flights between San Francisco and Manila, and connecting
+# flights to Chicago and the US East Coast. As it uses some time zone
+# designations that I've never seen before:....
+# Fri. 6:30A Lv. HONOLOLU (Pearl Harbor), H.I. H.L.T. Ar. 5:30P Sun.
+# " 3:00P Ar. MIDWAY ISLAND . . . . . . . . . M.L.T. Lv. 6:00A "
+#
+Zone Pacific/Midway -11:49:28 - LMT 1901
+ -11:00 - NST 1956 Jun 3
+ -11:00 1:00 NDT 1956 Sep 2
+ -11:00 - NST 1967 Apr # N=Nome
+ -11:00 - BST 1983 Nov 30 # B=Bering
+ -11:00 - SST # S=Samoa
+
+# Palmyra
+# uninhabited since World War II; was probably like Pacific/Kiritimati
+
+# Wake
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Wake 11:06:28 - LMT 1901
+ 12:00 - WAKT # Wake Time
+
+
+# Vanuatu
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Vanuatu 1983 only - Sep 25 0:00 1:00 S
+Rule Vanuatu 1984 1991 - Mar Sun>=23 0:00 0 -
+Rule Vanuatu 1984 only - Oct 23 0:00 1:00 S
+Rule Vanuatu 1985 1991 - Sep Sun>=23 0:00 1:00 S
+Rule Vanuatu 1992 1993 - Jan Sun>=23 0:00 0 -
+Rule Vanuatu 1992 only - Oct Sun>=23 0:00 1:00 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Efate 11:13:16 - LMT 1912 Jan 13 # Vila
+ 11:00 Vanuatu VU%sT # Vanuatu Time
+
+# Wallis and Futuna
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Pacific/Wallis 12:15:20 - LMT 1901
+ 12:00 - WFT # Wallis & Futuna Time
+
+###############################################################################
+
+# NOTES
+
+# This data is by no means authoritative; if you think you know better,
+# go ahead and edit the file (and please send any changes to
+# tz@iana.org for general use in the future).
+
+# From Paul Eggert (2006-03-22):
+# A good source for time zone historical data outside the U.S. is
+# Thomas G. Shanks and Rique Pottenger, The International Atlas (6th edition),
+# San Diego: ACS Publications, Inc. (2003).
+#
+# Gwillim Law writes that a good source
+# for recent time zone data is the International Air Transport
+# Association's Standard Schedules Information Manual (IATA SSIM),
+# published semiannually. Law sent in several helpful summaries
+# of the IATA's data after 1990.
+#
+# Except where otherwise noted, Shanks & Pottenger is the source for
+# entries through 1990, and IATA SSIM is the source for entries afterwards.
+#
+# Another source occasionally used is Edward W. Whitman, World Time Differences,
+# Whitman Publishing Co, 2 Niagara Av, Ealing, London (undated), which
+# I found in the UCLA library.
+#
+# A reliable and entertaining source about time zones is
+# Derek Howse, Greenwich time and longitude, Philip Wilson Publishers (1997).
+#
+# I invented the abbreviations marked `*' in the following table;
+# the rest are from earlier versions of this file, or from other sources.
+# Corrections are welcome!
+# std dst
+# LMT Local Mean Time
+# 8:00 WST WST Western Australia
+# 8:45 CWST CWST Central Western Australia*
+# 9:00 JST Japan
+# 9:30 CST CST Central Australia
+# 10:00 EST EST Eastern Australia
+# 10:00 ChST Chamorro
+# 10:30 LHST LHST Lord Howe*
+# 11:30 NZMT NZST New Zealand through 1945
+# 12:00 NZST NZDT New Zealand 1946-present
+# 12:45 CHAST CHADT Chatham*
+# -11:00 SST Samoa
+# -10:00 HST Hawaii
+# - 8:00 PST Pitcairn*
+#
+# See the `northamerica' file for Hawaii.
+# See the `southamerica' file for Easter I and the Galapagos Is.
+
+###############################################################################
+
+# Australia
+
+# From Paul Eggert (2005-12-08):
+# <a href="http://www.bom.gov.au/climate/averages/tables/dst_times.shtml">
+# Implementation Dates of Daylight Saving Time within Australia
+# </a> summarizes daylight saving issues in Australia.
+
+# From Arthur David Olson (2005-12-12):
+# <a href="http://www.lawlink.nsw.gov.au/lawlink/Corporate/ll_agdinfo.nsf/pages/community_relations_daylight_saving">
+# Lawlink NSW:Daylight Saving in New South Wales
+# </a> covers New South Wales in particular.
+
+# From John Mackin (1991-03-06):
+# We in Australia have _never_ referred to DST as `daylight' time.
+# It is called `summer' time. Now by a happy coincidence, `summer'
+# and `standard' happen to start with the same letter; hence, the
+# abbreviation does _not_ change...
+# The legislation does not actually define abbreviations, at least
+# in this State, but the abbreviation is just commonly taken to be the
+# initials of the phrase, and the legislation here uniformly uses
+# the phrase `summer time' and does not use the phrase `daylight
+# time'.
+# Announcers on the Commonwealth radio network, the ABC (for Australian
+# Broadcasting Commission), use the phrases `Eastern Standard Time'
+# or `Eastern Summer Time'. (Note, though, that as I say in the
+# current australasia file, there is really no such thing.) Announcers
+# on its overseas service, Radio Australia, use the same phrases
+# prefixed by the word `Australian' when referring to local times;
+# time announcements on that service, naturally enough, are made in UTC.
+
+# From Arthur David Olson (1992-03-08):
+# Given the above, what's chosen for year-round use is:
+# CST for any place operating at a GMTOFF of 9:30
+# WST for any place operating at a GMTOFF of 8:00
+# EST for any place operating at a GMTOFF of 10:00
+
+# From Chuck Soper (2006-06-01):
+# I recently found this Australian government web page on time zones:
+# <http://www.australia.gov.au/about-australia-13time>
+# And this government web page lists time zone names and abbreviations:
+# <http://www.bom.gov.au/climate/averages/tables/daysavtm.shtml>
+
+# From Paul Eggert (2001-04-05), summarizing a long discussion about "EST"
+# versus "AEST" etc.:
+#
+# I see the following points of dispute:
+#
+# * How important are unique time zone abbreviations?
+#
+# Here I tend to agree with the point (most recently made by Chris
+# Newman) that unique abbreviations should not be essential for proper
+# operation of software. We have other instances of ambiguity
+# (e.g. "IST" denoting both "Israel Standard Time" and "Indian
+# Standard Time"), and they are not likely to go away any time soon.
+# In the old days, some software mistakenly relied on unique
+# abbreviations, but this is becoming less true with time, and I don't
+# think it's that important to cater to such software these days.
+#
+# On the other hand, there is another motivation for unambiguous
+# abbreviations: it cuts down on human confusion. This is
+# particularly true for Australia, where "EST" can mean one thing for
+# time T and a different thing for time T plus 1 second.
+#
+# * Does the relevant legislation indicate which abbreviations should be used?
+#
+# Here I tend to think that things are a mess, just as they are in
+# many other countries. We Americans are currently disagreeing about
+# which abbreviation to use for the newly legislated Chamorro Standard
+# Time, for example.
+#
+# Personally, I would prefer to use common practice; I would like to
+# refer to legislation only for examples of common practice, or as a
+# tiebreaker.
+#
+# * Do Australians more often use "Eastern Daylight Time" or "Eastern
+# Summer Time"? Do they typically prefix the time zone names with
+# the word "Australian"?
+#
+# My own impression is that both "Daylight Time" and "Summer Time" are
+# common and are widely understood, but that "Summer Time" is more
+# popular; and that the leading "A" is also common but is omitted more
+# often than not. I just used AltaVista advanced search and got the
+# following count of page hits:
+#
+# 1,103 "Eastern Summer Time" AND domain:au
+# 971 "Australian Eastern Summer Time" AND domain:au
+# 613 "Eastern Daylight Time" AND domain:au
+# 127 "Australian Eastern Daylight Time" AND domain:au
+#
+# Here "Summer" seems quite a bit more popular than "Daylight",
+# particularly when we know the time zone is Australian and not US,
+# say. The "Australian" prefix seems to be popular for Eastern Summer
+# Time, but unpopular for Eastern Daylight Time.
+#
+# For abbreviations, tools like AltaVista are less useful because of
+# ambiguity. Many hits are not really time zones, unfortunately, and
+# many hits denote US time zones and not Australian ones. But here
+# are the hit counts anyway:
+#
+# 161,304 "EST" and domain:au
+# 25,156 "EDT" and domain:au
+# 18,263 "AEST" and domain:au
+# 10,416 "AEDT" and domain:au
+#
+# 14,538 "CST" and domain:au
+# 5,728 "CDT" and domain:au
+# 176 "ACST" and domain:au
+# 29 "ACDT" and domain:au
+#
+# 7,539 "WST" and domain:au
+# 68 "AWST" and domain:au
+#
+# This data suggest that Australians tend to omit the "A" prefix in
+# practice. The situation for "ST" versus "DT" is less clear, given
+# the ambiguities involved.
+#
+# * How do Australians feel about the abbreviations in the tz database?
+#
+# If you just count Australians on this list, I count 2 in favor and 3
+# against. One of the "against" votes (David Keegel) counseled delay,
+# saying that both AEST/AEDT and EST/EST are widely used and
+# understood in Australia.
+
+# From Paul Eggert (1995-12-19):
+# Shanks & Pottenger report 2:00 for all autumn changes in Australia and NZ.
+# Mark Prior writes that his newspaper
+# reports that NSW's fall 1995 change will occur at 2:00,
+# but Robert Elz says it's been 3:00 in Victoria since 1970
+# and perhaps the newspaper's `2:00' is referring to standard time.
+# For now we'll continue to assume 2:00s for changes since 1960.
+
+# From Eric Ulevik (1998-01-05):
+#
+# Here are some URLs to Australian time legislation. These URLs are stable,
+# and should probably be included in the data file. There are probably more
+# relevant entries in this database.
+#
+# NSW (including LHI and Broken Hill):
+# <a href="http://www.austlii.edu.au/au/legis/nsw/consol_act/sta1987137/index.html">
+# Standard Time Act 1987 (updated 1995-04-04)
+# </a>
+# ACT
+# <a href="http://www.austlii.edu.au/au/legis/act/consol_act/stasta1972279/index.html">
+# Standard Time and Summer Time Act 1972
+# </a>
+# SA
+# <a href="http://www.austlii.edu.au/au/legis/sa/consol_act/sta1898137/index.html">
+# Standard Time Act, 1898
+# </a>
+
+# From David Grosz (2005-06-13):
+# It was announced last week that Daylight Saving would be extended by
+# one week next year to allow for the 2006 Commonwealth Games.
+# Daylight Saving is now to end for next year only on the first Sunday
+# in April instead of the last Sunday in March.
+#
+# From Gwillim Law (2005-06-14):
+# I did some Googling and found that all of those states (and territory) plan
+# to extend DST together in 2006.
+# ACT: http://www.cmd.act.gov.au/mediareleases/fileread.cfm?file=86.txt
+# New South Wales: http://www.thecouriermail.news.com.au/common/story_page/0,5936,15538869%255E1702,00.html
+# South Australia: http://www.news.com.au/story/0,10117,15555031-1246,00.html
+# Tasmania: http://www.media.tas.gov.au/release.php?id=14772
+# Victoria: I wasn't able to find anything separate, but the other articles
+# allude to it.
+# But not Queensland
+# http://www.news.com.au/story/0,10117,15564030-1248,00.html.
+
+# Northern Territory
+
+# From George Shepherd via Simon Woodhead via Robert Elz (1991-03-06):
+# # The NORTHERN TERRITORY.. [ Courtesy N.T. Dept of the Chief Minister ]
+# # [ Nov 1990 ]
+# # N.T. have never utilised any DST due to sub-tropical/tropical location.
+# ...
+# Zone Australia/North 9:30 - CST
+
+# From Bradley White (1991-03-04):
+# A recent excerpt from an Australian newspaper...
+# the Northern Territory do[es] not have daylight saving.
+
+# Western Australia
+
+# From George Shepherd via Simon Woodhead via Robert Elz (1991-03-06):
+# # The state of WESTERN AUSTRALIA.. [ Courtesy W.A. dept Premier+Cabinet ]
+# # [ Nov 1990 ]
+# # W.A. suffers from a great deal of public and political opposition to
+# # DST in principle. A bill is brought before parliament in most years, but
+# # usually defeated either in the upper house, or in party caucus
+# # before reaching parliament.
+# ...
+# Zone Australia/West 8:00 AW %sST
+# ...
+# Rule AW 1974 only - Oct lastSun 2:00 1:00 D
+# Rule AW 1975 only - Mar Sun>=1 3:00 0 W
+# Rule AW 1983 only - Oct lastSun 2:00 1:00 D
+# Rule AW 1984 only - Mar Sun>=1 3:00 0 W
+
+# From Bradley White (1991-03-04):
+# A recent excerpt from an Australian newspaper...
+# Western Australia...do[es] not have daylight saving.
+
+# From John D. Newman via Bradley White (1991-11-02):
+# Western Australia is still on "winter time". Some DH in Sydney
+# rang me at home a few days ago at 6.00am. (He had just arrived at
+# work at 9.00am.)
+# W.A. is switching to Summer Time on Nov 17th just to confuse
+# everybody again.
+
+# From Arthur David Olson (1992-03-08):
+# The 1992 ending date used in the rules is a best guess;
+# it matches what was used in the past.
+
+# <a href="http://www.bom.gov.au/faq/faqgen.htm">
+# The Australian Bureau of Meteorology FAQ
+# </a> (1999-09-27) writes that Giles Meteorological Station uses
+# South Australian time even though it's located in Western Australia.
+
+# Queensland
+# From George Shepherd via Simon Woodhead via Robert Elz (1991-03-06):
+# # The state of QUEENSLAND.. [ Courtesy Qld. Dept Premier Econ&Trade Devel ]
+# # [ Dec 1990 ]
+# ...
+# Zone Australia/Queensland 10:00 AQ %sST
+# ...
+# Rule AQ 1971 only - Oct lastSun 2:00 1:00 D
+# Rule AQ 1972 only - Feb lastSun 3:00 0 E
+# Rule AQ 1989 max - Oct lastSun 2:00 1:00 D
+# Rule AQ 1990 max - Mar Sun>=1 3:00 0 E
+
+# From Bradley White (1989-12-24):
+# "Australia/Queensland" now observes daylight time (i.e. from
+# October 1989).
+
+# From Bradley White (1991-03-04):
+# A recent excerpt from an Australian newspaper...
+# ...Queensland...[has] agreed to end daylight saving
+# at 3am tomorrow (March 3)...
+
+# From John Mackin (1991-03-06):
+# I can certainly confirm for my part that Daylight Saving in NSW did in fact
+# end on Sunday, 3 March. I don't know at what hour, though. (It surprised
+# me.)
+
+# From Bradley White (1992-03-08):
+# ...there was recently a referendum in Queensland which resulted
+# in the experimental daylight saving system being abandoned. So, ...
+# ...
+# Rule QLD 1989 1991 - Oct lastSun 2:00 1:00 D
+# Rule QLD 1990 1992 - Mar Sun>=1 3:00 0 S
+# ...
+
+# From Arthur David Olson (1992-03-08):
+# The chosen rules the union of the 1971/1972 change and the 1989-1992 changes.
+
+# From Christopher Hunt (2006-11-21), after an advance warning
+# from Jesper Norgaard Welen (2006-11-01):
+# WA are trialing DST for three years.
+# <http://www.parliament.wa.gov.au/parliament/bills.nsf/9A1B183144403DA54825721200088DF1/$File/Bill175-1B.pdf>
+
+# From Rives McDow (2002-04-09):
+# The most interesting region I have found consists of three towns on the
+# southern coast.... South Australia observes daylight saving time; Western
+# Australia does not. The two states are one and a half hours apart. The
+# residents decided to forget about this nonsense of changing the clock so
+# much and set the local time 20 hours and 45 minutes from the
+# international date line, or right in the middle of the time of South
+# Australia and Western Australia....
+#
+# From Paul Eggert (2002-04-09):
+# This is confirmed by the section entitled
+# "What's the deal with time zones???" in
+# <http://www.earthsci.unimelb.edu.au/~awatkins/null.html>.
+#
+# From Alex Livingston (2006-12-07):
+# ... it was just on four years ago that I drove along the Eyre Highway,
+# which passes through eastern Western Australia close to the southern
+# coast of the continent.
+#
+# I paid particular attention to the time kept there. There can be no
+# dispute that UTC+08:45 was considered "the time" from the border
+# village just inside the border with South Australia to as far west
+# as just east of Caiguna. There can also be no dispute that Eucla is
+# the largest population centre in this zone....
+#
+# Now that Western Australia is observing daylight saving, the
+# question arose whether this part of the state would follow suit. I
+# just called the border village and confirmed that indeed they have,
+# meaning that they are now observing UTC+09:45.
+#
+# (2006-12-09):
+# I personally doubt that either experimentation with daylight saving
+# in WA or its introduction in SA had anything to do with the genesis
+# of this time zone. My hunch is that it's been around since well
+# before 1975. I remember seeing it noted on road maps decades ago.
+
+# From Paul Eggert (2006-12-15):
+# For lack of better info, assume the tradition dates back to the
+# introduction of standard time in 1895.
+
+
+# southeast Australia
+#
+# From Paul Eggert (2007-07-23):
+# Starting autumn 2008 Victoria, NSW, South Australia, Tasmania and the ACT
+# end DST the first Sunday in April and start DST the first Sunday in October.
+# http://www.theage.com.au/news/national/daylight-savings-to-span-six-months/2007/06/27/1182623966703.html
+
+
+# South Australia
+
+# From Bradley White (1991-03-04):
+# A recent excerpt from an Australian newspaper...
+# ...South Australia...[has] agreed to end daylight saving
+# at 3am tomorrow (March 3)...
+
+# From George Shepherd via Simon Woodhead via Robert Elz (1991-03-06):
+# # The state of SOUTH AUSTRALIA....[ Courtesy of S.A. Dept of Labour ]
+# # [ Nov 1990 ]
+# ...
+# Zone Australia/South 9:30 AS %sST
+# ...
+# Rule AS 1971 max - Oct lastSun 2:00 1:00 D
+# Rule AS 1972 1985 - Mar Sun>=1 3:00 0 C
+# Rule AS 1986 1990 - Mar Sun>=15 3:00 0 C
+# Rule AS 1991 max - Mar Sun>=1 3:00 0 C
+
+# From Bradley White (1992-03-11):
+# Recent correspondence with a friend in Adelaide
+# contained the following exchange: "Due to the Adelaide Festival,
+# South Australia delays setting back our clocks for a few weeks."
+
+# From Robert Elz (1992-03-13):
+# I heard that apparently (or at least, it appears that)
+# South Aus will have an extra 3 weeks daylight saving every even
+# numbered year (from 1990). That's when the Adelaide Festival
+# is on...
+
+# From Robert Elz (1992-03-16, 00:57:07 +1000):
+# DST didn't end in Adelaide today (yesterday)....
+# But whether it's "4th Sunday" or "2nd last Sunday" I have no idea whatever...
+# (it's just as likely to be "the Sunday we pick for this year"...).
+
+# From Bradley White (1994-04-11):
+# If Sun, 15 March, 1992 was at +1030 as kre asserts, but yet Sun, 20 March,
+# 1994 was at +0930 as John Connolly's customer seems to assert, then I can
+# only conclude that the actual rule is more complicated....
+
+# From John Warburton (1994-10-07):
+# The new Daylight Savings dates for South Australia ...
+# was gazetted in the Government Hansard on Sep 26 1994....
+# start on last Sunday in October and end in last sunday in March.
+
+# From Paul Eggert (2007-07-23):
+# See "southeast Australia" above for 2008 and later.
+
+# Tasmania
+
+# The rules for 1967 through 1991 were reported by George Shepherd
+# via Simon Woodhead via Robert Elz (1991-03-06):
+# # The state of TASMANIA.. [Courtesy Tasmanian Dept of Premier + Cabinet ]
+# # [ Nov 1990 ]
+
+# From Bill Hart via Guy Harris (1991-10-10):
+# Oh yes, the new daylight savings rules are uniquely tasmanian, we have
+# 6 weeks a year now when we are out of sync with the rest of Australia
+# (but nothing new about that).
+
+# From Alex Livingston (1999-10-04):
+# I heard on the ABC (Australian Broadcasting Corporation) radio news on the
+# (long) weekend that Tasmania, which usually goes its own way in this regard,
+# has decided to join with most of NSW, the ACT, and most of Victoria
+# (Australia) and start daylight saving on the last Sunday in August in 2000
+# instead of the first Sunday in October.
+
+# Sim Alam (2000-07-03) reported a legal citation for the 2000/2001 rules:
+# http://www.thelaw.tas.gov.au/fragview/42++1968+GS3A@EN+2000070300
+
+# From Paul Eggert (2007-07-23):
+# See "southeast Australia" above for 2008 and later.
+
+# Victoria
+
+# The rules for 1971 through 1991 were reported by George Shepherd
+# via Simon Woodhead via Robert Elz (1991-03-06):
+# # The state of VICTORIA.. [ Courtesy of Vic. Dept of Premier + Cabinet ]
+# # [ Nov 1990 ]
+
+# From Scott Harrington (2001-08-29):
+# On KQED's "City Arts and Lectures" program last night I heard an
+# interesting story about daylight savings time. Dr. John Heilbron was
+# discussing his book "The Sun in the Church: Cathedrals as Solar
+# Observatories"[1], and in particular the Shrine of Remembrance[2] located
+# in Melbourne, Australia.
+#
+# Apparently the shrine's main purpose is a beam of sunlight which
+# illuminates a special spot on the floor at the 11th hour of the 11th day
+# of the 11th month (Remembrance Day) every year in memory of Australia's
+# fallen WWI soldiers. And if you go there on Nov. 11, at 11am local time,
+# you will indeed see the sunbeam illuminate the special spot at the
+# expected time.
+#
+# However, that is only because of some special mirror contraption that had
+# to be employed, since due to daylight savings time, the true solar time of
+# the remembrance moment occurs one hour later (or earlier?). Perhaps
+# someone with more information on this jury-rig can tell us more.
+#
+# [1] http://www.hup.harvard.edu/catalog/HEISUN.html
+# [2] http://www.shrine.org.au
+
+# From Paul Eggert (2007-07-23):
+# See "southeast Australia" above for 2008 and later.
+
+# New South Wales
+
+# From Arthur David Olson:
+# New South Wales and subjurisdictions have their own ideas of a fun time.
+# Based on law library research by John Mackin,
+# who notes:
+# In Australia, time is not legislated federally, but rather by the
+# individual states. Thus, while such terms as ``Eastern Standard Time''
+# [I mean, of course, Australian EST, not any other kind] are in common
+# use, _they have NO REAL MEANING_, as they are not defined in the
+# legislation. This is very important to understand.
+# I have researched New South Wales time only...
+
+# From Eric Ulevik (1999-05-26):
+# DST will start in NSW on the last Sunday of August, rather than the usual
+# October in 2000. [See: Matthew Moore,
+# <a href="http://www.smh.com.au/news/9905/26/pageone/pageone4.html">
+# Two months more daylight saving
+# </a>
+# Sydney Morning Herald (1999-05-26).]
+
+# From Paul Eggert (1999-09-27):
+# See the following official NSW source:
+# <a href="http://dir.gis.nsw.gov.au/cgi-bin/genobject/document/other/daylightsaving/tigGmZ">
+# Daylight Saving in New South Wales.
+# </a>
+#
+# Narrabri Shire (NSW) council has announced it will ignore the extension of
+# daylight saving next year. See:
+# <a href="http://abc.net.au/news/regionals/neweng/monthly/regeng-22jul1999-1.htm">
+# Narrabri Council to ignore daylight saving
+# </a> (1999-07-22). For now, we'll wait to see if this really happens.
+#
+# Victoria will following NSW. See:
+# <a href="http://abc.net.au/local/news/olympics/1999/07/item19990728112314_1.htm">
+# Vic to extend daylight saving
+# </a> (1999-07-28).
+#
+# However, South Australia rejected the DST request. See:
+# <a href="http://abc.net.au/news/olympics/1999/07/item19990719151754_1.htm">
+# South Australia rejects Olympics daylight savings request
+# </a> (1999-07-19).
+#
+# Queensland also will not observe DST for the Olympics. See:
+# <a href="http://abc.net.au/news/olympics/1999/06/item19990601114608_1.htm">
+# Qld says no to daylight savings for Olympics
+# </a> (1999-06-01), which quotes Queensland Premier Peter Beattie as saying
+# ``Look you've got to remember in my family when this came up last time
+# I voted for it, my wife voted against it and she said to me it's all very
+# well for you, you don't have to worry about getting the children out of
+# bed, getting them to school, getting them to sleep at night.
+# I've been through all this argument domestically...my wife rules.''
+#
+# Broken Hill will stick with South Australian time in 2000. See:
+# <a href="http://abc.net.au/news/regionals/brokenh/monthly/regbrok-21jul1999-6.htm">
+# Broken Hill to be behind the times
+# </a> (1999-07-21).
+
+# IATA SSIM (1998-09) says that the spring 2000 change for Australian
+# Capital Territory, New South Wales except Lord Howe Island and Broken
+# Hill, and Victoria will be August 27, presumably due to the Sydney Olympics.
+
+# From Eric Ulevik, referring to Sydney's Sun Herald (2000-08-13), page 29:
+# The Queensland Premier Peter Beattie is encouraging northern NSW
+# towns to use Queensland time.
+
+# From Paul Eggert (2007-07-23):
+# See "southeast Australia" above for 2008 and later.
+
+# Yancowinna
+
+# From John Mackin (1989-01-04):
+# `Broken Hill' means the County of Yancowinna.
+
+# From George Shepherd via Simon Woodhead via Robert Elz (1991-03-06):
+# # YANCOWINNA.. [ Confirmation courtesy of Broken Hill Postmaster ]
+# # [ Dec 1990 ]
+# ...
+# # Yancowinna uses Central Standard Time, despite [its] location on the
+# # New South Wales side of the S.A. border. Most business and social dealings
+# # are with CST zones, therefore CST is legislated by local government
+# # although the switch to Summer Time occurs in line with N.S.W. There have
+# # been years when this did not apply, but the historical data is not
+# # presently available.
+# Zone Australia/Yancowinna 9:30 AY %sST
+# ...
+# Rule AY 1971 1985 - Oct lastSun 2:00 1:00 D
+# Rule AY 1972 only - Feb lastSun 3:00 0 C
+# [followed by other Rules]
+
+# Lord Howe Island
+
+# From George Shepherd via Simon Woodhead via Robert Elz (1991-03-06):
+# LHI... [ Courtesy of Pauline Van Winsen ]
+# [ Dec 1990 ]
+# Lord Howe Island is located off the New South Wales coast, and is half an
+# hour ahead of NSW time.
+
+# From James Lonergan, Secretary, Lord Howe Island Board (2000-01-27):
+# Lord Howe Island summer time in 2000/2001 will commence on the same
+# date as the rest of NSW (i.e. 2000-08-27). For your information the
+# Lord Howe Island Board (controlling authority for the Island) is
+# seeking the community's views on various options for summer time
+# arrangements on the Island, e.g. advance clocks by 1 full hour
+# instead of only 30 minutes. [Dependent] on the wishes of residents
+# the Board may approach the NSW government to change the existing
+# arrangements. The starting date for summer time on the Island will
+# however always coincide with the rest of NSW.
+
+# From James Lonergan, Secretary, Lord Howe Island Board (2000-10-25):
+# Lord Howe Island advances clocks by 30 minutes during DST in NSW and retards
+# clocks by 30 minutes when DST finishes. Since DST was most recently
+# introduced in NSW, the "changeover" time on the Island has been 02:00 as
+# shown on clocks on LHI. I guess this means that for 30 minutes at the start
+# of DST, LHI is actually 1 hour ahead of the rest of NSW.
+
+# From Paul Eggert (2006-03-22):
+# For Lord Howe dates we use Shanks & Pottenger through 1989, and
+# Lonergan thereafter. For times we use Lonergan.
+
+# From Paul Eggert (2007-07-23):
+# See "southeast Australia" above for 2008 and later.
+
+# From Steffen Thorsen (2009-04-28):
+# According to the official press release, South Australia's extended daylight
+# saving period will continue with the same rules as used during the 2008-2009
+# summer (southern hemisphere).
+#
+# From
+# <a href="http://www.safework.sa.gov.au/uploaded_files/DaylightDatesSet.pdf">
+# http://www.safework.sa.gov.au/uploaded_files/DaylightDatesSet.pdf
+# </a>
+# The extended daylight saving period that South Australia has been trialling
+# for over the last year is now set to be ongoing.
+# Daylight saving will continue to start on the first Sunday in October each
+# year and finish on the first Sunday in April the following year.
+# Industrial Relations Minister, Paul Caica, says this provides South Australia
+# with a consistent half hour time difference with NSW, Victoria, Tasmania and
+# the ACT for all 52 weeks of the year...
+#
+# We have a wrap-up here:
+# <a href="http://www.timeanddate.com/news/time/south-australia-extends-dst.html">
+# http://www.timeanddate.com/news/time/south-australia-extends-dst.html
+# </a>
+###############################################################################
+
+# New Zealand
+
+# From Mark Davies (1990-10-03):
+# the 1989/90 year was a trial of an extended "daylight saving" period.
+# This trial was deemed successful and the extended period adopted for
+# subsequent years (with the addition of a further week at the start).
+# source -- phone call to Ministry of Internal Affairs Head Office.
+
+# From George Shepherd via Simon Woodhead via Robert Elz (1991-03-06):
+# # The Country of New Zealand (Australia's east island -) Gee they hate that!
+# # or is Australia the west island of N.Z.
+# # [ courtesy of Geoff Tribble.. Auckland N.Z. ]
+# # [ Nov 1990 ]
+# ...
+# Rule NZ 1974 1988 - Oct lastSun 2:00 1:00 D
+# Rule NZ 1989 max - Oct Sun>=1 2:00 1:00 D
+# Rule NZ 1975 1989 - Mar Sun>=1 3:00 0 S
+# Rule NZ 1990 max - Mar lastSun 3:00 0 S
+# ...
+# Zone NZ 12:00 NZ NZ%sT # New Zealand
+# Zone NZ-CHAT 12:45 - NZ-CHAT # Chatham Island
+
+# From Arthur David Olson (1992-03-08):
+# The chosen rules use the Davies October 8 values for the start of DST in 1989
+# rather than the October 1 value.
+
+# From Paul Eggert (1995-12-19);
+# Shank & Pottenger report 2:00 for all autumn changes in Australia and NZ.
+# Robert Uzgalis writes that the New Zealand Daylight
+# Savings Time Order in Council dated 1990-06-18 specifies 2:00 standard
+# time on both the first Sunday in October and the third Sunday in March.
+# As with Australia, we'll assume the tradition is 2:00s, not 2:00.
+#
+# From Paul Eggert (2006-03-22):
+# The Department of Internal Affairs (DIA) maintains a brief history,
+# as does Carol Squires; see tz-link.htm for the full references.
+# Use these sources in preference to Shanks & Pottenger.
+#
+# For Chatham, IATA SSIM (1991/1999) gives the NZ rules but with
+# transitions at 2:45 local standard time; this confirms that Chatham
+# is always exactly 45 minutes ahead of Auckland.
+
+# From Colin Sharples (2007-04-30):
+# DST will now start on the last Sunday in September, and end on the
+# first Sunday in April. The changes take effect this year, meaning
+# that DST will begin on 2007-09-30 2008-04-06.
+# http://www.dia.govt.nz/diawebsite.nsf/wpg_URL/Services-Daylight-Saving-Daylight-saving-to-be-extended
+
+###############################################################################
+
+
+# Fiji
+
+# Howse writes (p 153) that in 1879 the British governor of Fiji
+# enacted an ordinance standardizing the islands on Antipodean Time
+# instead of the American system (which was one day behind).
+
+# From Rives McDow (1998-10-08):
+# Fiji will introduce DST effective 0200 local time, 1998-11-01
+# until 0300 local time 1999-02-28. Each year the DST period will
+# be from the first Sunday in November until the last Sunday in February.
+
+# From Paul Eggert (2000-01-08):
+# IATA SSIM (1999-09) says DST ends 0100 local time. Go with McDow.
+
+# From the BBC World Service (1998-10-31 11:32 UTC):
+# The Fijiian government says the main reasons for the time change is to
+# improve productivity and reduce road accidents. But correspondents say it
+# also hopes the move will boost Fiji's ability to compete with other pacific
+# islands in the effort to attract tourists to witness the dawning of the new
+# millenium.
+
+# http://www.fiji.gov.fj/press/2000_09/2000_09_13-05.shtml (2000-09-13)
+# reports that Fiji has discontinued DST.
+
+# Johnston
+
+# Johnston data is from usno1995.
+
+
+# Kiribati
+
+# From Paul Eggert (1996-01-22):
+# Today's _Wall Street Journal_ (page 1) reports that Kiribati
+# ``declared it the same day [throughout] the country as of Jan. 1, 1995''
+# as part of the competition to be first into the 21st century.
+
+
+# Kwajalein
+
+# In comp.risks 14.87 (26 August 1993), Peter Neumann writes:
+# I wonder what happened in Kwajalein, where there was NO Friday,
+# 1993-08-20. Thursday night at midnight Kwajalein switched sides with
+# respect to the International Date Line, to rejoin its fellow islands,
+# going from 11:59 p.m. Thursday to 12:00 m. Saturday in a blink.
+
+
+# N Mariana Is, Guam
+
+# Howse writes (p 153) ``The Spaniards, on the other hand, reached the
+# Philippines and the Ladrones from America,'' and implies that the Ladrones
+# (now called the Marianas) kept American date for quite some time.
+# For now, we assume the Ladrones switched at the same time as the Philippines;
+# see Asia/Manila.
+
+# US Public Law 106-564 (2000-12-23) made UTC+10 the official standard time,
+# under the name "Chamorro Standard Time". There is no official abbreviation,
+# but Congressman Robert A. Underwood, author of the bill that became law,
+# wrote in a press release (2000-12-27) that he will seek the use of "ChST".
+
+
+# Micronesia
+
+# Alan Eugene Davis writes (1996-03-16),
+# ``I am certain, having lived there for the past decade, that "Truk"
+# (now properly known as Chuuk) ... is in the time zone GMT+10.''
+#
+# Shanks & Pottenger write that Truk switched from UTC+10 to UTC+11
+# on 1978-10-01; ignore this for now.
+
+# From Paul Eggert (1999-10-29):
+# The Federated States of Micronesia Visitors Board writes in
+# <a href="http://www.fsmgov.org/info/clocks.html">
+# The Federated States of Micronesia - Visitor Information
+# </a> (1999-01-26)
+# that Truk and Yap are UTC+10, and Ponape and Kosrae are UTC+11.
+# We don't know when Kosrae switched from UTC+12; assume January 1 for now.
+
+
+# Midway
+
+# From Charles T O'Connor, KMTH DJ (1956),
+# quoted in the KTMH section of the Radio Heritage Collection
+# <http://radiodx.com/spdxr/KMTH.htm> (2002-12-31):
+# For the past two months we've been on what is known as Daylight
+# Saving Time. This time has put us on air at 5am in the morning,
+# your time down there in New Zealand. Starting September 2, 1956
+# we'll again go back to Standard Time. This'll mean that we'll go to
+# air at 6am your time.
+#
+# From Paul Eggert (2003-03-23):
+# We don't know the date of that quote, but we'll guess they
+# started DST on June 3. Possibly DST was observed other years
+# in Midway, but we have no record of it.
+
+
+# Pitcairn
+
+# From Rives McDow (1999-11-08):
+# A Proclamation was signed by the Governor of Pitcairn on the 27th March 1998
+# with regard to Pitcairn Standard Time. The Proclamation is as follows.
+#
+# The local time for general purposes in the Islands shall be
+# Co-ordinated Universal time minus 8 hours and shall be known
+# as Pitcairn Standard Time.
+#
+# ... I have also seen Pitcairn listed as UTC minus 9 hours in several
+# references, and can only assume that this was an error in interpretation
+# somehow in light of this proclamation.
+
+# From Rives McDow (1999-11-09):
+# The Proclamation regarding Pitcairn time came into effect on 27 April 1998
+# ... at midnight.
+
+# From Howie Phelps (1999-11-10), who talked to a Pitcairner via shortwave:
+# Betty Christian told me yesterday that their local time is the same as
+# Pacific Standard Time. They used to be 1/2 hour different from us here in
+# Sacramento but it was changed a couple of years ago.
+
+
+# Samoa
+
+# Howse writes (p 153, citing p 10 of the 1883-11-18 New York Herald)
+# that in 1879 the King of Samoa decided to change
+# ``the date in his kingdom from the Antipodean to the American system,
+# ordaining -- by a masterpiece of diplomatic flattery -- that
+# the Fourth of July should be celebrated twice in that year.''
+
+
+# Tonga
+
+# From Paul Eggert (1996-01-22):
+# Today's _Wall Street Journal_ (p 1) reports that ``Tonga has been plotting
+# to sneak ahead of [New Zealanders] by introducing daylight-saving time.''
+# Since Kiribati has moved the Date Line it's not clear what Tonga will do.
+
+# Don Mundell writes in the 1997-02-20 Tonga Chronicle
+# <a href="http://www.tongatapu.net.to/tonga/homeland/timebegins.htm">
+# How Tonga became `The Land where Time Begins'
+# </a>:
+
+# Until 1941 Tonga maintained a standard time 50 minutes ahead of NZST
+# 12 hours and 20 minutes ahead of GMT. When New Zealand adjusted its
+# standard time in 1940s, Tonga had the choice of subtracting from its
+# local time to come on the same standard time as New Zealand or of
+# advancing its time to maintain the differential of 13 degrees
+# (approximately 50 minutes ahead of New Zealand time).
+#
+# Because His Majesty King Taufa'ahau Tupou IV, then Crown Prince
+# Tungi, preferred to ensure Tonga's title as the land where time
+# begins, the Legislative Assembly approved the latter change.
+#
+# But some of the older, more conservative members from the outer
+# islands objected. "If at midnight on Dec. 31, we move ahead 40
+# minutes, as your Royal Highness wishes, what becomes of the 40
+# minutes we have lost?"
+#
+# The Crown Prince, presented an unanswerable argument: "Remember that
+# on the World Day of Prayer, you would be the first people on Earth
+# to say your prayers in the morning."
+
+# From Paul Eggert (2006-03-22):
+# Shanks & Pottenger say the transition was on 1968-10-01; go with Mundell.
+
+# From Eric Ulevik (1999-05-03):
+# Tonga's director of tourism, who is also secretary of the National Millenium
+# Committee, has a plan to get Tonga back in front.
+# He has proposed a one-off move to tropical daylight saving for Tonga from
+# October to March, which has won approval in principle from the Tongan
+# Government.
+
+# From Steffen Thorsen (1999-09-09):
+# * Tonga will introduce DST in November
+#
+# I was given this link by John Letts:
+# <a href="http://news.bbc.co.uk/hi/english/world/asia-pacific/newsid_424000/424764.stm">
+# http://news.bbc.co.uk/hi/english/world/asia-pacific/newsid_424000/424764.stm
+# </a>
+#
+# I have not been able to find exact dates for the transition in November
+# yet. By reading this article it seems like Fiji will be 14 hours ahead
+# of UTC as well, but as far as I know Fiji will only be 13 hours ahead
+# (12 + 1 hour DST).
+
+# From Arthur David Olson (1999-09-20):
+# According to <a href="http://www.tongaonline.com/news/sept1799.html">
+# http://www.tongaonline.com/news/sept1799.html
+# </a>:
+# "Daylight Savings Time will take effect on Oct. 2 through April 15, 2000
+# and annually thereafter from the first Saturday in October through the
+# third Saturday of April. Under the system approved by Privy Council on
+# Sept. 10, clocks must be turned ahead one hour on the opening day and
+# set back an hour on the closing date."
+# Alas, no indication of the time of day.
+
+# From Rives McDow (1999-10-06):
+# Tonga started its Daylight Saving on Saturday morning October 2nd at 0200am.
+# Daylight Saving ends on April 16 at 0300am which is Sunday morning.
+
+# From Steffen Thorsen (2000-10-31):
+# Back in March I found a notice on the website http://www.tongaonline.com
+# that Tonga changed back to standard time one month early, on March 19
+# instead of the original reported date April 16. Unfortunately, the article
+# is no longer available on the site, and I did not make a copy of the
+# text, and I have forgotten to report it here.
+# (Original URL was: http://www.tongaonline.com/news/march162000.htm )
+
+# From Rives McDow (2000-12-01):
+# Tonga is observing DST as of 2000-11-04 and will stop on 2001-01-27.
+
+# From Sione Moala-Mafi (2001-09-20) via Rives McDow:
+# At 2:00am on the first Sunday of November, the standard time in the Kingdom
+# shall be moved forward by one hour to 3:00am. At 2:00am on the last Sunday
+# of January the standard time in the Kingdom shall be moved backward by one
+# hour to 1:00am.
+
+# From Pulu 'Anau (2002-11-05):
+# The law was for 3 years, supposedly to get renewed. It wasn't.
+
+
+# Wake
+
+# From Vernice Anderson, Personal Secretary to Philip Jessup,
+# US Ambassador At Large (oral history interview, 1971-02-02):
+#
+# Saturday, the 14th [of October, 1950] -- ... The time was all the
+# more confusing at that point, because we had crossed the
+# International Date Line, thus getting two Sundays. Furthermore, we
+# discovered that Wake Island had two hours of daylight saving time
+# making calculation of time in Washington difficult if not almost
+# impossible.
+#
+# http://www.trumanlibrary.org/wake/meeting.htm
+
+# From Paul Eggert (2003-03-23):
+# We have no other report of DST in Wake Island, so omit this info for now.
+
+###############################################################################
+
+# The International Date Line
+
+# From Gwillim Law (2000-01-03):
+#
+# The International Date Line is not defined by any international standard,
+# convention, or treaty. Mapmakers are free to draw it as they please.
+# Reputable mapmakers will simply ensure that every point of land appears on
+# the correct side of the IDL, according to the date legally observed there.
+#
+# When Kiribati adopted a uniform date in 1995, thereby moving the Phoenix and
+# Line Islands to the west side of the IDL (or, if you prefer, moving the IDL
+# to the east side of the Phoenix and Line Islands), I suppose that most
+# mapmakers redrew the IDL following the boundary of Kiribati. Even that line
+# has a rather arbitrary nature. The straight-line boundaries between Pacific
+# island nations that are shown on many maps are based on an international
+# convention, but are not legally binding national borders.... The date is
+# governed by the IDL; therefore, even on the high seas, there may be some
+# places as late as fourteen hours later than UTC. And, since the IDL is not
+# an international standard, there are some places on the high seas where the
+# correct date is ambiguous.
+
+# From Wikipedia <http://en.wikipedia.org/wiki/Time_zone> (2005-08-31):
+# Before 1920, all ships kept local apparent time on the high seas by setting
+# their clocks at night or at the morning sight so that, given the ship's
+# speed and direction, it would be 12 o'clock when the Sun crossed the ship's
+# meridian (12 o'clock = local apparent noon). During 1917, at the
+# Anglo-French Conference on Time-keeping at Sea, it was recommended that all
+# ships, both military and civilian, should adopt hourly standard time zones
+# on the high seas. Whenever a ship was within the territorial waters of any
+# nation it would use that nation's standard time. The captain was permitted
+# to change his ship's clocks at a time of his choice following his ship's
+# entry into another zone time--he often chose midnight. These zones were
+# adopted by all major fleets between 1920 and 1925 but not by many
+# independent merchant ships until World War II.
+
+# From Paul Eggert, using references suggested by Oscar van Vlijmen
+# (2005-03-20):
+#
+# The American Practical Navigator (2002)
+# <http://pollux.nss.nima.mil/pubs/pubs_j_apn_sections.html?rid=187>
+# talks only about the 180-degree meridian with respect to ships in
+# international waters; it ignores the international date line.
diff --git a/misc/flot/examples/axes-time-zones/tz/backward b/misc/flot/examples/axes-time-zones/tz/backward
new file mode 100644
index 0000000..dc7769f
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/backward
@@ -0,0 +1,117 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# This file provides links between current names for time zones
+# and their old names. Many names changed in late 1993.
+
+Link Africa/Asmara Africa/Asmera
+Link Africa/Bamako Africa/Timbuktu
+Link America/Argentina/Catamarca America/Argentina/ComodRivadavia
+Link America/Adak America/Atka
+Link America/Argentina/Buenos_Aires America/Buenos_Aires
+Link America/Argentina/Catamarca America/Catamarca
+Link America/Atikokan America/Coral_Harbour
+Link America/Argentina/Cordoba America/Cordoba
+Link America/Tijuana America/Ensenada
+Link America/Indiana/Indianapolis America/Fort_Wayne
+Link America/Indiana/Indianapolis America/Indianapolis
+Link America/Argentina/Jujuy America/Jujuy
+Link America/Indiana/Knox America/Knox_IN
+Link America/Kentucky/Louisville America/Louisville
+Link America/Argentina/Mendoza America/Mendoza
+Link America/Rio_Branco America/Porto_Acre
+Link America/Argentina/Cordoba America/Rosario
+Link America/St_Thomas America/Virgin
+Link Asia/Ashgabat Asia/Ashkhabad
+Link Asia/Chongqing Asia/Chungking
+Link Asia/Dhaka Asia/Dacca
+Link Asia/Kathmandu Asia/Katmandu
+Link Asia/Kolkata Asia/Calcutta
+Link Asia/Macau Asia/Macao
+Link Asia/Jerusalem Asia/Tel_Aviv
+Link Asia/Ho_Chi_Minh Asia/Saigon
+Link Asia/Thimphu Asia/Thimbu
+Link Asia/Makassar Asia/Ujung_Pandang
+Link Asia/Ulaanbaatar Asia/Ulan_Bator
+Link Atlantic/Faroe Atlantic/Faeroe
+Link Europe/Oslo Atlantic/Jan_Mayen
+Link Australia/Sydney Australia/ACT
+Link Australia/Sydney Australia/Canberra
+Link Australia/Lord_Howe Australia/LHI
+Link Australia/Sydney Australia/NSW
+Link Australia/Darwin Australia/North
+Link Australia/Brisbane Australia/Queensland
+Link Australia/Adelaide Australia/South
+Link Australia/Hobart Australia/Tasmania
+Link Australia/Melbourne Australia/Victoria
+Link Australia/Perth Australia/West
+Link Australia/Broken_Hill Australia/Yancowinna
+Link America/Rio_Branco Brazil/Acre
+Link America/Noronha Brazil/DeNoronha
+Link America/Sao_Paulo Brazil/East
+Link America/Manaus Brazil/West
+Link America/Halifax Canada/Atlantic
+Link America/Winnipeg Canada/Central
+Link America/Regina Canada/East-Saskatchewan
+Link America/Toronto Canada/Eastern
+Link America/Edmonton Canada/Mountain
+Link America/St_Johns Canada/Newfoundland
+Link America/Vancouver Canada/Pacific
+Link America/Regina Canada/Saskatchewan
+Link America/Whitehorse Canada/Yukon
+Link America/Santiago Chile/Continental
+Link Pacific/Easter Chile/EasterIsland
+Link America/Havana Cuba
+Link Africa/Cairo Egypt
+Link Europe/Dublin Eire
+Link Europe/London Europe/Belfast
+Link Europe/Chisinau Europe/Tiraspol
+Link Europe/London GB
+Link Europe/London GB-Eire
+Link Etc/GMT GMT+0
+Link Etc/GMT GMT-0
+Link Etc/GMT GMT0
+Link Etc/GMT Greenwich
+Link Asia/Hong_Kong Hongkong
+Link Atlantic/Reykjavik Iceland
+Link Asia/Tehran Iran
+Link Asia/Jerusalem Israel
+Link America/Jamaica Jamaica
+Link Asia/Tokyo Japan
+Link Pacific/Kwajalein Kwajalein
+Link Africa/Tripoli Libya
+Link America/Tijuana Mexico/BajaNorte
+Link America/Mazatlan Mexico/BajaSur
+Link America/Mexico_City Mexico/General
+Link Pacific/Auckland NZ
+Link Pacific/Chatham NZ-CHAT
+Link America/Denver Navajo
+Link Asia/Shanghai PRC
+Link Pacific/Pago_Pago Pacific/Samoa
+Link Pacific/Chuuk Pacific/Yap
+Link Pacific/Chuuk Pacific/Truk
+Link Pacific/Pohnpei Pacific/Ponape
+Link Europe/Warsaw Poland
+Link Europe/Lisbon Portugal
+Link Asia/Taipei ROC
+Link Asia/Seoul ROK
+Link Asia/Singapore Singapore
+Link Europe/Istanbul Turkey
+Link Etc/UCT UCT
+Link America/Anchorage US/Alaska
+Link America/Adak US/Aleutian
+Link America/Phoenix US/Arizona
+Link America/Chicago US/Central
+Link America/Indiana/Indianapolis US/East-Indiana
+Link America/New_York US/Eastern
+Link Pacific/Honolulu US/Hawaii
+Link America/Indiana/Knox US/Indiana-Starke
+Link America/Detroit US/Michigan
+Link America/Denver US/Mountain
+Link America/Los_Angeles US/Pacific
+Link Pacific/Pago_Pago US/Samoa
+Link Etc/UTC UTC
+Link Etc/UTC Universal
+Link Europe/Moscow W-SU
+Link Etc/UTC Zulu
diff --git a/misc/flot/examples/axes-time-zones/tz/etcetera b/misc/flot/examples/axes-time-zones/tz/etcetera
new file mode 100644
index 0000000..a9ff729
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/etcetera
@@ -0,0 +1,81 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# These entries are mostly present for historical reasons, so that
+# people in areas not otherwise covered by the tz files could "zic -l"
+# to a time zone that was right for their area. These days, the
+# tz files cover almost all the inhabited world, and the only practical
+# need now for the entries that are not on UTC are for ships at sea
+# that cannot use POSIX TZ settings.
+
+Zone Etc/GMT 0 - GMT
+Zone Etc/UTC 0 - UTC
+Zone Etc/UCT 0 - UCT
+
+# The following link uses older naming conventions,
+# but it belongs here, not in the file `backward',
+# as functions like gmtime load the "GMT" file to handle leap seconds properly.
+# We want this to work even on installations that omit the other older names.
+Link Etc/GMT GMT
+
+Link Etc/UTC Etc/Universal
+Link Etc/UTC Etc/Zulu
+
+Link Etc/GMT Etc/Greenwich
+Link Etc/GMT Etc/GMT-0
+Link Etc/GMT Etc/GMT+0
+Link Etc/GMT Etc/GMT0
+
+# We use POSIX-style signs in the Zone names and the output abbreviations,
+# even though this is the opposite of what many people expect.
+# POSIX has positive signs west of Greenwich, but many people expect
+# positive signs east of Greenwich. For example, TZ='Etc/GMT+4' uses
+# the abbreviation "GMT+4" and corresponds to 4 hours behind UTC
+# (i.e. west of Greenwich) even though many people would expect it to
+# mean 4 hours ahead of UTC (i.e. east of Greenwich).
+#
+# In the draft 5 of POSIX 1003.1-200x, the angle bracket notation allows for
+# TZ='<GMT-4>+4'; if you want time zone abbreviations conforming to
+# ISO 8601 you can use TZ='<-0400>+4'. Thus the commonly-expected
+# offset is kept within the angle bracket (and is used for display)
+# while the POSIX sign is kept outside the angle bracket (and is used
+# for calculation).
+#
+# Do not use a TZ setting like TZ='GMT+4', which is four hours behind
+# GMT but uses the completely misleading abbreviation "GMT".
+
+# Earlier incarnations of this package were not POSIX-compliant,
+# and had lines such as
+# Zone GMT-12 -12 - GMT-1200
+# We did not want things to change quietly if someone accustomed to the old
+# way does a
+# zic -l GMT-12
+# so we moved the names into the Etc subdirectory.
+
+Zone Etc/GMT-14 14 - GMT-14 # 14 hours ahead of GMT
+Zone Etc/GMT-13 13 - GMT-13
+Zone Etc/GMT-12 12 - GMT-12
+Zone Etc/GMT-11 11 - GMT-11
+Zone Etc/GMT-10 10 - GMT-10
+Zone Etc/GMT-9 9 - GMT-9
+Zone Etc/GMT-8 8 - GMT-8
+Zone Etc/GMT-7 7 - GMT-7
+Zone Etc/GMT-6 6 - GMT-6
+Zone Etc/GMT-5 5 - GMT-5
+Zone Etc/GMT-4 4 - GMT-4
+Zone Etc/GMT-3 3 - GMT-3
+Zone Etc/GMT-2 2 - GMT-2
+Zone Etc/GMT-1 1 - GMT-1
+Zone Etc/GMT+1 -1 - GMT+1
+Zone Etc/GMT+2 -2 - GMT+2
+Zone Etc/GMT+3 -3 - GMT+3
+Zone Etc/GMT+4 -4 - GMT+4
+Zone Etc/GMT+5 -5 - GMT+5
+Zone Etc/GMT+6 -6 - GMT+6
+Zone Etc/GMT+7 -7 - GMT+7
+Zone Etc/GMT+8 -8 - GMT+8
+Zone Etc/GMT+9 -9 - GMT+9
+Zone Etc/GMT+10 -10 - GMT+10
+Zone Etc/GMT+11 -11 - GMT+11
+Zone Etc/GMT+12 -12 - GMT+12
diff --git a/misc/flot/examples/axes-time-zones/tz/europe b/misc/flot/examples/axes-time-zones/tz/europe
new file mode 100644
index 0000000..ad9816c
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/europe
@@ -0,0 +1,2856 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# This data is by no means authoritative; if you think you know better,
+# go ahead and edit the file (and please send any changes to
+# tz@iana.org for general use in the future).
+
+# From Paul Eggert (2006-03-22):
+# A good source for time zone historical data outside the U.S. is
+# Thomas G. Shanks and Rique Pottenger, The International Atlas (6th edition),
+# San Diego: ACS Publications, Inc. (2003).
+#
+# Gwillim Law writes that a good source
+# for recent time zone data is the International Air Transport
+# Association's Standard Schedules Information Manual (IATA SSIM),
+# published semiannually. Law sent in several helpful summaries
+# of the IATA's data after 1990.
+#
+# Except where otherwise noted, Shanks & Pottenger is the source for
+# entries through 1991, and IATA SSIM is the source for entries afterwards.
+#
+# Other sources occasionally used include:
+#
+# Edward W. Whitman, World Time Differences,
+# Whitman Publishing Co, 2 Niagara Av, Ealing, London (undated),
+# which I found in the UCLA library.
+#
+# <a href="http://www.pettswoodvillage.co.uk/Daylight_Savings_William_Willett.pdf">
+# William Willett, The Waste of Daylight, 19th edition
+# </a> (1914-03)
+#
+# Brazil's Departamento Servico da Hora (DSH),
+# <a href="http://pcdsh01.on.br/HISTHV.htm">
+# History of Summer Time
+# </a> (1998-09-21, in Portuguese)
+
+#
+# I invented the abbreviations marked `*' in the following table;
+# the rest are from earlier versions of this file, or from other sources.
+# Corrections are welcome!
+# std dst 2dst
+# LMT Local Mean Time
+# -4:00 AST ADT Atlantic
+# -3:00 WGT WGST Western Greenland*
+# -1:00 EGT EGST Eastern Greenland*
+# 0:00 GMT BST BDST Greenwich, British Summer
+# 0:00 GMT IST Greenwich, Irish Summer
+# 0:00 WET WEST WEMT Western Europe
+# 0:19:32.13 AMT NST Amsterdam, Netherlands Summer (1835-1937)*
+# 0:20 NET NEST Netherlands (1937-1940)*
+# 1:00 CET CEST CEMT Central Europe
+# 1:00:14 SET Swedish (1879-1899)*
+# 2:00 EET EEST Eastern Europe
+# 3:00 MSK MSD Moscow
+#
+# A reliable and entertaining source about time zones, especially in Britain,
+# Derek Howse, Greenwich time and longitude, Philip Wilson Publishers (1997).
+
+# From Peter Ilieve (1994-12-04),
+# The original six [EU members]: Belgium, France, (West) Germany, Italy,
+# Luxembourg, the Netherlands.
+# Plus, from 1 Jan 73: Denmark, Ireland, United Kingdom.
+# Plus, from 1 Jan 81: Greece.
+# Plus, from 1 Jan 86: Spain, Portugal.
+# Plus, from 1 Jan 95: Austria, Finland, Sweden. (Norway negotiated terms for
+# entry but in a referendum on 28 Nov 94 the people voted No by 52.2% to 47.8%
+# on a turnout of 88.6%. This was almost the same result as Norway's previous
+# referendum in 1972, they are the only country to have said No twice.
+# Referendums in the other three countries voted Yes.)
+# ...
+# Estonia ... uses EU dates but not at 01:00 GMT, they use midnight GMT.
+# I don't think they know yet what they will do from 1996 onwards.
+# ...
+# There shouldn't be any [current members who are not using EU rules].
+# A Directive has the force of law, member states are obliged to enact
+# national law to implement it. The only contentious issue was the
+# different end date for the UK and Ireland, and this was always allowed
+# in the Directive.
+
+
+###############################################################################
+
+# Britain (United Kingdom) and Ireland (Eire)
+
+# From Peter Ilieve (1994-07-06):
+#
+# On 17 Jan 1994 the Independent, a UK quality newspaper, had a piece about
+# historical vistas along the Thames in west London. There was a photo
+# and a sketch map showing some of the sightlines involved. One paragraph
+# of the text said:
+#
+# `An old stone obelisk marking a forgotten terrestrial meridian stands
+# beside the river at Kew. In the 18th century, before time and longitude
+# was standardised by the Royal Observatory in Greenwich, scholars observed
+# this stone and the movement of stars from Kew Observatory nearby. They
+# made their calculations and set the time for the Horse Guards and Parliament,
+# but now the stone is obscured by scrubwood and can only be seen by walking
+# along the towpath within a few yards of it.'
+#
+# I have a one inch to one mile map of London and my estimate of the stone's
+# position is 51 deg. 28' 30" N, 0 deg. 18' 45" W. The longitude should
+# be within about +-2". The Ordnance Survey grid reference is TQ172761.
+#
+# [This yields GMTOFF = -0:01:15 for London LMT in the 18th century.]
+
+# From Paul Eggert (1993-11-18):
+#
+# Howse writes that Britain was the first country to use standard time.
+# The railways cared most about the inconsistencies of local mean time,
+# and it was they who forced a uniform time on the country.
+# The original idea was credited to Dr. William Hyde Wollaston (1766-1828)
+# and was popularized by Abraham Follett Osler (1808-1903).
+# The first railway to adopt London time was the Great Western Railway
+# in November 1840; other railways followed suit, and by 1847 most
+# (though not all) railways used London time. On 1847-09-22 the
+# Railway Clearing House, an industry standards body, recommended that GMT be
+# adopted at all stations as soon as the General Post Office permitted it.
+# The transition occurred on 12-01 for the L&NW, the Caledonian,
+# and presumably other railways; the January 1848 Bradshaw's lists many
+# railways as using GMT. By 1855 the vast majority of public
+# clocks in Britain were set to GMT (though some, like the great clock
+# on Tom Tower at Christ Church, Oxford, were fitted with two minute hands,
+# one for local time and one for GMT). The last major holdout was the legal
+# system, which stubbornly stuck to local time for many years, leading
+# to oddities like polls opening at 08:13 and closing at 16:13.
+# The legal system finally switched to GMT when the Statutes (Definition
+# of Time) Act took effect; it received the Royal Assent on 1880-08-02.
+#
+# In the tables below, we condense this complicated story into a single
+# transition date for London, namely 1847-12-01. We don't know as much
+# about Dublin, so we use 1880-08-02, the legal transition time.
+
+# From Paul Eggert (2003-09-27):
+# Summer Time was first seriously proposed by William Willett (1857-1915),
+# a London builder and member of the Royal Astronomical Society
+# who circulated a pamphlet ``The Waste of Daylight'' (1907)
+# that proposed advancing clocks 20 minutes on each of four Sundays in April,
+# and retarding them by the same amount on four Sundays in September.
+# A bill was drafted in 1909 and introduced in Parliament several times,
+# but it met with ridicule and opposition, especially from farming interests.
+# Later editions of the pamphlet proposed one-hour summer time, and
+# it was eventually adopted as a wartime measure in 1916.
+# See: Summer Time Arrives Early, The Times (2000-05-18).
+# A monument to Willett was unveiled on 1927-05-21, in an open space in
+# a 45-acre wood near Chislehurst, Kent that was purchased by popular
+# subscription and open to the public. On the south face of the monolith,
+# designed by G. W. Miller, is the...William Willett Memorial Sundial,
+# which is permanently set to Summer Time.
+
+# From Winston Churchill (1934-04-28):
+# It is one of the paradoxes of history that we should owe the boon of
+# summer time, which gives every year to the people of this country
+# between 160 and 170 hours more daylight leisure, to a war which
+# plunged Europe into darkness for four years, and shook the
+# foundations of civilization throughout the world.
+# -- <a href="http://www.winstonchurchill.org/fh114willett.htm">
+# "A Silent Toast to William Willett", Pictorial Weekly
+# </a>
+
+# From Paul Eggert (1996-09-03):
+# The OED Supplement says that the English originally said ``Daylight Saving''
+# when they were debating the adoption of DST in 1908; but by 1916 this
+# term appears only in quotes taken from DST's opponents, whereas the
+# proponents (who eventually won the argument) are quoted as using ``Summer''.
+
+# From Arthur David Olson (1989-01-19):
+#
+# A source at the British Information Office in New York avers that it's
+# known as "British" Summer Time in all parts of the United Kingdom.
+
+# Date: 4 Jan 89 08:57:25 GMT (Wed)
+# From: Jonathan Leffler
+# [British Summer Time] is fixed annually by Act of Parliament.
+# If you can predict what Parliament will do, you should be in
+# politics making a fortune, not computing.
+
+# From Chris Carrier (1996-06-14):
+# I remember reading in various wartime issues of the London Times the
+# acronym BDST for British Double Summer Time. Look for the published
+# time of sunrise and sunset in The Times, when BDST was in effect, and
+# if you find a zone reference it will say, "All times B.D.S.T."
+
+# From Joseph S. Myers (1999-09-02):
+# ... some military cables (WO 219/4100 - this is a copy from the
+# main SHAEF archives held in the US National Archives, SHAEF/5252/8/516)
+# agree that the usage is BDST (this appears in a message dated 17 Feb 1945).
+
+# From Joseph S. Myers (2000-10-03):
+# On 18th April 1941, Sir Stephen Tallents of the BBC wrote to Sir
+# Alexander Maxwell of the Home Office asking whether there was any
+# official designation; the reply of the 21st was that there wasn't
+# but he couldn't think of anything better than the "Double British
+# Summer Time" that the BBC had been using informally.
+# http://student.cusu.cam.ac.uk/~jsm28/british-time/bbc-19410418.png
+# http://student.cusu.cam.ac.uk/~jsm28/british-time/ho-19410421.png
+
+# From Sir Alexander Maxwell in the above-mentioned letter (1941-04-21):
+# [N]o official designation has as far as I know been adopted for the time
+# which is to be introduced in May....
+# I cannot think of anything better than "Double British Summer Time"
+# which could not be said to run counter to any official description.
+
+# From Paul Eggert (2000-10-02):
+# Howse writes (p 157) `DBST' too, but `BDST' seems to have been common
+# and follows the more usual convention of putting the location name first,
+# so we use `BDST'.
+
+# Peter Ilieve (1998-04-19) described at length
+# the history of summer time legislation in the United Kingdom.
+# Since 1998 Joseph S. Myers has been updating
+# and extending this list, which can be found in
+# http://student.cusu.cam.ac.uk/~jsm28/british-time/
+# <a href="http://www.polyomino.org.uk/british-time/">
+# History of legal time in Britain
+# </a>
+# Rob Crowther (2012-01-04) reports that that URL no longer
+# exists, and the article can now be found at:
+# <a href="http://www.polyomino.org.uk/british-time/">
+# http://www.polyomino.org.uk/british-time/
+# </a>
+
+# From Joseph S. Myers (1998-01-06):
+#
+# The legal time in the UK outside of summer time is definitely GMT, not UTC;
+# see Lord Tanlaw's speech
+# <a href="http://www.parliament.the-stationery-office.co.uk/pa/ld199697/ldhansrd/pdvn/lds97/text/70611-20.htm#70611-20_head0">
+# (Lords Hansard 11 June 1997 columns 964 to 976)
+# </a>.
+
+# From Paul Eggert (2006-03-22):
+#
+# For lack of other data, follow Shanks & Pottenger for Eire in 1940-1948.
+#
+# Given Ilieve and Myers's data, the following claims by Shanks & Pottenger
+# are incorrect:
+# * Wales did not switch from GMT to daylight saving time until
+# 1921 Apr 3, when they began to conform with the rest of Great Britain.
+# Actually, Wales was identical after 1880.
+# * Eire had two transitions on 1916 Oct 1.
+# It actually just had one transition.
+# * Northern Ireland used single daylight saving time throughout WW II.
+# Actually, it conformed to Britain.
+# * GB-Eire changed standard time to 1 hour ahead of GMT on 1968-02-18.
+# Actually, that date saw the usual switch to summer time.
+# Standard time was not changed until 1968-10-27 (the clocks didn't change).
+#
+# Here is another incorrect claim by Shanks & Pottenger:
+# * Jersey, Guernsey, and the Isle of Man did not switch from GMT
+# to daylight saving time until 1921 Apr 3, when they began to
+# conform with Great Britain.
+# S.R.&O. 1916, No. 382 and HO 45/10811/312364 (quoted above) say otherwise.
+#
+# The following claim by Shanks & Pottenger is possible though doubtful;
+# we'll ignore it for now.
+# * Dublin's 1971-10-31 switch was at 02:00, even though London's was 03:00.
+#
+#
+# Whitman says Dublin Mean Time was -0:25:21, which is more precise than
+# Shanks & Pottenger.
+# Perhaps this was Dunsink Observatory Time, as Dunsink Observatory
+# (8 km NW of Dublin's center) seemingly was to Dublin as Greenwich was
+# to London. For example:
+#
+# "Timeball on the ballast office is down. Dunsink time."
+# -- James Joyce, Ulysses
+
+# From Joseph S. Myers (2005-01-26):
+# Irish laws are available online at www.irishstatutebook.ie. These include
+# various relating to legal time, for example:
+#
+# ZZA13Y1923.html ZZA12Y1924.html ZZA8Y1925.html ZZSIV20PG1267.html
+#
+# ZZSI71Y1947.html ZZSI128Y1948.html ZZSI23Y1949.html ZZSI41Y1950.html
+# ZZSI27Y1951.html ZZSI73Y1952.html
+#
+# ZZSI11Y1961.html ZZSI232Y1961.html ZZSI182Y1962.html
+# ZZSI167Y1963.html ZZSI257Y1964.html ZZSI198Y1967.html
+# ZZA23Y1968.html ZZA17Y1971.html
+#
+# ZZSI67Y1981.html ZZSI212Y1982.html ZZSI45Y1986.html
+# ZZSI264Y1988.html ZZSI52Y1990.html ZZSI371Y1992.html
+# ZZSI395Y1994.html ZZSI484Y1997.html ZZSI506Y2001.html
+#
+# [These are all relative to the root, e.g., the first is
+# <http://www.irishstatutebook.ie/ZZA13Y1923.html>.]
+#
+# (These are those I found, but there could be more. In any case these
+# should allow various updates to the comments in the europe file to cover
+# the laws applicable in Ireland.)
+#
+# (Note that the time in the Republic of Ireland since 1968 has been defined
+# in terms of standard time being GMT+1 with a period of winter time when it
+# is GMT, rather than standard time being GMT with a period of summer time
+# being GMT+1.)
+
+# From Paul Eggert (1999-03-28):
+# Clive Feather (<news:859845706.26043.0@office.demon.net>, 1997-03-31)
+# reports that Folkestone (Cheriton) Shuttle Terminal uses Concession Time
+# (CT), equivalent to French civil time.
+# Julian Hill (<news:36118128.5A14@virgin.net>, 1998-09-30) reports that
+# trains between Dollands Moor (the freight facility next door)
+# and Frethun run in CT.
+# My admittedly uninformed guess is that the terminal has two authorities,
+# the French concession operators and the British civil authorities,
+# and that the time depends on who you're talking to.
+# If, say, the British police were called to the station for some reason,
+# I would expect the official police report to use GMT/BST and not CET/CEST.
+# This is a borderline case, but for now let's stick to GMT/BST.
+
+# From an anonymous contributor (1996-06-02):
+# The law governing time in Ireland is under Statutory Instrument SI 395/94,
+# which gives force to European Union 7th Council Directive # 94/21/EC.
+# Under this directive, the Minister for Justice in Ireland makes appropriate
+# regulations. I spoke this morning with the Secretary of the Department of
+# Justice (tel +353 1 678 9711) who confirmed to me that the correct name is
+# "Irish Summer Time", abbreviated to "IST".
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+# Summer Time Act, 1916
+Rule GB-Eire 1916 only - May 21 2:00s 1:00 BST
+Rule GB-Eire 1916 only - Oct 1 2:00s 0 GMT
+# S.R.&O. 1917, No. 358
+Rule GB-Eire 1917 only - Apr 8 2:00s 1:00 BST
+Rule GB-Eire 1917 only - Sep 17 2:00s 0 GMT
+# S.R.&O. 1918, No. 274
+Rule GB-Eire 1918 only - Mar 24 2:00s 1:00 BST
+Rule GB-Eire 1918 only - Sep 30 2:00s 0 GMT
+# S.R.&O. 1919, No. 297
+Rule GB-Eire 1919 only - Mar 30 2:00s 1:00 BST
+Rule GB-Eire 1919 only - Sep 29 2:00s 0 GMT
+# S.R.&O. 1920, No. 458
+Rule GB-Eire 1920 only - Mar 28 2:00s 1:00 BST
+# S.R.&O. 1920, No. 1844
+Rule GB-Eire 1920 only - Oct 25 2:00s 0 GMT
+# S.R.&O. 1921, No. 363
+Rule GB-Eire 1921 only - Apr 3 2:00s 1:00 BST
+Rule GB-Eire 1921 only - Oct 3 2:00s 0 GMT
+# S.R.&O. 1922, No. 264
+Rule GB-Eire 1922 only - Mar 26 2:00s 1:00 BST
+Rule GB-Eire 1922 only - Oct 8 2:00s 0 GMT
+# The Summer Time Act, 1922
+Rule GB-Eire 1923 only - Apr Sun>=16 2:00s 1:00 BST
+Rule GB-Eire 1923 1924 - Sep Sun>=16 2:00s 0 GMT
+Rule GB-Eire 1924 only - Apr Sun>=9 2:00s 1:00 BST
+Rule GB-Eire 1925 1926 - Apr Sun>=16 2:00s 1:00 BST
+# The Summer Time Act, 1925
+Rule GB-Eire 1925 1938 - Oct Sun>=2 2:00s 0 GMT
+Rule GB-Eire 1927 only - Apr Sun>=9 2:00s 1:00 BST
+Rule GB-Eire 1928 1929 - Apr Sun>=16 2:00s 1:00 BST
+Rule GB-Eire 1930 only - Apr Sun>=9 2:00s 1:00 BST
+Rule GB-Eire 1931 1932 - Apr Sun>=16 2:00s 1:00 BST
+Rule GB-Eire 1933 only - Apr Sun>=9 2:00s 1:00 BST
+Rule GB-Eire 1934 only - Apr Sun>=16 2:00s 1:00 BST
+Rule GB-Eire 1935 only - Apr Sun>=9 2:00s 1:00 BST
+Rule GB-Eire 1936 1937 - Apr Sun>=16 2:00s 1:00 BST
+Rule GB-Eire 1938 only - Apr Sun>=9 2:00s 1:00 BST
+Rule GB-Eire 1939 only - Apr Sun>=16 2:00s 1:00 BST
+# S.R.&O. 1939, No. 1379
+Rule GB-Eire 1939 only - Nov Sun>=16 2:00s 0 GMT
+# S.R.&O. 1940, No. 172 and No. 1883
+Rule GB-Eire 1940 only - Feb Sun>=23 2:00s 1:00 BST
+# S.R.&O. 1941, No. 476
+Rule GB-Eire 1941 only - May Sun>=2 1:00s 2:00 BDST
+Rule GB-Eire 1941 1943 - Aug Sun>=9 1:00s 1:00 BST
+# S.R.&O. 1942, No. 506
+Rule GB-Eire 1942 1944 - Apr Sun>=2 1:00s 2:00 BDST
+# S.R.&O. 1944, No. 932
+Rule GB-Eire 1944 only - Sep Sun>=16 1:00s 1:00 BST
+# S.R.&O. 1945, No. 312
+Rule GB-Eire 1945 only - Apr Mon>=2 1:00s 2:00 BDST
+Rule GB-Eire 1945 only - Jul Sun>=9 1:00s 1:00 BST
+# S.R.&O. 1945, No. 1208
+Rule GB-Eire 1945 1946 - Oct Sun>=2 2:00s 0 GMT
+Rule GB-Eire 1946 only - Apr Sun>=9 2:00s 1:00 BST
+# The Summer Time Act, 1947
+Rule GB-Eire 1947 only - Mar 16 2:00s 1:00 BST
+Rule GB-Eire 1947 only - Apr 13 1:00s 2:00 BDST
+Rule GB-Eire 1947 only - Aug 10 1:00s 1:00 BST
+Rule GB-Eire 1947 only - Nov 2 2:00s 0 GMT
+# Summer Time Order, 1948 (S.I. 1948/495)
+Rule GB-Eire 1948 only - Mar 14 2:00s 1:00 BST
+Rule GB-Eire 1948 only - Oct 31 2:00s 0 GMT
+# Summer Time Order, 1949 (S.I. 1949/373)
+Rule GB-Eire 1949 only - Apr 3 2:00s 1:00 BST
+Rule GB-Eire 1949 only - Oct 30 2:00s 0 GMT
+# Summer Time Order, 1950 (S.I. 1950/518)
+# Summer Time Order, 1951 (S.I. 1951/430)
+# Summer Time Order, 1952 (S.I. 1952/451)
+Rule GB-Eire 1950 1952 - Apr Sun>=14 2:00s 1:00 BST
+Rule GB-Eire 1950 1952 - Oct Sun>=21 2:00s 0 GMT
+# revert to the rules of the Summer Time Act, 1925
+Rule GB-Eire 1953 only - Apr Sun>=16 2:00s 1:00 BST
+Rule GB-Eire 1953 1960 - Oct Sun>=2 2:00s 0 GMT
+Rule GB-Eire 1954 only - Apr Sun>=9 2:00s 1:00 BST
+Rule GB-Eire 1955 1956 - Apr Sun>=16 2:00s 1:00 BST
+Rule GB-Eire 1957 only - Apr Sun>=9 2:00s 1:00 BST
+Rule GB-Eire 1958 1959 - Apr Sun>=16 2:00s 1:00 BST
+Rule GB-Eire 1960 only - Apr Sun>=9 2:00s 1:00 BST
+# Summer Time Order, 1961 (S.I. 1961/71)
+# Summer Time (1962) Order, 1961 (S.I. 1961/2465)
+# Summer Time Order, 1963 (S.I. 1963/81)
+Rule GB-Eire 1961 1963 - Mar lastSun 2:00s 1:00 BST
+Rule GB-Eire 1961 1968 - Oct Sun>=23 2:00s 0 GMT
+# Summer Time (1964) Order, 1963 (S.I. 1963/2101)
+# Summer Time Order, 1964 (S.I. 1964/1201)
+# Summer Time Order, 1967 (S.I. 1967/1148)
+Rule GB-Eire 1964 1967 - Mar Sun>=19 2:00s 1:00 BST
+# Summer Time Order, 1968 (S.I. 1968/117)
+Rule GB-Eire 1968 only - Feb 18 2:00s 1:00 BST
+# The British Standard Time Act, 1968
+# (no summer time)
+# The Summer Time Act, 1972
+Rule GB-Eire 1972 1980 - Mar Sun>=16 2:00s 1:00 BST
+Rule GB-Eire 1972 1980 - Oct Sun>=23 2:00s 0 GMT
+# Summer Time Order, 1980 (S.I. 1980/1089)
+# Summer Time Order, 1982 (S.I. 1982/1673)
+# Summer Time Order, 1986 (S.I. 1986/223)
+# Summer Time Order, 1988 (S.I. 1988/931)
+Rule GB-Eire 1981 1995 - Mar lastSun 1:00u 1:00 BST
+Rule GB-Eire 1981 1989 - Oct Sun>=23 1:00u 0 GMT
+# Summer Time Order, 1989 (S.I. 1989/985)
+# Summer Time Order, 1992 (S.I. 1992/1729)
+# Summer Time Order 1994 (S.I. 1994/2798)
+Rule GB-Eire 1990 1995 - Oct Sun>=22 1:00u 0 GMT
+# Summer Time Order 1997 (S.I. 1997/2982)
+# See EU for rules starting in 1996.
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/London -0:01:15 - LMT 1847 Dec 1 0:00s
+ 0:00 GB-Eire %s 1968 Oct 27
+ 1:00 - BST 1971 Oct 31 2:00u
+ 0:00 GB-Eire %s 1996
+ 0:00 EU GMT/BST
+Link Europe/London Europe/Jersey
+Link Europe/London Europe/Guernsey
+Link Europe/London Europe/Isle_of_Man
+Zone Europe/Dublin -0:25:00 - LMT 1880 Aug 2
+ -0:25:21 - DMT 1916 May 21 2:00
+ -0:25:21 1:00 IST 1916 Oct 1 2:00s
+ 0:00 GB-Eire %s 1921 Dec 6 # independence
+ 0:00 GB-Eire GMT/IST 1940 Feb 25 2:00
+ 0:00 1:00 IST 1946 Oct 6 2:00
+ 0:00 - GMT 1947 Mar 16 2:00
+ 0:00 1:00 IST 1947 Nov 2 2:00
+ 0:00 - GMT 1948 Apr 18 2:00
+ 0:00 GB-Eire GMT/IST 1968 Oct 27
+ 1:00 - IST 1971 Oct 31 2:00u
+ 0:00 GB-Eire GMT/IST 1996
+ 0:00 EU GMT/IST
+
+###############################################################################
+
+# Europe
+
+# EU rules are for the European Union, previously known as the EC, EEC,
+# Common Market, etc.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule EU 1977 1980 - Apr Sun>=1 1:00u 1:00 S
+Rule EU 1977 only - Sep lastSun 1:00u 0 -
+Rule EU 1978 only - Oct 1 1:00u 0 -
+Rule EU 1979 1995 - Sep lastSun 1:00u 0 -
+Rule EU 1981 max - Mar lastSun 1:00u 1:00 S
+Rule EU 1996 max - Oct lastSun 1:00u 0 -
+# The most recent directive covers the years starting in 2002. See:
+# <a="http://eur-lex.europa.eu/LexUriServ/LexUriServ.do?uri=CELEX:32000L0084:EN:NOT">
+# Directive 2000/84/EC of the European Parliament and of the Council
+# of 19 January 2001 on summer-time arrangements.
+# </a>
+
+# W-Eur differs from EU only in that W-Eur uses standard time.
+Rule W-Eur 1977 1980 - Apr Sun>=1 1:00s 1:00 S
+Rule W-Eur 1977 only - Sep lastSun 1:00s 0 -
+Rule W-Eur 1978 only - Oct 1 1:00s 0 -
+Rule W-Eur 1979 1995 - Sep lastSun 1:00s 0 -
+Rule W-Eur 1981 max - Mar lastSun 1:00s 1:00 S
+Rule W-Eur 1996 max - Oct lastSun 1:00s 0 -
+
+# Older C-Eur rules are for convenience in the tables.
+# From 1977 on, C-Eur differs from EU only in that C-Eur uses standard time.
+Rule C-Eur 1916 only - Apr 30 23:00 1:00 S
+Rule C-Eur 1916 only - Oct 1 1:00 0 -
+Rule C-Eur 1917 1918 - Apr Mon>=15 2:00s 1:00 S
+Rule C-Eur 1917 1918 - Sep Mon>=15 2:00s 0 -
+Rule C-Eur 1940 only - Apr 1 2:00s 1:00 S
+Rule C-Eur 1942 only - Nov 2 2:00s 0 -
+Rule C-Eur 1943 only - Mar 29 2:00s 1:00 S
+Rule C-Eur 1943 only - Oct 4 2:00s 0 -
+Rule C-Eur 1944 1945 - Apr Mon>=1 2:00s 1:00 S
+# Whitman gives 1944 Oct 7; go with Shanks & Pottenger.
+Rule C-Eur 1944 only - Oct 2 2:00s 0 -
+# From Jesper Norgaard Welen (2008-07-13):
+#
+# I found what is probably a typo of 2:00 which should perhaps be 2:00s
+# in the C-Eur rule from tz database version 2008d (this part was
+# corrected in version 2008d). The circumstancial evidence is simply the
+# tz database itself, as seen below:
+#
+# Zone Europe/Paris 0:09:21 - LMT 1891 Mar 15 0:01
+# 0:00 France WE%sT 1945 Sep 16 3:00
+#
+# Zone Europe/Monaco 0:29:32 - LMT 1891 Mar 15
+# 0:00 France WE%sT 1945 Sep 16 3:00
+#
+# Zone Europe/Belgrade 1:22:00 - LMT 1884
+# 1:00 1:00 CEST 1945 Sep 16 2:00s
+#
+# Rule France 1945 only - Sep 16 3:00 0 -
+# Rule Belgium 1945 only - Sep 16 2:00s 0 -
+# Rule Neth 1945 only - Sep 16 2:00s 0 -
+#
+# The rule line to be changed is:
+#
+# Rule C-Eur 1945 only - Sep 16 2:00 0 -
+#
+# It seems that Paris, Monaco, Rule France, Rule Belgium all agree on
+# 2:00 standard time, e.g. 3:00 local time. However there are no
+# countries that use C-Eur rules in September 1945, so the only items
+# affected are apparently these ficticious zones that translates acronyms
+# CET and MET:
+#
+# Zone CET 1:00 C-Eur CE%sT
+# Zone MET 1:00 C-Eur ME%sT
+#
+# It this is right then the corrected version would look like:
+#
+# Rule C-Eur 1945 only - Sep 16 2:00s 0 -
+#
+# A small step for mankind though 8-)
+Rule C-Eur 1945 only - Sep 16 2:00s 0 -
+Rule C-Eur 1977 1980 - Apr Sun>=1 2:00s 1:00 S
+Rule C-Eur 1977 only - Sep lastSun 2:00s 0 -
+Rule C-Eur 1978 only - Oct 1 2:00s 0 -
+Rule C-Eur 1979 1995 - Sep lastSun 2:00s 0 -
+Rule C-Eur 1981 max - Mar lastSun 2:00s 1:00 S
+Rule C-Eur 1996 max - Oct lastSun 2:00s 0 -
+
+# E-Eur differs from EU only in that E-Eur switches at midnight local time.
+Rule E-Eur 1977 1980 - Apr Sun>=1 0:00 1:00 S
+Rule E-Eur 1977 only - Sep lastSun 0:00 0 -
+Rule E-Eur 1978 only - Oct 1 0:00 0 -
+Rule E-Eur 1979 1995 - Sep lastSun 0:00 0 -
+Rule E-Eur 1981 max - Mar lastSun 0:00 1:00 S
+Rule E-Eur 1996 max - Oct lastSun 0:00 0 -
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Russia 1917 only - Jul 1 23:00 1:00 MST # Moscow Summer Time
+Rule Russia 1917 only - Dec 28 0:00 0 MMT # Moscow Mean Time
+Rule Russia 1918 only - May 31 22:00 2:00 MDST # Moscow Double Summer Time
+Rule Russia 1918 only - Sep 16 1:00 1:00 MST
+Rule Russia 1919 only - May 31 23:00 2:00 MDST
+Rule Russia 1919 only - Jul 1 2:00 1:00 S
+Rule Russia 1919 only - Aug 16 0:00 0 -
+Rule Russia 1921 only - Feb 14 23:00 1:00 S
+Rule Russia 1921 only - Mar 20 23:00 2:00 M # Midsummer
+Rule Russia 1921 only - Sep 1 0:00 1:00 S
+Rule Russia 1921 only - Oct 1 0:00 0 -
+# Act No.925 of the Council of Ministers of the USSR (1980-10-24):
+Rule Russia 1981 1984 - Apr 1 0:00 1:00 S
+Rule Russia 1981 1983 - Oct 1 0:00 0 -
+# Act No.967 of the Council of Ministers of the USSR (1984-09-13), repeated in
+# Act No.227 of the Council of Ministers of the USSR (1989-03-14):
+Rule Russia 1984 1991 - Sep lastSun 2:00s 0 -
+Rule Russia 1985 1991 - Mar lastSun 2:00s 1:00 S
+#
+Rule Russia 1992 only - Mar lastSat 23:00 1:00 S
+Rule Russia 1992 only - Sep lastSat 23:00 0 -
+Rule Russia 1993 2010 - Mar lastSun 2:00s 1:00 S
+Rule Russia 1993 1995 - Sep lastSun 2:00s 0 -
+Rule Russia 1996 2010 - Oct lastSun 2:00s 0 -
+
+# From Alexander Krivenyshev (2011-06-14):
+# According to Kremlin press service, Russian President Dmitry Medvedev
+# signed a federal law "On calculation of time" on June 9, 2011.
+# According to the law Russia is abolishing daylight saving time.
+#
+# Medvedev signed a law "On the Calculation of Time" (in russian):
+# <a href="http://bmockbe.ru/events/?ID=7583">
+# http://bmockbe.ru/events/?ID=7583
+# </a>
+#
+# Medvedev signed a law on the calculation of the time (in russian):
+# <a href="http://www.regnum.ru/news/polit/1413906.html">
+# http://www.regnum.ru/news/polit/1413906.html
+# </a>
+
+# From Arthur David Olson (2011-06-15):
+# Take "abolishing daylight saving time" to mean that time is now considered
+# to be standard.
+
+# These are for backward compatibility with older versions.
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone WET 0:00 EU WE%sT
+Zone CET 1:00 C-Eur CE%sT
+Zone MET 1:00 C-Eur ME%sT
+Zone EET 2:00 EU EE%sT
+
+# Previous editions of this database used abbreviations like MET DST
+# for Central European Summer Time, but this didn't agree with common usage.
+
+# From Markus Kuhn (1996-07-12):
+# The official German names ... are
+#
+# Mitteleuropaeische Zeit (MEZ) = UTC+01:00
+# Mitteleuropaeische Sommerzeit (MESZ) = UTC+02:00
+#
+# as defined in the German Time Act (Gesetz ueber die Zeitbestimmung (ZeitG),
+# 1978-07-25, Bundesgesetzblatt, Jahrgang 1978, Teil I, S. 1110-1111)....
+# I wrote ... to the German Federal Physical-Technical Institution
+#
+# Physikalisch-Technische Bundesanstalt (PTB)
+# Laboratorium 4.41 "Zeiteinheit"
+# Postfach 3345
+# D-38023 Braunschweig
+# phone: +49 531 592-0
+#
+# ... I received today an answer letter from Dr. Peter Hetzel, head of the PTB
+# department for time and frequency transmission. He explained that the
+# PTB translates MEZ and MESZ into English as
+#
+# Central European Time (CET) = UTC+01:00
+# Central European Summer Time (CEST) = UTC+02:00
+
+
+# Albania
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Albania 1940 only - Jun 16 0:00 1:00 S
+Rule Albania 1942 only - Nov 2 3:00 0 -
+Rule Albania 1943 only - Mar 29 2:00 1:00 S
+Rule Albania 1943 only - Apr 10 3:00 0 -
+Rule Albania 1974 only - May 4 0:00 1:00 S
+Rule Albania 1974 only - Oct 2 0:00 0 -
+Rule Albania 1975 only - May 1 0:00 1:00 S
+Rule Albania 1975 only - Oct 2 0:00 0 -
+Rule Albania 1976 only - May 2 0:00 1:00 S
+Rule Albania 1976 only - Oct 3 0:00 0 -
+Rule Albania 1977 only - May 8 0:00 1:00 S
+Rule Albania 1977 only - Oct 2 0:00 0 -
+Rule Albania 1978 only - May 6 0:00 1:00 S
+Rule Albania 1978 only - Oct 1 0:00 0 -
+Rule Albania 1979 only - May 5 0:00 1:00 S
+Rule Albania 1979 only - Sep 30 0:00 0 -
+Rule Albania 1980 only - May 3 0:00 1:00 S
+Rule Albania 1980 only - Oct 4 0:00 0 -
+Rule Albania 1981 only - Apr 26 0:00 1:00 S
+Rule Albania 1981 only - Sep 27 0:00 0 -
+Rule Albania 1982 only - May 2 0:00 1:00 S
+Rule Albania 1982 only - Oct 3 0:00 0 -
+Rule Albania 1983 only - Apr 18 0:00 1:00 S
+Rule Albania 1983 only - Oct 1 0:00 0 -
+Rule Albania 1984 only - Apr 1 0:00 1:00 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Tirane 1:19:20 - LMT 1914
+ 1:00 - CET 1940 Jun 16
+ 1:00 Albania CE%sT 1984 Jul
+ 1:00 EU CE%sT
+
+# Andorra
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Andorra 0:06:04 - LMT 1901
+ 0:00 - WET 1946 Sep 30
+ 1:00 - CET 1985 Mar 31 2:00
+ 1:00 EU CE%sT
+
+# Austria
+
+# From Paul Eggert (2006-03-22): Shanks & Pottenger give 1918-06-16 and
+# 1945-11-18, but the Austrian Federal Office of Metrology and
+# Surveying (BEV) gives 1918-09-16 and for Vienna gives the "alleged"
+# date of 1945-04-12 with no time. For the 1980-04-06 transition
+# Shanks & Pottenger give 02:00, the BEV 00:00. Go with the BEV,
+# and guess 02:00 for 1945-04-12.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Austria 1920 only - Apr 5 2:00s 1:00 S
+Rule Austria 1920 only - Sep 13 2:00s 0 -
+Rule Austria 1946 only - Apr 14 2:00s 1:00 S
+Rule Austria 1946 1948 - Oct Sun>=1 2:00s 0 -
+Rule Austria 1947 only - Apr 6 2:00s 1:00 S
+Rule Austria 1948 only - Apr 18 2:00s 1:00 S
+Rule Austria 1980 only - Apr 6 0:00 1:00 S
+Rule Austria 1980 only - Sep 28 0:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Vienna 1:05:20 - LMT 1893 Apr
+ 1:00 C-Eur CE%sT 1920
+ 1:00 Austria CE%sT 1940 Apr 1 2:00s
+ 1:00 C-Eur CE%sT 1945 Apr 2 2:00s
+ 1:00 1:00 CEST 1945 Apr 12 2:00s
+ 1:00 - CET 1946
+ 1:00 Austria CE%sT 1981
+ 1:00 EU CE%sT
+
+# Belarus
+# From Yauhen Kharuzhy (2011-09-16):
+# By latest Belarus government act Europe/Minsk timezone was changed to
+# GMT+3 without DST (was GMT+2 with DST).
+#
+# Sources (Russian language):
+# 1.
+# <a href="http://www.belta.by/ru/all_news/society/V-Belarusi-otmenjaetsja-perexod-na-sezonnoe-vremja_i_572952.html">
+# http://www.belta.by/ru/all_news/society/V-Belarusi-otmenjaetsja-perexod-na-sezonnoe-vremja_i_572952.html
+# </a>
+# 2.
+# <a href="http://naviny.by/rubrics/society/2011/09/16/ic_articles_116_175144/">
+# http://naviny.by/rubrics/society/2011/09/16/ic_articles_116_175144/
+# </a>
+# 3.
+# <a href="http://news.tut.by/society/250578.html">
+# http://news.tut.by/society/250578.html
+# </a>
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Minsk 1:50:16 - LMT 1880
+ 1:50 - MMT 1924 May 2 # Minsk Mean Time
+ 2:00 - EET 1930 Jun 21
+ 3:00 - MSK 1941 Jun 28
+ 1:00 C-Eur CE%sT 1944 Jul 3
+ 3:00 Russia MSK/MSD 1990
+ 3:00 - MSK 1991 Mar 31 2:00s
+ 2:00 1:00 EEST 1991 Sep 29 2:00s
+ 2:00 - EET 1992 Mar 29 0:00s
+ 2:00 1:00 EEST 1992 Sep 27 0:00s
+ 2:00 Russia EE%sT 2011 Mar 27 2:00s
+ 3:00 - FET # Further-eastern European Time
+
+# Belgium
+#
+# From Paul Eggert (1997-07-02):
+# Entries from 1918 through 1991 are taken from:
+# Annuaire de L'Observatoire Royal de Belgique,
+# Avenue Circulaire, 3, B-1180 BRUXELLES, CLVIIe annee, 1991
+# (Imprimerie HAYEZ, s.p.r.l., Rue Fin, 4, 1080 BRUXELLES, MCMXC),
+# pp 8-9.
+# LMT before 1892 was 0:17:30, according to the official journal of Belgium:
+# Moniteur Belge, Samedi 30 Avril 1892, N.121.
+# Thanks to Pascal Delmoitie for these references.
+# The 1918 rules are listed for completeness; they apply to unoccupied Belgium.
+# Assume Brussels switched to WET in 1918 when the armistice took effect.
+#
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Belgium 1918 only - Mar 9 0:00s 1:00 S
+Rule Belgium 1918 1919 - Oct Sat>=1 23:00s 0 -
+Rule Belgium 1919 only - Mar 1 23:00s 1:00 S
+Rule Belgium 1920 only - Feb 14 23:00s 1:00 S
+Rule Belgium 1920 only - Oct 23 23:00s 0 -
+Rule Belgium 1921 only - Mar 14 23:00s 1:00 S
+Rule Belgium 1921 only - Oct 25 23:00s 0 -
+Rule Belgium 1922 only - Mar 25 23:00s 1:00 S
+Rule Belgium 1922 1927 - Oct Sat>=1 23:00s 0 -
+Rule Belgium 1923 only - Apr 21 23:00s 1:00 S
+Rule Belgium 1924 only - Mar 29 23:00s 1:00 S
+Rule Belgium 1925 only - Apr 4 23:00s 1:00 S
+# DSH writes that a royal decree of 1926-02-22 specified the Sun following 3rd
+# Sat in Apr (except if it's Easter, in which case it's one Sunday earlier),
+# to Sun following 1st Sat in Oct, and that a royal decree of 1928-09-15
+# changed the transition times to 02:00 GMT.
+Rule Belgium 1926 only - Apr 17 23:00s 1:00 S
+Rule Belgium 1927 only - Apr 9 23:00s 1:00 S
+Rule Belgium 1928 only - Apr 14 23:00s 1:00 S
+Rule Belgium 1928 1938 - Oct Sun>=2 2:00s 0 -
+Rule Belgium 1929 only - Apr 21 2:00s 1:00 S
+Rule Belgium 1930 only - Apr 13 2:00s 1:00 S
+Rule Belgium 1931 only - Apr 19 2:00s 1:00 S
+Rule Belgium 1932 only - Apr 3 2:00s 1:00 S
+Rule Belgium 1933 only - Mar 26 2:00s 1:00 S
+Rule Belgium 1934 only - Apr 8 2:00s 1:00 S
+Rule Belgium 1935 only - Mar 31 2:00s 1:00 S
+Rule Belgium 1936 only - Apr 19 2:00s 1:00 S
+Rule Belgium 1937 only - Apr 4 2:00s 1:00 S
+Rule Belgium 1938 only - Mar 27 2:00s 1:00 S
+Rule Belgium 1939 only - Apr 16 2:00s 1:00 S
+Rule Belgium 1939 only - Nov 19 2:00s 0 -
+Rule Belgium 1940 only - Feb 25 2:00s 1:00 S
+Rule Belgium 1944 only - Sep 17 2:00s 0 -
+Rule Belgium 1945 only - Apr 2 2:00s 1:00 S
+Rule Belgium 1945 only - Sep 16 2:00s 0 -
+Rule Belgium 1946 only - May 19 2:00s 1:00 S
+Rule Belgium 1946 only - Oct 7 2:00s 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Brussels 0:17:30 - LMT 1880
+ 0:17:30 - BMT 1892 May 1 12:00 # Brussels MT
+ 0:00 - WET 1914 Nov 8
+ 1:00 - CET 1916 May 1 0:00
+ 1:00 C-Eur CE%sT 1918 Nov 11 11:00u
+ 0:00 Belgium WE%sT 1940 May 20 2:00s
+ 1:00 C-Eur CE%sT 1944 Sep 3
+ 1:00 Belgium CE%sT 1977
+ 1:00 EU CE%sT
+
+# Bosnia and Herzegovina
+# see Serbia
+
+# Bulgaria
+#
+# From Plamen Simenov via Steffen Thorsen (1999-09-09):
+# A document of Government of Bulgaria (No.94/1997) says:
+# EET --> EETDST is in 03:00 Local time in last Sunday of March ...
+# EETDST --> EET is in 04:00 Local time in last Sunday of October
+#
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Bulg 1979 only - Mar 31 23:00 1:00 S
+Rule Bulg 1979 only - Oct 1 1:00 0 -
+Rule Bulg 1980 1982 - Apr Sat>=1 23:00 1:00 S
+Rule Bulg 1980 only - Sep 29 1:00 0 -
+Rule Bulg 1981 only - Sep 27 2:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Sofia 1:33:16 - LMT 1880
+ 1:56:56 - IMT 1894 Nov 30 # Istanbul MT?
+ 2:00 - EET 1942 Nov 2 3:00
+ 1:00 C-Eur CE%sT 1945
+ 1:00 - CET 1945 Apr 2 3:00
+ 2:00 - EET 1979 Mar 31 23:00
+ 2:00 Bulg EE%sT 1982 Sep 26 2:00
+ 2:00 C-Eur EE%sT 1991
+ 2:00 E-Eur EE%sT 1997
+ 2:00 EU EE%sT
+
+# Croatia
+# see Serbia
+
+# Cyprus
+# Please see the `asia' file for Asia/Nicosia.
+
+# Czech Republic
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Czech 1945 only - Apr 8 2:00s 1:00 S
+Rule Czech 1945 only - Nov 18 2:00s 0 -
+Rule Czech 1946 only - May 6 2:00s 1:00 S
+Rule Czech 1946 1949 - Oct Sun>=1 2:00s 0 -
+Rule Czech 1947 only - Apr 20 2:00s 1:00 S
+Rule Czech 1948 only - Apr 18 2:00s 1:00 S
+Rule Czech 1949 only - Apr 9 2:00s 1:00 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Prague 0:57:44 - LMT 1850
+ 0:57:44 - PMT 1891 Oct # Prague Mean Time
+ 1:00 C-Eur CE%sT 1944 Sep 17 2:00s
+ 1:00 Czech CE%sT 1979
+ 1:00 EU CE%sT
+
+# Denmark, Faroe Islands, and Greenland
+
+# From Jesper Norgaard Welen (2005-04-26):
+# http://www.hum.aau.dk/~poe/tid/tine/DanskTid.htm says that the law
+# [introducing standard time] was in effect from 1894-01-01....
+# The page http://www.retsinfo.dk/_GETDOCI_/ACCN/A18930008330-REGL
+# confirms this, and states that the law was put forth 1893-03-29.
+#
+# The EU treaty with effect from 1973:
+# http://www.retsinfo.dk/_GETDOCI_/ACCN/A19722110030-REGL
+#
+# This provoked a new law from 1974 to make possible summer time changes
+# in subsequenet decrees with the law
+# http://www.retsinfo.dk/_GETDOCI_/ACCN/A19740022330-REGL
+#
+# It seems however that no decree was set forward until 1980. I have
+# not found any decree, but in another related law, the effecting DST
+# changes are stated explicitly to be from 1980-04-06 at 02:00 to
+# 1980-09-28 at 02:00. If this is true, this differs slightly from
+# the EU rule in that DST runs to 02:00, not 03:00. We don't know
+# when Denmark began using the EU rule correctly, but we have only
+# confirmation of the 1980-time, so I presume it was correct in 1981:
+# The law is about the management of the extra hour, concerning
+# working hours reported and effect on obligatory-rest rules (which
+# was suspended on that night):
+# http://www.retsinfo.dk/_GETDOCI_/ACCN/C19801120554-REGL
+
+# From Jesper Norgaard Welen (2005-06-11):
+# The Herning Folkeblad (1980-09-26) reported that the night between
+# Saturday and Sunday the clock is set back from three to two.
+
+# From Paul Eggert (2005-06-11):
+# Hence the "02:00" of the 1980 law refers to standard time, not
+# wall-clock time, and so the EU rules were in effect in 1980.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Denmark 1916 only - May 14 23:00 1:00 S
+Rule Denmark 1916 only - Sep 30 23:00 0 -
+Rule Denmark 1940 only - May 15 0:00 1:00 S
+Rule Denmark 1945 only - Apr 2 2:00s 1:00 S
+Rule Denmark 1945 only - Aug 15 2:00s 0 -
+Rule Denmark 1946 only - May 1 2:00s 1:00 S
+Rule Denmark 1946 only - Sep 1 2:00s 0 -
+Rule Denmark 1947 only - May 4 2:00s 1:00 S
+Rule Denmark 1947 only - Aug 10 2:00s 0 -
+Rule Denmark 1948 only - May 9 2:00s 1:00 S
+Rule Denmark 1948 only - Aug 8 2:00s 0 -
+#
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Copenhagen 0:50:20 - LMT 1890
+ 0:50:20 - CMT 1894 Jan 1 # Copenhagen MT
+ 1:00 Denmark CE%sT 1942 Nov 2 2:00s
+ 1:00 C-Eur CE%sT 1945 Apr 2 2:00
+ 1:00 Denmark CE%sT 1980
+ 1:00 EU CE%sT
+Zone Atlantic/Faroe -0:27:04 - LMT 1908 Jan 11 # Torshavn
+ 0:00 - WET 1981
+ 0:00 EU WE%sT
+#
+# From Paul Eggert (2004-10-31):
+# During World War II, Germany maintained secret manned weather stations in
+# East Greenland and Franz Josef Land, but we don't know their time zones.
+# My source for this is Wilhelm Dege's book mentioned under Svalbard.
+#
+# From Paul Eggert (2006-03-22):
+# Greenland joined the EU as part of Denmark, obtained home rule on 1979-05-01,
+# and left the EU on 1985-02-01. It therefore should have been using EU
+# rules at least through 1984. Shanks & Pottenger say Scoresbysund and Godthab
+# used C-Eur rules after 1980, but IATA SSIM (1991/1996) says they use EU
+# rules since at least 1991. Assume EU rules since 1980.
+
+# From Gwillin Law (2001-06-06), citing
+# <http://www.statkart.no/efs/efshefter/2001/efs5-2001.pdf> (2001-03-15),
+# and with translations corrected by Steffen Thorsen:
+#
+# Greenland has four local times, and the relation to UTC
+# is according to the following time line:
+#
+# The military zone near Thule UTC-4
+# Standard Greenland time UTC-3
+# Scoresbysund UTC-1
+# Danmarkshavn UTC
+#
+# In the military area near Thule and in Danmarkshavn DST will not be
+# introduced.
+
+# From Rives McDow (2001-11-01):
+#
+# I correspond regularly with the Dansk Polarcenter, and wrote them at
+# the time to clarify the situation in Thule. Unfortunately, I have
+# not heard back from them regarding my recent letter. [But I have
+# info from earlier correspondence.]
+#
+# According to the center, a very small local time zone around Thule
+# Air Base keeps the time according to UTC-4, implementing daylight
+# savings using North America rules, changing the time at 02:00 local time....
+#
+# The east coast of Greenland north of the community of Scoresbysund
+# uses UTC in the same way as in Iceland, year round, with no dst.
+# There are just a few stations on this coast, including the
+# Danmarkshavn ICAO weather station mentioned in your September 29th
+# email. The other stations are two sledge patrol stations in
+# Mestersvig and Daneborg, the air force base at Station Nord, and the
+# DPC research station at Zackenberg.
+#
+# Scoresbysund and two small villages nearby keep time UTC-1 and use
+# the same daylight savings time period as in West Greenland (Godthab).
+#
+# The rest of Greenland, including Godthab (this area, although it
+# includes central Greenland, is known as west Greenland), keeps time
+# UTC-3, with daylight savings methods according to European rules.
+#
+# It is common procedure to use UTC 0 in the wilderness of East and
+# North Greenland, because it is mainly Icelandic aircraft operators
+# maintaining traffic in these areas. However, the official status of
+# this area is that it sticks with Godthab time. This area might be
+# considered a dual time zone in some respects because of this.
+
+# From Rives McDow (2001-11-19):
+# I heard back from someone stationed at Thule; the time change took place
+# there at 2:00 AM.
+
+# From Paul Eggert (2006-03-22):
+# From 1997 on the CIA map shows Danmarkshavn on GMT;
+# the 1995 map as like Godthab.
+# For lack of better info, assume they were like Godthab before 1996.
+# startkart.no says Thule does not observe DST, but this is clearly an error,
+# so go with Shanks & Pottenger for Thule transitions until this year.
+# For 2007 on assume Thule will stay in sync with US DST rules.
+#
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Thule 1991 1992 - Mar lastSun 2:00 1:00 D
+Rule Thule 1991 1992 - Sep lastSun 2:00 0 S
+Rule Thule 1993 2006 - Apr Sun>=1 2:00 1:00 D
+Rule Thule 1993 2006 - Oct lastSun 2:00 0 S
+Rule Thule 2007 max - Mar Sun>=8 2:00 1:00 D
+Rule Thule 2007 max - Nov Sun>=1 2:00 0 S
+#
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Danmarkshavn -1:14:40 - LMT 1916 Jul 28
+ -3:00 - WGT 1980 Apr 6 2:00
+ -3:00 EU WG%sT 1996
+ 0:00 - GMT
+Zone America/Scoresbysund -1:27:52 - LMT 1916 Jul 28 # Ittoqqortoormiit
+ -2:00 - CGT 1980 Apr 6 2:00
+ -2:00 C-Eur CG%sT 1981 Mar 29
+ -1:00 EU EG%sT
+Zone America/Godthab -3:26:56 - LMT 1916 Jul 28 # Nuuk
+ -3:00 - WGT 1980 Apr 6 2:00
+ -3:00 EU WG%sT
+Zone America/Thule -4:35:08 - LMT 1916 Jul 28 # Pituffik air base
+ -4:00 Thule A%sT
+
+# Estonia
+# From Peter Ilieve (1994-10-15):
+# A relative in Tallinn confirms the accuracy of the data for 1989 onwards
+# [through 1994] and gives the legal authority for it,
+# a regulation of the Government of Estonia, No. 111 of 1989....
+#
+# From Peter Ilieve (1996-10-28):
+# [IATA SSIM (1992/1996) claims that the Baltic republics switch at 01:00s,
+# but a relative confirms that Estonia still switches at 02:00s, writing:]
+# ``I do not [know] exactly but there are some little different
+# (confusing) rules for International Air and Railway Transport Schedules
+# conversion in Sunday connected with end of summer time in Estonia....
+# A discussion is running about the summer time efficiency and effect on
+# human physiology. It seems that Estonia maybe will not change to
+# summer time next spring.''
+
+# From Peter Ilieve (1998-11-04), heavily edited:
+# <a href="http://trip.rk.ee/cgi-bin/thw?${BASE}=akt&${OOHTML}=rtd&TA=1998&TO=1&AN=1390">
+# The 1998-09-22 Estonian time law
+# </a>
+# refers to the Eighth Directive and cites the association agreement between
+# the EU and Estonia, ratified by the Estonian law (RT II 1995, 22--27, 120).
+#
+# I also asked [my relative] whether they use any standard abbreviation
+# for their standard and summer times. He says no, they use "suveaeg"
+# (summer time) and "talveaeg" (winter time).
+
+# From <a href="http://www.baltictimes.com/">The Baltic Times</a> (1999-09-09)
+# via Steffen Thorsen:
+# This year will mark the last time Estonia shifts to summer time,
+# a council of the ruling coalition announced Sept. 6....
+# But what this could mean for Estonia's chances of joining the European
+# Union are still unclear. In 1994, the EU declared summer time compulsory
+# for all member states until 2001. Brussels has yet to decide what to do
+# after that.
+
+# From Mart Oruaas (2000-01-29):
+# Regulation no. 301 (1999-10-12) obsoletes previous regulation
+# no. 206 (1998-09-22) and thus sticks Estonia to +02:00 GMT for all
+# the year round. The regulation is effective 1999-11-01.
+
+# From Toomas Soome (2002-02-21):
+# The Estonian government has changed once again timezone politics.
+# Now we are using again EU rules.
+#
+# From Urmet Jaanes (2002-03-28):
+# The legislative reference is Government decree No. 84 on 2002-02-21.
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Tallinn 1:39:00 - LMT 1880
+ 1:39:00 - TMT 1918 Feb # Tallinn Mean Time
+ 1:00 C-Eur CE%sT 1919 Jul
+ 1:39:00 - TMT 1921 May
+ 2:00 - EET 1940 Aug 6
+ 3:00 - MSK 1941 Sep 15
+ 1:00 C-Eur CE%sT 1944 Sep 22
+ 3:00 Russia MSK/MSD 1989 Mar 26 2:00s
+ 2:00 1:00 EEST 1989 Sep 24 2:00s
+ 2:00 C-Eur EE%sT 1998 Sep 22
+ 2:00 EU EE%sT 1999 Nov 1
+ 2:00 - EET 2002 Feb 21
+ 2:00 EU EE%sT
+
+# Finland
+
+# From Hannu Strang (1994-09-25 06:03:37 UTC):
+# Well, here in Helsinki we're just changing from summer time to regular one,
+# and it's supposed to change at 4am...
+
+# From Janne Snabb (2010-0715):
+#
+# I noticed that the Finland data is not accurate for years 1981 and 1982.
+# During these two first trial years the DST adjustment was made one hour
+# earlier than in forthcoming years. Starting 1983 the adjustment was made
+# according to the central European standards.
+#
+# This is documented in Heikki Oja: Aikakirja 2007, published by The Almanac
+# Office of University of Helsinki, ISBN 952-10-3221-9, available online (in
+# Finnish) at
+#
+# <a href="http://almanakka.helsinki.fi/aikakirja/Aikakirja2007kokonaan.pdf">
+# http://almanakka.helsinki.fi/aikakirja/Aikakirja2007kokonaan.pdf
+# </a>
+#
+# Page 105 (56 in PDF version) has a handy table of all past daylight savings
+# transitions. It is easy enough to interpret without Finnish skills.
+#
+# This is also confirmed by Finnish Broadcasting Company's archive at:
+#
+# <a href="http://www.yle.fi/elavaarkisto/?s=s&g=1&ag=5&t=&a=3401">
+# http://www.yle.fi/elavaarkisto/?s=s&g=1&ag=5&t=&a=3401
+# </a>
+#
+# The news clip from 1981 says that "the time between 2 and 3 o'clock does not
+# exist tonight."
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Finland 1942 only - Apr 3 0:00 1:00 S
+Rule Finland 1942 only - Oct 3 0:00 0 -
+Rule Finland 1981 1982 - Mar lastSun 2:00 1:00 S
+Rule Finland 1981 1982 - Sep lastSun 3:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Helsinki 1:39:52 - LMT 1878 May 31
+ 1:39:52 - HMT 1921 May # Helsinki Mean Time
+ 2:00 Finland EE%sT 1983
+ 2:00 EU EE%sT
+
+# Aaland Is
+Link Europe/Helsinki Europe/Mariehamn
+
+
+# France
+
+# From Ciro Discepolo (2000-12-20):
+#
+# Henri Le Corre, Regimes Horaires pour le monde entier, Editions
+# Traditionnelles - Paris 2 books, 1993
+#
+# Gabriel, Traite de l'heure dans le monde, Guy Tredaniel editeur,
+# Paris, 1991
+#
+# Francoise Gauquelin, Problemes de l'heure resolus en astrologie,
+# Guy tredaniel, Paris 1987
+
+
+#
+# Shank & Pottenger seem to use `24:00' ambiguously; resolve it with Whitman.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule France 1916 only - Jun 14 23:00s 1:00 S
+Rule France 1916 1919 - Oct Sun>=1 23:00s 0 -
+Rule France 1917 only - Mar 24 23:00s 1:00 S
+Rule France 1918 only - Mar 9 23:00s 1:00 S
+Rule France 1919 only - Mar 1 23:00s 1:00 S
+Rule France 1920 only - Feb 14 23:00s 1:00 S
+Rule France 1920 only - Oct 23 23:00s 0 -
+Rule France 1921 only - Mar 14 23:00s 1:00 S
+Rule France 1921 only - Oct 25 23:00s 0 -
+Rule France 1922 only - Mar 25 23:00s 1:00 S
+# DSH writes that a law of 1923-05-24 specified 3rd Sat in Apr at 23:00 to 1st
+# Sat in Oct at 24:00; and that in 1930, because of Easter, the transitions
+# were Apr 12 and Oct 5. Go with Shanks & Pottenger.
+Rule France 1922 1938 - Oct Sat>=1 23:00s 0 -
+Rule France 1923 only - May 26 23:00s 1:00 S
+Rule France 1924 only - Mar 29 23:00s 1:00 S
+Rule France 1925 only - Apr 4 23:00s 1:00 S
+Rule France 1926 only - Apr 17 23:00s 1:00 S
+Rule France 1927 only - Apr 9 23:00s 1:00 S
+Rule France 1928 only - Apr 14 23:00s 1:00 S
+Rule France 1929 only - Apr 20 23:00s 1:00 S
+Rule France 1930 only - Apr 12 23:00s 1:00 S
+Rule France 1931 only - Apr 18 23:00s 1:00 S
+Rule France 1932 only - Apr 2 23:00s 1:00 S
+Rule France 1933 only - Mar 25 23:00s 1:00 S
+Rule France 1934 only - Apr 7 23:00s 1:00 S
+Rule France 1935 only - Mar 30 23:00s 1:00 S
+Rule France 1936 only - Apr 18 23:00s 1:00 S
+Rule France 1937 only - Apr 3 23:00s 1:00 S
+Rule France 1938 only - Mar 26 23:00s 1:00 S
+Rule France 1939 only - Apr 15 23:00s 1:00 S
+Rule France 1939 only - Nov 18 23:00s 0 -
+Rule France 1940 only - Feb 25 2:00 1:00 S
+# The French rules for 1941-1944 were not used in Paris, but Shanks & Pottenger
+# write that they were used in Monaco and in many French locations.
+# Le Corre writes that the upper limit of the free zone was Arneguy, Orthez,
+# Mont-de-Marsan, Bazas, Langon, Lamotte-Montravel, Marouil, La
+# Rochefoucault, Champagne-Mouton, La Roche-Posay, La Haye-Descartes,
+# Loches, Montrichard, Vierzon, Bourges, Moulins, Digoin,
+# Paray-le-Monial, Montceau-les-Mines, Chalons-sur-Saone, Arbois,
+# Dole, Morez, St-Claude, and Collonges (Haute-Savoie).
+Rule France 1941 only - May 5 0:00 2:00 M # Midsummer
+# Shanks & Pottenger say this transition occurred at Oct 6 1:00,
+# but go with Denis Excoffier (1997-12-12),
+# who quotes the Ephemerides Astronomiques for 1998 from Bureau des Longitudes
+# as saying 5/10/41 22hUT.
+Rule France 1941 only - Oct 6 0:00 1:00 S
+Rule France 1942 only - Mar 9 0:00 2:00 M
+Rule France 1942 only - Nov 2 3:00 1:00 S
+Rule France 1943 only - Mar 29 2:00 2:00 M
+Rule France 1943 only - Oct 4 3:00 1:00 S
+Rule France 1944 only - Apr 3 2:00 2:00 M
+Rule France 1944 only - Oct 8 1:00 1:00 S
+Rule France 1945 only - Apr 2 2:00 2:00 M
+Rule France 1945 only - Sep 16 3:00 0 -
+# Shanks & Pottenger give Mar 28 2:00 and Sep 26 3:00;
+# go with Excoffier's 28/3/76 0hUT and 25/9/76 23hUT.
+Rule France 1976 only - Mar 28 1:00 1:00 S
+Rule France 1976 only - Sep 26 1:00 0 -
+# Shanks & Pottenger give 0:09:20 for Paris Mean Time, and Whitman 0:09:05,
+# but Howse quotes the actual French legislation as saying 0:09:21.
+# Go with Howse. Howse writes that the time in France was officially based
+# on PMT-0:09:21 until 1978-08-09, when the time base finally switched to UTC.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Paris 0:09:21 - LMT 1891 Mar 15 0:01
+ 0:09:21 - PMT 1911 Mar 11 0:01 # Paris MT
+# Shanks & Pottenger give 1940 Jun 14 0:00; go with Excoffier and Le Corre.
+ 0:00 France WE%sT 1940 Jun 14 23:00
+# Le Corre says Paris stuck with occupied-France time after the liberation;
+# go with Shanks & Pottenger.
+ 1:00 C-Eur CE%sT 1944 Aug 25
+ 0:00 France WE%sT 1945 Sep 16 3:00
+ 1:00 France CE%sT 1977
+ 1:00 EU CE%sT
+
+# Germany
+
+# From Markus Kuhn (1998-09-29):
+# The German time zone web site by the Physikalisch-Technische
+# Bundesanstalt contains DST information back to 1916.
+# [See tz-link.htm for the URL.]
+
+# From Joerg Schilling (2002-10-23):
+# In 1945, Berlin was switched to Moscow Summer time (GMT+4) by
+# <a href="http://www.dhm.de/lemo/html/biografien/BersarinNikolai/">
+# General [Nikolai] Bersarin</a>.
+
+# From Paul Eggert (2003-03-08):
+# <a href="http://www.parlament-berlin.de/pds-fraktion.nsf/727459127c8b66ee8525662300459099/defc77cb784f180ac1256c2b0030274b/$FILE/bersarint.pdf">
+# http://www.parlament-berlin.de/pds-fraktion.nsf/727459127c8b66ee8525662300459099/defc77cb784f180ac1256c2b0030274b/$FILE/bersarint.pdf
+# </a>
+# says that Bersarin issued an order to use Moscow time on May 20.
+# However, Moscow did not observe daylight saving in 1945, so
+# this was equivalent to CEMT (GMT+3), not GMT+4.
+
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Germany 1946 only - Apr 14 2:00s 1:00 S
+Rule Germany 1946 only - Oct 7 2:00s 0 -
+Rule Germany 1947 1949 - Oct Sun>=1 2:00s 0 -
+# http://www.ptb.de/de/org/4/44/441/salt.htm says the following transition
+# occurred at 3:00 MEZ, not the 2:00 MEZ given in Shanks & Pottenger.
+# Go with the PTB.
+Rule Germany 1947 only - Apr 6 3:00s 1:00 S
+Rule Germany 1947 only - May 11 2:00s 2:00 M
+Rule Germany 1947 only - Jun 29 3:00 1:00 S
+Rule Germany 1948 only - Apr 18 2:00s 1:00 S
+Rule Germany 1949 only - Apr 10 2:00s 1:00 S
+
+Rule SovietZone 1945 only - May 24 2:00 2:00 M # Midsummer
+Rule SovietZone 1945 only - Sep 24 3:00 1:00 S
+Rule SovietZone 1945 only - Nov 18 2:00s 0 -
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Berlin 0:53:28 - LMT 1893 Apr
+ 1:00 C-Eur CE%sT 1945 May 24 2:00
+ 1:00 SovietZone CE%sT 1946
+ 1:00 Germany CE%sT 1980
+ 1:00 EU CE%sT
+
+# Georgia
+# Please see the "asia" file for Asia/Tbilisi.
+# Herodotus (Histories, IV.45) says Georgia north of the Phasis (now Rioni)
+# is in Europe. Our reference location Tbilisi is in the Asian part.
+
+# Gibraltar
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Gibraltar -0:21:24 - LMT 1880 Aug 2 0:00s
+ 0:00 GB-Eire %s 1957 Apr 14 2:00
+ 1:00 - CET 1982
+ 1:00 EU CE%sT
+
+# Greece
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+# Whitman gives 1932 Jul 5 - Nov 1; go with Shanks & Pottenger.
+Rule Greece 1932 only - Jul 7 0:00 1:00 S
+Rule Greece 1932 only - Sep 1 0:00 0 -
+# Whitman gives 1941 Apr 25 - ?; go with Shanks & Pottenger.
+Rule Greece 1941 only - Apr 7 0:00 1:00 S
+# Whitman gives 1942 Feb 2 - ?; go with Shanks & Pottenger.
+Rule Greece 1942 only - Nov 2 3:00 0 -
+Rule Greece 1943 only - Mar 30 0:00 1:00 S
+Rule Greece 1943 only - Oct 4 0:00 0 -
+# Whitman gives 1944 Oct 3 - Oct 31; go with Shanks & Pottenger.
+Rule Greece 1952 only - Jul 1 0:00 1:00 S
+Rule Greece 1952 only - Nov 2 0:00 0 -
+Rule Greece 1975 only - Apr 12 0:00s 1:00 S
+Rule Greece 1975 only - Nov 26 0:00s 0 -
+Rule Greece 1976 only - Apr 11 2:00s 1:00 S
+Rule Greece 1976 only - Oct 10 2:00s 0 -
+Rule Greece 1977 1978 - Apr Sun>=1 2:00s 1:00 S
+Rule Greece 1977 only - Sep 26 2:00s 0 -
+Rule Greece 1978 only - Sep 24 4:00 0 -
+Rule Greece 1979 only - Apr 1 9:00 1:00 S
+Rule Greece 1979 only - Sep 29 2:00 0 -
+Rule Greece 1980 only - Apr 1 0:00 1:00 S
+Rule Greece 1980 only - Sep 28 0:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Athens 1:34:52 - LMT 1895 Sep 14
+ 1:34:52 - AMT 1916 Jul 28 0:01 # Athens MT
+ 2:00 Greece EE%sT 1941 Apr 30
+ 1:00 Greece CE%sT 1944 Apr 4
+ 2:00 Greece EE%sT 1981
+ # Shanks & Pottenger say it switched to C-Eur in 1981;
+ # go with EU instead, since Greece joined it on Jan 1.
+ 2:00 EU EE%sT
+
+# Hungary
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Hungary 1918 only - Apr 1 3:00 1:00 S
+Rule Hungary 1918 only - Sep 29 3:00 0 -
+Rule Hungary 1919 only - Apr 15 3:00 1:00 S
+Rule Hungary 1919 only - Sep 15 3:00 0 -
+Rule Hungary 1920 only - Apr 5 3:00 1:00 S
+Rule Hungary 1920 only - Sep 30 3:00 0 -
+Rule Hungary 1945 only - May 1 23:00 1:00 S
+Rule Hungary 1945 only - Nov 3 0:00 0 -
+Rule Hungary 1946 only - Mar 31 2:00s 1:00 S
+Rule Hungary 1946 1949 - Oct Sun>=1 2:00s 0 -
+Rule Hungary 1947 1949 - Apr Sun>=4 2:00s 1:00 S
+Rule Hungary 1950 only - Apr 17 2:00s 1:00 S
+Rule Hungary 1950 only - Oct 23 2:00s 0 -
+Rule Hungary 1954 1955 - May 23 0:00 1:00 S
+Rule Hungary 1954 1955 - Oct 3 0:00 0 -
+Rule Hungary 1956 only - Jun Sun>=1 0:00 1:00 S
+Rule Hungary 1956 only - Sep lastSun 0:00 0 -
+Rule Hungary 1957 only - Jun Sun>=1 1:00 1:00 S
+Rule Hungary 1957 only - Sep lastSun 3:00 0 -
+Rule Hungary 1980 only - Apr 6 1:00 1:00 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Budapest 1:16:20 - LMT 1890 Oct
+ 1:00 C-Eur CE%sT 1918
+ 1:00 Hungary CE%sT 1941 Apr 6 2:00
+ 1:00 C-Eur CE%sT 1945
+ 1:00 Hungary CE%sT 1980 Sep 28 2:00s
+ 1:00 EU CE%sT
+
+# Iceland
+#
+# From Adam David (1993-11-06):
+# The name of the timezone in Iceland for system / mail / news purposes is GMT.
+#
+# (1993-12-05):
+# This material is paraphrased from the 1988 edition of the University of
+# Iceland Almanak.
+#
+# From January 1st, 1908 the whole of Iceland was standardised at 1 hour
+# behind GMT. Previously, local mean solar time was used in different parts
+# of Iceland, the almanak had been based on Reykjavik mean solar time which
+# was 1 hour and 28 minutes behind GMT.
+#
+# "first day of winter" referred to [below] means the first day of the 26 weeks
+# of winter, according to the old icelandic calendar that dates back to the
+# time the norsemen first settled Iceland. The first day of winter is always
+# Saturday, but is not dependent on the Julian or Gregorian calendars.
+#
+# (1993-12-10):
+# I have a reference from the Oxford Icelandic-English dictionary for the
+# beginning of winter, which ties it to the ecclesiastical calendar (and thus
+# to the julian/gregorian calendar) over the period in question.
+# the winter begins on the Saturday next before St. Luke's day
+# (old style), or on St. Luke's day, if a Saturday.
+# St. Luke's day ought to be traceable from ecclesiastical sources. "old style"
+# might be a reference to the Julian calendar as opposed to Gregorian, or it
+# might mean something else (???).
+#
+# From Paul Eggert (2006-03-22):
+# The Iceland Almanak, Shanks & Pottenger, and Whitman disagree on many points.
+# We go with the Almanak, except for one claim from Shanks & Pottenger, namely
+# that Reykavik was 21W57 from 1837 to 1908, local mean time before that.
+#
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Iceland 1917 1918 - Feb 19 23:00 1:00 S
+Rule Iceland 1917 only - Oct 21 1:00 0 -
+Rule Iceland 1918 only - Nov 16 1:00 0 -
+Rule Iceland 1939 only - Apr 29 23:00 1:00 S
+Rule Iceland 1939 only - Nov 29 2:00 0 -
+Rule Iceland 1940 only - Feb 25 2:00 1:00 S
+Rule Iceland 1940 only - Nov 3 2:00 0 -
+Rule Iceland 1941 only - Mar 2 1:00s 1:00 S
+Rule Iceland 1941 only - Nov 2 1:00s 0 -
+Rule Iceland 1942 only - Mar 8 1:00s 1:00 S
+Rule Iceland 1942 only - Oct 25 1:00s 0 -
+# 1943-1946 - first Sunday in March until first Sunday in winter
+Rule Iceland 1943 1946 - Mar Sun>=1 1:00s 1:00 S
+Rule Iceland 1943 1948 - Oct Sun>=22 1:00s 0 -
+# 1947-1967 - first Sunday in April until first Sunday in winter
+Rule Iceland 1947 1967 - Apr Sun>=1 1:00s 1:00 S
+# 1949 Oct transition delayed by 1 week
+Rule Iceland 1949 only - Oct 30 1:00s 0 -
+Rule Iceland 1950 1966 - Oct Sun>=22 1:00s 0 -
+Rule Iceland 1967 only - Oct 29 1:00s 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Atlantic/Reykjavik -1:27:24 - LMT 1837
+ -1:27:48 - RMT 1908 # Reykjavik Mean Time?
+ -1:00 Iceland IS%sT 1968 Apr 7 1:00s
+ 0:00 - GMT
+
+# Italy
+#
+# From Paul Eggert (2001-03-06):
+# Sicily and Sardinia each had their own time zones from 1866 to 1893,
+# called Palermo Time (+00:53:28) and Cagliari Time (+00:36:32).
+# During World War II, German-controlled Italy used German time.
+# But these events all occurred before the 1970 cutoff,
+# so record only the time in Rome.
+#
+# From Paul Eggert (2006-03-22):
+# For Italian DST we have three sources: Shanks & Pottenger, Whitman, and
+# F. Pollastri
+# <a href="http://toi.iriti.cnr.it/uk/ienitlt.html">
+# Day-light Saving Time in Italy (2006-02-03)
+# </a>
+# (`FP' below), taken from an Italian National Electrotechnical Institute
+# publication. When the three sources disagree, guess who's right, as follows:
+#
+# year FP Shanks&P. (S) Whitman (W) Go with:
+# 1916 06-03 06-03 24:00 06-03 00:00 FP & W
+# 09-30 09-30 24:00 09-30 01:00 FP; guess 24:00s
+# 1917 04-01 03-31 24:00 03-31 00:00 FP & S
+# 09-30 09-29 24:00 09-30 01:00 FP & W
+# 1918 03-09 03-09 24:00 03-09 00:00 FP & S
+# 10-06 10-05 24:00 10-06 01:00 FP & W
+# 1919 03-01 03-01 24:00 03-01 00:00 FP & S
+# 10-04 10-04 24:00 10-04 01:00 FP; guess 24:00s
+# 1920 03-20 03-20 24:00 03-20 00:00 FP & S
+# 09-18 09-18 24:00 10-01 01:00 FP; guess 24:00s
+# 1944 04-02 04-03 02:00 S (see C-Eur)
+# 09-16 10-02 03:00 FP; guess 24:00s
+# 1945 09-14 09-16 24:00 FP; guess 24:00s
+# 1970 05-21 05-31 00:00 S
+# 09-20 09-27 00:00 S
+#
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Italy 1916 only - Jun 3 0:00s 1:00 S
+Rule Italy 1916 only - Oct 1 0:00s 0 -
+Rule Italy 1917 only - Apr 1 0:00s 1:00 S
+Rule Italy 1917 only - Sep 30 0:00s 0 -
+Rule Italy 1918 only - Mar 10 0:00s 1:00 S
+Rule Italy 1918 1919 - Oct Sun>=1 0:00s 0 -
+Rule Italy 1919 only - Mar 2 0:00s 1:00 S
+Rule Italy 1920 only - Mar 21 0:00s 1:00 S
+Rule Italy 1920 only - Sep 19 0:00s 0 -
+Rule Italy 1940 only - Jun 15 0:00s 1:00 S
+Rule Italy 1944 only - Sep 17 0:00s 0 -
+Rule Italy 1945 only - Apr 2 2:00 1:00 S
+Rule Italy 1945 only - Sep 15 0:00s 0 -
+Rule Italy 1946 only - Mar 17 2:00s 1:00 S
+Rule Italy 1946 only - Oct 6 2:00s 0 -
+Rule Italy 1947 only - Mar 16 0:00s 1:00 S
+Rule Italy 1947 only - Oct 5 0:00s 0 -
+Rule Italy 1948 only - Feb 29 2:00s 1:00 S
+Rule Italy 1948 only - Oct 3 2:00s 0 -
+Rule Italy 1966 1968 - May Sun>=22 0:00 1:00 S
+Rule Italy 1966 1969 - Sep Sun>=22 0:00 0 -
+Rule Italy 1969 only - Jun 1 0:00 1:00 S
+Rule Italy 1970 only - May 31 0:00 1:00 S
+Rule Italy 1970 only - Sep lastSun 0:00 0 -
+Rule Italy 1971 1972 - May Sun>=22 0:00 1:00 S
+Rule Italy 1971 only - Sep lastSun 1:00 0 -
+Rule Italy 1972 only - Oct 1 0:00 0 -
+Rule Italy 1973 only - Jun 3 0:00 1:00 S
+Rule Italy 1973 1974 - Sep lastSun 0:00 0 -
+Rule Italy 1974 only - May 26 0:00 1:00 S
+Rule Italy 1975 only - Jun 1 0:00s 1:00 S
+Rule Italy 1975 1977 - Sep lastSun 0:00s 0 -
+Rule Italy 1976 only - May 30 0:00s 1:00 S
+Rule Italy 1977 1979 - May Sun>=22 0:00s 1:00 S
+Rule Italy 1978 only - Oct 1 0:00s 0 -
+Rule Italy 1979 only - Sep 30 0:00s 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Rome 0:49:56 - LMT 1866 Sep 22
+ 0:49:56 - RMT 1893 Nov 1 0:00s # Rome Mean
+ 1:00 Italy CE%sT 1942 Nov 2 2:00s
+ 1:00 C-Eur CE%sT 1944 Jul
+ 1:00 Italy CE%sT 1980
+ 1:00 EU CE%sT
+
+Link Europe/Rome Europe/Vatican
+Link Europe/Rome Europe/San_Marino
+
+# Latvia
+
+# From Liene Kanepe (1998-09-17):
+
+# I asked about this matter Scientific Secretary of the Institute of Astronomy
+# of The University of Latvia Dr. paed Mr. Ilgonis Vilks. I also searched the
+# correct data in juridical acts and I found some juridical documents about
+# changes in the counting of time in Latvia from 1981....
+#
+# Act No.35 of the Council of Ministers of Latvian SSR of 1981-01-22 ...
+# according to the Act No.925 of the Council of Ministers of USSR of 1980-10-24
+# ...: all year round the time of 2nd time zone + 1 hour, in addition turning
+# the hands of the clock 1 hour forward on 1 April at 00:00 (GMT 31 March 21:00)
+# and 1 hour backward on the 1 October at 00:00 (GMT 30 September 20:00).
+#
+# Act No.592 of the Council of Ministers of Latvian SSR of 1984-09-24 ...
+# according to the Act No.967 of the Council of Ministers of USSR of 1984-09-13
+# ...: all year round the time of 2nd time zone + 1 hour, in addition turning
+# the hands of the clock 1 hour forward on the last Sunday of March at 02:00
+# (GMT 23:00 on the previous day) and 1 hour backward on the last Sunday of
+# September at 03:00 (GMT 23:00 on the previous day).
+#
+# Act No.81 of the Council of Ministers of Latvian SSR of 1989-03-22 ...
+# according to the Act No.227 of the Council of Ministers of USSR of 1989-03-14
+# ...: since the last Sunday of March 1989 in Lithuanian SSR, Latvian SSR,
+# Estonian SSR and Kaliningrad region of Russian Federation all year round the
+# time of 2nd time zone (Moscow time minus one hour). On the territory of Latvia
+# transition to summer time is performed on the last Sunday of March at 02:00
+# (GMT 00:00), turning the hands of the clock 1 hour forward. The end of
+# daylight saving time is performed on the last Sunday of September at 03:00
+# (GMT 00:00), turning the hands of the clock 1 hour backward. Exception is
+# 1989-03-26, when we must not turn the hands of the clock....
+#
+# The Regulations of the Cabinet of Ministers of the Republic of Latvia of
+# 1997-01-21 on transition to Summer time ... established the same order of
+# daylight savings time settings as in the States of the European Union.
+
+# From Andrei Ivanov (2000-03-06):
+# This year Latvia will not switch to Daylight Savings Time (as specified in
+# <a href="http://www.lv-laiks.lv/wwwraksti/2000/071072/vd4.htm">
+# The Regulations of the Cabinet of Ministers of the Rep. of Latvia of
+# 29-Feb-2000 (#79)</a>, in Latvian for subscribers only).
+
+# <a href="http://www.rferl.org/newsline/2001/01/3-CEE/cee-030101.html">
+# From RFE/RL Newsline (2001-01-03), noted after a heads-up by Rives McDow:
+# </a>
+# The Latvian government on 2 January decided that the country will
+# institute daylight-saving time this spring, LETA reported.
+# Last February the three Baltic states decided not to turn back their
+# clocks one hour in the spring....
+# Minister of Economy Aigars Kalvitis noted that Latvia had too few
+# daylight hours and thus decided to comply with a draft European
+# Commission directive that provides for instituting daylight-saving
+# time in EU countries between 2002 and 2006. The Latvian government
+# urged Lithuania and Estonia to adopt a similar time policy, but it
+# appears that they will not do so....
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Latvia 1989 1996 - Mar lastSun 2:00s 1:00 S
+Rule Latvia 1989 1996 - Sep lastSun 2:00s 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Riga 1:36:24 - LMT 1880
+ 1:36:24 - RMT 1918 Apr 15 2:00 #Riga Mean Time
+ 1:36:24 1:00 LST 1918 Sep 16 3:00 #Latvian Summer
+ 1:36:24 - RMT 1919 Apr 1 2:00
+ 1:36:24 1:00 LST 1919 May 22 3:00
+ 1:36:24 - RMT 1926 May 11
+ 2:00 - EET 1940 Aug 5
+ 3:00 - MSK 1941 Jul
+ 1:00 C-Eur CE%sT 1944 Oct 13
+ 3:00 Russia MSK/MSD 1989 Mar lastSun 2:00s
+ 2:00 1:00 EEST 1989 Sep lastSun 2:00s
+ 2:00 Latvia EE%sT 1997 Jan 21
+ 2:00 EU EE%sT 2000 Feb 29
+ 2:00 - EET 2001 Jan 2
+ 2:00 EU EE%sT
+
+# Liechtenstein
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Vaduz 0:38:04 - LMT 1894 Jun
+ 1:00 - CET 1981
+ 1:00 EU CE%sT
+
+# Lithuania
+
+# From Paul Eggert (1996-11-22):
+# IATA SSIM (1992/1996) says Lithuania uses W-Eur rules, but since it is
+# known to be wrong about Estonia and Latvia, assume it's wrong here too.
+
+# From Marius Gedminas (1998-08-07):
+# I would like to inform that in this year Lithuanian time zone
+# (Europe/Vilnius) was changed.
+
+# From <a href="http://www.elta.lt/">ELTA</a> No. 972 (2582) (1999-09-29),
+# via Steffen Thorsen:
+# Lithuania has shifted back to the second time zone (GMT plus two hours)
+# to be valid here starting from October 31,
+# as decided by the national government on Wednesday....
+# The Lithuanian government also announced plans to consider a
+# motion to give up shifting to summer time in spring, as it was
+# already done by Estonia.
+
+# From the <a href="http://www.tourism.lt/informa/ff.htm">
+# Fact File, Lithuanian State Department of Tourism
+# </a> (2000-03-27): Local time is GMT+2 hours ..., no daylight saving.
+
+# From a user via Klaus Marten (2003-02-07):
+# As a candidate for membership of the European Union, Lithuania will
+# observe Summer Time in 2003, changing its clocks at the times laid
+# down in EU Directive 2000/84 of 19.I.01 (i.e. at the same times as its
+# neighbour Latvia). The text of the Lithuanian government Order of
+# 7.XI.02 to this effect can be found at
+# http://www.lrvk.lt/nut/11/n1749.htm
+
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Vilnius 1:41:16 - LMT 1880
+ 1:24:00 - WMT 1917 # Warsaw Mean Time
+ 1:35:36 - KMT 1919 Oct 10 # Kaunas Mean Time
+ 1:00 - CET 1920 Jul 12
+ 2:00 - EET 1920 Oct 9
+ 1:00 - CET 1940 Aug 3
+ 3:00 - MSK 1941 Jun 24
+ 1:00 C-Eur CE%sT 1944 Aug
+ 3:00 Russia MSK/MSD 1991 Mar 31 2:00s
+ 2:00 1:00 EEST 1991 Sep 29 2:00s
+ 2:00 C-Eur EE%sT 1998
+ 2:00 - EET 1998 Mar 29 1:00u
+ 1:00 EU CE%sT 1999 Oct 31 1:00u
+ 2:00 - EET 2003 Jan 1
+ 2:00 EU EE%sT
+
+# Luxembourg
+# Whitman disagrees with most of these dates in minor ways;
+# go with Shanks & Pottenger.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Lux 1916 only - May 14 23:00 1:00 S
+Rule Lux 1916 only - Oct 1 1:00 0 -
+Rule Lux 1917 only - Apr 28 23:00 1:00 S
+Rule Lux 1917 only - Sep 17 1:00 0 -
+Rule Lux 1918 only - Apr Mon>=15 2:00s 1:00 S
+Rule Lux 1918 only - Sep Mon>=15 2:00s 0 -
+Rule Lux 1919 only - Mar 1 23:00 1:00 S
+Rule Lux 1919 only - Oct 5 3:00 0 -
+Rule Lux 1920 only - Feb 14 23:00 1:00 S
+Rule Lux 1920 only - Oct 24 2:00 0 -
+Rule Lux 1921 only - Mar 14 23:00 1:00 S
+Rule Lux 1921 only - Oct 26 2:00 0 -
+Rule Lux 1922 only - Mar 25 23:00 1:00 S
+Rule Lux 1922 only - Oct Sun>=2 1:00 0 -
+Rule Lux 1923 only - Apr 21 23:00 1:00 S
+Rule Lux 1923 only - Oct Sun>=2 2:00 0 -
+Rule Lux 1924 only - Mar 29 23:00 1:00 S
+Rule Lux 1924 1928 - Oct Sun>=2 1:00 0 -
+Rule Lux 1925 only - Apr 5 23:00 1:00 S
+Rule Lux 1926 only - Apr 17 23:00 1:00 S
+Rule Lux 1927 only - Apr 9 23:00 1:00 S
+Rule Lux 1928 only - Apr 14 23:00 1:00 S
+Rule Lux 1929 only - Apr 20 23:00 1:00 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Luxembourg 0:24:36 - LMT 1904 Jun
+ 1:00 Lux CE%sT 1918 Nov 25
+ 0:00 Lux WE%sT 1929 Oct 6 2:00s
+ 0:00 Belgium WE%sT 1940 May 14 3:00
+ 1:00 C-Eur WE%sT 1944 Sep 18 3:00
+ 1:00 Belgium CE%sT 1977
+ 1:00 EU CE%sT
+
+# Macedonia
+# see Serbia
+
+# Malta
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Malta 1973 only - Mar 31 0:00s 1:00 S
+Rule Malta 1973 only - Sep 29 0:00s 0 -
+Rule Malta 1974 only - Apr 21 0:00s 1:00 S
+Rule Malta 1974 only - Sep 16 0:00s 0 -
+Rule Malta 1975 1979 - Apr Sun>=15 2:00 1:00 S
+Rule Malta 1975 1980 - Sep Sun>=15 2:00 0 -
+Rule Malta 1980 only - Mar 31 2:00 1:00 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Malta 0:58:04 - LMT 1893 Nov 2 0:00s # Valletta
+ 1:00 Italy CE%sT 1942 Nov 2 2:00s
+ 1:00 C-Eur CE%sT 1945 Apr 2 2:00s
+ 1:00 Italy CE%sT 1973 Mar 31
+ 1:00 Malta CE%sT 1981
+ 1:00 EU CE%sT
+
+# Moldova
+
+# From Paul Eggert (2006-03-22):
+# A previous version of this database followed Shanks & Pottenger, who write
+# that Tiraspol switched to Moscow time on 1992-01-19 at 02:00.
+# However, this is most likely an error, as Moldova declared independence
+# on 1991-08-27 (the 1992-01-19 date is that of a Russian decree).
+# In early 1992 there was large-scale interethnic violence in the area
+# and it's possible that some Russophones continued to observe Moscow time.
+# But [two people] separately reported via
+# Jesper Norgaard that as of 2001-01-24 Tiraspol was like Chisinau.
+# The Tiraspol entry has therefore been removed for now.
+#
+# From Alexander Krivenyshev (2011-10-17):
+# Pridnestrovian Moldavian Republic (PMR, also known as
+# "Pridnestrovie") has abolished seasonal clock change (no transition
+# to the Winter Time).
+#
+# News (in Russian):
+# <a href="http://www.kyivpost.ua/russia/news/pridnestrove-otkazalos-ot-perehoda-na-zimnee-vremya-30954.html">
+# http://www.kyivpost.ua/russia/news/pridnestrove-otkazalos-ot-perehoda-na-zimnee-vremya-30954.html
+# </a>
+#
+# <a href="http://www.allmoldova.com/moldova-news/1249064116.html">
+# http://www.allmoldova.com/moldova-news/1249064116.html
+# </a>
+#
+# The substance of this change (reinstatement of the Tiraspol entry)
+# is from a patch from Petr Machata (2011-10-17)
+#
+# From Tim Parenti (2011-10-19)
+# In addition, being situated at +4651+2938 would give Tiraspol
+# a pre-1880 LMT offset of 1:58:32.
+#
+# (which agrees with the earlier entry that had been removed)
+#
+# From Alexander Krivenyshev (2011-10-26)
+# NO need to divide Moldova into two timezones at this point.
+# As of today, Transnistria (Pridnestrovie)- Tiraspol reversed its own
+# decision to abolish DST this winter.
+# Following Moldova and neighboring Ukraine- Transnistria (Pridnestrovie)-
+# Tiraspol will go back to winter time on October 30, 2011.
+# News from Moldova (in russian):
+# <a href="http://ru.publika.md/link_317061.html">
+# http://ru.publika.md/link_317061.html
+# </a>
+
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Chisinau 1:55:20 - LMT 1880
+ 1:55 - CMT 1918 Feb 15 # Chisinau MT
+ 1:44:24 - BMT 1931 Jul 24 # Bucharest MT
+ 2:00 Romania EE%sT 1940 Aug 15
+ 2:00 1:00 EEST 1941 Jul 17
+ 1:00 C-Eur CE%sT 1944 Aug 24
+ 3:00 Russia MSK/MSD 1990
+ 3:00 - MSK 1990 May 6
+ 2:00 - EET 1991
+ 2:00 Russia EE%sT 1992
+ 2:00 E-Eur EE%sT 1997
+# See Romania commentary for the guessed 1997 transition to EU rules.
+ 2:00 EU EE%sT
+
+# Monaco
+# Shanks & Pottenger give 0:09:20 for Paris Mean Time; go with Howse's
+# more precise 0:09:21.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Monaco 0:29:32 - LMT 1891 Mar 15
+ 0:09:21 - PMT 1911 Mar 11 # Paris Mean Time
+ 0:00 France WE%sT 1945 Sep 16 3:00
+ 1:00 France CE%sT 1977
+ 1:00 EU CE%sT
+
+# Montenegro
+# see Serbia
+
+# Netherlands
+
+# Howse writes that the Netherlands' railways used GMT between 1892 and 1940,
+# but for other purposes the Netherlands used Amsterdam mean time.
+
+# However, Robert H. van Gent writes (2001-04-01):
+# Howse's statement is only correct up to 1909. From 1909-05-01 (00:00:00
+# Amsterdam mean time) onwards, the whole of the Netherlands (including
+# the Dutch railways) was required by law to observe Amsterdam mean time
+# (19 minutes 32.13 seconds ahead of GMT). This had already been the
+# common practice (except for the railways) for many decades but it was
+# not until 1909 when the Dutch government finally defined this by law.
+# On 1937-07-01 this was changed to 20 minutes (exactly) ahead of GMT and
+# was generally known as Dutch Time ("Nederlandse Tijd").
+#
+# (2001-04-08):
+# 1892-05-01 was the date when the Dutch railways were by law required to
+# observe GMT while the remainder of the Netherlands adhered to the common
+# practice of following Amsterdam mean time.
+#
+# (2001-04-09):
+# In 1835 the authorities of the province of North Holland requested the
+# municipal authorities of the towns and cities in the province to observe
+# Amsterdam mean time but I do not know in how many cases this request was
+# actually followed.
+#
+# From 1852 onwards the Dutch telegraph offices were by law required to
+# observe Amsterdam mean time. As the time signals from the observatory of
+# Leiden were also distributed by the telegraph system, I assume that most
+# places linked up with the telegraph (and railway) system automatically
+# adopted Amsterdam mean time.
+#
+# Although the early Dutch railway companies initially observed a variety
+# of times, most of them had adopted Amsterdam mean time by 1858 but it
+# was not until 1866 when they were all required by law to observe
+# Amsterdam mean time.
+
+# The data before 1945 are taken from
+# <http://www.phys.uu.nl/~vgent/wettijd/wettijd.htm>.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Neth 1916 only - May 1 0:00 1:00 NST # Netherlands Summer Time
+Rule Neth 1916 only - Oct 1 0:00 0 AMT # Amsterdam Mean Time
+Rule Neth 1917 only - Apr 16 2:00s 1:00 NST
+Rule Neth 1917 only - Sep 17 2:00s 0 AMT
+Rule Neth 1918 1921 - Apr Mon>=1 2:00s 1:00 NST
+Rule Neth 1918 1921 - Sep lastMon 2:00s 0 AMT
+Rule Neth 1922 only - Mar lastSun 2:00s 1:00 NST
+Rule Neth 1922 1936 - Oct Sun>=2 2:00s 0 AMT
+Rule Neth 1923 only - Jun Fri>=1 2:00s 1:00 NST
+Rule Neth 1924 only - Mar lastSun 2:00s 1:00 NST
+Rule Neth 1925 only - Jun Fri>=1 2:00s 1:00 NST
+# From 1926 through 1939 DST began 05-15, except that it was delayed by a week
+# in years when 05-15 fell in the Pentecost weekend.
+Rule Neth 1926 1931 - May 15 2:00s 1:00 NST
+Rule Neth 1932 only - May 22 2:00s 1:00 NST
+Rule Neth 1933 1936 - May 15 2:00s 1:00 NST
+Rule Neth 1937 only - May 22 2:00s 1:00 NST
+Rule Neth 1937 only - Jul 1 0:00 1:00 S
+Rule Neth 1937 1939 - Oct Sun>=2 2:00s 0 -
+Rule Neth 1938 1939 - May 15 2:00s 1:00 S
+Rule Neth 1945 only - Apr 2 2:00s 1:00 S
+Rule Neth 1945 only - Sep 16 2:00s 0 -
+#
+# Amsterdam Mean Time was +00:19:32.13 exactly, but the .13 is omitted
+# below because the current format requires GMTOFF to be an integer.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Amsterdam 0:19:32 - LMT 1835
+ 0:19:32 Neth %s 1937 Jul 1
+ 0:20 Neth NE%sT 1940 May 16 0:00 # Dutch Time
+ 1:00 C-Eur CE%sT 1945 Apr 2 2:00
+ 1:00 Neth CE%sT 1977
+ 1:00 EU CE%sT
+
+# Norway
+# http://met.no/met/met_lex/q_u/sommertid.html (2004-01) agrees with Shanks &
+# Pottenger.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Norway 1916 only - May 22 1:00 1:00 S
+Rule Norway 1916 only - Sep 30 0:00 0 -
+Rule Norway 1945 only - Apr 2 2:00s 1:00 S
+Rule Norway 1945 only - Oct 1 2:00s 0 -
+Rule Norway 1959 1964 - Mar Sun>=15 2:00s 1:00 S
+Rule Norway 1959 1965 - Sep Sun>=15 2:00s 0 -
+Rule Norway 1965 only - Apr 25 2:00s 1:00 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Oslo 0:43:00 - LMT 1895 Jan 1
+ 1:00 Norway CE%sT 1940 Aug 10 23:00
+ 1:00 C-Eur CE%sT 1945 Apr 2 2:00
+ 1:00 Norway CE%sT 1980
+ 1:00 EU CE%sT
+
+# Svalbard & Jan Mayen
+
+# From Steffen Thorsen (2001-05-01):
+# Although I could not find it explicitly, it seems that Jan Mayen and
+# Svalbard have been using the same time as Norway at least since the
+# time they were declared as parts of Norway. Svalbard was declared
+# as a part of Norway by law of 1925-07-17 no 11, section 4 and Jan
+# Mayen by law of 1930-02-27 no 2, section 2. (From
+# http://www.lovdata.no/all/nl-19250717-011.html and
+# http://www.lovdata.no/all/nl-19300227-002.html). The law/regulation
+# for normal/standard time in Norway is from 1894-06-29 no 1 (came
+# into operation on 1895-01-01) and Svalbard/Jan Mayen seem to be a
+# part of this law since 1925/1930. (From
+# http://www.lovdata.no/all/nl-18940629-001.html ) I have not been
+# able to find if Jan Mayen used a different time zone (e.g. -0100)
+# before 1930. Jan Mayen has only been "inhabitated" since 1921 by
+# Norwegian meteorologists and maybe used the same time as Norway ever
+# since 1921. Svalbard (Arctic/Longyearbyen) has been inhabited since
+# before 1895, and therefore probably changed the local time somewhere
+# between 1895 and 1925 (inclusive).
+
+# From Paul Eggert (2001-05-01):
+#
+# Actually, Jan Mayen was never occupied by Germany during World War II,
+# so it must have diverged from Oslo time during the war, as Oslo was
+# keeping Berlin time.
+#
+# <http://home.no.net/janmayen/history.htm> says that the meteorologists
+# burned down their station in 1940 and left the island, but returned in
+# 1941 with a small Norwegian garrison and continued operations despite
+# frequent air ttacks from Germans. In 1943 the Americans established a
+# radiolocating station on the island, called "Atlantic City". Possibly
+# the UTC offset changed during the war, but I think it unlikely that
+# Jan Mayen used German daylight-saving rules.
+#
+# Svalbard is more complicated, as it was raided in August 1941 by an
+# Allied party that evacuated the civilian population to England (says
+# <http://www.bartleby.com/65/sv/Svalbard.html>). The Svalbard FAQ
+# <http://www.svalbard.com/SvalbardFAQ.html> says that the Germans were
+# expelled on 1942-05-14. However, small parties of Germans did return,
+# and according to Wilhelm Dege's book "War North of 80" (1954)
+# <http://www.ucalgary.ca/UofC/departments/UP/1-55238/1-55238-110-2.html>
+# the German armed forces at the Svalbard weather station code-named
+# Haudegen did not surrender to the Allies until September 1945.
+#
+# All these events predate our cutoff date of 1970. Unless we can
+# come up with more definitive info about the timekeeping during the
+# war years it's probably best just do...the following for now:
+Link Europe/Oslo Arctic/Longyearbyen
+
+# Poland
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Poland 1918 1919 - Sep 16 2:00s 0 -
+Rule Poland 1919 only - Apr 15 2:00s 1:00 S
+Rule Poland 1944 only - Apr 3 2:00s 1:00 S
+# Whitman gives 1944 Nov 30; go with Shanks & Pottenger.
+Rule Poland 1944 only - Oct 4 2:00 0 -
+# For 1944-1948 Whitman gives the previous day; go with Shanks & Pottenger.
+Rule Poland 1945 only - Apr 29 0:00 1:00 S
+Rule Poland 1945 only - Nov 1 0:00 0 -
+# For 1946 on the source is Kazimierz Borkowski,
+# Torun Center for Astronomy, Dept. of Radio Astronomy, Nicolaus Copernicus U.,
+# <http://www.astro.uni.torun.pl/~kb/Artykuly/U-PA/Czas2.htm#tth_tAb1>
+# Thanks to Przemyslaw Augustyniak (2005-05-28) for this reference.
+# He also gives these further references:
+# Mon Pol nr 13, poz 162 (1995) <http://www.abc.com.pl/serwis/mp/1995/0162.htm>
+# Druk nr 2180 (2003) <http://www.senat.gov.pl/k5/dok/sejm/053/2180.pdf>
+Rule Poland 1946 only - Apr 14 0:00s 1:00 S
+Rule Poland 1946 only - Oct 7 2:00s 0 -
+Rule Poland 1947 only - May 4 2:00s 1:00 S
+Rule Poland 1947 1949 - Oct Sun>=1 2:00s 0 -
+Rule Poland 1948 only - Apr 18 2:00s 1:00 S
+Rule Poland 1949 only - Apr 10 2:00s 1:00 S
+Rule Poland 1957 only - Jun 2 1:00s 1:00 S
+Rule Poland 1957 1958 - Sep lastSun 1:00s 0 -
+Rule Poland 1958 only - Mar 30 1:00s 1:00 S
+Rule Poland 1959 only - May 31 1:00s 1:00 S
+Rule Poland 1959 1961 - Oct Sun>=1 1:00s 0 -
+Rule Poland 1960 only - Apr 3 1:00s 1:00 S
+Rule Poland 1961 1964 - May lastSun 1:00s 1:00 S
+Rule Poland 1962 1964 - Sep lastSun 1:00s 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Warsaw 1:24:00 - LMT 1880
+ 1:24:00 - WMT 1915 Aug 5 # Warsaw Mean Time
+ 1:00 C-Eur CE%sT 1918 Sep 16 3:00
+ 2:00 Poland EE%sT 1922 Jun
+ 1:00 Poland CE%sT 1940 Jun 23 2:00
+ 1:00 C-Eur CE%sT 1944 Oct
+ 1:00 Poland CE%sT 1977
+ 1:00 W-Eur CE%sT 1988
+ 1:00 EU CE%sT
+
+# Portugal
+#
+# From Rui Pedro Salgueiro (1992-11-12):
+# Portugal has recently (September, 27) changed timezone
+# (from WET to MET or CET) to harmonize with EEC.
+#
+# Martin Bruckmann (1996-02-29) reports via Peter Ilieve
+# that Portugal is reverting to 0:00 by not moving its clocks this spring.
+# The new Prime Minister was fed up with getting up in the dark in the winter.
+#
+# From Paul Eggert (1996-11-12):
+# IATA SSIM (1991-09) reports several 1991-09 and 1992-09 transitions
+# at 02:00u, not 01:00u. Assume that these are typos.
+# IATA SSIM (1991/1992) reports that the Azores were at -1:00.
+# IATA SSIM (1993-02) says +0:00; later issues (through 1996-09) say -1:00.
+# Guess that the Azores changed to EU rules in 1992 (since that's when Portugal
+# harmonized with the EU), and that they stayed +0:00 that winter.
+#
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+# DSH writes that despite Decree 1,469 (1915), the change to the clocks was not
+# done every year, depending on what Spain did, because of railroad schedules.
+# Go with Shanks & Pottenger.
+Rule Port 1916 only - Jun 17 23:00 1:00 S
+# Whitman gives 1916 Oct 31; go with Shanks & Pottenger.
+Rule Port 1916 only - Nov 1 1:00 0 -
+Rule Port 1917 only - Feb 28 23:00s 1:00 S
+Rule Port 1917 1921 - Oct 14 23:00s 0 -
+Rule Port 1918 only - Mar 1 23:00s 1:00 S
+Rule Port 1919 only - Feb 28 23:00s 1:00 S
+Rule Port 1920 only - Feb 29 23:00s 1:00 S
+Rule Port 1921 only - Feb 28 23:00s 1:00 S
+Rule Port 1924 only - Apr 16 23:00s 1:00 S
+Rule Port 1924 only - Oct 14 23:00s 0 -
+Rule Port 1926 only - Apr 17 23:00s 1:00 S
+Rule Port 1926 1929 - Oct Sat>=1 23:00s 0 -
+Rule Port 1927 only - Apr 9 23:00s 1:00 S
+Rule Port 1928 only - Apr 14 23:00s 1:00 S
+Rule Port 1929 only - Apr 20 23:00s 1:00 S
+Rule Port 1931 only - Apr 18 23:00s 1:00 S
+# Whitman gives 1931 Oct 8; go with Shanks & Pottenger.
+Rule Port 1931 1932 - Oct Sat>=1 23:00s 0 -
+Rule Port 1932 only - Apr 2 23:00s 1:00 S
+Rule Port 1934 only - Apr 7 23:00s 1:00 S
+# Whitman gives 1934 Oct 5; go with Shanks & Pottenger.
+Rule Port 1934 1938 - Oct Sat>=1 23:00s 0 -
+# Shanks & Pottenger give 1935 Apr 30; go with Whitman.
+Rule Port 1935 only - Mar 30 23:00s 1:00 S
+Rule Port 1936 only - Apr 18 23:00s 1:00 S
+# Whitman gives 1937 Apr 2; go with Shanks & Pottenger.
+Rule Port 1937 only - Apr 3 23:00s 1:00 S
+Rule Port 1938 only - Mar 26 23:00s 1:00 S
+Rule Port 1939 only - Apr 15 23:00s 1:00 S
+# Whitman gives 1939 Oct 7; go with Shanks & Pottenger.
+Rule Port 1939 only - Nov 18 23:00s 0 -
+Rule Port 1940 only - Feb 24 23:00s 1:00 S
+# Shanks & Pottenger give 1940 Oct 7; go with Whitman.
+Rule Port 1940 1941 - Oct 5 23:00s 0 -
+Rule Port 1941 only - Apr 5 23:00s 1:00 S
+Rule Port 1942 1945 - Mar Sat>=8 23:00s 1:00 S
+Rule Port 1942 only - Apr 25 22:00s 2:00 M # Midsummer
+Rule Port 1942 only - Aug 15 22:00s 1:00 S
+Rule Port 1942 1945 - Oct Sat>=24 23:00s 0 -
+Rule Port 1943 only - Apr 17 22:00s 2:00 M
+Rule Port 1943 1945 - Aug Sat>=25 22:00s 1:00 S
+Rule Port 1944 1945 - Apr Sat>=21 22:00s 2:00 M
+Rule Port 1946 only - Apr Sat>=1 23:00s 1:00 S
+Rule Port 1946 only - Oct Sat>=1 23:00s 0 -
+Rule Port 1947 1949 - Apr Sun>=1 2:00s 1:00 S
+Rule Port 1947 1949 - Oct Sun>=1 2:00s 0 -
+# Shanks & Pottenger say DST was observed in 1950; go with Whitman.
+# Whitman gives Oct lastSun for 1952 on; go with Shanks & Pottenger.
+Rule Port 1951 1965 - Apr Sun>=1 2:00s 1:00 S
+Rule Port 1951 1965 - Oct Sun>=1 2:00s 0 -
+Rule Port 1977 only - Mar 27 0:00s 1:00 S
+Rule Port 1977 only - Sep 25 0:00s 0 -
+Rule Port 1978 1979 - Apr Sun>=1 0:00s 1:00 S
+Rule Port 1978 only - Oct 1 0:00s 0 -
+Rule Port 1979 1982 - Sep lastSun 1:00s 0 -
+Rule Port 1980 only - Mar lastSun 0:00s 1:00 S
+Rule Port 1981 1982 - Mar lastSun 1:00s 1:00 S
+Rule Port 1983 only - Mar lastSun 2:00s 1:00 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+# Shanks & Pottenger say the transition from LMT to WET occurred 1911-05-24;
+# Willett says 1912-01-01. Go with Willett.
+Zone Europe/Lisbon -0:36:32 - LMT 1884
+ -0:36:32 - LMT 1912 Jan 1 # Lisbon Mean Time
+ 0:00 Port WE%sT 1966 Apr 3 2:00
+ 1:00 - CET 1976 Sep 26 1:00
+ 0:00 Port WE%sT 1983 Sep 25 1:00s
+ 0:00 W-Eur WE%sT 1992 Sep 27 1:00s
+ 1:00 EU CE%sT 1996 Mar 31 1:00u
+ 0:00 EU WE%sT
+Zone Atlantic/Azores -1:42:40 - LMT 1884 # Ponta Delgada
+ -1:54:32 - HMT 1911 May 24 # Horta Mean Time
+ -2:00 Port AZO%sT 1966 Apr 3 2:00 # Azores Time
+ -1:00 Port AZO%sT 1983 Sep 25 1:00s
+ -1:00 W-Eur AZO%sT 1992 Sep 27 1:00s
+ 0:00 EU WE%sT 1993 Mar 28 1:00u
+ -1:00 EU AZO%sT
+Zone Atlantic/Madeira -1:07:36 - LMT 1884 # Funchal
+ -1:07:36 - FMT 1911 May 24 # Funchal Mean Time
+ -1:00 Port MAD%sT 1966 Apr 3 2:00 # Madeira Time
+ 0:00 Port WE%sT 1983 Sep 25 1:00s
+ 0:00 EU WE%sT
+
+# Romania
+#
+# From Paul Eggert (1999-10-07):
+# <a href="http://www.nineoclock.ro/POL/1778pol.html">
+# Nine O'clock</a> (1998-10-23) reports that the switch occurred at
+# 04:00 local time in fall 1998. For lack of better info,
+# assume that Romania and Moldova switched to EU rules in 1997,
+# the same year as Bulgaria.
+#
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Romania 1932 only - May 21 0:00s 1:00 S
+Rule Romania 1932 1939 - Oct Sun>=1 0:00s 0 -
+Rule Romania 1933 1939 - Apr Sun>=2 0:00s 1:00 S
+Rule Romania 1979 only - May 27 0:00 1:00 S
+Rule Romania 1979 only - Sep lastSun 0:00 0 -
+Rule Romania 1980 only - Apr 5 23:00 1:00 S
+Rule Romania 1980 only - Sep lastSun 1:00 0 -
+Rule Romania 1991 1993 - Mar lastSun 0:00s 1:00 S
+Rule Romania 1991 1993 - Sep lastSun 0:00s 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Bucharest 1:44:24 - LMT 1891 Oct
+ 1:44:24 - BMT 1931 Jul 24 # Bucharest MT
+ 2:00 Romania EE%sT 1981 Mar 29 2:00s
+ 2:00 C-Eur EE%sT 1991
+ 2:00 Romania EE%sT 1994
+ 2:00 E-Eur EE%sT 1997
+ 2:00 EU EE%sT
+
+# Russia
+
+# From Paul Eggert (2006-03-22):
+# Except for Moscow after 1919-07-01, I invented the time zone abbreviations.
+# Moscow time zone abbreviations after 1919-07-01, and Moscow rules after 1991,
+# are from Andrey A. Chernov. The rest is from Shanks & Pottenger,
+# except we follow Chernov's report that 1992 DST transitions were Sat
+# 23:00, not Sun 02:00s.
+#
+# From Stanislaw A. Kuzikowski (1994-06-29):
+# But now it is some months since Novosibirsk is 3 hours ahead of Moscow!
+# I do not know why they have decided to make this change;
+# as far as I remember it was done exactly during winter->summer switching
+# so we (Novosibirsk) simply did not switch.
+#
+# From Andrey A. Chernov (1996-10-04):
+# `MSK' and `MSD' were born and used initially on Moscow computers with
+# UNIX-like OSes by several developer groups (e.g. Demos group, Kiae group)....
+# The next step was the UUCP network, the Relcom predecessor
+# (used mainly for mail), and MSK/MSD was actively used there.
+#
+# From Chris Carrier (1996-10-30):
+# According to a friend of mine who rode the Trans-Siberian Railroad from
+# Moscow to Irkutsk in 1995, public air and rail transport in Russia ...
+# still follows Moscow time, no matter where in Russia it is located.
+#
+# For Grozny, Chechnya, we have the following story from
+# John Daniszewski, "Scavengers in the Rubble", Los Angeles Times (2001-02-07):
+# News--often false--is spread by word of mouth. A rumor that it was
+# time to move the clocks back put this whole city out of sync with
+# the rest of Russia for two weeks--even soldiers stationed here began
+# enforcing curfew at the wrong time.
+#
+# From Gwillim Law (2001-06-05):
+# There's considerable evidence that Sakhalin Island used to be in
+# UTC+11, and has changed to UTC+10, in this decade. I start with the
+# SSIM, which listed Yuzhno-Sakhalinsk in zone RU10 along with Magadan
+# until February 1997, and then in RU9 with Khabarovsk and Vladivostok
+# since September 1997.... Although the Kuril Islands are
+# administratively part of Sakhalin oblast', they appear to have
+# remained on UTC+11 along with Magadan.
+#
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+#
+# Kaliningradskaya oblast'.
+Zone Europe/Kaliningrad 1:22:00 - LMT 1893 Apr
+ 1:00 C-Eur CE%sT 1945
+ 2:00 Poland CE%sT 1946
+ 3:00 Russia MSK/MSD 1991 Mar 31 2:00s
+ 2:00 Russia EE%sT 2011 Mar 27 2:00s
+ 3:00 - FET # Further-eastern European Time
+#
+# From Oscar van Vlijmen (2001-08-25): [This region consists of]
+# Respublika Adygeya, Arkhangel'skaya oblast',
+# Belgorodskaya oblast', Bryanskaya oblast', Vladimirskaya oblast',
+# Vologodskaya oblast', Voronezhskaya oblast',
+# Respublika Dagestan, Ivanovskaya oblast', Respublika Ingushetiya,
+# Kabarbino-Balkarskaya Respublika, Respublika Kalmykiya,
+# Kalyzhskaya oblast', Respublika Karachaevo-Cherkessiya,
+# Respublika Kareliya, Respublika Komi,
+# Kostromskaya oblast', Krasnodarskij kraj, Kurskaya oblast',
+# Leningradskaya oblast', Lipetskaya oblast', Respublika Marij El,
+# Respublika Mordoviya, Moskva, Moskovskaya oblast',
+# Murmanskaya oblast', Nenetskij avtonomnyj okrug,
+# Nizhegorodskaya oblast', Novgorodskaya oblast', Orlovskaya oblast',
+# Penzenskaya oblast', Pskovskaya oblast', Rostovskaya oblast',
+# Ryazanskaya oblast', Sankt-Peterburg,
+# Respublika Severnaya Osetiya, Smolenskaya oblast',
+# Stavropol'skij kraj, Tambovskaya oblast', Respublika Tatarstan,
+# Tverskaya oblast', Tyl'skaya oblast', Ul'yanovskaya oblast',
+# Chechenskaya Respublika, Chuvashskaya oblast',
+# Yaroslavskaya oblast'
+Zone Europe/Moscow 2:30:20 - LMT 1880
+ 2:30 - MMT 1916 Jul 3 # Moscow Mean Time
+ 2:30:48 Russia %s 1919 Jul 1 2:00
+ 3:00 Russia MSK/MSD 1922 Oct
+ 2:00 - EET 1930 Jun 21
+ 3:00 Russia MSK/MSD 1991 Mar 31 2:00s
+ 2:00 Russia EE%sT 1992 Jan 19 2:00s
+ 3:00 Russia MSK/MSD 2011 Mar 27 2:00s
+ 4:00 - MSK
+#
+# Astrakhanskaya oblast', Kirovskaya oblast', Saratovskaya oblast',
+# Volgogradskaya oblast'. Shanks & Pottenger say Kirov is still at +0400
+# but Wikipedia (2006-05-09) says +0300. Perhaps it switched after the
+# others? But we have no data.
+Zone Europe/Volgograd 2:57:40 - LMT 1920 Jan 3
+ 3:00 - TSAT 1925 Apr 6 # Tsaritsyn Time
+ 3:00 - STAT 1930 Jun 21 # Stalingrad Time
+ 4:00 - STAT 1961 Nov 11
+ 4:00 Russia VOL%sT 1989 Mar 26 2:00s # Volgograd T
+ 3:00 Russia VOL%sT 1991 Mar 31 2:00s
+ 4:00 - VOLT 1992 Mar 29 2:00s
+ 3:00 Russia VOL%sT 2011 Mar 27 2:00s
+ 4:00 - VOLT
+#
+# From Oscar van Vlijmen (2001-08-25): [This region consists of]
+# Samarskaya oblast', Udmyrtskaya respublika
+Zone Europe/Samara 3:20:36 - LMT 1919 Jul 1 2:00
+ 3:00 - SAMT 1930 Jun 21
+ 4:00 - SAMT 1935 Jan 27
+ 4:00 Russia KUY%sT 1989 Mar 26 2:00s # Kuybyshev
+ 3:00 Russia KUY%sT 1991 Mar 31 2:00s
+ 2:00 Russia KUY%sT 1991 Sep 29 2:00s
+ 3:00 - KUYT 1991 Oct 20 3:00
+ 4:00 Russia SAM%sT 2010 Mar 28 2:00s # Samara Time
+ 3:00 Russia SAM%sT 2011 Mar 27 2:00s
+ 4:00 - SAMT
+
+#
+# From Oscar van Vlijmen (2001-08-25): [This region consists of]
+# Respublika Bashkortostan, Komi-Permyatskij avtonomnyj okrug,
+# Kurganskaya oblast', Orenburgskaya oblast', Permskaya oblast',
+# Sverdlovskaya oblast', Tyumenskaya oblast',
+# Khanty-Manskijskij avtonomnyj okrug, Chelyabinskaya oblast',
+# Yamalo-Nenetskij avtonomnyj okrug.
+Zone Asia/Yekaterinburg 4:02:24 - LMT 1919 Jul 15 4:00
+ 4:00 - SVET 1930 Jun 21 # Sverdlovsk Time
+ 5:00 Russia SVE%sT 1991 Mar 31 2:00s
+ 4:00 Russia SVE%sT 1992 Jan 19 2:00s
+ 5:00 Russia YEK%sT 2011 Mar 27 2:00s
+ 6:00 - YEKT # Yekaterinburg Time
+#
+# From Oscar van Vlijmen (2001-08-25): [This region consists of]
+# Respublika Altaj, Altajskij kraj, Omskaya oblast'.
+Zone Asia/Omsk 4:53:36 - LMT 1919 Nov 14
+ 5:00 - OMST 1930 Jun 21 # Omsk TIme
+ 6:00 Russia OMS%sT 1991 Mar 31 2:00s
+ 5:00 Russia OMS%sT 1992 Jan 19 2:00s
+ 6:00 Russia OMS%sT 2011 Mar 27 2:00s
+ 7:00 - OMST
+#
+# From Paul Eggert (2006-08-19): I'm guessing about Tomsk here; it's
+# not clear when it switched from +7 to +6.
+# Novosibirskaya oblast', Tomskaya oblast'.
+Zone Asia/Novosibirsk 5:31:40 - LMT 1919 Dec 14 6:00
+ 6:00 - NOVT 1930 Jun 21 # Novosibirsk Time
+ 7:00 Russia NOV%sT 1991 Mar 31 2:00s
+ 6:00 Russia NOV%sT 1992 Jan 19 2:00s
+ 7:00 Russia NOV%sT 1993 May 23 # say Shanks & P.
+ 6:00 Russia NOV%sT 2011 Mar 27 2:00s
+ 7:00 - NOVT
+
+# From Alexander Krivenyshev (2009-10-13):
+# Kemerovo oblast' (Kemerovo region) in Russia will change current time zone on
+# March 28, 2010:
+# from current Russia Zone 6 - Krasnoyarsk Time Zone (KRA) UTC +0700
+# to Russia Zone 5 - Novosibirsk Time Zone (NOV) UTC +0600
+#
+# This is according to Government of Russia decree # 740, on September
+# 14, 2009 "Application in the territory of the Kemerovo region the Fifth
+# time zone." ("Russia Zone 5" or old "USSR Zone 5" is GMT +0600)
+#
+# Russian Government web site (Russian language)
+# <a href="http://www.government.ru/content/governmentactivity/rfgovernmentdecisions/archiv">
+# http://www.government.ru/content/governmentactivity/rfgovernmentdecisions/archive/2009/09/14/991633.htm
+# </a>
+# or Russian-English translation by WorldTimeZone.com with reference
+# map to local region and new Russia Time Zone map after March 28, 2010
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_russia03.html">
+# http://www.worldtimezone.com/dst_news/dst_news_russia03.html
+# </a>
+#
+# Thus, when Russia will switch to DST on the night of March 28, 2010
+# Kemerovo region (Kemerovo oblast') will not change the clock.
+#
+# As a result, Kemerovo oblast' will be in the same time zone as
+# Novosibirsk, Omsk, Tomsk, Barnaul and Altai Republic.
+
+Zone Asia/Novokuznetsk 5:48:48 - NMT 1920 Jan 6
+ 6:00 - KRAT 1930 Jun 21 # Krasnoyarsk Time
+ 7:00 Russia KRA%sT 1991 Mar 31 2:00s
+ 6:00 Russia KRA%sT 1992 Jan 19 2:00s
+ 7:00 Russia KRA%sT 2010 Mar 28 2:00s
+ 6:00 Russia NOV%sT 2011 Mar 27 2:00s
+ 7:00 - NOVT # Novosibirsk/Novokuznetsk Time
+
+#
+# From Oscar van Vlijmen (2001-08-25): [This region consists of]
+# Krasnoyarskij kraj,
+# Tajmyrskij (Dolgano-Nenetskij) avtonomnyj okrug,
+# Respublika Tuva, Respublika Khakasiya, Evenkijskij avtonomnyj okrug.
+Zone Asia/Krasnoyarsk 6:11:20 - LMT 1920 Jan 6
+ 6:00 - KRAT 1930 Jun 21 # Krasnoyarsk Time
+ 7:00 Russia KRA%sT 1991 Mar 31 2:00s
+ 6:00 Russia KRA%sT 1992 Jan 19 2:00s
+ 7:00 Russia KRA%sT 2011 Mar 27 2:00s
+ 8:00 - KRAT
+#
+# From Oscar van Vlijmen (2001-08-25): [This region consists of]
+# Respublika Buryatiya, Irkutskaya oblast',
+# Ust'-Ordynskij Buryatskij avtonomnyj okrug.
+Zone Asia/Irkutsk 6:57:20 - LMT 1880
+ 6:57:20 - IMT 1920 Jan 25 # Irkutsk Mean Time
+ 7:00 - IRKT 1930 Jun 21 # Irkutsk Time
+ 8:00 Russia IRK%sT 1991 Mar 31 2:00s
+ 7:00 Russia IRK%sT 1992 Jan 19 2:00s
+ 8:00 Russia IRK%sT 2011 Mar 27 2:00s
+ 9:00 - IRKT
+#
+# From Oscar van Vlijmen (2003-10-18): [This region consists of]
+# Aginskij Buryatskij avtonomnyj okrug, Amurskaya oblast',
+# [parts of] Respublika Sakha (Yakutiya), Chitinskaya oblast'.
+
+# From Oscar van Vlijmen (2009-11-29):
+# ...some regions of [Russia] were merged with others since 2005...
+# Some names were changed, no big deal, except for one instance: a new name.
+# YAK/YAKST: UTC+9 Zabajkal'skij kraj.
+
+# From Oscar van Vlijmen (2009-11-29):
+# The Sakha districts are: Aldanskij, Amginskij, Anabarskij,
+# Verkhnevilyujskij, Vilyujskij, Gornyj,
+# Zhiganskij, Kobyajskij, Lenskij, Megino-Kangalasskij, Mirninskij,
+# Namskij, Nyurbinskij, Olenyokskij, Olyokminskij,
+# Suntarskij, Tattinskij, Ust'-Aldanskij, Khangalasskij,
+# Churapchinskij, Eveno-Bytantajskij Natsional'nij.
+
+Zone Asia/Yakutsk 8:38:40 - LMT 1919 Dec 15
+ 8:00 - YAKT 1930 Jun 21 # Yakutsk Time
+ 9:00 Russia YAK%sT 1991 Mar 31 2:00s
+ 8:00 Russia YAK%sT 1992 Jan 19 2:00s
+ 9:00 Russia YAK%sT 2011 Mar 27 2:00s
+ 10:00 - YAKT
+#
+# From Oscar van Vlijmen (2003-10-18): [This region consists of]
+# Evrejskaya avtonomnaya oblast', Khabarovskij kraj, Primorskij kraj,
+# [parts of] Respublika Sakha (Yakutiya).
+
+# From Oscar van Vlijmen (2009-11-29):
+# The Sakha districts are: Bulunskij, Verkhoyanskij, Tomponskij, Ust'-Majskij,
+# Ust'-Yanskij.
+Zone Asia/Vladivostok 8:47:44 - LMT 1922 Nov 15
+ 9:00 - VLAT 1930 Jun 21 # Vladivostok Time
+ 10:00 Russia VLA%sT 1991 Mar 31 2:00s
+ 9:00 Russia VLA%sST 1992 Jan 19 2:00s
+ 10:00 Russia VLA%sT 2011 Mar 27 2:00s
+ 11:00 - VLAT
+#
+# Sakhalinskaya oblast'.
+# The Zone name should be Yuzhno-Sakhalinsk, but that's too long.
+Zone Asia/Sakhalin 9:30:48 - LMT 1905 Aug 23
+ 9:00 - CJT 1938
+ 9:00 - JST 1945 Aug 25
+ 11:00 Russia SAK%sT 1991 Mar 31 2:00s # Sakhalin T.
+ 10:00 Russia SAK%sT 1992 Jan 19 2:00s
+ 11:00 Russia SAK%sT 1997 Mar lastSun 2:00s
+ 10:00 Russia SAK%sT 2011 Mar 27 2:00s
+ 11:00 - SAKT
+#
+# From Oscar van Vlijmen (2003-10-18): [This region consists of]
+# Magadanskaya oblast', Respublika Sakha (Yakutiya).
+# Probably also: Kuril Islands.
+
+# From Oscar van Vlijmen (2009-11-29):
+# The Sakha districts are: Abyjskij, Allaikhovskij, Verkhhhnekolymskij, Momskij,
+# Nizhnekolymskij, Ojmyakonskij, Srednekolymskij.
+Zone Asia/Magadan 10:03:12 - LMT 1924 May 2
+ 10:00 - MAGT 1930 Jun 21 # Magadan Time
+ 11:00 Russia MAG%sT 1991 Mar 31 2:00s
+ 10:00 Russia MAG%sT 1992 Jan 19 2:00s
+ 11:00 Russia MAG%sT 2011 Mar 27 2:00s
+ 12:00 - MAGT
+#
+# From Oscar van Vlijmen (2001-08-25): [This region consists of]
+# Kamchatskaya oblast', Koryakskij avtonomnyj okrug.
+#
+# The Zone name should be Asia/Petropavlovsk-Kamchatski, but that's too long.
+Zone Asia/Kamchatka 10:34:36 - LMT 1922 Nov 10
+ 11:00 - PETT 1930 Jun 21 # P-K Time
+ 12:00 Russia PET%sT 1991 Mar 31 2:00s
+ 11:00 Russia PET%sT 1992 Jan 19 2:00s
+ 12:00 Russia PET%sT 2010 Mar 28 2:00s
+ 11:00 Russia PET%sT 2011 Mar 27 2:00s
+ 12:00 - PETT
+#
+# Chukotskij avtonomnyj okrug
+Zone Asia/Anadyr 11:49:56 - LMT 1924 May 2
+ 12:00 - ANAT 1930 Jun 21 # Anadyr Time
+ 13:00 Russia ANA%sT 1982 Apr 1 0:00s
+ 12:00 Russia ANA%sT 1991 Mar 31 2:00s
+ 11:00 Russia ANA%sT 1992 Jan 19 2:00s
+ 12:00 Russia ANA%sT 2010 Mar 28 2:00s
+ 11:00 Russia ANA%sT 2011 Mar 27 2:00s
+ 12:00 - ANAT
+
+# Serbia
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Belgrade 1:22:00 - LMT 1884
+ 1:00 - CET 1941 Apr 18 23:00
+ 1:00 C-Eur CE%sT 1945
+ 1:00 - CET 1945 May 8 2:00s
+ 1:00 1:00 CEST 1945 Sep 16 2:00s
+# Metod Kozelj reports that the legal date of
+# transition to EU rules was 1982-11-27, for all of Yugoslavia at the time.
+# Shanks & Pottenger don't give as much detail, so go with Kozelj.
+ 1:00 - CET 1982 Nov 27
+ 1:00 EU CE%sT
+Link Europe/Belgrade Europe/Ljubljana # Slovenia
+Link Europe/Belgrade Europe/Podgorica # Montenegro
+Link Europe/Belgrade Europe/Sarajevo # Bosnia and Herzegovina
+Link Europe/Belgrade Europe/Skopje # Macedonia
+Link Europe/Belgrade Europe/Zagreb # Croatia
+
+# Slovakia
+Link Europe/Prague Europe/Bratislava
+
+# Slovenia
+# see Serbia
+
+# Spain
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+# For 1917-1919 Whitman gives Apr Sat>=1 - Oct Sat>=1;
+# go with Shanks & Pottenger.
+Rule Spain 1917 only - May 5 23:00s 1:00 S
+Rule Spain 1917 1919 - Oct 6 23:00s 0 -
+Rule Spain 1918 only - Apr 15 23:00s 1:00 S
+Rule Spain 1919 only - Apr 5 23:00s 1:00 S
+# Whitman gives 1921 Feb 28 - Oct 14; go with Shanks & Pottenger.
+Rule Spain 1924 only - Apr 16 23:00s 1:00 S
+# Whitman gives 1924 Oct 14; go with Shanks & Pottenger.
+Rule Spain 1924 only - Oct 4 23:00s 0 -
+Rule Spain 1926 only - Apr 17 23:00s 1:00 S
+# Whitman says no DST in 1929; go with Shanks & Pottenger.
+Rule Spain 1926 1929 - Oct Sat>=1 23:00s 0 -
+Rule Spain 1927 only - Apr 9 23:00s 1:00 S
+Rule Spain 1928 only - Apr 14 23:00s 1:00 S
+Rule Spain 1929 only - Apr 20 23:00s 1:00 S
+# Whitman gives 1937 Jun 16, 1938 Apr 16, 1940 Apr 13;
+# go with Shanks & Pottenger.
+Rule Spain 1937 only - May 22 23:00s 1:00 S
+Rule Spain 1937 1939 - Oct Sat>=1 23:00s 0 -
+Rule Spain 1938 only - Mar 22 23:00s 1:00 S
+Rule Spain 1939 only - Apr 15 23:00s 1:00 S
+Rule Spain 1940 only - Mar 16 23:00s 1:00 S
+# Whitman says no DST 1942-1945; go with Shanks & Pottenger.
+Rule Spain 1942 only - May 2 22:00s 2:00 M # Midsummer
+Rule Spain 1942 only - Sep 1 22:00s 1:00 S
+Rule Spain 1943 1946 - Apr Sat>=13 22:00s 2:00 M
+Rule Spain 1943 only - Oct 3 22:00s 1:00 S
+Rule Spain 1944 only - Oct 10 22:00s 1:00 S
+Rule Spain 1945 only - Sep 30 1:00 1:00 S
+Rule Spain 1946 only - Sep 30 0:00 0 -
+Rule Spain 1949 only - Apr 30 23:00 1:00 S
+Rule Spain 1949 only - Sep 30 1:00 0 -
+Rule Spain 1974 1975 - Apr Sat>=13 23:00 1:00 S
+Rule Spain 1974 1975 - Oct Sun>=1 1:00 0 -
+Rule Spain 1976 only - Mar 27 23:00 1:00 S
+Rule Spain 1976 1977 - Sep lastSun 1:00 0 -
+Rule Spain 1977 1978 - Apr 2 23:00 1:00 S
+Rule Spain 1978 only - Oct 1 1:00 0 -
+# The following rules are copied from Morocco from 1967 through 1978.
+Rule SpainAfrica 1967 only - Jun 3 12:00 1:00 S
+Rule SpainAfrica 1967 only - Oct 1 0:00 0 -
+Rule SpainAfrica 1974 only - Jun 24 0:00 1:00 S
+Rule SpainAfrica 1974 only - Sep 1 0:00 0 -
+Rule SpainAfrica 1976 1977 - May 1 0:00 1:00 S
+Rule SpainAfrica 1976 only - Aug 1 0:00 0 -
+Rule SpainAfrica 1977 only - Sep 28 0:00 0 -
+Rule SpainAfrica 1978 only - Jun 1 0:00 1:00 S
+Rule SpainAfrica 1978 only - Aug 4 0:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Madrid -0:14:44 - LMT 1901 Jan 1 0:00s
+ 0:00 Spain WE%sT 1946 Sep 30
+ 1:00 Spain CE%sT 1979
+ 1:00 EU CE%sT
+Zone Africa/Ceuta -0:21:16 - LMT 1901
+ 0:00 - WET 1918 May 6 23:00
+ 0:00 1:00 WEST 1918 Oct 7 23:00
+ 0:00 - WET 1924
+ 0:00 Spain WE%sT 1929
+ 0:00 SpainAfrica WE%sT 1984 Mar 16
+ 1:00 - CET 1986
+ 1:00 EU CE%sT
+Zone Atlantic/Canary -1:01:36 - LMT 1922 Mar # Las Palmas de Gran C.
+ -1:00 - CANT 1946 Sep 30 1:00 # Canaries Time
+ 0:00 - WET 1980 Apr 6 0:00s
+ 0:00 1:00 WEST 1980 Sep 28 0:00s
+ 0:00 EU WE%sT
+# IATA SSIM (1996-09) says the Canaries switch at 2:00u, not 1:00u.
+# Ignore this for now, as the Canaries are part of the EU.
+
+# Sweden
+
+# From Ivan Nilsson (2001-04-13), superseding Shanks & Pottenger:
+#
+# The law "Svensk forfattningssamling 1878, no 14" about standard time in 1879:
+# From the beginning of 1879 (that is 01-01 00:00) the time for all
+# places in the country is "the mean solar time for the meridian at
+# three degrees, or twelve minutes of time, to the west of the
+# meridian of the Observatory of Stockholm". The law is dated 1878-05-31.
+#
+# The observatory at that time had the meridian 18 degrees 03' 30"
+# eastern longitude = 01:12:14 in time. Less 12 minutes gives the
+# national standard time as 01:00:14 ahead of GMT....
+#
+# About the beginning of CET in Sweden. The lawtext ("Svensk
+# forfattningssamling 1899, no 44") states, that "from the beginning
+# of 1900... ... the same as the mean solar time for the meridian at
+# the distance of one hour of time from the meridian of the English
+# observatory at Greenwich, or at 12 minutes 14 seconds to the west
+# from the meridian of the Observatory of Stockholm". The law is dated
+# 1899-06-16. In short: At 1900-01-01 00:00:00 the new standard time
+# in Sweden is 01:00:00 ahead of GMT.
+#
+# 1916: The lawtext ("Svensk forfattningssamling 1916, no 124") states
+# that "1916-05-15 is considered to begin one hour earlier". It is
+# pretty obvious that at 05-14 23:00 the clocks are set to 05-15 00:00....
+# Further the law says, that "1916-09-30 is considered to end one hour later".
+#
+# The laws regulating [DST] are available on the site of the Swedish
+# Parliament beginning with 1985 - the laws regulating 1980/1984 are
+# not available on the site (to my knowledge they are only available
+# in Swedish): <http://www.riksdagen.se/english/work/sfst.asp> (type
+# "sommartid" without the quotes in the field "Fritext" and then click
+# the Sok-button).
+#
+# (2001-05-13):
+#
+# I have now found a newspaper stating that at 1916-10-01 01:00
+# summertime the church-clocks etc were set back one hour to show
+# 1916-10-01 00:00 standard time. The article also reports that some
+# people thought the switch to standard time would take place already
+# at 1916-10-01 00:00 summer time, but they had to wait for another
+# hour before the event took place.
+#
+# Source: The newspaper "Dagens Nyheter", 1916-10-01, page 7 upper left.
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Stockholm 1:12:12 - LMT 1879 Jan 1
+ 1:00:14 - SET 1900 Jan 1 # Swedish Time
+ 1:00 - CET 1916 May 14 23:00
+ 1:00 1:00 CEST 1916 Oct 1 01:00
+ 1:00 - CET 1980
+ 1:00 EU CE%sT
+
+# Switzerland
+# From Howse:
+# By the end of the 18th century clocks and watches became commonplace
+# and their performance improved enormously. Communities began to keep
+# mean time in preference to apparent time -- Geneva from 1780 ....
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+# From Whitman (who writes ``Midnight?''):
+# Rule Swiss 1940 only - Nov 2 0:00 1:00 S
+# Rule Swiss 1940 only - Dec 31 0:00 0 -
+# From Shanks & Pottenger:
+# Rule Swiss 1941 1942 - May Sun>=1 2:00 1:00 S
+# Rule Swiss 1941 1942 - Oct Sun>=1 0:00 0 -
+
+# From Alois Treindl (2008-12-17):
+# I have researched the DST usage in Switzerland during the 1940ies.
+#
+# As I wrote in an earlier message, I suspected the current tzdata values
+# to be wrong. This is now verified.
+#
+# I have found copies of the original ruling by the Swiss Federal
+# government, in 'Eidgen[o]ssische Gesetzessammlung 1941 and 1942' (Swiss
+# federal law collection)...
+#
+# DST began on Monday 5 May 1941, 1:00 am by shifting the clocks to 2:00 am
+# DST ended on Monday 6 Oct 1941, 2:00 am by shifting the clocks to 1:00 am.
+#
+# DST began on Monday, 4 May 1942 at 01:00 am
+# DST ended on Monday, 5 Oct 1942 at 02:00 am
+#
+# There was no DST in 1940, I have checked the law collection carefully.
+# It is also indicated by the fact that the 1942 entry in the law
+# collection points back to 1941 as a reference, but no reference to any
+# other years are made.
+#
+# Newspaper articles I have read in the archives on 6 May 1941 reported
+# about the introduction of DST (Sommerzeit in German) during the previous
+# night as an absolute novelty, because this was the first time that such
+# a thing had happened in Switzerland.
+#
+# I have also checked 1916, because one book source (Gabriel, Traite de
+# l'heure dans le monde) claims that Switzerland had DST in 1916. This is
+# false, no official document could be found. Probably Gabriel got misled
+# by references to Germany, which introduced DST in 1916 for the first time.
+#
+# The tzdata rules for Switzerland must be changed to:
+# Rule Swiss 1941 1942 - May Mon>=1 1:00 1:00 S
+# Rule Swiss 1941 1942 - Oct Mon>=1 2:00 0 -
+#
+# The 1940 rules must be deleted.
+#
+# One further detail for Switzerland, which is probably out of scope for
+# most users of tzdata:
+# The zone file
+# Zone Europe/Zurich 0:34:08 - LMT 1848 Sep 12
+# 0:29:44 - BMT 1894 Jun #Bern Mean Time
+# 1:00 Swiss CE%sT 1981
+# 1:00 EU CE%sT
+# describes all of Switzerland correctly, with the exception of
+# the Cantone Geneve (Geneva, Genf). Between 1848 and 1894 Geneve did not
+# follow Bern Mean Time but kept its own local mean time.
+# To represent this, an extra zone would be needed.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Swiss 1941 1942 - May Mon>=1 1:00 1:00 S
+Rule Swiss 1941 1942 - Oct Mon>=1 2:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Zurich 0:34:08 - LMT 1848 Sep 12
+ 0:29:44 - BMT 1894 Jun # Bern Mean Time
+ 1:00 Swiss CE%sT 1981
+ 1:00 EU CE%sT
+
+# Turkey
+
+# From Amar Devegowda (2007-01-03):
+# The time zone rules for Istanbul, Turkey have not been changed for years now.
+# ... The latest rules are available at -
+# http://www.timeanddate.com/worldclock/timezone.html?n=107
+# From Steffen Thorsen (2007-01-03):
+# I have been able to find press records back to 1996 which all say that
+# DST started 01:00 local time and end at 02:00 local time. I am not sure
+# what happened before that. One example for each year from 1996 to 2001:
+# http://newspot.byegm.gov.tr/arsiv/1996/21/N4.htm
+# http://www.byegm.gov.tr/YAYINLARIMIZ/CHR/ING97/03/97X03X25.TXT
+# http://www.byegm.gov.tr/YAYINLARIMIZ/CHR/ING98/03/98X03X02.HTM
+# http://www.byegm.gov.tr/YAYINLARIMIZ/CHR/ING99/10/99X10X26.HTM#%2016
+# http://www.byegm.gov.tr/YAYINLARIMIZ/CHR/ING2000/03/00X03X06.HTM#%2021
+# http://www.byegm.gov.tr/YAYINLARIMIZ/CHR/ING2001/03/23x03x01.HTM#%2027
+# From Paul Eggert (2007-01-03):
+# Prefer the above source to Shanks & Pottenger for time stamps after 1990.
+
+# From Steffen Thorsen (2007-03-09):
+# Starting 2007 though, it seems that they are adopting EU's 1:00 UTC
+# start/end time, according to the following page (2007-03-07):
+# http://www.ntvmsnbc.com/news/402029.asp
+# The official document is located here - it is in Turkish...:
+# http://rega.basbakanlik.gov.tr/eskiler/2007/03/20070307-7.htm
+# I was able to locate the following seemingly official document
+# (on a non-government server though) describing dates between 2002 and 2006:
+# http://www.alomaliye.com/bkk_2002_3769.htm
+
+# From G&ouml;kdeniz Karada&#x011f; (2011-03-10):
+#
+# According to the articles linked below, Turkey will change into summer
+# time zone (GMT+3) on March 28, 2011 at 3:00 a.m. instead of March 27.
+# This change is due to a nationwide exam on 27th.
+#
+# <a href="http://www.worldbulletin.net/?aType=haber&ArticleID=70872">
+# http://www.worldbulletin.net/?aType=haber&ArticleID=70872
+# </a>
+# Turkish:
+# <a href="http://www.hurriyet.com.tr/ekonomi/17230464.asp?gid=373">
+# http://www.hurriyet.com.tr/ekonomi/17230464.asp?gid=373
+# </a>
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Turkey 1916 only - May 1 0:00 1:00 S
+Rule Turkey 1916 only - Oct 1 0:00 0 -
+Rule Turkey 1920 only - Mar 28 0:00 1:00 S
+Rule Turkey 1920 only - Oct 25 0:00 0 -
+Rule Turkey 1921 only - Apr 3 0:00 1:00 S
+Rule Turkey 1921 only - Oct 3 0:00 0 -
+Rule Turkey 1922 only - Mar 26 0:00 1:00 S
+Rule Turkey 1922 only - Oct 8 0:00 0 -
+# Whitman gives 1923 Apr 28 - Sep 16 and no DST in 1924-1925;
+# go with Shanks & Pottenger.
+Rule Turkey 1924 only - May 13 0:00 1:00 S
+Rule Turkey 1924 1925 - Oct 1 0:00 0 -
+Rule Turkey 1925 only - May 1 0:00 1:00 S
+Rule Turkey 1940 only - Jun 30 0:00 1:00 S
+Rule Turkey 1940 only - Oct 5 0:00 0 -
+Rule Turkey 1940 only - Dec 1 0:00 1:00 S
+Rule Turkey 1941 only - Sep 21 0:00 0 -
+Rule Turkey 1942 only - Apr 1 0:00 1:00 S
+# Whitman omits the next two transition and gives 1945 Oct 1;
+# go with Shanks & Pottenger.
+Rule Turkey 1942 only - Nov 1 0:00 0 -
+Rule Turkey 1945 only - Apr 2 0:00 1:00 S
+Rule Turkey 1945 only - Oct 8 0:00 0 -
+Rule Turkey 1946 only - Jun 1 0:00 1:00 S
+Rule Turkey 1946 only - Oct 1 0:00 0 -
+Rule Turkey 1947 1948 - Apr Sun>=16 0:00 1:00 S
+Rule Turkey 1947 1950 - Oct Sun>=2 0:00 0 -
+Rule Turkey 1949 only - Apr 10 0:00 1:00 S
+Rule Turkey 1950 only - Apr 19 0:00 1:00 S
+Rule Turkey 1951 only - Apr 22 0:00 1:00 S
+Rule Turkey 1951 only - Oct 8 0:00 0 -
+Rule Turkey 1962 only - Jul 15 0:00 1:00 S
+Rule Turkey 1962 only - Oct 8 0:00 0 -
+Rule Turkey 1964 only - May 15 0:00 1:00 S
+Rule Turkey 1964 only - Oct 1 0:00 0 -
+Rule Turkey 1970 1972 - May Sun>=2 0:00 1:00 S
+Rule Turkey 1970 1972 - Oct Sun>=2 0:00 0 -
+Rule Turkey 1973 only - Jun 3 1:00 1:00 S
+Rule Turkey 1973 only - Nov 4 3:00 0 -
+Rule Turkey 1974 only - Mar 31 2:00 1:00 S
+Rule Turkey 1974 only - Nov 3 5:00 0 -
+Rule Turkey 1975 only - Mar 30 0:00 1:00 S
+Rule Turkey 1975 1976 - Oct lastSun 0:00 0 -
+Rule Turkey 1976 only - Jun 1 0:00 1:00 S
+Rule Turkey 1977 1978 - Apr Sun>=1 0:00 1:00 S
+Rule Turkey 1977 only - Oct 16 0:00 0 -
+Rule Turkey 1979 1980 - Apr Sun>=1 3:00 1:00 S
+Rule Turkey 1979 1982 - Oct Mon>=11 0:00 0 -
+Rule Turkey 1981 1982 - Mar lastSun 3:00 1:00 S
+Rule Turkey 1983 only - Jul 31 0:00 1:00 S
+Rule Turkey 1983 only - Oct 2 0:00 0 -
+Rule Turkey 1985 only - Apr 20 0:00 1:00 S
+Rule Turkey 1985 only - Sep 28 0:00 0 -
+Rule Turkey 1986 1990 - Mar lastSun 2:00s 1:00 S
+Rule Turkey 1986 1990 - Sep lastSun 2:00s 0 -
+Rule Turkey 1991 2006 - Mar lastSun 1:00s 1:00 S
+Rule Turkey 1991 1995 - Sep lastSun 1:00s 0 -
+Rule Turkey 1996 2006 - Oct lastSun 1:00s 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Europe/Istanbul 1:55:52 - LMT 1880
+ 1:56:56 - IMT 1910 Oct # Istanbul Mean Time?
+ 2:00 Turkey EE%sT 1978 Oct 15
+ 3:00 Turkey TR%sT 1985 Apr 20 # Turkey Time
+ 2:00 Turkey EE%sT 2007
+ 2:00 EU EE%sT 2011 Mar 27 1:00u
+ 2:00 - EET 2011 Mar 28 1:00u
+ 2:00 EU EE%sT
+Link Europe/Istanbul Asia/Istanbul # Istanbul is in both continents.
+
+# Ukraine
+#
+# From Igor Karpov, who works for the Ukranian Ministry of Justice,
+# via Garrett Wollman (2003-01-27):
+# BTW, I've found the official document on this matter. It's goverment
+# regulations number 509, May 13, 1996. In my poor translation it says:
+# "Time in Ukraine is set to second timezone (Kiev time). Each last Sunday
+# of March at 3am the time is changing to 4am and each last Sunday of
+# October the time at 4am is changing to 3am"
+
+# From Alexander Krivenyshev (2011-09-20):
+# On September 20, 2011 the deputies of the Verkhovna Rada agreed to
+# abolish the transfer clock to winter time.
+#
+# Bill number 8330 of MP from the Party of Regions Oleg Nadoshi got
+# approval from 266 deputies.
+#
+# Ukraine abolishes transter back to the winter time (in Russian)
+# <a href="http://news.mail.ru/politics/6861560/">
+# http://news.mail.ru/politics/6861560/
+# </a>
+#
+# The Ukrainians will no longer change the clock (in Russian)
+# <a href="http://www.segodnya.ua/news/14290482.html">
+# http://www.segodnya.ua/news/14290482.html
+# </a>
+#
+# Deputies cancelled the winter time (in Russian)
+# <a href="http://www.pravda.com.ua/rus/news/2011/09/20/6600616/">
+# http://www.pravda.com.ua/rus/news/2011/09/20/6600616/
+# </a>
+#
+# From Philip Pizzey (2011-10-18):
+# Today my Ukrainian colleagues have informed me that the
+# Ukrainian parliament have decided that they will go to winter
+# time this year after all.
+#
+# From Udo Schwedt (2011-10-18):
+# As far as I understand, the recent change to the Ukranian time zone
+# (Europe/Kiev) to introduce permanent daylight saving time (similar
+# to Russia) was reverted today:
+#
+# <a href="http://portal.rada.gov.ua/rada/control/en/publish/article/info_left?art_id=287324&cat_id=105995">
+# http://portal.rada.gov.ua/rada/control/en/publish/article/info_left?art_id=287324&cat_id=105995
+# </a>
+#
+# Also reported by Alexander Bokovoy (2011-10-18) who also noted:
+# The law documents themselves are at
+#
+# <a href="http://w1.c1.rada.gov.ua/pls/zweb_n/webproc4_1?id=&pf3511=41484">
+# http://w1.c1.rada.gov.ua/pls/zweb_n/webproc4_1?id=&pf3511=41484
+# </a>
+
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+# Most of Ukraine since 1970 has been like Kiev.
+# "Kyiv" is the transliteration of the Ukrainian name, but
+# "Kiev" is more common in English.
+Zone Europe/Kiev 2:02:04 - LMT 1880
+ 2:02:04 - KMT 1924 May 2 # Kiev Mean Time
+ 2:00 - EET 1930 Jun 21
+ 3:00 - MSK 1941 Sep 20
+ 1:00 C-Eur CE%sT 1943 Nov 6
+ 3:00 Russia MSK/MSD 1990
+ 3:00 - MSK 1990 Jul 1 2:00
+ 2:00 - EET 1992
+ 2:00 E-Eur EE%sT 1995
+ 2:00 EU EE%sT
+# Ruthenia used CET 1990/1991.
+# "Uzhhorod" is the transliteration of the Ukrainian name, but
+# "Uzhgorod" is more common in English.
+Zone Europe/Uzhgorod 1:29:12 - LMT 1890 Oct
+ 1:00 - CET 1940
+ 1:00 C-Eur CE%sT 1944 Oct
+ 1:00 1:00 CEST 1944 Oct 26
+ 1:00 - CET 1945 Jun 29
+ 3:00 Russia MSK/MSD 1990
+ 3:00 - MSK 1990 Jul 1 2:00
+ 1:00 - CET 1991 Mar 31 3:00
+ 2:00 - EET 1992
+ 2:00 E-Eur EE%sT 1995
+ 2:00 EU EE%sT
+# Zaporozh'ye and eastern Lugansk oblasts observed DST 1990/1991.
+# "Zaporizhia" is the transliteration of the Ukrainian name, but
+# "Zaporozh'ye" is more common in English. Use the common English
+# spelling, except omit the apostrophe as it is not allowed in
+# portable Posix file names.
+Zone Europe/Zaporozhye 2:20:40 - LMT 1880
+ 2:20 - CUT 1924 May 2 # Central Ukraine T
+ 2:00 - EET 1930 Jun 21
+ 3:00 - MSK 1941 Aug 25
+ 1:00 C-Eur CE%sT 1943 Oct 25
+ 3:00 Russia MSK/MSD 1991 Mar 31 2:00
+ 2:00 E-Eur EE%sT 1995
+ 2:00 EU EE%sT
+# Central Crimea used Moscow time 1994/1997.
+Zone Europe/Simferopol 2:16:24 - LMT 1880
+ 2:16 - SMT 1924 May 2 # Simferopol Mean T
+ 2:00 - EET 1930 Jun 21
+ 3:00 - MSK 1941 Nov
+ 1:00 C-Eur CE%sT 1944 Apr 13
+ 3:00 Russia MSK/MSD 1990
+ 3:00 - MSK 1990 Jul 1 2:00
+ 2:00 - EET 1992
+# From Paul Eggert (2006-03-22):
+# The _Economist_ (1994-05-28, p 45) reports that central Crimea switched
+# from Kiev to Moscow time sometime after the January 1994 elections.
+# Shanks (1999) says ``date of change uncertain'', but implies that it happened
+# sometime between the 1994 DST switches. Shanks & Pottenger simply say
+# 1994-09-25 03:00, but that can't be right. For now, guess it
+# changed in May.
+ 2:00 E-Eur EE%sT 1994 May
+# From IATA SSIM (1994/1997), which also says that Kerch is still like Kiev.
+ 3:00 E-Eur MSK/MSD 1996 Mar 31 3:00s
+ 3:00 1:00 MSD 1996 Oct 27 3:00s
+# IATA SSIM (1997-09) says Crimea switched to EET/EEST.
+# Assume it happened in March by not changing the clocks.
+ 3:00 Russia MSK/MSD 1997
+ 3:00 - MSK 1997 Mar lastSun 1:00u
+ 2:00 EU EE%sT
+
+###############################################################################
+
+# One source shows that Bulgaria, Cyprus, Finland, and Greece observe DST from
+# the last Sunday in March to the last Sunday in September in 1986.
+# The source shows Romania changing a day later than everybody else.
+#
+# According to Bernard Sieloff's source, Poland is in the MET time zone but
+# uses the WE DST rules. The Western USSR uses EET+1 and ME DST rules.
+# Bernard Sieloff's source claims Romania switches on the same day, but at
+# 00:00 standard time (i.e., 01:00 DST). It also claims that Turkey
+# switches on the same day, but switches on at 01:00 standard time
+# and off at 00:00 standard time (i.e., 01:00 DST)
+
+# ...
+# Date: Wed, 28 Jan 87 16:56:27 -0100
+# From: Tom Hofmann
+# ...
+#
+# ...the European time rules are...standardized since 1981, when
+# most European coun[tr]ies started DST. Before that year, only
+# a few countries (UK, France, Italy) had DST, each according
+# to own national rules. In 1981, however, DST started on
+# 'Apr firstSun', and not on 'Mar lastSun' as in the following
+# years...
+# But also since 1981 there are some more national exceptions
+# than listed in 'europe': Switzerland, for example, joined DST
+# one year later, Denmark ended DST on 'Oct 1' instead of 'Sep
+# lastSun' in 1981---I don't know how they handle now.
+#
+# Finally, DST ist always from 'Apr 1' to 'Oct 1' in the
+# Soviet Union (as far as I know).
+#
+# Tom Hofmann, Scientific Computer Center, CIBA-GEIGY AG,
+# 4002 Basle, Switzerland
+# ...
+
+# ...
+# Date: Wed, 4 Feb 87 22:35:22 +0100
+# From: Dik T. Winter
+# ...
+#
+# The information from Tom Hofmann is (as far as I know) not entirely correct.
+# After a request from chongo at amdahl I tried to retrieve all information
+# about DST in Europe. I was able to find all from about 1969.
+#
+# ...standardization on DST in Europe started in about 1977 with switches on
+# first Sunday in April and last Sunday in September...
+# In 1981 UK joined Europe insofar that
+# the starting day for both shifted to last Sunday in March. And from 1982
+# the whole of Europe used DST, with switch dates April 1 and October 1 in
+# the Sov[i]et Union. In 1985 the SU reverted to standard Europe[a]n switch
+# dates...
+#
+# It should also be remembered that time-zones are not constants; e.g.
+# Portugal switched in 1976 from MET (or CET) to WET with DST...
+# Note also that though there were rules for switch dates not
+# all countries abided to these dates, and many individual deviations
+# occurred, though not since 1982 I believe. Another note: it is always
+# assumed that DST is 1 hour ahead of normal time, this need not be the
+# case; at least in the Netherlands there have been times when DST was 2 hours
+# in advance of normal time.
+#
+# ...
+# dik t. winter, cwi, amsterdam, nederland
+# ...
+
+# From Bob Devine (1988-01-28):
+# ...
+# Greece: Last Sunday in April to last Sunday in September (iffy on dates).
+# Since 1978. Change at midnight.
+# ...
+# Monaco: has same DST as France.
+# ...
diff --git a/misc/flot/examples/axes-time-zones/tz/factory b/misc/flot/examples/axes-time-zones/tz/factory
new file mode 100644
index 0000000..d29a585
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/factory
@@ -0,0 +1,10 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# For companies who don't want to put time zone specification in
+# their installation procedures. When users run date, they'll get the message.
+# Also useful for the "comp.sources" version.
+
+# Zone NAME GMTOFF RULES FORMAT
+Zone Factory 0 - "Local time zone must be set--see zic manual page"
diff --git a/misc/flot/examples/axes-time-zones/tz/iso3166.tab b/misc/flot/examples/axes-time-zones/tz/iso3166.tab
new file mode 100644
index 0000000..b952ca1
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/iso3166.tab
@@ -0,0 +1,276 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+# ISO 3166 alpha-2 country codes
+#
+# From Paul Eggert (2006-09-27):
+#
+# This file contains a table with the following columns:
+# 1. ISO 3166-1 alpha-2 country code, current as of
+# ISO 3166-1 Newsletter VI-1 (2007-09-21). See:
+# <a href="http://www.iso.org/iso/en/prods-services/iso3166ma/index.html">
+# ISO 3166 Maintenance agency (ISO 3166/MA)
+# </a>.
+# 2. The usual English name for the country,
+# chosen so that alphabetic sorting of subsets produces helpful lists.
+# This is not the same as the English name in the ISO 3166 tables.
+#
+# Columns are separated by a single tab.
+# The table is sorted by country code.
+#
+# Lines beginning with `#' are comments.
+#
+# From Arthur David Olson (2011-08-17):
+# Resynchronized today with the ISO 3166 site (adding SS for South Sudan).
+#
+#country-
+#code country name
+AD Andorra
+AE United Arab Emirates
+AF Afghanistan
+AG Antigua & Barbuda
+AI Anguilla
+AL Albania
+AM Armenia
+AO Angola
+AQ Antarctica
+AR Argentina
+AS Samoa (American)
+AT Austria
+AU Australia
+AW Aruba
+AX Aaland Islands
+AZ Azerbaijan
+BA Bosnia & Herzegovina
+BB Barbados
+BD Bangladesh
+BE Belgium
+BF Burkina Faso
+BG Bulgaria
+BH Bahrain
+BI Burundi
+BJ Benin
+BL St Barthelemy
+BM Bermuda
+BN Brunei
+BO Bolivia
+BQ Bonaire Sint Eustatius & Saba
+BR Brazil
+BS Bahamas
+BT Bhutan
+BV Bouvet Island
+BW Botswana
+BY Belarus
+BZ Belize
+CA Canada
+CC Cocos (Keeling) Islands
+CD Congo (Dem. Rep.)
+CF Central African Rep.
+CG Congo (Rep.)
+CH Switzerland
+CI Cote d'Ivoire
+CK Cook Islands
+CL Chile
+CM Cameroon
+CN China
+CO Colombia
+CR Costa Rica
+CU Cuba
+CV Cape Verde
+CW Curacao
+CX Christmas Island
+CY Cyprus
+CZ Czech Republic
+DE Germany
+DJ Djibouti
+DK Denmark
+DM Dominica
+DO Dominican Republic
+DZ Algeria
+EC Ecuador
+EE Estonia
+EG Egypt
+EH Western Sahara
+ER Eritrea
+ES Spain
+ET Ethiopia
+FI Finland
+FJ Fiji
+FK Falkland Islands
+FM Micronesia
+FO Faroe Islands
+FR France
+GA Gabon
+GB Britain (UK)
+GD Grenada
+GE Georgia
+GF French Guiana
+GG Guernsey
+GH Ghana
+GI Gibraltar
+GL Greenland
+GM Gambia
+GN Guinea
+GP Guadeloupe
+GQ Equatorial Guinea
+GR Greece
+GS South Georgia & the South Sandwich Islands
+GT Guatemala
+GU Guam
+GW Guinea-Bissau
+GY Guyana
+HK Hong Kong
+HM Heard Island & McDonald Islands
+HN Honduras
+HR Croatia
+HT Haiti
+HU Hungary
+ID Indonesia
+IE Ireland
+IL Israel
+IM Isle of Man
+IN India
+IO British Indian Ocean Territory
+IQ Iraq
+IR Iran
+IS Iceland
+IT Italy
+JE Jersey
+JM Jamaica
+JO Jordan
+JP Japan
+KE Kenya
+KG Kyrgyzstan
+KH Cambodia
+KI Kiribati
+KM Comoros
+KN St Kitts & Nevis
+KP Korea (North)
+KR Korea (South)
+KW Kuwait
+KY Cayman Islands
+KZ Kazakhstan
+LA Laos
+LB Lebanon
+LC St Lucia
+LI Liechtenstein
+LK Sri Lanka
+LR Liberia
+LS Lesotho
+LT Lithuania
+LU Luxembourg
+LV Latvia
+LY Libya
+MA Morocco
+MC Monaco
+MD Moldova
+ME Montenegro
+MF St Martin (French part)
+MG Madagascar
+MH Marshall Islands
+MK Macedonia
+ML Mali
+MM Myanmar (Burma)
+MN Mongolia
+MO Macau
+MP Northern Mariana Islands
+MQ Martinique
+MR Mauritania
+MS Montserrat
+MT Malta
+MU Mauritius
+MV Maldives
+MW Malawi
+MX Mexico
+MY Malaysia
+MZ Mozambique
+NA Namibia
+NC New Caledonia
+NE Niger
+NF Norfolk Island
+NG Nigeria
+NI Nicaragua
+NL Netherlands
+NO Norway
+NP Nepal
+NR Nauru
+NU Niue
+NZ New Zealand
+OM Oman
+PA Panama
+PE Peru
+PF French Polynesia
+PG Papua New Guinea
+PH Philippines
+PK Pakistan
+PL Poland
+PM St Pierre & Miquelon
+PN Pitcairn
+PR Puerto Rico
+PS Palestine
+PT Portugal
+PW Palau
+PY Paraguay
+QA Qatar
+RE Reunion
+RO Romania
+RS Serbia
+RU Russia
+RW Rwanda
+SA Saudi Arabia
+SB Solomon Islands
+SC Seychelles
+SD Sudan
+SE Sweden
+SG Singapore
+SH St Helena
+SI Slovenia
+SJ Svalbard & Jan Mayen
+SK Slovakia
+SL Sierra Leone
+SM San Marino
+SN Senegal
+SO Somalia
+SR Suriname
+SS South Sudan
+ST Sao Tome & Principe
+SV El Salvador
+SX Sint Maarten
+SY Syria
+SZ Swaziland
+TC Turks & Caicos Is
+TD Chad
+TF French Southern & Antarctic Lands
+TG Togo
+TH Thailand
+TJ Tajikistan
+TK Tokelau
+TL East Timor
+TM Turkmenistan
+TN Tunisia
+TO Tonga
+TR Turkey
+TT Trinidad & Tobago
+TV Tuvalu
+TW Taiwan
+TZ Tanzania
+UA Ukraine
+UG Uganda
+UM US minor outlying islands
+US United States
+UY Uruguay
+UZ Uzbekistan
+VA Vatican City
+VC St Vincent
+VE Venezuela
+VG Virgin Islands (UK)
+VI Virgin Islands (US)
+VN Vietnam
+VU Vanuatu
+WF Wallis & Futuna
+WS Samoa (western)
+YE Yemen
+YT Mayotte
+ZA South Africa
+ZM Zambia
+ZW Zimbabwe
diff --git a/misc/flot/examples/axes-time-zones/tz/leapseconds b/misc/flot/examples/axes-time-zones/tz/leapseconds
new file mode 100644
index 0000000..5b5c70e
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/leapseconds
@@ -0,0 +1,100 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# Allowance for leapseconds added to each timezone file.
+
+# The International Earth Rotation Service periodically uses leap seconds
+# to keep UTC to within 0.9 s of UT1
+# (which measures the true angular orientation of the earth in space); see
+# Terry J Quinn, The BIPM and the accurate measure of time,
+# Proc IEEE 79, 7 (July 1991), 894-905.
+# There were no leap seconds before 1972, because the official mechanism
+# accounting for the discrepancy between atomic time and the earth's rotation
+# did not exist until the early 1970s.
+
+# The correction (+ or -) is made at the given time, so lines
+# will typically look like:
+# Leap YEAR MON DAY 23:59:60 + R/S
+# or
+# Leap YEAR MON DAY 23:59:59 - R/S
+
+# If the leapsecond is Rolling (R) the given time is local time
+# If the leapsecond is Stationary (S) the given time is UTC
+
+# Leap YEAR MONTH DAY HH:MM:SS CORR R/S
+Leap 1972 Jun 30 23:59:60 + S
+Leap 1972 Dec 31 23:59:60 + S
+Leap 1973 Dec 31 23:59:60 + S
+Leap 1974 Dec 31 23:59:60 + S
+Leap 1975 Dec 31 23:59:60 + S
+Leap 1976 Dec 31 23:59:60 + S
+Leap 1977 Dec 31 23:59:60 + S
+Leap 1978 Dec 31 23:59:60 + S
+Leap 1979 Dec 31 23:59:60 + S
+Leap 1981 Jun 30 23:59:60 + S
+Leap 1982 Jun 30 23:59:60 + S
+Leap 1983 Jun 30 23:59:60 + S
+Leap 1985 Jun 30 23:59:60 + S
+Leap 1987 Dec 31 23:59:60 + S
+Leap 1989 Dec 31 23:59:60 + S
+Leap 1990 Dec 31 23:59:60 + S
+Leap 1992 Jun 30 23:59:60 + S
+Leap 1993 Jun 30 23:59:60 + S
+Leap 1994 Jun 30 23:59:60 + S
+Leap 1995 Dec 31 23:59:60 + S
+Leap 1997 Jun 30 23:59:60 + S
+Leap 1998 Dec 31 23:59:60 + S
+Leap 2005 Dec 31 23:59:60 + S
+Leap 2008 Dec 31 23:59:60 + S
+Leap 2012 Jun 30 23:59:60 + S
+
+# INTERNATIONAL EARTH ROTATION AND REFERENCE SYSTEMS SERVICE (IERS)
+#
+# SERVICE INTERNATIONAL DE LA ROTATION TERRESTRE ET DES SYSTEMES DE REFERENCE
+#
+#
+# SERVICE DE LA ROTATION TERRESTRE
+# OBSERVATOIRE DE PARIS
+# 61, Av. de l'Observatoire 75014 PARIS (France)
+# Tel. : 33 (0) 1 40 51 22 26
+# FAX : 33 (0) 1 40 51 22 91
+# e-mail : (E-Mail Removed)
+# http://hpiers.obspm.fr/eop-pc
+#
+# Paris, 5 January 2012
+#
+#
+# Bulletin C 43
+#
+# To authorities responsible
+# for the measurement and
+# distribution of time
+#
+#
+# UTC TIME STEP
+# on the 1st of July 2012
+#
+#
+# A positive leap second will be introduced at the end of June 2012.
+# The sequence of dates of the UTC second markers will be:
+#
+# 2012 June 30, 23h 59m 59s
+# 2012 June 30, 23h 59m 60s
+# 2012 July 1, 0h 0m 0s
+#
+# The difference between UTC and the International Atomic Time TAI is:
+#
+# from 2009 January 1, 0h UTC, to 2012 July 1 0h UTC : UTC-TAI = - 34s
+# from 2012 July 1, 0h UTC, until further notice : UTC-TAI = - 35s
+#
+# Leap seconds can be introduced in UTC at the end of the months of December
+# or June, depending on the evolution of UT1-TAI. Bulletin C is mailed every
+# six months, either to announce a time step in UTC or to confirm that there
+# will be no time step at the next possible date.
+#
+#
+# Daniel GAMBIS
+# Head
+# Earth Orientation Center of IERS
+# Observatoire de Paris, France
diff --git a/misc/flot/examples/axes-time-zones/tz/northamerica b/misc/flot/examples/axes-time-zones/tz/northamerica
new file mode 100644
index 0000000..772d7a4
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/northamerica
@@ -0,0 +1,3235 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# also includes Central America and the Caribbean
+
+# This data is by no means authoritative; if you think you know better,
+# go ahead and edit the file (and please send any changes to
+# tz@iana.org for general use in the future).
+
+# From Paul Eggert (1999-03-22):
+# A reliable and entertaining source about time zones is
+# Derek Howse, Greenwich time and longitude, Philip Wilson Publishers (1997).
+
+###############################################################################
+
+# United States
+
+# From Paul Eggert (1999-03-31):
+# Howse writes (pp 121-125) that time zones were invented by
+# Professor Charles Ferdinand Dowd (1825-1904),
+# Principal of Temple Grove Ladies' Seminary (Saratoga Springs, NY).
+# His pamphlet ``A System of National Time for Railroads'' (1870)
+# was the result of his proposals at the Convention of Railroad Trunk Lines
+# in New York City (1869-10). His 1870 proposal was based on Washington, DC,
+# but in 1872-05 he moved the proposed origin to Greenwich.
+# His proposal was adopted by the railroads on 1883-11-18 at 12:00,
+# and the most of the country soon followed suit.
+
+# From Paul Eggert (2005-04-16):
+# That 1883 transition occurred at 12:00 new time, not at 12:00 old time.
+# See p 46 of David Prerau, Seize the daylight, Thunder's Mouth Press (2005).
+
+# From Paul Eggert (2006-03-22):
+# A good source for time zone historical data in the US is
+# Thomas G. Shanks, The American Atlas (5th edition),
+# San Diego: ACS Publications, Inc. (1991).
+# Make sure you have the errata sheet; the book is somewhat useless without it.
+# It is the source for most of the pre-1991 US entries below.
+
+# From Paul Eggert (2001-03-06):
+# Daylight Saving Time was first suggested as a joke by Benjamin Franklin
+# in his whimsical essay ``An Economical Project for Diminishing the Cost
+# of Light'' published in the Journal de Paris (1784-04-26).
+# Not everyone is happy with the results:
+#
+# I don't really care how time is reckoned so long as there is some
+# agreement about it, but I object to being told that I am saving
+# daylight when my reason tells me that I am doing nothing of the kind.
+# I even object to the implication that I am wasting something
+# valuable if I stay in bed after the sun has risen. As an admirer
+# of moonlight I resent the bossy insistence of those who want to
+# reduce my time for enjoying it. At the back of the Daylight Saving
+# scheme I detect the bony, blue-fingered hand of Puritanism, eager
+# to push people into bed earlier, and get them up earlier, to make
+# them healthy, wealthy and wise in spite of themselves.
+#
+# -- Robertson Davies, The diary of Samuel Marchbanks,
+# Clarke, Irwin (1947), XIX, Sunday
+#
+# For more about the first ten years of DST in the United States, see
+# Robert Garland's <a href="http://www.clpgh.org/exhibit/dst.html">
+# Ten years of daylight saving from the Pittsburgh standpoint
+# (Carnegie Library of Pittsburgh, 1927)</a>.
+#
+# Shanks says that DST was called "War Time" in the US in 1918 and 1919.
+# However, DST was imposed by the Standard Time Act of 1918, which
+# was the first nationwide legal time standard, and apparently
+# time was just called "Standard Time" or "Daylight Saving Time".
+
+# From Arthur David Olson:
+# US Daylight Saving Time ended on the last Sunday of *October* in 1974.
+# See, for example, the front page of the Saturday, 1974-10-26
+# and Sunday, 1974-10-27 editions of the Washington Post.
+
+# From Arthur David Olson:
+# Before the Uniform Time Act of 1966 took effect in 1967, observance of
+# Daylight Saving Time in the US was by local option, except during wartime.
+
+# From Arthur David Olson (2000-09-25):
+# Last night I heard part of a rebroadcast of a 1945 Arch Oboler radio drama.
+# In the introduction, Oboler spoke of "Eastern Peace Time."
+# An AltaVista search turned up
+# <a href="http://rowayton.org/rhs/hstaug45.html">:
+# "When the time is announced over the radio now, it is 'Eastern Peace
+# Time' instead of the old familiar 'Eastern War Time.' Peace is wonderful."
+# </a> (August 1945) by way of confirmation.
+
+# From Joseph Gallant citing
+# George H. Douglas, _The Early Days of Radio Broadcasting_ (1987):
+# At 7 P.M. (Eastern War Time) [on 1945-08-14], the networks were set
+# to switch to London for Attlee's address, but the American people
+# never got to hear his speech live. According to one press account,
+# CBS' Bob Trout was first to announce the word of Japan's surrender,
+# but a few seconds later, NBC, ABC and Mutual also flashed the word
+# of surrender, all of whom interrupting the bells of Big Ben in
+# London which were to precede Mr. Attlee's speech.
+
+# From Paul Eggert (2003-02-09): It was Robert St John, not Bob Trout. From
+# Myrna Oliver's obituary of St John on page B16 of today's Los Angeles Times:
+#
+# ... a war-weary U.S. clung to radios, awaiting word of Japan's surrender.
+# Any announcement from Asia would reach St. John's New York newsroom on a
+# wire service teletype machine, which had prescribed signals for major news.
+# Associated Press, for example, would ring five bells before spewing out
+# typed copy of an important story, and 10 bells for news "of transcendental
+# importance."
+#
+# On Aug. 14, stalling while talking steadily into the NBC networks' open
+# microphone, St. John heard five bells and waited only to hear a sixth bell,
+# before announcing confidently: "Ladies and gentlemen, World War II is over.
+# The Japanese have agreed to our surrender terms."
+#
+# He had scored a 20-second scoop on other broadcasters.
+
+# From Arthur David Olson (2005-08-22):
+# Paul has been careful to use the "US" rules only in those locations
+# that are part of the United States; this reflects the real scope of
+# U.S. government action. So even though the "US" rules have changed
+# in the latest release, other countries won't be affected.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule US 1918 1919 - Mar lastSun 2:00 1:00 D
+Rule US 1918 1919 - Oct lastSun 2:00 0 S
+Rule US 1942 only - Feb 9 2:00 1:00 W # War
+Rule US 1945 only - Aug 14 23:00u 1:00 P # Peace
+Rule US 1945 only - Sep 30 2:00 0 S
+Rule US 1967 2006 - Oct lastSun 2:00 0 S
+Rule US 1967 1973 - Apr lastSun 2:00 1:00 D
+Rule US 1974 only - Jan 6 2:00 1:00 D
+Rule US 1975 only - Feb 23 2:00 1:00 D
+Rule US 1976 1986 - Apr lastSun 2:00 1:00 D
+Rule US 1987 2006 - Apr Sun>=1 2:00 1:00 D
+Rule US 2007 max - Mar Sun>=8 2:00 1:00 D
+Rule US 2007 max - Nov Sun>=1 2:00 0 S
+
+# From Arthur David Olson, 2005-12-19
+# We generate the files specified below to guard against old files with
+# obsolete information being left in the time zone binary directory.
+# We limit the list to names that have appeared in previous versions of
+# this time zone package.
+# We do these as separate Zones rather than as Links to avoid problems if
+# a particular place changes whether it observes DST.
+# We put these specifications here in the northamerica file both to
+# increase the chances that they'll actually get compiled and to
+# avoid the need to duplicate the US rules in another file.
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone EST -5:00 - EST
+Zone MST -7:00 - MST
+Zone HST -10:00 - HST
+Zone EST5EDT -5:00 US E%sT
+Zone CST6CDT -6:00 US C%sT
+Zone MST7MDT -7:00 US M%sT
+Zone PST8PDT -8:00 US P%sT
+
+# From Bob Devine (1988-01-28):
+# ...Alaska (and Hawaii) had the timezone names changed in 1967.
+# old new
+# Pacific Standard Time(PST) -same-
+# Yukon Standard Time(YST) -same-
+# Central Alaska S.T. (CAT) Alaska-Hawaii St[an]dard Time (AHST)
+# Nome Standard Time (NT) Bering Standard Time (BST)
+#
+# ...Alaska's timezone lines were redrawn in 1983 to give only 2 tz.
+# The YST zone now covers nearly all of the state, AHST just part
+# of the Aleutian islands. No DST.
+
+# From Paul Eggert (1995-12-19):
+# The tables below use `NST', not `NT', for Nome Standard Time.
+# I invented `CAWT' for Central Alaska War Time.
+
+# From U. S. Naval Observatory (1989-01-19):
+# USA EASTERN 5 H BEHIND UTC NEW YORK, WASHINGTON
+# USA EASTERN 4 H BEHIND UTC APR 3 - OCT 30
+# USA CENTRAL 6 H BEHIND UTC CHICAGO, HOUSTON
+# USA CENTRAL 5 H BEHIND UTC APR 3 - OCT 30
+# USA MOUNTAIN 7 H BEHIND UTC DENVER
+# USA MOUNTAIN 6 H BEHIND UTC APR 3 - OCT 30
+# USA PACIFIC 8 H BEHIND UTC L.A., SAN FRANCISCO
+# USA PACIFIC 7 H BEHIND UTC APR 3 - OCT 30
+# USA ALASKA STD 9 H BEHIND UTC MOST OF ALASKA (AKST)
+# USA ALASKA STD 8 H BEHIND UTC APR 3 - OCT 30 (AKDT)
+# USA ALEUTIAN 10 H BEHIND UTC ISLANDS WEST OF 170W
+# USA - " - 9 H BEHIND UTC APR 3 - OCT 30
+# USA HAWAII 10 H BEHIND UTC
+# USA BERING 11 H BEHIND UTC SAMOA, MIDWAY
+
+# From Arthur David Olson (1989-01-21):
+# The above dates are for 1988.
+# Note the "AKST" and "AKDT" abbreviations, the claim that there's
+# no DST in Samoa, and the claim that there is DST in Alaska and the
+# Aleutians.
+
+# From Arthur David Olson (1988-02-13):
+# Legal standard time zone names, from United States Code (1982 Edition and
+# Supplement III), Title 15, Chapter 6, Section 260 and forward. First, names
+# up to 1967-04-01 (when most provisions of the Uniform Time Act of 1966
+# took effect), as explained in sections 263 and 261:
+# (none)
+# United States standard eastern time
+# United States standard mountain time
+# United States standard central time
+# United States standard Pacific time
+# (none)
+# United States standard Alaska time
+# (none)
+# Next, names from 1967-04-01 until 1983-11-30 (the date for
+# public law 98-181):
+# Atlantic standard time
+# eastern standard time
+# central standard time
+# mountain standard time
+# Pacific standard time
+# Yukon standard time
+# Alaska-Hawaii standard time
+# Bering standard time
+# And after 1983-11-30:
+# Atlantic standard time
+# eastern standard time
+# central standard time
+# mountain standard time
+# Pacific standard time
+# Alaska standard time
+# Hawaii-Aleutian standard time
+# Samoa standard time
+# The law doesn't give abbreviations.
+#
+# From Paul Eggert (2000-01-08), following a heads-up from Rives McDow:
+# Public law 106-564 (2000-12-23) introduced the abbreviation
+# "Chamorro Standard Time" for time in Guam and the Northern Marianas.
+# See the file "australasia".
+
+# From Arthur David Olson, 2005-08-09
+# The following was signed into law on 2005-08-08.
+#
+# H.R. 6, Energy Policy Act of 2005, SEC. 110. DAYLIGHT SAVINGS.
+# (a) Amendment- Section 3(a) of the Uniform Time Act of 1966 (15
+# U.S.C. 260a(a)) is amended--
+# (1) by striking `first Sunday of April' and inserting `second
+# Sunday of March'; and
+# (2) by striking `last Sunday of October' and inserting `first
+# Sunday of November'.
+# (b) Effective Date- Subsection (a) shall take effect 1 year after the
+# date of enactment of this Act or March 1, 2007, whichever is later.
+# (c) Report to Congress- Not later than 9 months after the effective
+# date stated in subsection (b), the Secretary shall report to Congress
+# on the impact of this section on energy consumption in the United
+# States.
+# (d) Right to Revert- Congress retains the right to revert the
+# Daylight Saving Time back to the 2005 time schedules once the
+# Department study is complete.
+
+# US eastern time, represented by New York
+
+# Connecticut, Delaware, District of Columbia, most of Florida,
+# Georgia, southeast Indiana (Dearborn and Ohio counties), eastern Kentucky
+# (except America/Kentucky/Louisville below), Maine, Maryland, Massachusetts,
+# New Hampshire, New Jersey, New York, North Carolina, Ohio,
+# Pennsylvania, Rhode Island, South Carolina, eastern Tennessee,
+# Vermont, Virginia, West Virginia
+
+# From Dave Cantor (2004-11-02):
+# Early this summer I had the occasion to visit the Mount Washington
+# Observatory weather station atop (of course!) Mount Washington [, NH]....
+# One of the staff members said that the station was on Eastern Standard Time
+# and didn't change their clocks for Daylight Saving ... so that their
+# reports will always have times which are 5 hours behind UTC.
+
+# From Paul Eggert (2005-08-26):
+# According to today's Huntsville Times
+# <http://www.al.com/news/huntsvilletimes/index.ssf?/base/news/1125047783228320.xml&coll=1>
+# a few towns on Alabama's "eastern border with Georgia, such as Phenix City
+# in Russell County, Lanett in Chambers County and some towns in Lee County,
+# set their watches and clocks on Eastern time." It quotes H.H. "Bubba"
+# Roberts, city administrator in Phenix City. as saying "We are in the Central
+# time zone, but we do go by the Eastern time zone because so many people work
+# in Columbus."
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER
+Rule NYC 1920 only - Mar lastSun 2:00 1:00 D
+Rule NYC 1920 only - Oct lastSun 2:00 0 S
+Rule NYC 1921 1966 - Apr lastSun 2:00 1:00 D
+Rule NYC 1921 1954 - Sep lastSun 2:00 0 S
+Rule NYC 1955 1966 - Oct lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/New_York -4:56:02 - LMT 1883 Nov 18 12:03:58
+ -5:00 US E%sT 1920
+ -5:00 NYC E%sT 1942
+ -5:00 US E%sT 1946
+ -5:00 NYC E%sT 1967
+ -5:00 US E%sT
+
+# US central time, represented by Chicago
+
+# Alabama, Arkansas, Florida panhandle (Bay, Calhoun, Escambia,
+# Gulf, Holmes, Jackson, Okaloosa, Santa Rosa, Walton, and
+# Washington counties), Illinois, western Indiana
+# (Gibson, Jasper, Lake, LaPorte, Newton, Porter, Posey, Spencer,
+# Vanderburgh, and Warrick counties), Iowa, most of Kansas, western
+# Kentucky, Louisiana, Minnesota, Mississippi, Missouri, eastern
+# Nebraska, eastern North Dakota, Oklahoma, eastern South Dakota,
+# western Tennessee, most of Texas, Wisconsin
+
+# From Larry M. Smith (2006-04-26) re Wisconsin:
+# http://www.legis.state.wi.us/statutes/Stat0175.pdf ...
+# is currently enforced at the 01:00 time of change. Because the local
+# "bar time" in the state corresponds to 02:00, a number of citations
+# are issued for the "sale of class 'B' alcohol after prohibited
+# hours" within the deviated hour of this change every year....
+#
+# From Douglas R. Bomberg (2007-03-12):
+# Wisconsin has enacted (nearly eleventh-hour) legislation to get WI
+# Statue 175 closer in synch with the US Congress' intent....
+# http://www.legis.state.wi.us/2007/data/acts/07Act3.pdf
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER
+Rule Chicago 1920 only - Jun 13 2:00 1:00 D
+Rule Chicago 1920 1921 - Oct lastSun 2:00 0 S
+Rule Chicago 1921 only - Mar lastSun 2:00 1:00 D
+Rule Chicago 1922 1966 - Apr lastSun 2:00 1:00 D
+Rule Chicago 1922 1954 - Sep lastSun 2:00 0 S
+Rule Chicago 1955 1966 - Oct lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Chicago -5:50:36 - LMT 1883 Nov 18 12:09:24
+ -6:00 US C%sT 1920
+ -6:00 Chicago C%sT 1936 Mar 1 2:00
+ -5:00 - EST 1936 Nov 15 2:00
+ -6:00 Chicago C%sT 1942
+ -6:00 US C%sT 1946
+ -6:00 Chicago C%sT 1967
+ -6:00 US C%sT
+# Oliver County, ND switched from mountain to central time on 1992-10-25.
+Zone America/North_Dakota/Center -6:45:12 - LMT 1883 Nov 18 12:14:48
+ -7:00 US M%sT 1992 Oct 25 02:00
+ -6:00 US C%sT
+# Morton County, ND, switched from mountain to central time on
+# 2003-10-26, except for the area around Mandan which was already central time.
+# See <http://dmses.dot.gov/docimages/p63/135818.pdf>.
+# Officially this switch also included part of Sioux County, and
+# Jones, Mellette, and Todd Counties in South Dakota;
+# but in practice these other counties were already observing central time.
+# See <http://www.epa.gov/fedrgstr/EPA-IMPACT/2003/October/Day-28/i27056.htm>.
+Zone America/North_Dakota/New_Salem -6:45:39 - LMT 1883 Nov 18 12:14:21
+ -7:00 US M%sT 2003 Oct 26 02:00
+ -6:00 US C%sT
+
+# From Josh Findley (2011-01-21):
+# ...it appears that Mercer County, North Dakota, changed from the
+# mountain time zone to the central time zone at the last transition from
+# daylight-saving to standard time (on Nov. 7, 2010):
+# <a href="http://www.gpo.gov/fdsys/pkg/FR-2010-09-29/html/2010-24376.htm">
+# http://www.gpo.gov/fdsys/pkg/FR-2010-09-29/html/2010-24376.htm
+# </a>
+# <a href="http://www.bismarcktribune.com/news/local/article_1eb1b588-c758-11df-b472-001cc4c03286.html">
+# http://www.bismarcktribune.com/news/local/article_1eb1b588-c758-11df-b472-001cc4c03286.html
+# </a>
+
+# From Andy Lipscomb (2011-01-24):
+# ...according to the Census Bureau, the largest city is Beulah (although
+# it's commonly referred to as Beulah-Hazen, with Hazen being the next
+# largest city in Mercer County). Google Maps places Beulah's city hall
+# at 4715'51" north, 10146'40" west, which yields an offset of 6h47'07".
+
+Zone America/North_Dakota/Beulah -6:47:07 - LMT 1883 Nov 18 12:12:53
+ -7:00 US M%sT 2010 Nov 7 2:00
+ -6:00 US C%sT
+
+# US mountain time, represented by Denver
+#
+# Colorado, far western Kansas, Montana, western
+# Nebraska, Nevada border (Jackpot, Owyhee, and Mountain City),
+# New Mexico, southwestern North Dakota,
+# western South Dakota, far western Texas (El Paso County, Hudspeth County,
+# and Pine Springs and Nickel Creek in Culberson County), Utah, Wyoming
+#
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER
+Rule Denver 1920 1921 - Mar lastSun 2:00 1:00 D
+Rule Denver 1920 only - Oct lastSun 2:00 0 S
+Rule Denver 1921 only - May 22 2:00 0 S
+Rule Denver 1965 1966 - Apr lastSun 2:00 1:00 D
+Rule Denver 1965 1966 - Oct lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Denver -6:59:56 - LMT 1883 Nov 18 12:00:04
+ -7:00 US M%sT 1920
+ -7:00 Denver M%sT 1942
+ -7:00 US M%sT 1946
+ -7:00 Denver M%sT 1967
+ -7:00 US M%sT
+
+# US Pacific time, represented by Los Angeles
+#
+# California, northern Idaho (Benewah, Bonner, Boundary, Clearwater,
+# Idaho, Kootenai, Latah, Lewis, Nez Perce, and Shoshone counties,
+# and the northern three-quarters of Idaho county),
+# most of Nevada, most of Oregon, and Washington
+#
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER
+Rule CA 1948 only - Mar 14 2:00 1:00 D
+Rule CA 1949 only - Jan 1 2:00 0 S
+Rule CA 1950 1966 - Apr lastSun 2:00 1:00 D
+Rule CA 1950 1961 - Sep lastSun 2:00 0 S
+Rule CA 1962 1966 - Oct lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Los_Angeles -7:52:58 - LMT 1883 Nov 18 12:07:02
+ -8:00 US P%sT 1946
+ -8:00 CA P%sT 1967
+ -8:00 US P%sT
+
+# Alaska
+# AK%sT is the modern abbreviation for -9:00 per USNO.
+#
+# From Paul Eggert (2001-05-30):
+# Howse writes that Alaska switched from the Julian to the Gregorian calendar,
+# and from east-of-GMT to west-of-GMT days, when the US bought it from Russia.
+# This was on 1867-10-18, a Friday; the previous day was 1867-10-06 Julian,
+# also a Friday. Include only the time zone part of this transition,
+# ignoring the switch from Julian to Gregorian, since we can't represent
+# the Julian calendar.
+#
+# As far as we know, none of the exact locations mentioned below were
+# permanently inhabited in 1867 by anyone using either calendar.
+# (Yakutat was colonized by the Russians in 1799, but the settlement
+# was destroyed in 1805 by a Yakutat-kon war party.) However, there
+# were nearby inhabitants in some cases and for our purposes perhaps
+# it's best to simply use the official transition.
+#
+
+# From Steve Ferguson (2011-01-31):
+# The author lives in Alaska and many of the references listed are only
+# available to Alaskan residents.
+#
+# <a href="http://www.alaskahistoricalsociety.org/index.cfm?section=discover%20alaska&page=Glimpses%20of%20the%20Past&viewpost=2&ContentId=98">
+# http://www.alaskahistoricalsociety.org/index.cfm?section=discover%20alaska&page=Glimpses%20of%20the%20Past&viewpost=2&ContentId=98
+# </a>
+
+# From Arthur David Olson (2011-02-01):
+# Here's database-relevant material from the 2001 "Alaska History" article:
+#
+# On September 20 [1979]...DOT...officials decreed that on April 27,
+# 1980, Juneau and other nearby communities would move to Yukon Time.
+# Sitka, Petersburg, Wrangell, and Ketchikan, however, would remain on
+# Pacific Time.
+#
+# ...on September 22, 1980, DOT Secretary Neil E. Goldschmidt rescinded the
+# Department's September 1979 decision. Juneau and other communities in
+# northern Southeast reverted to Pacific Time on October 26.
+#
+# On October 28 [1983]...the Metlakatla Indian Community Council voted
+# unanimously to keep the reservation on Pacific Time.
+#
+# According to DOT official Joanne Petrie, Indian reservations are not
+# bound to follow time zones imposed by neighboring jurisdictions.
+#
+# (The last is consistent with how the database now handles the Navajo
+# Nation.)
+
+# From Arthur David Olson (2011-02-09):
+# I just spoke by phone with a staff member at the Metlakatla Indian
+# Community office (using contact information available at
+# <a href="http://www.commerce.state.ak.us/dca/commdb/CIS.cfm?Comm_Boro_name=Metlakatla">
+# http://www.commerce.state.ak.us/dca/commdb/CIS.cfm?Comm_Boro_name=Metlakatla
+# </a>).
+# It's shortly after 1:00 here on the east coast of the United States;
+# the staffer said it was shortly after 10:00 there. When I asked whether
+# that meant they were on Pacific time, they said no--they were on their
+# own time. I asked about daylight saving; they said it wasn't used. I
+# did not inquire about practices in the past.
+
+# From Arthur David Olson (2011-08-17):
+# For lack of better information, assume that Metlakatla's
+# abandonment of use of daylight saving resulted from the 1983 vote.
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Juneau 15:02:19 - LMT 1867 Oct 18
+ -8:57:41 - LMT 1900 Aug 20 12:00
+ -8:00 - PST 1942
+ -8:00 US P%sT 1946
+ -8:00 - PST 1969
+ -8:00 US P%sT 1980 Apr 27 2:00
+ -9:00 US Y%sT 1980 Oct 26 2:00
+ -8:00 US P%sT 1983 Oct 30 2:00
+ -9:00 US Y%sT 1983 Nov 30
+ -9:00 US AK%sT
+Zone America/Sitka 14:58:47 - LMT 1867 Oct 18
+ -9:01:13 - LMT 1900 Aug 20 12:00
+ -8:00 - PST 1942
+ -8:00 US P%sT 1946
+ -8:00 - PST 1969
+ -8:00 US P%sT 1983 Oct 30 2:00
+ -9:00 US Y%sT 1983 Nov 30
+ -9:00 US AK%sT
+Zone America/Metlakatla 15:13:42 - LMT 1867 Oct 18
+ -8:46:18 - LMT 1900 Aug 20 12:00
+ -8:00 - PST 1942
+ -8:00 US P%sT 1946
+ -8:00 - PST 1969
+ -8:00 US P%sT 1983 Oct 30 2:00
+ -8:00 - MeST
+Zone America/Yakutat 14:41:05 - LMT 1867 Oct 18
+ -9:18:55 - LMT 1900 Aug 20 12:00
+ -9:00 - YST 1942
+ -9:00 US Y%sT 1946
+ -9:00 - YST 1969
+ -9:00 US Y%sT 1983 Nov 30
+ -9:00 US AK%sT
+Zone America/Anchorage 14:00:24 - LMT 1867 Oct 18
+ -9:59:36 - LMT 1900 Aug 20 12:00
+ -10:00 - CAT 1942
+ -10:00 US CAT/CAWT 1945 Aug 14 23:00u
+ -10:00 US CAT/CAPT 1946 # Peace
+ -10:00 - CAT 1967 Apr
+ -10:00 - AHST 1969
+ -10:00 US AH%sT 1983 Oct 30 2:00
+ -9:00 US Y%sT 1983 Nov 30
+ -9:00 US AK%sT
+Zone America/Nome 12:58:21 - LMT 1867 Oct 18
+ -11:01:38 - LMT 1900 Aug 20 12:00
+ -11:00 - NST 1942
+ -11:00 US N%sT 1946
+ -11:00 - NST 1967 Apr
+ -11:00 - BST 1969
+ -11:00 US B%sT 1983 Oct 30 2:00
+ -9:00 US Y%sT 1983 Nov 30
+ -9:00 US AK%sT
+Zone America/Adak 12:13:21 - LMT 1867 Oct 18
+ -11:46:38 - LMT 1900 Aug 20 12:00
+ -11:00 - NST 1942
+ -11:00 US N%sT 1946
+ -11:00 - NST 1967 Apr
+ -11:00 - BST 1969
+ -11:00 US B%sT 1983 Oct 30 2:00
+ -10:00 US AH%sT 1983 Nov 30
+ -10:00 US HA%sT
+# The following switches don't quite make our 1970 cutoff.
+#
+# Shanks writes that part of southwest Alaska (e.g. Aniak)
+# switched from -11:00 to -10:00 on 1968-09-22 at 02:00,
+# and another part (e.g. Akiak) made the same switch five weeks later.
+#
+# From David Flater (2004-11-09):
+# In e-mail, 2004-11-02, Ray Hudson, historian/liaison to the Unalaska
+# Historic Preservation Commission, provided this information, which
+# suggests that Unalaska deviated from statutory time from early 1967
+# possibly until 1983:
+#
+# Minutes of the Unalaska City Council Meeting, January 10, 1967:
+# "Except for St. Paul and Akutan, Unalaska is the only important
+# location not on Alaska Standard Time. The following resolution was
+# made by William Robinson and seconded by Henry Swanson: Be it
+# resolved that the City of Unalaska hereby goes to Alaska Standard
+# Time as of midnight Friday, January 13, 1967 (1 A.M. Saturday,
+# January 14, Alaska Standard Time.) This resolution was passed with
+# three votes for and one against."
+
+# Hawaii
+
+# From Arthur David Olson (2010-12-09):
+# "Hawaiian Time" by Robert C. Schmitt and Doak C. Cox appears on pages 207-225
+# of volume 26 of The Hawaiian Journal of History (1992). As of 2010-12-09,
+# the article is available at
+# <a href="http://evols.library.manoa.hawaii.edu/bitstream/10524/239/2/JL26215.pdf">
+# http://evols.library.manoa.hawaii.edu/bitstream/10524/239/2/JL26215.pdf
+# </a>
+# and indicates that standard time was adopted effective noon, January
+# 13, 1896 (page 218), that in "1933, the Legislature decreed daylight
+# saving for the period between the last Sunday of each April and the
+# last Sunday of each September, but less than a month later repealed the
+# act," (page 220), that year-round daylight saving time was in effect
+# from 1942-02-09 to 1945-09-30 (page 221, with no time of day given for
+# when clocks changed) and that clocks were changed by 30 minutes
+# effective the second Sunday of June, 1947 (page 219, with no time of
+# day given for when clocks changed). A footnote for the 1933 changes
+# cites Session Laws of Hawaii 1933, "Act. 90 (approved 26 Apr. 1933)
+# and Act 163 (approved 21 May 1933)."
+
+# From Arthur David Olson (2011-01-19):
+# The following is from "Laws of the Territory of Hawaii Passed by the
+# Seventeenth Legislature: Regular Session 1933," available (as of
+# 2011-01-19) at American University's Pence Law Library. Page 85: "Act
+# 90...At 2 o'clock ante meridian of the last Sunday in April of each
+# year, the standard time of this Territory shall be advanced one
+# hour...This Act shall take effect upon its approval. Approved this 26th
+# day of April, A. D. 1933. LAWRENCE M JUDD, Governor of the Territory of
+# Hawaii." Page 172: "Act 163...Act 90 of the Session Laws of 1933 is
+# hereby repealed...This Act shall take effect upon its approval, upon
+# which date the standard time of this Territory shall be restored to
+# that existing immediately prior to the taking effect of said Act 90.
+# Approved this 21st day of May, A. D. 1933. LAWRENCE M. JUDD, Governor
+# of the Territory of Hawaii."
+#
+# Note that 1933-05-21 was a Sunday.
+# We're left to guess the time of day when Act 163 was approved; guess noon.
+
+Zone Pacific/Honolulu -10:31:26 - LMT 1896 Jan 13 12:00 #Schmitt&Cox
+ -10:30 - HST 1933 Apr 30 2:00 #Laws 1933
+ -10:30 1:00 HDT 1933 May 21 12:00 #Laws 1933+12
+ -10:30 - HST 1942 Feb 09 2:00 #Schmitt&Cox+2
+ -10:30 1:00 HDT 1945 Sep 30 2:00 #Schmitt&Cox+2
+ -10:30 - HST 1947 Jun 8 2:00 #Schmitt&Cox+2
+ -10:00 - HST
+
+# Now we turn to US areas that have diverged from the consensus since 1970.
+
+# Arizona mostly uses MST.
+
+# From Paul Eggert (2002-10-20):
+#
+# The information in the rest of this paragraph is derived from the
+# <a href="http://www.dlapr.lib.az.us/links/daylight.htm">
+# Daylight Saving Time web page (2002-01-23)</a> maintained by the
+# Arizona State Library, Archives and Public Records.
+# Between 1944-01-01 and 1944-04-01 the State of Arizona used standard
+# time, but by federal law railroads, airlines, bus lines, military
+# personnel, and some engaged in interstate commerce continued to
+# observe war (i.e., daylight saving) time. The 1944-03-17 Phoenix
+# Gazette says that was the date the law changed, and that 04-01 was
+# the date the state's clocks would change. In 1945 the State of
+# Arizona used standard time all year, again with exceptions only as
+# mandated by federal law. Arizona observed DST in 1967, but Arizona
+# Laws 1968, ch. 183 (effective 1968-03-21) repealed DST.
+#
+# Shanks says the 1944 experiment came to an end on 1944-03-17.
+# Go with the Arizona State Library instead.
+
+Zone America/Phoenix -7:28:18 - LMT 1883 Nov 18 11:31:42
+ -7:00 US M%sT 1944 Jan 1 00:01
+ -7:00 - MST 1944 Apr 1 00:01
+ -7:00 US M%sT 1944 Oct 1 00:01
+ -7:00 - MST 1967
+ -7:00 US M%sT 1968 Mar 21
+ -7:00 - MST
+# From Arthur David Olson (1988-02-13):
+# A writer from the Inter Tribal Council of Arizona, Inc.,
+# notes in private correspondence dated 1987-12-28 that "Presently, only the
+# Navajo Nation participates in the Daylight Saving Time policy, due to its
+# large size and location in three states." (The "only" means that other
+# tribal nations don't use DST.)
+
+Link America/Denver America/Shiprock
+
+# Southern Idaho (Ada, Adams, Bannock, Bear Lake, Bingham, Blaine,
+# Boise, Bonneville, Butte, Camas, Canyon, Caribou, Cassia, Clark,
+# Custer, Elmore, Franklin, Fremont, Gem, Gooding, Jefferson, Jerome,
+# Lemhi, Lincoln, Madison, Minidoka, Oneida, Owyhee, Payette, Power,
+# Teton, Twin Falls, Valley, Washington counties, and the southern
+# quarter of Idaho county) and eastern Oregon (most of Malheur County)
+# switched four weeks late in 1974.
+#
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Boise -7:44:49 - LMT 1883 Nov 18 12:15:11
+ -8:00 US P%sT 1923 May 13 2:00
+ -7:00 US M%sT 1974
+ -7:00 - MST 1974 Feb 3 2:00
+ -7:00 US M%sT
+
+# Indiana
+#
+# For a map of Indiana's time zone regions, see:
+# <a href="http://www.mccsc.edu/time.html">
+# What time is it in Indiana?
+# </a> (2006-03-01)
+#
+# From Paul Eggert (2007-08-17):
+# Since 1970, most of Indiana has been like America/Indiana/Indianapolis,
+# with the following exceptions:
+#
+# - Gibson, Jasper, Lake, LaPorte, Newton, Porter, Posey, Spencer,
+# Vandenburgh, and Warrick counties have been like America/Chicago.
+#
+# - Dearborn and Ohio counties have been like America/New_York.
+#
+# - Clark, Floyd, and Harrison counties have been like
+# America/Kentucky/Louisville.
+#
+# - Crawford, Daviess, Dubois, Knox, Martin, Perry, Pike, Pulaski, Starke,
+# and Switzerland counties have their own time zone histories as noted below.
+#
+# Shanks partitioned Indiana into 345 regions, each with its own time history,
+# and wrote ``Even newspaper reports present contradictory information.''
+# Those Hoosiers! Such a flighty and changeable people!
+# Fortunately, most of the complexity occurred before our cutoff date of 1970.
+#
+# Other than Indianapolis, the Indiana place names are so nondescript
+# that they would be ambiguous if we left them at the `America' level.
+# So we reluctantly put them all in a subdirectory `America/Indiana'.
+
+# From Paul Eggert (2005-08-16):
+# http://www.mccsc.edu/time.html says that Indiana will use DST starting 2006.
+
+# From Nathan Stratton Treadway (2006-03-30):
+# http://www.dot.gov/affairs/dot0406.htm [3705 B]
+# From Deborah Goldsmith (2006-01-18):
+# http://dmses.dot.gov/docimages/pdf95/382329_web.pdf [2.9 MB]
+# From Paul Eggert (2006-01-20):
+# It says "DOT is relocating the time zone boundary in Indiana to move Starke,
+# Pulaski, Knox, Daviess, Martin, Pike, Dubois, and Perry Counties from the
+# Eastern Time Zone to the Central Time Zone.... The effective date of
+# this rule is 2:OO a.m. EST Sunday, April 2, 2006, which is the
+# changeover date from standard time to Daylight Saving Time."
+# Strictly speaking, this means the affected counties will change their
+# clocks twice that night, but this obviously is in error. The intent
+# is that 01:59:59 EST be followed by 02:00:00 CDT.
+
+# From Gwillim Law (2007-02-10):
+# The Associated Press has been reporting that Pulaski County, Indiana is
+# going to switch from Central to Eastern Time on March 11, 2007....
+# http://www.indystar.com/apps/pbcs.dll/article?AID=/20070207/LOCAL190108/702070524/0/LOCAL
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER
+Rule Indianapolis 1941 only - Jun 22 2:00 1:00 D
+Rule Indianapolis 1941 1954 - Sep lastSun 2:00 0 S
+Rule Indianapolis 1946 1954 - Apr lastSun 2:00 1:00 D
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Indiana/Indianapolis -5:44:38 - LMT 1883 Nov 18 12:15:22
+ -6:00 US C%sT 1920
+ -6:00 Indianapolis C%sT 1942
+ -6:00 US C%sT 1946
+ -6:00 Indianapolis C%sT 1955 Apr 24 2:00
+ -5:00 - EST 1957 Sep 29 2:00
+ -6:00 - CST 1958 Apr 27 2:00
+ -5:00 - EST 1969
+ -5:00 US E%sT 1971
+ -5:00 - EST 2006
+ -5:00 US E%sT
+#
+# Eastern Crawford County, Indiana, left its clocks alone in 1974,
+# as well as from 1976 through 2005.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER
+Rule Marengo 1951 only - Apr lastSun 2:00 1:00 D
+Rule Marengo 1951 only - Sep lastSun 2:00 0 S
+Rule Marengo 1954 1960 - Apr lastSun 2:00 1:00 D
+Rule Marengo 1954 1960 - Sep lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Indiana/Marengo -5:45:23 - LMT 1883 Nov 18 12:14:37
+ -6:00 US C%sT 1951
+ -6:00 Marengo C%sT 1961 Apr 30 2:00
+ -5:00 - EST 1969
+ -5:00 US E%sT 1974 Jan 6 2:00
+ -6:00 1:00 CDT 1974 Oct 27 2:00
+ -5:00 US E%sT 1976
+ -5:00 - EST 2006
+ -5:00 US E%sT
+#
+# Daviess, Dubois, Knox, and Martin Counties, Indiana,
+# switched from eastern to central time in April 2006, then switched back
+# in November 2007.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER
+Rule Vincennes 1946 only - Apr lastSun 2:00 1:00 D
+Rule Vincennes 1946 only - Sep lastSun 2:00 0 S
+Rule Vincennes 1953 1954 - Apr lastSun 2:00 1:00 D
+Rule Vincennes 1953 1959 - Sep lastSun 2:00 0 S
+Rule Vincennes 1955 only - May 1 0:00 1:00 D
+Rule Vincennes 1956 1963 - Apr lastSun 2:00 1:00 D
+Rule Vincennes 1960 only - Oct lastSun 2:00 0 S
+Rule Vincennes 1961 only - Sep lastSun 2:00 0 S
+Rule Vincennes 1962 1963 - Oct lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Indiana/Vincennes -5:50:07 - LMT 1883 Nov 18 12:09:53
+ -6:00 US C%sT 1946
+ -6:00 Vincennes C%sT 1964 Apr 26 2:00
+ -5:00 - EST 1969
+ -5:00 US E%sT 1971
+ -5:00 - EST 2006 Apr 2 2:00
+ -6:00 US C%sT 2007 Nov 4 2:00
+ -5:00 US E%sT
+#
+# Perry County, Indiana, switched from eastern to central time in April 2006.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER
+Rule Perry 1946 only - Apr lastSun 2:00 1:00 D
+Rule Perry 1946 only - Sep lastSun 2:00 0 S
+Rule Perry 1953 1954 - Apr lastSun 2:00 1:00 D
+Rule Perry 1953 1959 - Sep lastSun 2:00 0 S
+Rule Perry 1955 only - May 1 0:00 1:00 D
+Rule Perry 1956 1963 - Apr lastSun 2:00 1:00 D
+Rule Perry 1960 only - Oct lastSun 2:00 0 S
+Rule Perry 1961 only - Sep lastSun 2:00 0 S
+Rule Perry 1962 1963 - Oct lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Indiana/Tell_City -5:47:03 - LMT 1883 Nov 18 12:12:57
+ -6:00 US C%sT 1946
+ -6:00 Perry C%sT 1964 Apr 26 2:00
+ -5:00 - EST 1969
+ -5:00 US E%sT 1971
+ -5:00 - EST 2006 Apr 2 2:00
+ -6:00 US C%sT
+#
+# Pike County, Indiana moved from central to eastern time in 1977,
+# then switched back in 2006, then switched back again in 2007.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER
+Rule Pike 1955 only - May 1 0:00 1:00 D
+Rule Pike 1955 1960 - Sep lastSun 2:00 0 S
+Rule Pike 1956 1964 - Apr lastSun 2:00 1:00 D
+Rule Pike 1961 1964 - Oct lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Indiana/Petersburg -5:49:07 - LMT 1883 Nov 18 12:10:53
+ -6:00 US C%sT 1955
+ -6:00 Pike C%sT 1965 Apr 25 2:00
+ -5:00 - EST 1966 Oct 30 2:00
+ -6:00 US C%sT 1977 Oct 30 2:00
+ -5:00 - EST 2006 Apr 2 2:00
+ -6:00 US C%sT 2007 Nov 4 2:00
+ -5:00 US E%sT
+#
+# Starke County, Indiana moved from central to eastern time in 1991,
+# then switched back in 2006.
+# From Arthur David Olson (1991-10-28):
+# An article on page A3 of the Sunday, 1991-10-27 Washington Post
+# notes that Starke County switched from Central time to Eastern time as of
+# 1991-10-27.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER
+Rule Starke 1947 1961 - Apr lastSun 2:00 1:00 D
+Rule Starke 1947 1954 - Sep lastSun 2:00 0 S
+Rule Starke 1955 1956 - Oct lastSun 2:00 0 S
+Rule Starke 1957 1958 - Sep lastSun 2:00 0 S
+Rule Starke 1959 1961 - Oct lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Indiana/Knox -5:46:30 - LMT 1883 Nov 18 12:13:30
+ -6:00 US C%sT 1947
+ -6:00 Starke C%sT 1962 Apr 29 2:00
+ -5:00 - EST 1963 Oct 27 2:00
+ -6:00 US C%sT 1991 Oct 27 2:00
+ -5:00 - EST 2006 Apr 2 2:00
+ -6:00 US C%sT
+#
+# Pulaski County, Indiana, switched from eastern to central time in
+# April 2006 and then switched back in March 2007.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER
+Rule Pulaski 1946 1960 - Apr lastSun 2:00 1:00 D
+Rule Pulaski 1946 1954 - Sep lastSun 2:00 0 S
+Rule Pulaski 1955 1956 - Oct lastSun 2:00 0 S
+Rule Pulaski 1957 1960 - Sep lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Indiana/Winamac -5:46:25 - LMT 1883 Nov 18 12:13:35
+ -6:00 US C%sT 1946
+ -6:00 Pulaski C%sT 1961 Apr 30 2:00
+ -5:00 - EST 1969
+ -5:00 US E%sT 1971
+ -5:00 - EST 2006 Apr 2 2:00
+ -6:00 US C%sT 2007 Mar 11 2:00
+ -5:00 US E%sT
+#
+# Switzerland County, Indiana, did not observe DST from 1973 through 2005.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Indiana/Vevay -5:40:16 - LMT 1883 Nov 18 12:19:44
+ -6:00 US C%sT 1954 Apr 25 2:00
+ -5:00 - EST 1969
+ -5:00 US E%sT 1973
+ -5:00 - EST 2006
+ -5:00 US E%sT
+
+# Part of Kentucky left its clocks alone in 1974.
+# This also includes Clark, Floyd, and Harrison counties in Indiana.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER
+Rule Louisville 1921 only - May 1 2:00 1:00 D
+Rule Louisville 1921 only - Sep 1 2:00 0 S
+Rule Louisville 1941 1961 - Apr lastSun 2:00 1:00 D
+Rule Louisville 1941 only - Sep lastSun 2:00 0 S
+Rule Louisville 1946 only - Jun 2 2:00 0 S
+Rule Louisville 1950 1955 - Sep lastSun 2:00 0 S
+Rule Louisville 1956 1960 - Oct lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Kentucky/Louisville -5:43:02 - LMT 1883 Nov 18 12:16:58
+ -6:00 US C%sT 1921
+ -6:00 Louisville C%sT 1942
+ -6:00 US C%sT 1946
+ -6:00 Louisville C%sT 1961 Jul 23 2:00
+ -5:00 - EST 1968
+ -5:00 US E%sT 1974 Jan 6 2:00
+ -6:00 1:00 CDT 1974 Oct 27 2:00
+ -5:00 US E%sT
+#
+# Wayne County, Kentucky
+#
+# From
+# <a href="http://www.lake-cumberland.com/life/archive/news990129time.shtml">
+# Lake Cumberland LIFE
+# </a> (1999-01-29) via WKYM-101.7:
+# Clinton County has joined Wayne County in asking the DoT to change from
+# the Central to the Eastern time zone.... The Wayne County government made
+# the same request in December. And while Russell County officials have not
+# taken action, the majority of respondents to a poll conducted there in
+# August indicated they would like to change to "fast time" also.
+# The three Lake Cumberland counties are the farthest east of any U.S.
+# location in the Central time zone.
+#
+# From Rich Wales (2000-08-29):
+# After prolonged debate, and despite continuing deep differences of opinion,
+# Wayne County (central Kentucky) is switching from Central (-0600) to Eastern
+# (-0500) time. They won't "fall back" this year. See Sara Shipley,
+# The difference an hour makes, Nando Times (2000-08-29 15:33 -0400).
+#
+# From Paul Eggert (2001-07-16):
+# The final rule was published in the
+# <a href="http://frwebgate.access.gpo.gov/cgi-bin/getdoc.cgi?dbname=2000_register&docid=fr17au00-22">
+# Federal Register 65, 160 (2000-08-17), page 50154-50158.
+# </a>
+#
+Zone America/Kentucky/Monticello -5:39:24 - LMT 1883 Nov 18 12:20:36
+ -6:00 US C%sT 1946
+ -6:00 - CST 1968
+ -6:00 US C%sT 2000 Oct 29 2:00
+ -5:00 US E%sT
+
+
+# From Rives McDow (2000-08-30):
+# Here ... are all the changes in the US since 1985.
+# Kearny County, KS (put all of county on central;
+# previously split between MST and CST) ... 1990-10
+# Starke County, IN (from CST to EST) ... 1991-10
+# Oliver County, ND (from MST to CST) ... 1992-10
+# West Wendover, NV (from PST TO MST) ... 1999-10
+# Wayne County, KY (from CST to EST) ... 2000-10
+#
+# From Paul Eggert (2001-07-17):
+# We don't know where the line used to be within Kearny County, KS,
+# so omit that change for now.
+# See America/Indiana/Knox for the Starke County, IN change.
+# See America/North_Dakota/Center for the Oliver County, ND change.
+# West Wendover, NV officially switched from Pacific to mountain time on
+# 1999-10-31. See the
+# <a href="http://frwebgate.access.gpo.gov/cgi-bin/getdoc.cgi?dbname=1999_register&docid=fr21oc99-15">
+# Federal Register 64, 203 (1999-10-21), page 56705-56707.
+# </a>
+# However, the Federal Register says that West Wendover already operated
+# on mountain time, and the rule merely made this official;
+# hence a separate tz entry is not needed.
+
+# Michigan
+#
+# From Bob Devine (1988-01-28):
+# Michigan didn't observe DST from 1968 to 1973.
+#
+# From Paul Eggert (1999-03-31):
+# Shanks writes that Michigan started using standard time on 1885-09-18,
+# but Howse writes (pp 124-125, referring to Popular Astronomy, 1901-01)
+# that Detroit kept
+#
+# local time until 1900 when the City Council decreed that clocks should
+# be put back twenty-eight minutes to Central Standard Time. Half the
+# city obeyed, half refused. After considerable debate, the decision
+# was rescinded and the city reverted to Sun time. A derisive offer to
+# erect a sundial in front of the city hall was referred to the
+# Committee on Sewers. Then, in 1905, Central time was adopted
+# by city vote.
+#
+# This story is too entertaining to be false, so go with Howse over Shanks.
+#
+# From Paul Eggert (2001-03-06):
+# Garland (1927) writes ``Cleveland and Detroit advanced their clocks
+# one hour in 1914.'' This change is not in Shanks. We have no more
+# info, so omit this for now.
+#
+# Most of Michigan observed DST from 1973 on, but was a bit late in 1975.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER
+Rule Detroit 1948 only - Apr lastSun 2:00 1:00 D
+Rule Detroit 1948 only - Sep lastSun 2:00 0 S
+Rule Detroit 1967 only - Jun 14 2:00 1:00 D
+Rule Detroit 1967 only - Oct lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Detroit -5:32:11 - LMT 1905
+ -6:00 - CST 1915 May 15 2:00
+ -5:00 - EST 1942
+ -5:00 US E%sT 1946
+ -5:00 Detroit E%sT 1973
+ -5:00 US E%sT 1975
+ -5:00 - EST 1975 Apr 27 2:00
+ -5:00 US E%sT
+#
+# Dickinson, Gogebic, Iron, and Menominee Counties, Michigan,
+# switched from EST to CST/CDT in 1973.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER
+Rule Menominee 1946 only - Apr lastSun 2:00 1:00 D
+Rule Menominee 1946 only - Sep lastSun 2:00 0 S
+Rule Menominee 1966 only - Apr lastSun 2:00 1:00 D
+Rule Menominee 1966 only - Oct lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Menominee -5:50:27 - LMT 1885 Sep 18 12:00
+ -6:00 US C%sT 1946
+ -6:00 Menominee C%sT 1969 Apr 27 2:00
+ -5:00 - EST 1973 Apr 29 2:00
+ -6:00 US C%sT
+
+# Navassa
+# administered by the US Fish and Wildlife Service
+# claimed by US under the provisions of the 1856 Guano Islands Act
+# also claimed by Haiti
+# occupied 1857/1900 by the Navassa Phosphate Co
+# US lighthouse 1917/1996-09
+# currently uninhabited
+# see Mark Fineman, ``An Isle Rich in Guano and Discord'',
+# _Los Angeles Times_ (1998-11-10), A1, A10; it cites
+# Jimmy Skaggs, _The Great Guano Rush_ (1994).
+
+################################################################################
+
+
+# From Paul Eggert (2006-03-22):
+# A good source for time zone historical data outside the U.S. is
+# Thomas G. Shanks and Rique Pottenger, The International Atlas (6th edition),
+# San Diego: ACS Publications, Inc. (2003).
+#
+# Gwillim Law writes that a good source
+# for recent time zone data is the International Air Transport
+# Association's Standard Schedules Information Manual (IATA SSIM),
+# published semiannually. Law sent in several helpful summaries
+# of the IATA's data after 1990.
+#
+# Except where otherwise noted, Shanks & Pottenger is the source for
+# entries through 1990, and IATA SSIM is the source for entries afterwards.
+#
+# Other sources occasionally used include:
+#
+# Edward W. Whitman, World Time Differences,
+# Whitman Publishing Co, 2 Niagara Av, Ealing, London (undated),
+# which I found in the UCLA library.
+#
+# <a href="http://www.pettswoodvillage.co.uk/Daylight_Savings_William_Willett.pdf">
+# William Willett, The Waste of Daylight, 19th edition
+# </a> (1914-03)
+#
+# See the `europe' file for Greenland.
+
+# Canada
+
+# From Alain LaBont<e'> (1994-11-14):
+# I post here the time zone abbreviations standardized in Canada
+# for both English and French in the CAN/CSA-Z234.4-89 standard....
+#
+# UTC Standard time Daylight savings time
+# offset French English French English
+# -2:30 - - HAT NDT
+# -3 - - HAA ADT
+# -3:30 HNT NST - -
+# -4 HNA AST HAE EDT
+# -5 HNE EST HAC CDT
+# -6 HNC CST HAR MDT
+# -7 HNR MST HAP PDT
+# -8 HNP PST HAY YDT
+# -9 HNY YST - -
+#
+# HN: Heure Normale ST: Standard Time
+# HA: Heure Avanc<e'>e DT: Daylight saving Time
+#
+# A: de l'Atlantique Atlantic
+# C: du Centre Central
+# E: de l'Est Eastern
+# M: Mountain
+# N: Newfoundland
+# P: du Pacifique Pacific
+# R: des Rocheuses
+# T: de Terre-Neuve
+# Y: du Yukon Yukon
+#
+# From Paul Eggert (1994-11-22):
+# Alas, this sort of thing must be handled by localization software.
+
+# Unless otherwise specified, the data for Canada are all from Shanks
+# & Pottenger.
+
+# From Chris Walton (2006-04-01, 2006-04-25, 2006-06-26, 2007-01-31,
+# 2007-03-01):
+# The British Columbia government announced yesterday that it will
+# adjust daylight savings next year to align with changes in the
+# U.S. and the rest of Canada....
+# http://www2.news.gov.bc.ca/news_releases_2005-2009/2006AG0014-000330.htm
+# ...
+# Nova Scotia
+# Daylight saving time will be extended by four weeks starting in 2007....
+# http://www.gov.ns.ca/just/regulations/rg2/2006/ma1206.pdf
+#
+# [For New Brunswick] the new legislation dictates that the time change is to
+# be done at 02:00 instead of 00:01.
+# http://www.gnb.ca/0062/acts/BBA-2006/Chap-19.pdf
+# ...
+# Manitoba has traditionally changed the clock every fall at 03:00.
+# As of 2006, the transition is to take place one hour earlier at 02:00.
+# http://web2.gov.mb.ca/laws/statutes/ccsm/o030e.php
+# ...
+# [Alberta, Ontario, Quebec] will follow US rules.
+# http://www.qp.gov.ab.ca/documents/spring/CH03_06.CFM
+# http://www.e-laws.gov.on.ca/DBLaws/Source/Regs/English/2006/R06111_e.htm
+# http://www2.publicationsduquebec.gouv.qc.ca/dynamicSearch/telecharge.php?type=5&file=2006C39A.PDF
+# ...
+# P.E.I. will follow US rules....
+# http://www.assembly.pe.ca/bills/pdf_chapter/62/3/chapter-41.pdf
+# ...
+# Province of Newfoundland and Labrador....
+# http://www.hoa.gov.nl.ca/hoa/bills/Bill0634.htm
+# ...
+# Yukon
+# http://www.gov.yk.ca/legislation/regs/oic2006_127.pdf
+# ...
+# N.W.T. will follow US rules. Whoever maintains the government web site
+# does not seem to believe in bookmarks. To see the news release, click the
+# following link and search for "Daylight Savings Time Change". Press the
+# "Daylight Savings Time Change" link; it will fire off a popup using
+# JavaScript.
+# http://www.exec.gov.nt.ca/currentnews/currentPR.asp?mode=archive
+# ...
+# Nunavut
+# An amendment to the Interpretation Act was registered on February 19/2007....
+# http://action.attavik.ca/home/justice-gn/attach/2007/gaz02part2.pdf
+
+# From Paul Eggert (2006-04-25):
+# H. David Matthews and Mary Vincent's map
+# <a href="http://www.canadiangeographic.ca/Magazine/SO98/geomap.asp">
+# "It's about TIME", _Canadian Geographic_ (September-October 1998)
+# </a> contains detailed boundaries for regions observing nonstandard
+# time and daylight saving time arrangements in Canada circa 1998.
+#
+# INMS, the Institute for National Measurement Standards in Ottawa, has <a
+# href="http://inms-ienm.nrc-cnrc.gc.ca/en/time_services/daylight_saving_e.php">
+# information about standard and daylight saving time zones in Canada.
+# </a> (updated periodically).
+# Its unofficial information is often taken from Matthews and Vincent.
+
+# From Paul Eggert (2006-06-27):
+# For now, assume all of DST-observing Canada will fall into line with the
+# new US DST rules,
+
+# From Chris Walton (2011-12-01)
+# In the first of Tammy Hardwick's articles
+# <a href="http://www.ilovecreston.com/?p=articles&t=spec&ar=260">
+# http://www.ilovecreston.com/?p=articles&t=spec&ar=260
+# </a>
+# she quotes the Friday November 1/1918 edition of the Creston Review.
+# The quote includes these two statements:
+# 'Sunday the CPR went back to the old system of time...'
+# '... The daylight saving scheme was dropped all over Canada at the same time,'
+# These statements refer to a transition from daylight time to standard time
+# that occurred nationally on Sunday October 27/1918. This transition was
+# also documented in the Saturday October 26/1918 edition of the Toronto Star.
+
+# In light of that evidence, we alter the date from the earlier believed
+# Oct 31, to Oct 27, 1918 (and Sunday is a more likely transition day
+# than Thursday) in all Canadian rulesets.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Canada 1918 only - Apr 14 2:00 1:00 D
+Rule Canada 1918 only - Oct 27 2:00 0 S
+Rule Canada 1942 only - Feb 9 2:00 1:00 W # War
+Rule Canada 1945 only - Aug 14 23:00u 1:00 P # Peace
+Rule Canada 1945 only - Sep 30 2:00 0 S
+Rule Canada 1974 1986 - Apr lastSun 2:00 1:00 D
+Rule Canada 1974 2006 - Oct lastSun 2:00 0 S
+Rule Canada 1987 2006 - Apr Sun>=1 2:00 1:00 D
+Rule Canada 2007 max - Mar Sun>=8 2:00 1:00 D
+Rule Canada 2007 max - Nov Sun>=1 2:00 0 S
+
+
+# Newfoundland and Labrador
+
+# From Paul Eggert (2000-10-02):
+# Matthews and Vincent (1998) write that Labrador should use NST/NDT,
+# but the only part of Labrador that follows the rules is the
+# southeast corner, including Port Hope Simpson and Mary's Harbour,
+# but excluding, say, Black Tickle.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule StJohns 1917 only - Apr 8 2:00 1:00 D
+Rule StJohns 1917 only - Sep 17 2:00 0 S
+# Whitman gives 1919 Apr 5 and 1920 Apr 5; go with Shanks & Pottenger.
+Rule StJohns 1919 only - May 5 23:00 1:00 D
+Rule StJohns 1919 only - Aug 12 23:00 0 S
+# For 1931-1935 Whitman gives Apr same date; go with Shanks & Pottenger.
+Rule StJohns 1920 1935 - May Sun>=1 23:00 1:00 D
+Rule StJohns 1920 1935 - Oct lastSun 23:00 0 S
+# For 1936-1941 Whitman gives May Sun>=8 and Oct Sun>=1; go with Shanks &
+# Pottenger.
+Rule StJohns 1936 1941 - May Mon>=9 0:00 1:00 D
+Rule StJohns 1936 1941 - Oct Mon>=2 0:00 0 S
+# Whitman gives the following transitions:
+# 1942 03-01/12-31, 1943 05-30/09-05, 1944 07-10/09-02, 1945 01-01/10-07
+# but go with Shanks & Pottenger and assume they used Canadian rules.
+# For 1946-9 Whitman gives May 5,4,9,1 - Oct 1,5,3,2, and for 1950 he gives
+# Apr 30 - Sep 24; go with Shanks & Pottenger.
+Rule StJohns 1946 1950 - May Sun>=8 2:00 1:00 D
+Rule StJohns 1946 1950 - Oct Sun>=2 2:00 0 S
+Rule StJohns 1951 1986 - Apr lastSun 2:00 1:00 D
+Rule StJohns 1951 1959 - Sep lastSun 2:00 0 S
+Rule StJohns 1960 1986 - Oct lastSun 2:00 0 S
+# From Paul Eggert (2000-10-02):
+# INMS (2000-09-12) says that, since 1988 at least, Newfoundland switches
+# at 00:01 local time. For now, assume it started in 1987.
+
+# From Michael Pelley (2011-09-12):
+# We received today, Monday, September 12, 2011, notification that the
+# changes to the Newfoundland Standard Time Act have been proclaimed.
+# The change in the Act stipulates that the change from Daylight Savings
+# Time to Standard Time and from Standard Time to Daylight Savings Time
+# now occurs at 2:00AM.
+# ...
+# <a href="http://www.assembly.nl.ca/legislation/sr/annualstatutes/2011/1106.chp.htm">
+# http://www.assembly.nl.ca/legislation/sr/annualstatutes/2011/1106.chp.htm
+# </a>
+# ...
+# MICHAEL PELLEY | Manager of Enterprise Architecture - Solution Delivery
+# Office of the Chief Information Officer
+# Executive Council
+# Government of Newfoundland & Labrador
+
+Rule StJohns 1987 only - Apr Sun>=1 0:01 1:00 D
+Rule StJohns 1987 2006 - Oct lastSun 0:01 0 S
+Rule StJohns 1988 only - Apr Sun>=1 0:01 2:00 DD
+Rule StJohns 1989 2006 - Apr Sun>=1 0:01 1:00 D
+Rule StJohns 2007 2011 - Mar Sun>=8 0:01 1:00 D
+Rule StJohns 2007 2010 - Nov Sun>=1 0:01 0 S
+#
+# St John's has an apostrophe, but Posix file names can't have apostrophes.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/St_Johns -3:30:52 - LMT 1884
+ -3:30:52 StJohns N%sT 1918
+ -3:30:52 Canada N%sT 1919
+ -3:30:52 StJohns N%sT 1935 Mar 30
+ -3:30 StJohns N%sT 1942 May 11
+ -3:30 Canada N%sT 1946
+ -3:30 StJohns N%sT 2011 Nov
+ -3:30 Canada N%sT
+
+# most of east Labrador
+
+# The name `Happy Valley-Goose Bay' is too long; use `Goose Bay'.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Goose_Bay -4:01:40 - LMT 1884 # Happy Valley-Goose Bay
+ -3:30:52 - NST 1918
+ -3:30:52 Canada N%sT 1919
+ -3:30:52 - NST 1935 Mar 30
+ -3:30 - NST 1936
+ -3:30 StJohns N%sT 1942 May 11
+ -3:30 Canada N%sT 1946
+ -3:30 StJohns N%sT 1966 Mar 15 2:00
+ -4:00 StJohns A%sT 2011 Nov
+ -4:00 Canada A%sT
+
+
+# west Labrador, Nova Scotia, Prince Edward I
+
+# From Paul Eggert (2006-03-22):
+# Shanks & Pottenger write that since 1970 most of this region has been like
+# Halifax. Many locales did not observe peacetime DST until 1972;
+# Glace Bay, NS is the largest that we know of.
+# Shanks & Pottenger also write that Liverpool, NS was the only town
+# in Canada to observe DST in 1971 but not 1970; for now we'll assume
+# this is a typo.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Halifax 1916 only - Apr 1 0:00 1:00 D
+Rule Halifax 1916 only - Oct 1 0:00 0 S
+Rule Halifax 1920 only - May 9 0:00 1:00 D
+Rule Halifax 1920 only - Aug 29 0:00 0 S
+Rule Halifax 1921 only - May 6 0:00 1:00 D
+Rule Halifax 1921 1922 - Sep 5 0:00 0 S
+Rule Halifax 1922 only - Apr 30 0:00 1:00 D
+Rule Halifax 1923 1925 - May Sun>=1 0:00 1:00 D
+Rule Halifax 1923 only - Sep 4 0:00 0 S
+Rule Halifax 1924 only - Sep 15 0:00 0 S
+Rule Halifax 1925 only - Sep 28 0:00 0 S
+Rule Halifax 1926 only - May 16 0:00 1:00 D
+Rule Halifax 1926 only - Sep 13 0:00 0 S
+Rule Halifax 1927 only - May 1 0:00 1:00 D
+Rule Halifax 1927 only - Sep 26 0:00 0 S
+Rule Halifax 1928 1931 - May Sun>=8 0:00 1:00 D
+Rule Halifax 1928 only - Sep 9 0:00 0 S
+Rule Halifax 1929 only - Sep 3 0:00 0 S
+Rule Halifax 1930 only - Sep 15 0:00 0 S
+Rule Halifax 1931 1932 - Sep Mon>=24 0:00 0 S
+Rule Halifax 1932 only - May 1 0:00 1:00 D
+Rule Halifax 1933 only - Apr 30 0:00 1:00 D
+Rule Halifax 1933 only - Oct 2 0:00 0 S
+Rule Halifax 1934 only - May 20 0:00 1:00 D
+Rule Halifax 1934 only - Sep 16 0:00 0 S
+Rule Halifax 1935 only - Jun 2 0:00 1:00 D
+Rule Halifax 1935 only - Sep 30 0:00 0 S
+Rule Halifax 1936 only - Jun 1 0:00 1:00 D
+Rule Halifax 1936 only - Sep 14 0:00 0 S
+Rule Halifax 1937 1938 - May Sun>=1 0:00 1:00 D
+Rule Halifax 1937 1941 - Sep Mon>=24 0:00 0 S
+Rule Halifax 1939 only - May 28 0:00 1:00 D
+Rule Halifax 1940 1941 - May Sun>=1 0:00 1:00 D
+Rule Halifax 1946 1949 - Apr lastSun 2:00 1:00 D
+Rule Halifax 1946 1949 - Sep lastSun 2:00 0 S
+Rule Halifax 1951 1954 - Apr lastSun 2:00 1:00 D
+Rule Halifax 1951 1954 - Sep lastSun 2:00 0 S
+Rule Halifax 1956 1959 - Apr lastSun 2:00 1:00 D
+Rule Halifax 1956 1959 - Sep lastSun 2:00 0 S
+Rule Halifax 1962 1973 - Apr lastSun 2:00 1:00 D
+Rule Halifax 1962 1973 - Oct lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Halifax -4:14:24 - LMT 1902 Jun 15
+ -4:00 Halifax A%sT 1918
+ -4:00 Canada A%sT 1919
+ -4:00 Halifax A%sT 1942 Feb 9 2:00s
+ -4:00 Canada A%sT 1946
+ -4:00 Halifax A%sT 1974
+ -4:00 Canada A%sT
+Zone America/Glace_Bay -3:59:48 - LMT 1902 Jun 15
+ -4:00 Canada A%sT 1953
+ -4:00 Halifax A%sT 1954
+ -4:00 - AST 1972
+ -4:00 Halifax A%sT 1974
+ -4:00 Canada A%sT
+
+# New Brunswick
+
+# From Paul Eggert (2007-01-31):
+# The Time Definition Act <http://www.gnb.ca/0062/PDF-acts/t-06.pdf>
+# says they changed at 00:01 through 2006, and
+# <http://www.canlii.org/nb/laws/sta/t-6/20030127/whole.html> makes it
+# clear that this was the case since at least 1993.
+# For now, assume it started in 1993.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Moncton 1933 1935 - Jun Sun>=8 1:00 1:00 D
+Rule Moncton 1933 1935 - Sep Sun>=8 1:00 0 S
+Rule Moncton 1936 1938 - Jun Sun>=1 1:00 1:00 D
+Rule Moncton 1936 1938 - Sep Sun>=1 1:00 0 S
+Rule Moncton 1939 only - May 27 1:00 1:00 D
+Rule Moncton 1939 1941 - Sep Sat>=21 1:00 0 S
+Rule Moncton 1940 only - May 19 1:00 1:00 D
+Rule Moncton 1941 only - May 4 1:00 1:00 D
+Rule Moncton 1946 1972 - Apr lastSun 2:00 1:00 D
+Rule Moncton 1946 1956 - Sep lastSun 2:00 0 S
+Rule Moncton 1957 1972 - Oct lastSun 2:00 0 S
+Rule Moncton 1993 2006 - Apr Sun>=1 0:01 1:00 D
+Rule Moncton 1993 2006 - Oct lastSun 0:01 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Moncton -4:19:08 - LMT 1883 Dec 9
+ -5:00 - EST 1902 Jun 15
+ -4:00 Canada A%sT 1933
+ -4:00 Moncton A%sT 1942
+ -4:00 Canada A%sT 1946
+ -4:00 Moncton A%sT 1973
+ -4:00 Canada A%sT 1993
+ -4:00 Moncton A%sT 2007
+ -4:00 Canada A%sT
+
+# Quebec
+
+# From Paul Eggert (2006-07-09):
+# Shanks & Pottenger write that since 1970 most of Quebec has been
+# like Montreal.
+
+# From Paul Eggert (2006-06-27):
+# Matthews and Vincent (1998) also write that Quebec east of the -63
+# meridian is supposed to observe AST, but residents as far east as
+# Natashquan use EST/EDT, and residents east of Natashquan use AST.
+# In "Official time in Quebec" the Quebec department of justice writes in
+# http://www.justice.gouv.qc.ca/english/publications/generale/temps-regl-1-a.htm
+# that "The residents of the Municipality of the
+# Cote-Nord-du-Golfe-Saint-Laurent and the municipalities of Saint-Augustin,
+# Bonne-Esperance and Blanc-Sablon apply the Official Time Act as it is
+# written and use Atlantic standard time all year round. The same applies to
+# the residents of the Native facilities along the lower North Shore."
+# <http://www.assnat.qc.ca/eng/37legislature2/Projets-loi/Publics/06-a002.htm>
+# says this common practice was codified into law as of 2007.
+# For lack of better info, guess this practice began around 1970, contra to
+# Shanks & Pottenger who have this region observing AST/ADT.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Mont 1917 only - Mar 25 2:00 1:00 D
+Rule Mont 1917 only - Apr 24 0:00 0 S
+Rule Mont 1919 only - Mar 31 2:30 1:00 D
+Rule Mont 1919 only - Oct 25 2:30 0 S
+Rule Mont 1920 only - May 2 2:30 1:00 D
+Rule Mont 1920 1922 - Oct Sun>=1 2:30 0 S
+Rule Mont 1921 only - May 1 2:00 1:00 D
+Rule Mont 1922 only - Apr 30 2:00 1:00 D
+Rule Mont 1924 only - May 17 2:00 1:00 D
+Rule Mont 1924 1926 - Sep lastSun 2:30 0 S
+Rule Mont 1925 1926 - May Sun>=1 2:00 1:00 D
+# The 1927-to-1937 rules can be expressed more simply as
+# Rule Mont 1927 1937 - Apr lastSat 24:00 1:00 D
+# Rule Mont 1927 1937 - Sep lastSat 24:00 0 S
+# The rules below avoid use of 24:00
+# (which pre-1998 versions of zic cannot handle).
+Rule Mont 1927 only - May 1 0:00 1:00 D
+Rule Mont 1927 1932 - Sep lastSun 0:00 0 S
+Rule Mont 1928 1931 - Apr lastSun 0:00 1:00 D
+Rule Mont 1932 only - May 1 0:00 1:00 D
+Rule Mont 1933 1940 - Apr lastSun 0:00 1:00 D
+Rule Mont 1933 only - Oct 1 0:00 0 S
+Rule Mont 1934 1939 - Sep lastSun 0:00 0 S
+Rule Mont 1946 1973 - Apr lastSun 2:00 1:00 D
+Rule Mont 1945 1948 - Sep lastSun 2:00 0 S
+Rule Mont 1949 1950 - Oct lastSun 2:00 0 S
+Rule Mont 1951 1956 - Sep lastSun 2:00 0 S
+Rule Mont 1957 1973 - Oct lastSun 2:00 0 S
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Blanc-Sablon -3:48:28 - LMT 1884
+ -4:00 Canada A%sT 1970
+ -4:00 - AST
+Zone America/Montreal -4:54:16 - LMT 1884
+ -5:00 Mont E%sT 1918
+ -5:00 Canada E%sT 1919
+ -5:00 Mont E%sT 1942 Feb 9 2:00s
+ -5:00 Canada E%sT 1946
+ -5:00 Mont E%sT 1974
+ -5:00 Canada E%sT
+
+
+# Ontario
+
+# From Paul Eggert (2006-07-09):
+# Shanks & Pottenger write that since 1970 most of Ontario has been like
+# Toronto.
+# Thunder Bay skipped DST in 1973.
+# Many smaller locales did not observe peacetime DST until 1974;
+# Nipigon (EST) and Rainy River (CST) are the largest that we know of.
+# Far west Ontario is like Winnipeg; far east Quebec is like Halifax.
+
+# From Mark Brader (2003-07-26):
+# [According to the Toronto Star] Orillia, Ontario, adopted DST
+# effective Saturday, 1912-06-22, 22:00; the article mentions that
+# Port Arthur (now part of Thunder Bay, Ontario) as well as Moose Jaw
+# have already done so. In Orillia DST was to run until Saturday,
+# 1912-08-31 (no time mentioned), but it was met with considerable
+# hostility from certain segments of the public, and was revoked after
+# only two weeks -- I copied it as Saturday, 1912-07-07, 22:00, but
+# presumably that should be -07-06. (1912-06-19, -07-12; also letters
+# earlier in June).
+#
+# Kenora, Ontario, was to abandon DST on 1914-06-01 (-05-21).
+
+# From Paul Eggert (1997-10-17):
+# Mark Brader writes that an article in the 1997-10-14 Toronto Star
+# says that Atikokan, Ontario currently does not observe DST,
+# but will vote on 11-10 whether to use EST/EDT.
+# He also writes that the
+# <a href="http://www.gov.on.ca/MBS/english/publications/statregs/conttext.html">
+# Ontario Time Act (1990, Chapter T.9)
+# </a>
+# says that Ontario east of 90W uses EST/EDT, and west of 90W uses CST/CDT.
+# Officially Atikokan is therefore on CST/CDT, and most likely this report
+# concerns a non-official time observed as a matter of local practice.
+#
+# From Paul Eggert (2000-10-02):
+# Matthews and Vincent (1998) write that Atikokan, Pickle Lake, and
+# New Osnaburgh observe CST all year, that Big Trout Lake observes
+# CST/CDT, and that Upsala and Shebandowan observe EST/EDT, all in
+# violation of the official Ontario rules.
+#
+# From Paul Eggert (2006-07-09):
+# Chris Walton (2006-07-06) mentioned an article by Stephanie MacLellan in the
+# 2005-07-21 Chronicle-Journal, which said:
+#
+# The clocks in Atikokan stay set on standard time year-round.
+# This means they spend about half the time on central time and
+# the other half on eastern time.
+#
+# For the most part, the system works, Mayor Dennis Brown said.
+#
+# "The majority of businesses in Atikokan deal more with Eastern
+# Canada, but there are some that deal with Western Canada," he
+# said. "I don't see any changes happening here."
+#
+# Walton also writes "Supposedly Pickle Lake and Mishkeegogamang
+# [New Osnaburgh] follow the same practice."
+
+# From Garry McKinnon (2006-07-14) via Chris Walton:
+# I chatted with a member of my board who has an outstanding memory
+# and a long history in Atikokan (and in the telecom industry) and he
+# can say for certain that Atikokan has been practicing the current
+# time keeping since 1952, at least.
+
+# From Paul Eggert (2006-07-17):
+# Shanks & Pottenger say that Atikokan has agreed with Rainy River
+# ever since standard time was introduced, but the information from
+# McKinnon sounds more authoritative. For now, assume that Atikokan
+# switched to EST immediately after WWII era daylight saving time
+# ended. This matches the old (less-populous) America/Coral_Harbour
+# entry since our cutoff date of 1970, so we can move
+# America/Coral_Harbour to the 'backward' file.
+
+# From Mark Brader (2010-03-06):
+#
+# Currently the database has:
+#
+# # Ontario
+#
+# # From Paul Eggert (2006-07-09):
+# # Shanks & Pottenger write that since 1970 most of Ontario has been like
+# # Toronto.
+# # Thunder Bay skipped DST in 1973.
+# # Many smaller locales did not observe peacetime DST until 1974;
+# # Nipigon (EST) and Rainy River (CST) are the largest that we know of.
+#
+# In the (Toronto) Globe and Mail for Saturday, 1955-09-24, in the bottom
+# right corner of page 1, it says that Toronto will return to standard
+# time at 2 am Sunday morning (which agrees with the database), and that:
+#
+# The one-hour setback will go into effect throughout most of Ontario,
+# except in areas like Windsor which remains on standard time all year.
+#
+# Windsor is, of course, a lot larger than Nipigon.
+#
+# I only came across this incidentally. I don't know if Windsor began
+# observing DST when Detroit did, or in 1974, or on some other date.
+#
+# By the way, the article continues by noting that:
+#
+# Some cities in the United States have pushed the deadline back
+# three weeks and will change over from daylight saving in October.
+
+# From Arthur David Olson (2010-07-17):
+#
+# "Standard Time and Time Zones in Canada" appeared in
+# The Journal of The Royal Astronomical Society of Canada,
+# volume 26, number 2 (February 1932) and, as of 2010-07-17,
+# was available at
+# <a href="http://adsabs.harvard.edu/full/1932JRASC..26...49S">
+# http://adsabs.harvard.edu/full/1932JRASC..26...49S
+# </a>
+#
+# It includes the text below (starting on page 57):
+#
+# A list of the places in Canada using daylight saving time would
+# require yearly revision. From information kindly furnished by
+# the provincial governments and by the postmasters in many cities
+# and towns, it is found that the following places used daylight sav-
+# ing in 1930. The information for the province of Quebec is definite,
+# for the other provinces only approximate:
+#
+# Province Daylight saving time used
+# Prince Edward Island Not used.
+# Nova Scotia In Halifax only.
+# New Brunswick In St. John only.
+# Quebec In the following places:
+# Montreal Lachine
+# Quebec Mont-Royal
+# Levis Iberville
+# St. Lambert Cap de la Madeleine
+# Verdun Loretteville
+# Westmount Richmond
+# Outremont St. Jerome
+# Longueuil Greenfield Park
+# Arvida Waterloo
+# Chambly-Canton Beaulieu
+# Melbourne La Tuque
+# St. Theophile Buckingham
+# Ontario Used generally in the cities and towns along
+# the southerly part of the province. Not
+# used in the northwesterlhy part.
+# Manitoba Not used.
+# Saskatchewan In Regina only.
+# Alberta Not used.
+# British Columbia Not used.
+#
+# With some exceptions, the use of daylight saving may be said to be limited
+# to those cities and towns lying between Quebec city and Windsor, Ont.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Toronto 1919 only - Mar 30 23:30 1:00 D
+Rule Toronto 1919 only - Oct 26 0:00 0 S
+Rule Toronto 1920 only - May 2 2:00 1:00 D
+Rule Toronto 1920 only - Sep 26 0:00 0 S
+Rule Toronto 1921 only - May 15 2:00 1:00 D
+Rule Toronto 1921 only - Sep 15 2:00 0 S
+Rule Toronto 1922 1923 - May Sun>=8 2:00 1:00 D
+# Shanks & Pottenger say 1923-09-19; assume it's a typo and that "-16"
+# was meant.
+Rule Toronto 1922 1926 - Sep Sun>=15 2:00 0 S
+Rule Toronto 1924 1927 - May Sun>=1 2:00 1:00 D
+# The 1927-to-1939 rules can be expressed more simply as
+# Rule Toronto 1927 1937 - Sep Sun>=25 2:00 0 S
+# Rule Toronto 1928 1937 - Apr Sun>=25 2:00 1:00 D
+# Rule Toronto 1938 1940 - Apr lastSun 2:00 1:00 D
+# Rule Toronto 1938 1939 - Sep lastSun 2:00 0 S
+# The rules below avoid use of Sun>=25
+# (which pre-2004 versions of zic cannot handle).
+Rule Toronto 1927 1932 - Sep lastSun 2:00 0 S
+Rule Toronto 1928 1931 - Apr lastSun 2:00 1:00 D
+Rule Toronto 1932 only - May 1 2:00 1:00 D
+Rule Toronto 1933 1940 - Apr lastSun 2:00 1:00 D
+Rule Toronto 1933 only - Oct 1 2:00 0 S
+Rule Toronto 1934 1939 - Sep lastSun 2:00 0 S
+Rule Toronto 1945 1946 - Sep lastSun 2:00 0 S
+Rule Toronto 1946 only - Apr lastSun 2:00 1:00 D
+Rule Toronto 1947 1949 - Apr lastSun 0:00 1:00 D
+Rule Toronto 1947 1948 - Sep lastSun 0:00 0 S
+Rule Toronto 1949 only - Nov lastSun 0:00 0 S
+Rule Toronto 1950 1973 - Apr lastSun 2:00 1:00 D
+Rule Toronto 1950 only - Nov lastSun 2:00 0 S
+Rule Toronto 1951 1956 - Sep lastSun 2:00 0 S
+# Shanks & Pottenger say Toronto ended DST a week early in 1971,
+# namely on 1971-10-24, but Mark Brader wrote (2003-05-31) that this
+# is wrong, and that he had confirmed it by checking the 1971-10-30
+# Toronto Star, which said that DST was ending 1971-10-31 as usual.
+Rule Toronto 1957 1973 - Oct lastSun 2:00 0 S
+
+# From Paul Eggert (2003-07-27):
+# Willett (1914-03) writes (p. 17) "In the Cities of Fort William, and
+# Port Arthur, Ontario, the principle of the Bill has been in
+# operation for the past three years, and in the City of Moose Jaw,
+# Saskatchewan, for one year."
+
+# From David Bryan via Tory Tronrud, Director/Curator,
+# Thunder Bay Museum (2003-11-12):
+# There is some suggestion, however, that, by-law or not, daylight
+# savings time was being practiced in Fort William and Port Arthur
+# before 1909.... [I]n 1910, the line between the Eastern and Central
+# Time Zones was permanently moved about two hundred miles west to
+# include the Thunder Bay area.... When Canada adopted daylight
+# savings time in 1916, Fort William and Port Arthur, having done so
+# already, did not change their clocks.... During the Second World
+# War,... [t]he cities agreed to implement DST during the summer
+# months for the remainder of the war years.
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Toronto -5:17:32 - LMT 1895
+ -5:00 Canada E%sT 1919
+ -5:00 Toronto E%sT 1942 Feb 9 2:00s
+ -5:00 Canada E%sT 1946
+ -5:00 Toronto E%sT 1974
+ -5:00 Canada E%sT
+Zone America/Thunder_Bay -5:57:00 - LMT 1895
+ -6:00 - CST 1910
+ -5:00 - EST 1942
+ -5:00 Canada E%sT 1970
+ -5:00 Mont E%sT 1973
+ -5:00 - EST 1974
+ -5:00 Canada E%sT
+Zone America/Nipigon -5:53:04 - LMT 1895
+ -5:00 Canada E%sT 1940 Sep 29
+ -5:00 1:00 EDT 1942 Feb 9 2:00s
+ -5:00 Canada E%sT
+Zone America/Rainy_River -6:18:16 - LMT 1895
+ -6:00 Canada C%sT 1940 Sep 29
+ -6:00 1:00 CDT 1942 Feb 9 2:00s
+ -6:00 Canada C%sT
+Zone America/Atikokan -6:06:28 - LMT 1895
+ -6:00 Canada C%sT 1940 Sep 29
+ -6:00 1:00 CDT 1942 Feb 9 2:00s
+ -6:00 Canada C%sT 1945 Sep 30 2:00
+ -5:00 - EST
+
+
+# Manitoba
+
+# From Rob Douglas (2006-04-06):
+# the old Manitoba Time Act - as amended by Bill 2, assented to
+# March 27, 1987 ... said ...
+# "between two o'clock Central Standard Time in the morning of
+# the first Sunday of April of each year and two o'clock Central
+# Standard Time in the morning of the last Sunday of October next
+# following, one hour in advance of Central Standard Time."...
+# I believe that the English legislation [of the old time act] had =
+# been assented to (March 22, 1967)....
+# Also, as far as I can tell, there was no order-in-council varying
+# the time of Daylight Saving Time for 2005 and so the provisions of
+# the 1987 version would apply - the changeover was at 2:00 Central
+# Standard Time (i.e. not until 3:00 Central Daylight Time).
+
+# From Paul Eggert (2006-04-10):
+# Shanks & Pottenger say Manitoba switched at 02:00 (not 02:00s)
+# starting 1966. Since 02:00s is clearly correct for 1967 on, assume
+# it was also 02:00s in 1966.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Winn 1916 only - Apr 23 0:00 1:00 D
+Rule Winn 1916 only - Sep 17 0:00 0 S
+Rule Winn 1918 only - Apr 14 2:00 1:00 D
+Rule Winn 1918 only - Oct 27 2:00 0 S
+Rule Winn 1937 only - May 16 2:00 1:00 D
+Rule Winn 1937 only - Sep 26 2:00 0 S
+Rule Winn 1942 only - Feb 9 2:00 1:00 W # War
+Rule Winn 1945 only - Aug 14 23:00u 1:00 P # Peace
+Rule Winn 1945 only - Sep lastSun 2:00 0 S
+Rule Winn 1946 only - May 12 2:00 1:00 D
+Rule Winn 1946 only - Oct 13 2:00 0 S
+Rule Winn 1947 1949 - Apr lastSun 2:00 1:00 D
+Rule Winn 1947 1949 - Sep lastSun 2:00 0 S
+Rule Winn 1950 only - May 1 2:00 1:00 D
+Rule Winn 1950 only - Sep 30 2:00 0 S
+Rule Winn 1951 1960 - Apr lastSun 2:00 1:00 D
+Rule Winn 1951 1958 - Sep lastSun 2:00 0 S
+Rule Winn 1959 only - Oct lastSun 2:00 0 S
+Rule Winn 1960 only - Sep lastSun 2:00 0 S
+Rule Winn 1963 only - Apr lastSun 2:00 1:00 D
+Rule Winn 1963 only - Sep 22 2:00 0 S
+Rule Winn 1966 1986 - Apr lastSun 2:00s 1:00 D
+Rule Winn 1966 2005 - Oct lastSun 2:00s 0 S
+Rule Winn 1987 2005 - Apr Sun>=1 2:00s 1:00 D
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Winnipeg -6:28:36 - LMT 1887 Jul 16
+ -6:00 Winn C%sT 2006
+ -6:00 Canada C%sT
+
+
+# Saskatchewan
+
+# From Mark Brader (2003-07-26):
+# The first actual adoption of DST in Canada was at the municipal
+# level. As the [Toronto] Star put it (1912-06-07), "While people
+# elsewhere have long been talking of legislation to save daylight,
+# the city of Moose Jaw [Saskatchewan] has acted on its own hook."
+# DST in Moose Jaw began on Saturday, 1912-06-01 (no time mentioned:
+# presumably late evening, as below), and would run until "the end of
+# the summer". The discrepancy between municipal time and railroad
+# time was noted.
+
+# From Paul Eggert (2003-07-27):
+# Willett (1914-03) notes that DST "has been in operation ... in the
+# City of Moose Jaw, Saskatchewan, for one year."
+
+# From Paul Eggert (2006-03-22):
+# Shanks & Pottenger say that since 1970 this region has mostly been as Regina.
+# Some western towns (e.g. Swift Current) switched from MST/MDT to CST in 1972.
+# Other western towns (e.g. Lloydminster) are like Edmonton.
+# Matthews and Vincent (1998) write that Denare Beach and Creighton
+# are like Winnipeg, in violation of Saskatchewan law.
+
+# From W. Jones (1992-11-06):
+# The. . .below is based on information I got from our law library, the
+# provincial archives, and the provincial Community Services department.
+# A precise history would require digging through newspaper archives, and
+# since you didn't say what you wanted, I didn't bother.
+#
+# Saskatchewan is split by a time zone meridian (105W) and over the years
+# the boundary became pretty ragged as communities near it reevaluated
+# their affiliations in one direction or the other. In 1965 a provincial
+# referendum favoured legislating common time practices.
+#
+# On 15 April 1966 the Time Act (c. T-14, Revised Statutes of
+# Saskatchewan 1978) was proclaimed, and established that the eastern
+# part of Saskatchewan would use CST year round, that districts in
+# northwest Saskatchewan would by default follow CST but could opt to
+# follow Mountain Time rules (thus 1 hour difference in the winter and
+# zero in the summer), and that districts in southwest Saskatchewan would
+# by default follow MT but could opt to follow CST.
+#
+# It took a few years for the dust to settle (I know one story of a town
+# on one time zone having its school in another, such that a mom had to
+# serve her family lunch in two shifts), but presently it seems that only
+# a few towns on the border with Alberta (e.g. Lloydminster) follow MT
+# rules any more; all other districts appear to have used CST year round
+# since sometime in the 1960s.
+
+# From Chris Walton (2006-06-26):
+# The Saskatchewan time act which was last updated in 1996 is about 30 pages
+# long and rather painful to read.
+# http://www.qp.gov.sk.ca/documents/English/Statutes/Statutes/T14.pdf
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Regina 1918 only - Apr 14 2:00 1:00 D
+Rule Regina 1918 only - Oct 27 2:00 0 S
+Rule Regina 1930 1934 - May Sun>=1 0:00 1:00 D
+Rule Regina 1930 1934 - Oct Sun>=1 0:00 0 S
+Rule Regina 1937 1941 - Apr Sun>=8 0:00 1:00 D
+Rule Regina 1937 only - Oct Sun>=8 0:00 0 S
+Rule Regina 1938 only - Oct Sun>=1 0:00 0 S
+Rule Regina 1939 1941 - Oct Sun>=8 0:00 0 S
+Rule Regina 1942 only - Feb 9 2:00 1:00 W # War
+Rule Regina 1945 only - Aug 14 23:00u 1:00 P # Peace
+Rule Regina 1945 only - Sep lastSun 2:00 0 S
+Rule Regina 1946 only - Apr Sun>=8 2:00 1:00 D
+Rule Regina 1946 only - Oct Sun>=8 2:00 0 S
+Rule Regina 1947 1957 - Apr lastSun 2:00 1:00 D
+Rule Regina 1947 1957 - Sep lastSun 2:00 0 S
+Rule Regina 1959 only - Apr lastSun 2:00 1:00 D
+Rule Regina 1959 only - Oct lastSun 2:00 0 S
+#
+Rule Swift 1957 only - Apr lastSun 2:00 1:00 D
+Rule Swift 1957 only - Oct lastSun 2:00 0 S
+Rule Swift 1959 1961 - Apr lastSun 2:00 1:00 D
+Rule Swift 1959 only - Oct lastSun 2:00 0 S
+Rule Swift 1960 1961 - Sep lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Regina -6:58:36 - LMT 1905 Sep
+ -7:00 Regina M%sT 1960 Apr lastSun 2:00
+ -6:00 - CST
+Zone America/Swift_Current -7:11:20 - LMT 1905 Sep
+ -7:00 Canada M%sT 1946 Apr lastSun 2:00
+ -7:00 Regina M%sT 1950
+ -7:00 Swift M%sT 1972 Apr lastSun 2:00
+ -6:00 - CST
+
+
+# Alberta
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Edm 1918 1919 - Apr Sun>=8 2:00 1:00 D
+Rule Edm 1918 only - Oct 27 2:00 0 S
+Rule Edm 1919 only - May 27 2:00 0 S
+Rule Edm 1920 1923 - Apr lastSun 2:00 1:00 D
+Rule Edm 1920 only - Oct lastSun 2:00 0 S
+Rule Edm 1921 1923 - Sep lastSun 2:00 0 S
+Rule Edm 1942 only - Feb 9 2:00 1:00 W # War
+Rule Edm 1945 only - Aug 14 23:00u 1:00 P # Peace
+Rule Edm 1945 only - Sep lastSun 2:00 0 S
+Rule Edm 1947 only - Apr lastSun 2:00 1:00 D
+Rule Edm 1947 only - Sep lastSun 2:00 0 S
+Rule Edm 1967 only - Apr lastSun 2:00 1:00 D
+Rule Edm 1967 only - Oct lastSun 2:00 0 S
+Rule Edm 1969 only - Apr lastSun 2:00 1:00 D
+Rule Edm 1969 only - Oct lastSun 2:00 0 S
+Rule Edm 1972 1986 - Apr lastSun 2:00 1:00 D
+Rule Edm 1972 2006 - Oct lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Edmonton -7:33:52 - LMT 1906 Sep
+ -7:00 Edm M%sT 1987
+ -7:00 Canada M%sT
+
+
+# British Columbia
+
+# From Paul Eggert (2006-03-22):
+# Shanks & Pottenger write that since 1970 most of this region has
+# been like Vancouver.
+# Dawson Creek uses MST. Much of east BC is like Edmonton.
+# Matthews and Vincent (1998) write that Creston is like Dawson Creek.
+
+# It seems though that (re: Creston) is not entirely correct:
+
+# From Chris Walton (2011-12-01):
+# There are two areas within the Canadian province of British Columbia
+# that do not currently observe daylight saving:
+# a) The Creston Valley (includes the town of Creston and surrounding area)
+# b) The eastern half of the Peace River Regional District
+# (includes the cities of Dawson Creek and Fort St. John)
+
+# Earlier this year I stumbled across a detailed article about the time
+# keeping history of Creston; it was written by Tammy Hardwick who is the
+# manager of the Creston & District Museum. The article was written in May 2009.
+# <a href="http://www.ilovecreston.com/?p=articles&t=spec&ar=260">
+# http://www.ilovecreston.com/?p=articles&t=spec&ar=260
+# </a>
+# According to the article, Creston has not changed its clocks since June 1918.
+# i.e. Creston has been stuck on UTC-7 for 93 years.
+# Dawson Creek, on the other hand, changed its clocks as recently as April 1972.
+
+# Unfortunately the exact date for the time change in June 1918 remains
+# unknown and will be difficult to ascertain. I e-mailed Tammy a few months
+# ago to ask if Sunday June 2 was a reasonable guess. She said it was just
+# as plausible as any other date (in June). She also said that after writing the
+# article she had discovered another time change in 1916; this is the subject
+# of another article which she wrote in October 2010.
+# <a href="http://www.creston.museum.bc.ca/index.php?module=comments&uop=view_comment&cm+id=56">
+# http://www.creston.museum.bc.ca/index.php?module=comments&uop=view_comment&cm+id=56
+# </a>
+
+# Here is a summary of the three clock change events in Creston's history:
+# 1. 1884 or 1885: adoption of Mountain Standard Time (GMT-7)
+# Exact date unknown
+# 2. Oct 1916: switch to Pacific Standard Time (GMT-8)
+# Exact date in October unknown; Sunday October 1 is a reasonable guess.
+# 3. June 1918: switch to Pacific Daylight Time (GMT-7)
+# Exact date in June unknown; Sunday June 2 is a reasonable guess.
+# note#1:
+# On Oct 27/1918 when daylight saving ended in the rest of Canada,
+# Creston did not change its clocks.
+# note#2:
+# During WWII when the Federal Government legislated a mandatory clock change,
+# Creston did not oblige.
+# note#3:
+# There is no guarantee that Creston will remain on Mountain Standard Time
+# (UTC-7) forever.
+# The subject was debated at least once this year by the town Council.
+# <a href="http://www.bclocalnews.com/kootenay_rockies/crestonvalleyadvance/news/116760809.html">
+# http://www.bclocalnews.com/kootenay_rockies/crestonvalleyadvance/news/116760809.html
+# </a>
+
+# During a period WWII, summer time (Daylight saying) was mandatory in Canada.
+# In Creston, that was handled by shifting the area to PST (-8:00) then applying
+# summer time to cause the offset to be -7:00, the same as it had been before
+# the change. It can be argued that the timezone abbreviation during this
+# period should be PDT rather than MST, but that doesn't seem important enough
+# (to anyone) to further complicate the rules.
+
+# The transition dates (and times) are guesses.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Vanc 1918 only - Apr 14 2:00 1:00 D
+Rule Vanc 1918 only - Oct 27 2:00 0 S
+Rule Vanc 1942 only - Feb 9 2:00 1:00 W # War
+Rule Vanc 1945 only - Aug 14 23:00u 1:00 P # Peace
+Rule Vanc 1945 only - Sep 30 2:00 0 S
+Rule Vanc 1946 1986 - Apr lastSun 2:00 1:00 D
+Rule Vanc 1946 only - Oct 13 2:00 0 S
+Rule Vanc 1947 1961 - Sep lastSun 2:00 0 S
+Rule Vanc 1962 2006 - Oct lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Vancouver -8:12:28 - LMT 1884
+ -8:00 Vanc P%sT 1987
+ -8:00 Canada P%sT
+Zone America/Dawson_Creek -8:00:56 - LMT 1884
+ -8:00 Canada P%sT 1947
+ -8:00 Vanc P%sT 1972 Aug 30 2:00
+ -7:00 - MST
+Zone America/Creston -7:46:04 - LMT 1884
+ -7:00 - MST 1916 Oct 1
+ -8:00 - PST 1918 Jun 2
+ -7:00 - MST
+
+# Northwest Territories, Nunavut, Yukon
+
+# From Paul Eggert (2006-03-22):
+# Dawson switched to PST in 1973. Inuvik switched to MST in 1979.
+# Mathew Englander (1996-10-07) gives the following refs:
+# * 1967. Paragraph 28(34)(g) of the Interpretation Act, S.C. 1967-68,
+# c. 7 defines Yukon standard time as UTC-9. This is still valid;
+# see Interpretation Act, R.S.C. 1985, c. I-21, s. 35(1).
+# * C.O. 1973/214 switched Yukon to PST on 1973-10-28 00:00.
+# * O.I.C. 1980/02 established DST.
+# * O.I.C. 1987/056 changed DST to Apr firstSun 2:00 to Oct lastSun 2:00.
+# Shanks & Pottenger say Yukon's 1973-10-28 switch was at 2:00; go
+# with Englander.
+# From Chris Walton (2006-06-26):
+# Here is a link to the old daylight saving portion of the interpretation
+# act which was last updated in 1987:
+# http://www.gov.yk.ca/legislation/regs/oic1987_056.pdf
+
+# From Rives McDow (1999-09-04):
+# Nunavut ... moved ... to incorporate the whole territory into one time zone.
+# <a href="http://www.nunatsiaq.com/nunavut/nvt90903_13.html">
+# Nunavut moves to single time zone Oct. 31
+# </a>
+#
+# From Antoine Leca (1999-09-06):
+# We then need to create a new timezone for the Kitikmeot region of Nunavut
+# to differentiate it from the Yellowknife region.
+
+# From Paul Eggert (1999-09-20):
+# <a href="http://www.nunavut.com/basicfacts/english/basicfacts_1territory.html">
+# Basic Facts: The New Territory
+# </a> (1999) reports that Pangnirtung operates on eastern time,
+# and that Coral Harbour does not observe DST. We don't know when
+# Pangnirtung switched to eastern time; we'll guess 1995.
+
+# From Rives McDow (1999-11-08):
+# On October 31, when the rest of Nunavut went to Central time,
+# Pangnirtung wobbled. Here is the result of their wobble:
+#
+# The following businesses and organizations in Pangnirtung use Central Time:
+#
+# First Air, Power Corp, Nunavut Construction, Health Center, RCMP,
+# Eastern Arctic National Parks, A & D Specialist
+#
+# The following businesses and organizations in Pangnirtung use Eastern Time:
+#
+# Hamlet office, All other businesses, Both schools, Airport operator
+#
+# This has made for an interesting situation there, which warranted the news.
+# No one there that I spoke with seems concerned, or has plans to
+# change the local methods of keeping time, as it evidently does not
+# really interfere with any activities or make things difficult locally.
+# They plan to celebrate New Year's turn-over twice, one hour apart,
+# so it appears that the situation will last at least that long.
+# The Nunavut Intergovernmental Affairs hopes that they will "come to
+# their senses", but the locals evidently don't see any problem with
+# the current state of affairs.
+
+# From Michaela Rodrigue, writing in the
+# <a href="http://www.nunatsiaq.com/archives/nunavut991130/nvt91119_17.html">
+# Nunatsiaq News (1999-11-19)</a>:
+# Clyde River, Pangnirtung and Sanikiluaq now operate with two time zones,
+# central - or Nunavut time - for government offices, and eastern time
+# for municipal offices and schools.... Igloolik [was similar but then]
+# made the switch to central time on Saturday, Nov. 6.
+
+# From Paul Eggert (2000-10-02):
+# Matthews and Vincent (1998) say the following, but we lack histories
+# for these potential new Zones.
+#
+# The Canadian Forces station at Alert uses Eastern Time while the
+# handful of residents at the Eureka weather station [in the Central
+# zone] skip daylight savings. Baffin Island, which is crossed by the
+# Central, Eastern and Atlantic Time zones only uses Eastern Time.
+# Gjoa Haven, Taloyoak and Pelly Bay all use Mountain instead of
+# Central Time and Southampton Island [in the Central zone] is not
+# required to use daylight savings.
+
+# From
+# <a href="http://www.nunatsiaq.com/archives/nunavut001130/nvt21110_02.html">
+# Nunavut now has two time zones
+# </a> (2000-11-10):
+# The Nunavut government would allow its employees in Kugluktuk and
+# Cambridge Bay to operate on central time year-round, putting them
+# one hour behind the rest of Nunavut for six months during the winter.
+# At the end of October the two communities had rebelled against
+# Nunavut's unified time zone, refusing to shift to eastern time with
+# the rest of the territory for the winter. Cambridge Bay remained on
+# central time, while Kugluktuk, even farther west, reverted to
+# mountain time, which they had used before the advent of Nunavut's
+# unified time zone in 1999.
+#
+# From Rives McDow (2001-01-20), quoting the Nunavut government:
+# The preceding decision came into effect at midnight, Saturday Nov 4, 2000.
+
+# From Paul Eggert (2000-12-04):
+# Let's just keep track of the official times for now.
+
+# From Rives McDow (2001-03-07):
+# The premier of Nunavut has issued a ministerial statement advising
+# that effective 2001-04-01, the territory of Nunavut will revert
+# back to three time zones (mountain, central, and eastern). Of the
+# cities in Nunavut, Coral Harbor is the only one that I know of that
+# has said it will not observe dst, staying on EST year round. I'm
+# checking for more info, and will get back to you if I come up with
+# more.
+# [Also see <http://www.nunatsiaq.com/nunavut/nvt10309_06.html> (2001-03-09).]
+
+# From Gwillim Law (2005-05-21):
+# According to maps at
+# http://inms-ienm.nrc-cnrc.gc.ca/images/time_services/TZ01SWE.jpg
+# http://inms-ienm.nrc-cnrc.gc.ca/images/time_services/TZ01SSE.jpg
+# (both dated 2003), and
+# http://www.canadiangeographic.ca/Magazine/SO98/geomap.asp
+# (from a 1998 Canadian Geographic article), the de facto and de jure time
+# for Southampton Island (at the north end of Hudson Bay) is UTC-5 all year
+# round. Using Google, it's easy to find other websites that confirm this.
+# I wasn't able to find how far back this time regimen goes, but since it
+# predates the creation of Nunavut, it probably goes back many years....
+# The Inuktitut name of Coral Harbour is Sallit, but it's rarely used.
+#
+# From Paul Eggert (2005-07-26):
+# For lack of better information, assume that Southampton Island observed
+# daylight saving only during wartime.
+
+# From Chris Walton (2007-03-01):
+# ... the community of Resolute (located on Cornwallis Island in
+# Nunavut) moved from Central Time to Eastern Time last November.
+# Basically the community did not change its clocks at the end of
+# daylight saving....
+# http://www.nnsl.com/frames/newspapers/2006-11/nov13_06none.html
+
+# From Chris Walton (2011-03-21):
+# Back in 2007 I initiated the creation of a new "zone file" for Resolute
+# Bay. Resolute Bay is a small community located about 900km north of
+# the Arctic Circle. The zone file was required because Resolute Bay had
+# decided to use UTC-5 instead of UTC-6 for the winter of 2006-2007.
+#
+# According to new information which I received last week, Resolute Bay
+# went back to using UTC-6 in the winter of 2007-2008...
+#
+# On March 11/2007 most of Canada went onto daylight saving. On March
+# 14/2007 I phoned the Resolute Bay hamlet office to do a "time check." I
+# talked to somebody that was both knowledgeable and helpful. I was able
+# to confirm that Resolute Bay was still operating on UTC-5. It was
+# explained to me that Resolute Bay had been on the Eastern Time zone
+# (EST) in the winter, and was now back on the Central Time zone (CDT).
+# i.e. the time zone had changed twice in the last year but the clocks
+# had not moved. The residents had to know which time zone they were in
+# so they could follow the correct TV schedule...
+#
+# On Nov 02/2008 most of Canada went onto standard time. On Nov 03/2008 I
+# phoned the Resolute Bay hamlet office...[D]ue to the challenging nature
+# of the phone call, I decided to seek out an alternate source of
+# information. I found an e-mail address for somebody by the name of
+# Stephanie Adams whose job was listed as "Inns North Support Officer for
+# Arctic Co-operatives." I was under the impression that Stephanie lived
+# and worked in Resolute Bay...
+#
+# On March 14/2011 I phoned the hamlet office again. I was told that
+# Resolute Bay had been using Central Standard Time over the winter of
+# 2010-2011 and that the clocks had therefore been moved one hour ahead
+# on March 13/2011. The person I talked to was aware that Resolute Bay
+# had previously experimented with Eastern Standard Time but he could not
+# tell me when the practice had stopped.
+#
+# On March 17/2011 I searched the Web to find an e-mail address of
+# somebody that might be able to tell me exactly when Resolute Bay went
+# off Eastern Standard Time. I stumbled on the name "Aziz Kheraj." Aziz
+# used to be the mayor of Resolute Bay and he apparently owns half the
+# businesses including "South Camp Inn." This website has some info on
+# Aziz:
+# <a href="http://www.uphere.ca/node/493">
+# http://www.uphere.ca/node/493
+# </a>
+#
+# I sent Aziz an e-mail asking when Resolute Bay had stopped using
+# Eastern Standard Time.
+#
+# Aziz responded quickly with this: "hi, The time was not changed for the
+# 1 year only, the following year, the community went back to the old way
+# of "spring ahead-fall behind" currently we are zulu plus 5 hrs and in
+# the winter Zulu plus 6 hrs"
+#
+# This of course conflicted with everything I had ascertained in November 2008.
+#
+# I sent Aziz a copy of my 2008 e-mail exchange with Stephanie. Aziz
+# responded with this: "Hi, Stephanie lives in Winnipeg. I live here, You
+# may want to check with the weather office in Resolute Bay or do a
+# search on the weather through Env. Canada. web site"
+#
+# If I had realized the Stephanie did not live in Resolute Bay I would
+# never have contacted her. I now believe that all the information I
+# obtained in November 2008 should be ignored...
+# I apologize for reporting incorrect information in 2008.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule NT_YK 1918 only - Apr 14 2:00 1:00 D
+Rule NT_YK 1918 only - Oct 27 2:00 0 S
+Rule NT_YK 1919 only - May 25 2:00 1:00 D
+Rule NT_YK 1919 only - Nov 1 0:00 0 S
+Rule NT_YK 1942 only - Feb 9 2:00 1:00 W # War
+Rule NT_YK 1945 only - Aug 14 23:00u 1:00 P # Peace
+Rule NT_YK 1945 only - Sep 30 2:00 0 S
+Rule NT_YK 1965 only - Apr lastSun 0:00 2:00 DD
+Rule NT_YK 1965 only - Oct lastSun 2:00 0 S
+Rule NT_YK 1980 1986 - Apr lastSun 2:00 1:00 D
+Rule NT_YK 1980 2006 - Oct lastSun 2:00 0 S
+Rule NT_YK 1987 2006 - Apr Sun>=1 2:00 1:00 D
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+# aka Panniqtuuq
+Zone America/Pangnirtung 0 - zzz 1921 # trading post est.
+ -4:00 NT_YK A%sT 1995 Apr Sun>=1 2:00
+ -5:00 Canada E%sT 1999 Oct 31 2:00
+ -6:00 Canada C%sT 2000 Oct 29 2:00
+ -5:00 Canada E%sT
+# formerly Frobisher Bay
+Zone America/Iqaluit 0 - zzz 1942 Aug # Frobisher Bay est.
+ -5:00 NT_YK E%sT 1999 Oct 31 2:00
+ -6:00 Canada C%sT 2000 Oct 29 2:00
+ -5:00 Canada E%sT
+# aka Qausuittuq
+Zone America/Resolute 0 - zzz 1947 Aug 31 # Resolute founded
+ -6:00 NT_YK C%sT 2000 Oct 29 2:00
+ -5:00 - EST 2001 Apr 1 3:00
+ -6:00 Canada C%sT 2006 Oct 29 2:00
+ -5:00 - EST 2007 Mar 11 3:00
+ -6:00 Canada C%sT
+# aka Kangiqiniq
+Zone America/Rankin_Inlet 0 - zzz 1957 # Rankin Inlet founded
+ -6:00 NT_YK C%sT 2000 Oct 29 2:00
+ -5:00 - EST 2001 Apr 1 3:00
+ -6:00 Canada C%sT
+# aka Iqaluktuuttiaq
+Zone America/Cambridge_Bay 0 - zzz 1920 # trading post est.?
+ -7:00 NT_YK M%sT 1999 Oct 31 2:00
+ -6:00 Canada C%sT 2000 Oct 29 2:00
+ -5:00 - EST 2000 Nov 5 0:00
+ -6:00 - CST 2001 Apr 1 3:00
+ -7:00 Canada M%sT
+Zone America/Yellowknife 0 - zzz 1935 # Yellowknife founded?
+ -7:00 NT_YK M%sT 1980
+ -7:00 Canada M%sT
+Zone America/Inuvik 0 - zzz 1953 # Inuvik founded
+ -8:00 NT_YK P%sT 1979 Apr lastSun 2:00
+ -7:00 NT_YK M%sT 1980
+ -7:00 Canada M%sT
+Zone America/Whitehorse -9:00:12 - LMT 1900 Aug 20
+ -9:00 NT_YK Y%sT 1966 Jul 1 2:00
+ -8:00 NT_YK P%sT 1980
+ -8:00 Canada P%sT
+Zone America/Dawson -9:17:40 - LMT 1900 Aug 20
+ -9:00 NT_YK Y%sT 1973 Oct 28 0:00
+ -8:00 NT_YK P%sT 1980
+ -8:00 Canada P%sT
+
+
+###############################################################################
+
+# Mexico
+
+# From Paul Eggert (2001-03-05):
+# The Investigation and Analysis Service of the
+# Mexican Library of Congress (MLoC) has published a
+# <a href="http://www.cddhcu.gob.mx/bibliot/publica/inveyana/polisoc/horver/">
+# history of Mexican local time (in Spanish)
+# </a>.
+#
+# Here are the discrepancies between Shanks & Pottenger (S&P) and the MLoC.
+# (In all cases we go with the MLoC.)
+# S&P report that Baja was at -8:00 in 1922/1923.
+# S&P say the 1930 transition in Baja was 1930-11-16.
+# S&P report no DST during summer 1931.
+# S&P report a transition at 1932-03-30 23:00, not 1932-04-01.
+
+# From Gwillim Law (2001-02-20):
+# There are some other discrepancies between the Decrees page and the
+# tz database. I think they can best be explained by supposing that
+# the researchers who prepared the Decrees page failed to find some of
+# the relevant documents.
+
+# From Alan Perry (1996-02-15):
+# A guy from our Mexico subsidiary finally found the Presidential Decree
+# outlining the timezone changes in Mexico.
+#
+# ------------- Begin Forwarded Message -------------
+#
+# I finally got my hands on the Official Presidential Decree that sets up the
+# rules for the DST changes. The rules are:
+#
+# 1. The country is divided in 3 timezones:
+# - Baja California Norte (the Mexico/BajaNorte TZ)
+# - Baja California Sur, Nayarit, Sinaloa and Sonora (the Mexico/BajaSur TZ)
+# - The rest of the country (the Mexico/General TZ)
+#
+# 2. From the first Sunday in April at 2:00 AM to the last Sunday in October
+# at 2:00 AM, the times in each zone are as follows:
+# BajaNorte: GMT+7
+# BajaSur: GMT+6
+# General: GMT+5
+#
+# 3. The rest of the year, the times are as follows:
+# BajaNorte: GMT+8
+# BajaSur: GMT+7
+# General: GMT+6
+#
+# The Decree was published in Mexico's Official Newspaper on January 4th.
+#
+# -------------- End Forwarded Message --------------
+# From Paul Eggert (1996-06-12):
+# For an English translation of the decree, see
+# <a href="http://mexico-travel.com/extra/timezone_eng.html">
+# ``Diario Oficial: Time Zone Changeover'' (1996-01-04).
+# </a>
+
+# From Rives McDow (1998-10-08):
+# The State of Quintana Roo has reverted back to central STD and DST times
+# (i.e. UTC -0600 and -0500 as of 1998-08-02).
+
+# From Rives McDow (2000-01-10):
+# Effective April 4, 1999 at 2:00 AM local time, Sonora changed to the time
+# zone 5 hours from the International Date Line, and will not observe daylight
+# savings time so as to stay on the same time zone as the southern part of
+# Arizona year round.
+
+# From Jesper Norgaard, translating
+# <http://www.reforma.com/nacional/articulo/064327/> (2001-01-17):
+# In Oaxaca, the 55.000 teachers from the Section 22 of the National
+# Syndicate of Education Workers, refuse to apply daylight saving each
+# year, so that the more than 10,000 schools work at normal hour the
+# whole year.
+
+# From Gwillim Law (2001-01-19):
+# <http://www.reforma.com/negocios_y_dinero/articulo/064481/> ... says
+# (translated):...
+# January 17, 2000 - The Energy Secretary, Ernesto Martens, announced
+# that Summer Time will be reduced from seven to five months, starting
+# this year....
+# <http://www.publico.com.mx/scripts/texto3.asp?action=pagina&pag=21&pos=p&secc=naci&date=01/17/2001>
+# [translated], says "summer time will ... take effect on the first Sunday
+# in May, and end on the last Sunday of September.
+
+# From Arthur David Olson (2001-01-25):
+# The 2001-01-24 traditional Washington Post contained the page one
+# story "Timely Issue Divides Mexicans."...
+# http://www.washingtonpost.com/wp-dyn/articles/A37383-2001Jan23.html
+# ... Mexico City Mayor Lopez Obrador "...is threatening to keep
+# Mexico City and its 20 million residents on a different time than
+# the rest of the country..." In particular, Lopez Obrador would abolish
+# observation of Daylight Saving Time.
+
+# <a href="http://www.conae.gob.mx/ahorro/decretohorver2001.html#decre">
+# Official statute published by the Energy Department
+# </a> (2001-02-01) shows Baja and Chihauhua as still using US DST rules,
+# and Sonora with no DST. This was reported by Jesper Norgaard (2001-02-03).
+
+# From Paul Eggert (2001-03-03):
+#
+# <a href="http://www.latimes.com/news/nation/20010303/t000018766.html">
+# James F. Smith writes in today's LA Times
+# </a>
+# * Sonora will continue to observe standard time.
+# * Last week Mexico City's mayor Andres Manuel Lopez Obrador decreed that
+# the Federal District will not adopt DST.
+# * 4 of 16 district leaders announced they'll ignore the decree.
+# * The decree does not affect federal-controlled facilities including
+# the airport, banks, hospitals, and schools.
+#
+# For now we'll assume that the Federal District will bow to federal rules.
+
+# From Jesper Norgaard (2001-04-01):
+# I found some references to the Mexican application of daylight
+# saving, which modifies what I had already sent you, stating earlier
+# that a number of northern Mexican states would go on daylight
+# saving. The modification reverts this to only cover Baja California
+# (Norte), while all other states (except Sonora, who has no daylight
+# saving all year) will follow the original decree of president
+# Vicente Fox, starting daylight saving May 6, 2001 and ending
+# September 30, 2001.
+# References: "Diario de Monterrey" <www.diariodemonterrey.com/index.asp>
+# Palabra <http://palabra.infosel.com/010331/primera/ppri3101.pdf> (2001-03-31)
+
+# From Reuters (2001-09-04):
+# Mexico's Supreme Court on Tuesday declared that daylight savings was
+# unconstitutional in Mexico City, creating the possibility the
+# capital will be in a different time zone from the rest of the nation
+# next year.... The Supreme Court's ruling takes effect at 2:00
+# a.m. (0800 GMT) on Sept. 30, when Mexico is scheduled to revert to
+# standard time. "This is so residents of the Federal District are not
+# subject to unexpected time changes," a statement from the court said.
+
+# From Jesper Norgaard Welen (2002-03-12):
+# ... consulting my local grocery store(!) and my coworkers, they all insisted
+# that a new decision had been made to reinstate US style DST in Mexico....
+# http://www.conae.gob.mx/ahorro/horaver2001_m1_2002.html (2002-02-20)
+# confirms this. Sonora as usual is the only state where DST is not applied.
+
+# From Steffen Thorsen (2009-12-28):
+#
+# Steffen Thorsen wrote:
+# > Mexico's House of Representatives has approved a proposal for northern
+# > Mexico's border cities to share the same daylight saving schedule as
+# > the United States.
+# Now this has passed both the Congress and the Senate, so starting from
+# 2010, some border regions will be the same:
+# <a href="http://www.signonsandiego.com/news/2009/dec/28/clocks-will-match-both-sides-border/">
+# http://www.signonsandiego.com/news/2009/dec/28/clocks-will-match-both-sides-border/
+# </a>
+# <a href="http://www.elmananarey.com/diario/noticia/nacional/noticias/empatan_horario_de_frontera_con_eu/621939">
+# http://www.elmananarey.com/diario/noticia/nacional/noticias/empatan_horario_de_frontera_con_eu/621939
+# </a>
+# (Spanish)
+#
+# Could not find the new law text, but the proposed law text changes are here:
+# <a href="http://gaceta.diputados.gob.mx/Gaceta/61/2009/dic/20091210-V.pdf">
+# http://gaceta.diputados.gob.mx/Gaceta/61/2009/dic/20091210-V.pdf
+# </a>
+# (Gaceta Parlamentaria)
+#
+# There is also a list of the votes here:
+# <a href="http://gaceta.diputados.gob.mx/Gaceta/61/2009/dic/V2-101209.html">
+# http://gaceta.diputados.gob.mx/Gaceta/61/2009/dic/V2-101209.html
+# </a>
+#
+# Our page:
+# <a href="http://www.timeanddate.com/news/time/north-mexico-dst-change.html">
+# http://www.timeanddate.com/news/time/north-mexico-dst-change.html
+# </a>
+
+# From Arthur David Olson (2010-01-20):
+# The page
+# <a href="http://dof.gob.mx/nota_detalle.php?codigo=5127480&fecha=06/01/2010">
+# http://dof.gob.mx/nota_detalle.php?codigo=5127480&fecha=06/01/2010
+# </a>
+# includes this text:
+# En los municipios fronterizos de Tijuana y Mexicali en Baja California;
+# Ju&aacute;rez y Ojinaga en Chihuahua; Acu&ntilde;a y Piedras Negras en Coahuila;
+# An&aacute;huac en Nuevo Le&oacute;n; y Nuevo Laredo, Reynosa y Matamoros en
+# Tamaulipas, la aplicaci&oacute;n de este horario estacional surtir&aacute; efecto
+# desde las dos horas del segundo domingo de marzo y concluir&aacute; a las dos
+# horas del primer domingo de noviembre.
+# En los municipios fronterizos que se encuentren ubicados en la franja
+# fronteriza norte en el territorio comprendido entre la l&iacute;nea
+# internacional y la l&iacute;nea paralela ubicada a una distancia de veinte
+# kil&oacute;metros, as&iacute; como la Ciudad de Ensenada, Baja California, hacia el
+# interior del pa&iacute;s, la aplicaci&oacute;n de este horario estacional surtir&aacute;
+# efecto desde las dos horas del segundo domingo de marzo y concluir&aacute; a
+# las dos horas del primer domingo de noviembre.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Mexico 1939 only - Feb 5 0:00 1:00 D
+Rule Mexico 1939 only - Jun 25 0:00 0 S
+Rule Mexico 1940 only - Dec 9 0:00 1:00 D
+Rule Mexico 1941 only - Apr 1 0:00 0 S
+Rule Mexico 1943 only - Dec 16 0:00 1:00 W # War
+Rule Mexico 1944 only - May 1 0:00 0 S
+Rule Mexico 1950 only - Feb 12 0:00 1:00 D
+Rule Mexico 1950 only - Jul 30 0:00 0 S
+Rule Mexico 1996 2000 - Apr Sun>=1 2:00 1:00 D
+Rule Mexico 1996 2000 - Oct lastSun 2:00 0 S
+Rule Mexico 2001 only - May Sun>=1 2:00 1:00 D
+Rule Mexico 2001 only - Sep lastSun 2:00 0 S
+Rule Mexico 2002 max - Apr Sun>=1 2:00 1:00 D
+Rule Mexico 2002 max - Oct lastSun 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+# Quintana Roo
+Zone America/Cancun -5:47:04 - LMT 1922 Jan 1 0:12:56
+ -6:00 - CST 1981 Dec 23
+ -5:00 Mexico E%sT 1998 Aug 2 2:00
+ -6:00 Mexico C%sT
+# Campeche, Yucatan
+Zone America/Merida -5:58:28 - LMT 1922 Jan 1 0:01:32
+ -6:00 - CST 1981 Dec 23
+ -5:00 - EST 1982 Dec 2
+ -6:00 Mexico C%sT
+# Coahuila, Durango, Nuevo Leon, Tamaulipas (near US border)
+Zone America/Matamoros -6:40:00 - LMT 1921 Dec 31 23:20:00
+ -6:00 - CST 1988
+ -6:00 US C%sT 1989
+ -6:00 Mexico C%sT 2010
+ -6:00 US C%sT
+# Coahuila, Durango, Nuevo Leon, Tamaulipas (away from US border)
+Zone America/Monterrey -6:41:16 - LMT 1921 Dec 31 23:18:44
+ -6:00 - CST 1988
+ -6:00 US C%sT 1989
+ -6:00 Mexico C%sT
+# Central Mexico
+Zone America/Mexico_City -6:36:36 - LMT 1922 Jan 1 0:23:24
+ -7:00 - MST 1927 Jun 10 23:00
+ -6:00 - CST 1930 Nov 15
+ -7:00 - MST 1931 May 1 23:00
+ -6:00 - CST 1931 Oct
+ -7:00 - MST 1932 Apr 1
+ -6:00 Mexico C%sT 2001 Sep 30 02:00
+ -6:00 - CST 2002 Feb 20
+ -6:00 Mexico C%sT
+# Chihuahua (near US border)
+Zone America/Ojinaga -6:57:40 - LMT 1922 Jan 1 0:02:20
+ -7:00 - MST 1927 Jun 10 23:00
+ -6:00 - CST 1930 Nov 15
+ -7:00 - MST 1931 May 1 23:00
+ -6:00 - CST 1931 Oct
+ -7:00 - MST 1932 Apr 1
+ -6:00 - CST 1996
+ -6:00 Mexico C%sT 1998
+ -6:00 - CST 1998 Apr Sun>=1 3:00
+ -7:00 Mexico M%sT 2010
+ -7:00 US M%sT
+# Chihuahua (away from US border)
+Zone America/Chihuahua -7:04:20 - LMT 1921 Dec 31 23:55:40
+ -7:00 - MST 1927 Jun 10 23:00
+ -6:00 - CST 1930 Nov 15
+ -7:00 - MST 1931 May 1 23:00
+ -6:00 - CST 1931 Oct
+ -7:00 - MST 1932 Apr 1
+ -6:00 - CST 1996
+ -6:00 Mexico C%sT 1998
+ -6:00 - CST 1998 Apr Sun>=1 3:00
+ -7:00 Mexico M%sT
+# Sonora
+Zone America/Hermosillo -7:23:52 - LMT 1921 Dec 31 23:36:08
+ -7:00 - MST 1927 Jun 10 23:00
+ -6:00 - CST 1930 Nov 15
+ -7:00 - MST 1931 May 1 23:00
+ -6:00 - CST 1931 Oct
+ -7:00 - MST 1932 Apr 1
+ -6:00 - CST 1942 Apr 24
+ -7:00 - MST 1949 Jan 14
+ -8:00 - PST 1970
+ -7:00 Mexico M%sT 1999
+ -7:00 - MST
+
+# From Alexander Krivenyshev (2010-04-21):
+# According to news, Bah&iacute;a de Banderas (Mexican state of Nayarit)
+# changed time zone UTC-7 to new time zone UTC-6 on April 4, 2010 (to
+# share the same time zone as nearby city Puerto Vallarta, Jalisco).
+#
+# (Spanish)
+# Bah&iacute;a de Banderas homologa su horario al del centro del
+# pa&iacute;s, a partir de este domingo
+# <a href="http://www.nayarit.gob.mx/notes.asp?id=20748">
+# http://www.nayarit.gob.mx/notes.asp?id=20748
+# </a>
+#
+# Bah&iacute;a de Banderas homologa su horario con el del Centro del
+# Pa&iacute;s
+# <a href="http://www.bahiadebanderas.gob.mx/principal/index.php?option=com_content&view=article&id=261:bahia-de-banderas-homologa-su-horario-con-el-del-centro-del-pais&catid=42:comunicacion-social&Itemid=50">
+# http://www.bahiadebanderas.gob.mx/principal/index.php?option=com_content&view=article&id=261:bahia-de-banderas-homologa-su-horario-con-el-del-centro-del-pais&catid=42:comunicacion-social&Itemid=50"
+# </a>
+#
+# (English)
+# Puerto Vallarta and Bah&iacute;a de Banderas: One Time Zone
+# <a href="http://virtualvallarta.com/puertovallarta/puertovallarta/localnews/2009-12-03-Puerto-Vallarta-and-Bahia-de-Banderas-One-Time-Zone.shtml">
+# http://virtualvallarta.com/puertovallarta/puertovallarta/localnews/2009-12-03-Puerto-Vallarta-and-Bahia-de-Banderas-One-Time-Zone.shtml
+# </a>
+#
+# or
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_mexico08.html">
+# http://www.worldtimezone.com/dst_news/dst_news_mexico08.html
+# </a>
+#
+# "Mexico's Senate approved the amendments to the Mexican Schedule System that
+# will allow Bah&iacute;a de Banderas and Puerto Vallarta to share the same time
+# zone ..."
+# Baja California Sur, Nayarit, Sinaloa
+
+# From Arthur David Olson (2010-05-01):
+# Use "Bahia_Banderas" to keep the name to fourteen characters.
+
+Zone America/Mazatlan -7:05:40 - LMT 1921 Dec 31 23:54:20
+ -7:00 - MST 1927 Jun 10 23:00
+ -6:00 - CST 1930 Nov 15
+ -7:00 - MST 1931 May 1 23:00
+ -6:00 - CST 1931 Oct
+ -7:00 - MST 1932 Apr 1
+ -6:00 - CST 1942 Apr 24
+ -7:00 - MST 1949 Jan 14
+ -8:00 - PST 1970
+ -7:00 Mexico M%sT
+
+Zone America/Bahia_Banderas -7:01:00 - LMT 1921 Dec 31 23:59:00
+ -7:00 - MST 1927 Jun 10 23:00
+ -6:00 - CST 1930 Nov 15
+ -7:00 - MST 1931 May 1 23:00
+ -6:00 - CST 1931 Oct
+ -7:00 - MST 1932 Apr 1
+ -6:00 - CST 1942 Apr 24
+ -7:00 - MST 1949 Jan 14
+ -8:00 - PST 1970
+ -7:00 Mexico M%sT 2010 Apr 4 2:00
+ -6:00 Mexico C%sT
+
+# Baja California (near US border)
+Zone America/Tijuana -7:48:04 - LMT 1922 Jan 1 0:11:56
+ -7:00 - MST 1924
+ -8:00 - PST 1927 Jun 10 23:00
+ -7:00 - MST 1930 Nov 15
+ -8:00 - PST 1931 Apr 1
+ -8:00 1:00 PDT 1931 Sep 30
+ -8:00 - PST 1942 Apr 24
+ -8:00 1:00 PWT 1945 Aug 14 23:00u
+ -8:00 1:00 PPT 1945 Nov 12 # Peace
+ -8:00 - PST 1948 Apr 5
+ -8:00 1:00 PDT 1949 Jan 14
+ -8:00 - PST 1954
+ -8:00 CA P%sT 1961
+ -8:00 - PST 1976
+ -8:00 US P%sT 1996
+ -8:00 Mexico P%sT 2001
+ -8:00 US P%sT 2002 Feb 20
+ -8:00 Mexico P%sT 2010
+ -8:00 US P%sT
+# Baja California (away from US border)
+Zone America/Santa_Isabel -7:39:28 - LMT 1922 Jan 1 0:20:32
+ -7:00 - MST 1924
+ -8:00 - PST 1927 Jun 10 23:00
+ -7:00 - MST 1930 Nov 15
+ -8:00 - PST 1931 Apr 1
+ -8:00 1:00 PDT 1931 Sep 30
+ -8:00 - PST 1942 Apr 24
+ -8:00 1:00 PWT 1945 Aug 14 23:00u
+ -8:00 1:00 PPT 1945 Nov 12 # Peace
+ -8:00 - PST 1948 Apr 5
+ -8:00 1:00 PDT 1949 Jan 14
+ -8:00 - PST 1954
+ -8:00 CA P%sT 1961
+ -8:00 - PST 1976
+ -8:00 US P%sT 1996
+ -8:00 Mexico P%sT 2001
+ -8:00 US P%sT 2002 Feb 20
+ -8:00 Mexico P%sT
+# From Paul Eggert (2006-03-22):
+# Formerly there was an America/Ensenada zone, which differed from
+# America/Tijuana only in that it did not observe DST from 1976
+# through 1995. This was as per Shanks (1999). But Shanks & Pottenger say
+# Ensenada did not observe DST from 1948 through 1975. Guy Harris reports
+# that the 1987 OAG says "Only Ensenada, Mexicale, San Felipe and
+# Tijuana observe DST," which agrees with Shanks & Pottenger but implies that
+# DST-observance was a town-by-town matter back then. This concerns
+# data after 1970 so most likely there should be at least one Zone
+# other than America/Tijuana for Baja, but it's not clear yet what its
+# name or contents should be.
+#
+# Revillagigedo Is
+# no information
+
+###############################################################################
+
+# Anguilla
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Anguilla -4:12:16 - LMT 1912 Mar 2
+ -4:00 - AST
+
+# Antigua and Barbuda
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Antigua -4:07:12 - LMT 1912 Mar 2
+ -5:00 - EST 1951
+ -4:00 - AST
+
+# Bahamas
+#
+# From Sue Williams (2006-12-07):
+# The Bahamas announced about a month ago that they plan to change their DST
+# rules to sync with the U.S. starting in 2007....
+# http://www.jonesbahamas.com/?c=45&a=10412
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Bahamas 1964 1975 - Oct lastSun 2:00 0 S
+Rule Bahamas 1964 1975 - Apr lastSun 2:00 1:00 D
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Nassau -5:09:24 - LMT 1912 Mar 2
+ -5:00 Bahamas E%sT 1976
+ -5:00 US E%sT
+
+# Barbados
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Barb 1977 only - Jun 12 2:00 1:00 D
+Rule Barb 1977 1978 - Oct Sun>=1 2:00 0 S
+Rule Barb 1978 1980 - Apr Sun>=15 2:00 1:00 D
+Rule Barb 1979 only - Sep 30 2:00 0 S
+Rule Barb 1980 only - Sep 25 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Barbados -3:58:28 - LMT 1924 # Bridgetown
+ -3:58:28 - BMT 1932 # Bridgetown Mean Time
+ -4:00 Barb A%sT
+
+# Belize
+# Whitman entirely disagrees with Shanks; go with Shanks & Pottenger.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Belize 1918 1942 - Oct Sun>=2 0:00 0:30 HD
+Rule Belize 1919 1943 - Feb Sun>=9 0:00 0 S
+Rule Belize 1973 only - Dec 5 0:00 1:00 D
+Rule Belize 1974 only - Feb 9 0:00 0 S
+Rule Belize 1982 only - Dec 18 0:00 1:00 D
+Rule Belize 1983 only - Feb 12 0:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Belize -5:52:48 - LMT 1912 Apr
+ -6:00 Belize C%sT
+
+# Bermuda
+
+# From Dan Jones, reporting in The Royal Gazette (2006-06-26):
+
+# Next year, however, clocks in the US will go forward on the second Sunday
+# in March, until the first Sunday in November. And, after the Time Zone
+# (Seasonal Variation) Bill 2006 was passed in the House of Assembly on
+# Friday, the same thing will happen in Bermuda.
+# http://www.theroyalgazette.com/apps/pbcs.dll/article?AID=/20060529/NEWS/105290135
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Atlantic/Bermuda -4:19:04 - LMT 1930 Jan 1 2:00 # Hamilton
+ -4:00 - AST 1974 Apr 28 2:00
+ -4:00 Bahamas A%sT 1976
+ -4:00 US A%sT
+
+# Cayman Is
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Cayman -5:25:32 - LMT 1890 # Georgetown
+ -5:07:12 - KMT 1912 Feb # Kingston Mean Time
+ -5:00 - EST
+
+# Costa Rica
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule CR 1979 1980 - Feb lastSun 0:00 1:00 D
+Rule CR 1979 1980 - Jun Sun>=1 0:00 0 S
+Rule CR 1991 1992 - Jan Sat>=15 0:00 1:00 D
+# IATA SSIM (1991-09) says the following was at 1:00;
+# go with Shanks & Pottenger.
+Rule CR 1991 only - Jul 1 0:00 0 S
+Rule CR 1992 only - Mar 15 0:00 0 S
+# There are too many San Joses elsewhere, so we'll use `Costa Rica'.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Costa_Rica -5:36:20 - LMT 1890 # San Jose
+ -5:36:20 - SJMT 1921 Jan 15 # San Jose Mean Time
+ -6:00 CR C%sT
+# Coco
+# no information; probably like America/Costa_Rica
+
+# Cuba
+
+# From Arthur David Olson (1999-03-29):
+# The 1999-03-28 exhibition baseball game held in Havana, Cuba, between
+# the Cuban National Team and the Baltimore Orioles was carried live on
+# the Orioles Radio Network, including affiliate WTOP in Washington, DC.
+# During the game, play-by-play announcer Jim Hunter noted that
+# "We'll be losing two hours of sleep...Cuba switched to Daylight Saving
+# Time today." (The "two hour" remark referred to losing one hour of
+# sleep on 1999-03-28--when the announcers were in Cuba as it switched
+# to DST--and one more hour on 1999-04-04--when the announcers will have
+# returned to Baltimore, which switches on that date.)
+
+# From Evert van der Veer via Steffen Thorsen (2004-10-28):
+# Cuba is not going back to standard time this year.
+# From Paul Eggert (2006-03-22):
+# http://www.granma.cu/ingles/2004/septiembre/juev30/41medid-i.html
+# says that it's due to a problem at the Antonio Guiteras
+# thermoelectric plant, and says "This October there will be no return
+# to normal hours (after daylight saving time)".
+# For now, let's assume that it's a temporary measure.
+
+# From Carlos A. Carnero Delgado (2005-11-12):
+# This year (just like in 2004-2005) there's no change in time zone
+# adjustment in Cuba. We will stay in daylight saving time:
+# http://www.granma.cu/espanol/2005/noviembre/mier9/horario.html
+
+# From Jesper Norgaard Welen (2006-10-21):
+# An article in GRANMA INTERNACIONAL claims that Cuba will end
+# the 3 years of permanent DST next weekend, see
+# http://www.granma.cu/ingles/2006/octubre/lun16/43horario.html
+# "On Saturday night, October 28 going into Sunday, October 29, at 01:00,
+# watches should be set back one hour -- going back to 00:00 hours -- returning
+# to the normal schedule....
+
+# From Paul Eggert (2007-03-02):
+# http://www.granma.cubaweb.cu/english/news/art89.html, dated yesterday,
+# says Cuban clocks will advance at midnight on March 10.
+# For lack of better information, assume Cuba will use US rules,
+# except that it switches at midnight standard time as usual.
+#
+# From Steffen Thorsen (2007-10-25):
+# Carlos Alberto Fonseca Arauz informed me that Cuba will end DST one week
+# earlier - on the last Sunday of October, just like in 2006.
+#
+# He supplied these references:
+#
+# http://www.prensalatina.com.mx/article.asp?ID=%7B4CC32C1B-A9F7-42FB-8A07-8631AFC923AF%7D&language=ES
+# http://actualidad.terra.es/sociedad/articulo/cuba_llama_ahorrar_energia_cambio_1957044.htm
+#
+# From Alex Kryvenishev (2007-10-25):
+# Here is also article from Granma (Cuba):
+#
+# [Regira] el Horario Normal desde el [proximo] domingo 28 de octubre
+# http://www.granma.cubaweb.cu/2007/10/24/nacional/artic07.html
+#
+# http://www.worldtimezone.com/dst_news/dst_news_cuba03.html
+
+# From Arthur David Olson (2008-03-09):
+# I'm in Maryland which is now observing United States Eastern Daylight
+# Time. At 9:44 local time I used RealPlayer to listen to
+# <a href="http://media.enet.cu/radioreloj">
+# http://media.enet.cu/radioreloj
+# </a>, a Cuban information station, and heard
+# the time announced as "ocho cuarenta y cuatro" ("eight forty-four"),
+# indicating that Cuba is still on standard time.
+
+# From Steffen Thorsen (2008-03-12):
+# It seems that Cuba will start DST on Sunday, 2007-03-16...
+# It was announced yesterday, according to this source (in Spanish):
+# <a href="http://www.nnc.cubaweb.cu/marzo-2008/cien-1-11-3-08.htm">
+# http://www.nnc.cubaweb.cu/marzo-2008/cien-1-11-3-08.htm
+# </a>
+#
+# Some more background information is posted here:
+# <a href="http://www.timeanddate.com/news/time/cuba-starts-dst-march-16.html">
+# http://www.timeanddate.com/news/time/cuba-starts-dst-march-16.html
+# </a>
+#
+# The article also says that Cuba has been observing DST since 1963,
+# while Shanks (and tzdata) has 1965 as the first date (except in the
+# 1940's). Many other web pages in Cuba also claim that it has been
+# observed since 1963, but with the exception of 1970 - an exception
+# which is not present in tzdata/Shanks. So there is a chance we need to
+# change some historic records as well.
+#
+# One example:
+# <a href="http://www.radiohc.cu/espanol/noticias/mar07/11mar/hor.htm">
+# http://www.radiohc.cu/espanol/noticias/mar07/11mar/hor.htm
+# </a>
+
+# From Jesper Norgaard Welen (2008-03-13):
+# The Cuban time change has just been confirmed on the most authoritative
+# web site, the Granma. Please check out
+# <a href="http://www.granma.cubaweb.cu/2008/03/13/nacional/artic10.html">
+# http://www.granma.cubaweb.cu/2008/03/13/nacional/artic10.html
+# </a>
+#
+# Basically as expected after Steffen Thorsens information, the change
+# will take place midnight between Saturday and Sunday.
+
+# From Arthur David Olson (2008-03-12):
+# Assume Sun>=15 (third Sunday) going forward.
+
+# From Alexander Krivenyshev (2009-03-04)
+# According to the Radio Reloj - Cuba will start Daylight Saving Time on
+# midnight between Saturday, March 07, 2009 and Sunday, March 08, 2009-
+# not on midnight March 14 / March 15 as previously thought.
+#
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_cuba05.html">
+# http://www.worldtimezone.com/dst_news/dst_news_cuba05.html
+# (in Spanish)
+# </a>
+
+# From Arthur David Olson (2009-03-09)
+# I listened over the Internet to
+# <a href="http://media.enet.cu/readioreloj">
+# http://media.enet.cu/readioreloj
+# </a>
+# this morning; when it was 10:05 a. m. here in Bethesda, Maryland the
+# the time was announced as "diez cinco"--the same time as here, indicating
+# that has indeed switched to DST. Assume second Sunday from 2009 forward.
+
+# From Steffen Thorsen (2011-03-08):
+# Granma announced that Cuba is going to start DST on 2011-03-20 00:00:00
+# this year. Nothing about the end date known so far (if that has
+# changed at all).
+#
+# Source:
+# <a href="http://granma.co.cu/2011/03/08/nacional/artic01.html">
+# http://granma.co.cu/2011/03/08/nacional/artic01.html
+# </a>
+#
+# Our info:
+# <a href="http://www.timeanddate.com/news/time/cuba-starts-dst-2011.html">
+# http://www.timeanddate.com/news/time/cuba-starts-dst-2011.html
+# </a>
+#
+# From Steffen Thorsen (2011-10-30)
+# Cuba will end DST two weeks later this year. Instead of going back
+# tonight, it has been delayed to 2011-11-13 at 01:00.
+#
+# One source (Spanish)
+# <a href="http://www.radioangulo.cu/noticias/cuba/17105-cuba-restablecera-el-horario-del-meridiano-de-greenwich.html">
+# http://www.radioangulo.cu/noticias/cuba/17105-cuba-restablecera-el-horario-del-meridiano-de-greenwich.html
+# </a>
+#
+# Our page:
+# <a href="http://www.timeanddate.com/news/time/cuba-time-changes-2011.html">
+# http://www.timeanddate.com/news/time/cuba-time-changes-2011.html
+# </a>
+#
+# From Steffen Thorsen (2012-03-01)
+# According to Radio Reloj, Cuba will start DST on Midnight between March
+# 31 and April 1.
+#
+# Radio Reloj has the following info (Spanish):
+# <a href="http://www.radioreloj.cu/index.php/noticias-radio-reloj/71-miscelaneas/7529-cuba-aplicara-el-horario-de-verano-desde-el-1-de-abril">
+# http://www.radioreloj.cu/index.php/noticias-radio-reloj/71-miscelaneas/7529-cuba-aplicara-el-horario-de-verano-desde-el-1-de-abril
+# </a>
+#
+# Our info on it:
+# <a href="http://www.timeanddate.com/news/time/cuba-starts-dst-2012.html">
+# http://www.timeanddate.com/news/time/cuba-starts-dst-2012.html
+# </a>
+
+# From Steffen Thorsen (2012-11-03):
+# Radio Reloj and many other sources report that Cuba is changing back
+# to standard time on 2012-11-04:
+# http://www.radioreloj.cu/index.php/noticias-radio-reloj/36-nacionales/9961-regira-horario-normal-en-cuba-desde-el-domingo-cuatro-de-noviembre
+# From Paul Eggert (2012-11-03):
+# For now, assume the future rule is first Sunday in November.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Cuba 1928 only - Jun 10 0:00 1:00 D
+Rule Cuba 1928 only - Oct 10 0:00 0 S
+Rule Cuba 1940 1942 - Jun Sun>=1 0:00 1:00 D
+Rule Cuba 1940 1942 - Sep Sun>=1 0:00 0 S
+Rule Cuba 1945 1946 - Jun Sun>=1 0:00 1:00 D
+Rule Cuba 1945 1946 - Sep Sun>=1 0:00 0 S
+Rule Cuba 1965 only - Jun 1 0:00 1:00 D
+Rule Cuba 1965 only - Sep 30 0:00 0 S
+Rule Cuba 1966 only - May 29 0:00 1:00 D
+Rule Cuba 1966 only - Oct 2 0:00 0 S
+Rule Cuba 1967 only - Apr 8 0:00 1:00 D
+Rule Cuba 1967 1968 - Sep Sun>=8 0:00 0 S
+Rule Cuba 1968 only - Apr 14 0:00 1:00 D
+Rule Cuba 1969 1977 - Apr lastSun 0:00 1:00 D
+Rule Cuba 1969 1971 - Oct lastSun 0:00 0 S
+Rule Cuba 1972 1974 - Oct 8 0:00 0 S
+Rule Cuba 1975 1977 - Oct lastSun 0:00 0 S
+Rule Cuba 1978 only - May 7 0:00 1:00 D
+Rule Cuba 1978 1990 - Oct Sun>=8 0:00 0 S
+Rule Cuba 1979 1980 - Mar Sun>=15 0:00 1:00 D
+Rule Cuba 1981 1985 - May Sun>=5 0:00 1:00 D
+Rule Cuba 1986 1989 - Mar Sun>=14 0:00 1:00 D
+Rule Cuba 1990 1997 - Apr Sun>=1 0:00 1:00 D
+Rule Cuba 1991 1995 - Oct Sun>=8 0:00s 0 S
+Rule Cuba 1996 only - Oct 6 0:00s 0 S
+Rule Cuba 1997 only - Oct 12 0:00s 0 S
+Rule Cuba 1998 1999 - Mar lastSun 0:00s 1:00 D
+Rule Cuba 1998 2003 - Oct lastSun 0:00s 0 S
+Rule Cuba 2000 2004 - Apr Sun>=1 0:00s 1:00 D
+Rule Cuba 2006 2010 - Oct lastSun 0:00s 0 S
+Rule Cuba 2007 only - Mar Sun>=8 0:00s 1:00 D
+Rule Cuba 2008 only - Mar Sun>=15 0:00s 1:00 D
+Rule Cuba 2009 2010 - Mar Sun>=8 0:00s 1:00 D
+Rule Cuba 2011 only - Mar Sun>=15 0:00s 1:00 D
+Rule Cuba 2011 only - Nov 13 0:00s 0 S
+Rule Cuba 2012 only - Apr 1 0:00s 1:00 D
+Rule Cuba 2012 max - Nov Sun>=1 0:00s 0 S
+Rule Cuba 2013 max - Mar Sun>=8 0:00s 1:00 D
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Havana -5:29:28 - LMT 1890
+ -5:29:36 - HMT 1925 Jul 19 12:00 # Havana MT
+ -5:00 Cuba C%sT
+
+# Dominica
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Dominica -4:05:36 - LMT 1911 Jul 1 0:01 # Roseau
+ -4:00 - AST
+
+# Dominican Republic
+
+# From Steffen Thorsen (2000-10-30):
+# Enrique Morales reported to me that the Dominican Republic has changed the
+# time zone to Eastern Standard Time as of Sunday 29 at 2 am....
+# http://www.listin.com.do/antes/261000/republica/princi.html
+
+# From Paul Eggert (2000-12-04):
+# That URL (2000-10-26, in Spanish) says they planned to use US-style DST.
+
+# From Rives McDow (2000-12-01):
+# Dominican Republic changed its mind and presidential decree on Tuesday,
+# November 28, 2000, with a new decree. On Sunday, December 3 at 1:00 AM the
+# Dominican Republic will be reverting to 8 hours from the International Date
+# Line, and will not be using DST in the foreseeable future. The reason they
+# decided to use DST was to be in synch with Puerto Rico, who was also going
+# to implement DST. When Puerto Rico didn't implement DST, the president
+# decided to revert.
+
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule DR 1966 only - Oct 30 0:00 1:00 D
+Rule DR 1967 only - Feb 28 0:00 0 S
+Rule DR 1969 1973 - Oct lastSun 0:00 0:30 HD
+Rule DR 1970 only - Feb 21 0:00 0 S
+Rule DR 1971 only - Jan 20 0:00 0 S
+Rule DR 1972 1974 - Jan 21 0:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Santo_Domingo -4:39:36 - LMT 1890
+ -4:40 - SDMT 1933 Apr 1 12:00 # S. Dom. MT
+ -5:00 DR E%sT 1974 Oct 27
+ -4:00 - AST 2000 Oct 29 02:00
+ -5:00 US E%sT 2000 Dec 3 01:00
+ -4:00 - AST
+
+# El Salvador
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Salv 1987 1988 - May Sun>=1 0:00 1:00 D
+Rule Salv 1987 1988 - Sep lastSun 0:00 0 S
+# There are too many San Salvadors elsewhere, so use America/El_Salvador
+# instead of America/San_Salvador.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/El_Salvador -5:56:48 - LMT 1921 # San Salvador
+ -6:00 Salv C%sT
+
+# Grenada
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Grenada -4:07:00 - LMT 1911 Jul # St George's
+ -4:00 - AST
+
+# Guadeloupe
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Guadeloupe -4:06:08 - LMT 1911 Jun 8 # Pointe a Pitre
+ -4:00 - AST
+# St Barthelemy
+Link America/Guadeloupe America/St_Barthelemy
+# St Martin (French part)
+Link America/Guadeloupe America/Marigot
+
+# Guatemala
+#
+# From Gwillim Law (2006-04-22), after a heads-up from Oscar van Vlijmen:
+# Diario Co Latino, at
+# http://www.diariocolatino.com/internacionales/detalles.asp?NewsID=8079,
+# says in an article dated 2006-04-19 that the Guatemalan government had
+# decided on that date to advance official time by 60 minutes, to lessen the
+# impact of the elevated cost of oil.... Daylight saving time will last from
+# 2006-04-29 24:00 (Guatemalan standard time) to 2006-09-30 (time unspecified).
+# From Paul Eggert (2006-06-22):
+# The Ministry of Energy and Mines, press release CP-15/2006
+# (2006-04-19), says DST ends at 24:00. See
+# <http://www.sieca.org.gt/Sitio_publico/Energeticos/Doc/Medidas/Cambio_Horario_Nac_190406.pdf>.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Guat 1973 only - Nov 25 0:00 1:00 D
+Rule Guat 1974 only - Feb 24 0:00 0 S
+Rule Guat 1983 only - May 21 0:00 1:00 D
+Rule Guat 1983 only - Sep 22 0:00 0 S
+Rule Guat 1991 only - Mar 23 0:00 1:00 D
+Rule Guat 1991 only - Sep 7 0:00 0 S
+Rule Guat 2006 only - Apr 30 0:00 1:00 D
+Rule Guat 2006 only - Oct 1 0:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Guatemala -6:02:04 - LMT 1918 Oct 5
+ -6:00 Guat C%sT
+
+# Haiti
+# From Gwillim Law (2005-04-15):
+# Risto O. Nykanen wrote me that Haiti is now on DST.
+# I searched for confirmation, and I found a
+# <a href="http://www.haitianconsulate.org/time.doc"> press release
+# on the Web page of the Haitian Consulate in Chicago (2005-03-31),
+# </a>. Translated from French, it says:
+#
+# "The Prime Minister's Communication Office notifies the public in general
+# and the press in particular that, following a decision of the Interior
+# Ministry and the Territorial Collectivities [I suppose that means the
+# provinces], Haiti will move to Eastern Daylight Time in the night from next
+# Saturday the 2nd to Sunday the 3rd.
+#
+# "Consequently, the Prime Minister's Communication Office wishes to inform
+# the population that the country's clocks will be set forward one hour
+# starting at midnight. This provision will hold until the last Saturday in
+# October 2005.
+#
+# "Port-au-Prince, March 31, 2005"
+#
+# From Steffen Thorsen (2006-04-04):
+# I have been informed by users that Haiti observes DST this year like
+# last year, so the current "only" rule for 2005 might be changed to a
+# "max" rule or to last until 2006. (Who knows if they will observe DST
+# next year or if they will extend their DST like US/Canada next year).
+#
+# I have found this article about it (in French):
+# http://www.haitipressnetwork.com/news.cfm?articleID=7612
+#
+# The reason seems to be an energy crisis.
+
+# From Stephen Colebourne (2007-02-22):
+# Some IATA info: Haiti won't be having DST in 2007.
+
+# From Steffen Thorsen (2012-03-11):
+# According to several news sources, Haiti will observe DST this year,
+# apparently using the same start and end date as USA/Canada.
+# So this means they have already changed their time.
+#
+# (Sources in French):
+# <a href="http://www.alterpresse.org/spip.php?article12510">
+# http://www.alterpresse.org/spip.php?article12510
+# </a>
+# <a href="http://radiovision2000haiti.net/home/?p=13253">
+# http://radiovision2000haiti.net/home/?p=13253
+# </a>
+#
+# Our coverage:
+# <a href="http://www.timeanddate.com/news/time/haiti-dst-2012.html">
+# http://www.timeanddate.com/news/time/haiti-dst-2012.html
+# </a>
+
+# From Arthur David Olson (2012-03-11):
+# The alterpresse.org source seems to show a US-style leap from 2:00 a.m. to
+# 3:00 a.m. rather than the traditional Haitian jump at midnight.
+# Assume a US-style fall back as well XXX.
+# Do not yet assume that the change carries forward past 2012 XXX.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Haiti 1983 only - May 8 0:00 1:00 D
+Rule Haiti 1984 1987 - Apr lastSun 0:00 1:00 D
+Rule Haiti 1983 1987 - Oct lastSun 0:00 0 S
+# Shanks & Pottenger say AT is 2:00, but IATA SSIM (1991/1997) says 1:00s.
+# Go with IATA.
+Rule Haiti 1988 1997 - Apr Sun>=1 1:00s 1:00 D
+Rule Haiti 1988 1997 - Oct lastSun 1:00s 0 S
+Rule Haiti 2005 2006 - Apr Sun>=1 0:00 1:00 D
+Rule Haiti 2005 2006 - Oct lastSun 0:00 0 S
+Rule Haiti 2012 only - Mar Sun>=8 2:00 1:00 D
+Rule Haiti 2012 only - Nov Sun>=1 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Port-au-Prince -4:49:20 - LMT 1890
+ -4:49 - PPMT 1917 Jan 24 12:00 # P-a-P MT
+ -5:00 Haiti E%sT
+
+# Honduras
+# Shanks & Pottenger say 1921 Jan 1; go with Whitman's more precise Apr 1.
+
+# From Paul Eggert (2006-05-05):
+# worldtimezone.com reports a 2006-05-02 Spanish-language AP article
+# saying Honduras will start using DST midnight Saturday, effective 4
+# months until September. La Tribuna reported today
+# <http://www.latribuna.hn/99299.html> that Manuel Zelaya, the president
+# of Honduras, refused to back down on this.
+
+# From Jesper Norgaard Welen (2006-08-08):
+# It seems that Honduras has returned from DST to standard time this Monday at
+# 00:00 hours (prolonging Sunday to 25 hours duration).
+# http://www.worldtimezone.com/dst_news/dst_news_honduras04.html
+
+# From Paul Eggert (2006-08-08):
+# Also see Diario El Heraldo, The country returns to standard time (2006-08-08)
+# <http://www.elheraldo.hn/nota.php?nid=54941&sec=12>.
+# It mentions executive decree 18-2006.
+
+# From Steffen Thorsen (2006-08-17):
+# Honduras will observe DST from 2007 to 2009, exact dates are not
+# published, I have located this authoritative source:
+# http://www.presidencia.gob.hn/noticia.aspx?nId=47
+
+# From Steffen Thorsen (2007-03-30):
+# http://www.laprensahn.com/pais_nota.php?id04962=7386
+# So it seems that Honduras will not enter DST this year....
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Hond 1987 1988 - May Sun>=1 0:00 1:00 D
+Rule Hond 1987 1988 - Sep lastSun 0:00 0 S
+Rule Hond 2006 only - May Sun>=1 0:00 1:00 D
+Rule Hond 2006 only - Aug Mon>=1 0:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Tegucigalpa -5:48:52 - LMT 1921 Apr
+ -6:00 Hond C%sT
+#
+# Great Swan I ceded by US to Honduras in 1972
+
+# Jamaica
+
+# From Bob Devine (1988-01-28):
+# Follows US rules.
+
+# From U. S. Naval Observatory (1989-01-19):
+# JAMAICA 5 H BEHIND UTC
+
+# From Shanks & Pottenger:
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Jamaica -5:07:12 - LMT 1890 # Kingston
+ -5:07:12 - KMT 1912 Feb # Kingston Mean Time
+ -5:00 - EST 1974 Apr 28 2:00
+ -5:00 US E%sT 1984
+ -5:00 - EST
+
+# Martinique
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Martinique -4:04:20 - LMT 1890 # Fort-de-France
+ -4:04:20 - FFMT 1911 May # Fort-de-France MT
+ -4:00 - AST 1980 Apr 6
+ -4:00 1:00 ADT 1980 Sep 28
+ -4:00 - AST
+
+# Montserrat
+# From Paul Eggert (2006-03-22):
+# In 1995 volcanic eruptions forced evacuation of Plymouth, the capital.
+# world.gazetteer.com says Cork Hill is the most populous location now.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Montserrat -4:08:52 - LMT 1911 Jul 1 0:01 # Cork Hill
+ -4:00 - AST
+
+# Nicaragua
+#
+# This uses Shanks & Pottenger for times before 2005.
+#
+# From Steffen Thorsen (2005-04-12):
+# I've got reports from 8 different people that Nicaragua just started
+# DST on Sunday 2005-04-10, in order to save energy because of
+# expensive petroleum. The exact end date for DST is not yet
+# announced, only "September" but some sites also say "mid-September".
+# Some background information is available on the President's official site:
+# http://www.presidencia.gob.ni/Presidencia/Files_index/Secretaria/Notas%20de%20Prensa/Presidente/2005/ABRIL/Gobierno-de-nicaragua-adelanta-hora-oficial-06abril.htm
+# The Decree, no 23-2005 is available here:
+# http://www.presidencia.gob.ni/buscador_gaceta/BD/DECRETOS/2005/Decreto%2023-2005%20Se%20adelanta%20en%20una%20hora%20en%20todo%20el%20territorio%20nacional%20apartir%20de%20las%2024horas%20del%2009%20de%20Abril.pdf
+#
+# From Paul Eggert (2005-05-01):
+# The decree doesn't say anything about daylight saving, but for now let's
+# assume that it is daylight saving....
+#
+# From Gwillim Law (2005-04-21):
+# The Associated Press story on the time change, which can be found at
+# http://www.lapalmainteractivo.com/guias/content/gen/ap/America_Latina/AMC_GEN_NICARAGUA_HORA.html
+# and elsewhere, says (fifth paragraph, translated from Spanish): "The last
+# time that a change of clocks was applied to save energy was in the year 2000
+# during the Arnoldo Aleman administration."...
+# The northamerica file says that Nicaragua has been on UTC-6 continuously
+# since December 1998. I wasn't able to find any details of Nicaraguan time
+# changes in 2000. Perhaps a note could be added to the northamerica file, to
+# the effect that we have indirect evidence that DST was observed in 2000.
+#
+# From Jesper Norgaard Welen (2005-11-02):
+# Nicaragua left DST the 2005-10-02 at 00:00 (local time).
+# http://www.presidencia.gob.ni/presidencia/files_index/secretaria/comunicados/2005/septiembre/26septiembre-cambio-hora.htm
+# (2005-09-26)
+#
+# From Jesper Norgaard Welen (2006-05-05):
+# http://www.elnuevodiario.com.ni/2006/05/01/nacionales/18410
+# (my informal translation)
+# By order of the president of the republic, Enrique Bolanos, Nicaragua
+# advanced by sixty minutes their official time, yesterday at 2 in the
+# morning, and will stay that way until 30.th. of september.
+#
+# From Jesper Norgaard Welen (2006-09-30):
+# http://www.presidencia.gob.ni/buscador_gaceta/BD/DECRETOS/2006/D-063-2006P-PRN-Cambio-Hora.pdf
+# My informal translation runs:
+# The natural sun time is restored in all the national territory, in that the
+# time is returned one hour at 01:00 am of October 1 of 2006.
+#
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Nic 1979 1980 - Mar Sun>=16 0:00 1:00 D
+Rule Nic 1979 1980 - Jun Mon>=23 0:00 0 S
+Rule Nic 2005 only - Apr 10 0:00 1:00 D
+Rule Nic 2005 only - Oct Sun>=1 0:00 0 S
+Rule Nic 2006 only - Apr 30 2:00 1:00 D
+Rule Nic 2006 only - Oct Sun>=1 1:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Managua -5:45:08 - LMT 1890
+ -5:45:12 - MMT 1934 Jun 23 # Managua Mean Time?
+ -6:00 - CST 1973 May
+ -5:00 - EST 1975 Feb 16
+ -6:00 Nic C%sT 1992 Jan 1 4:00
+ -5:00 - EST 1992 Sep 24
+ -6:00 - CST 1993
+ -5:00 - EST 1997
+ -6:00 Nic C%sT
+
+# Panama
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Panama -5:18:08 - LMT 1890
+ -5:19:36 - CMT 1908 Apr 22 # Colon Mean Time
+ -5:00 - EST
+
+# Puerto Rico
+# There are too many San Juans elsewhere, so we'll use `Puerto_Rico'.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Puerto_Rico -4:24:25 - LMT 1899 Mar 28 12:00 # San Juan
+ -4:00 - AST 1942 May 3
+ -4:00 US A%sT 1946
+ -4:00 - AST
+
+# St Kitts-Nevis
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/St_Kitts -4:10:52 - LMT 1912 Mar 2 # Basseterre
+ -4:00 - AST
+
+# St Lucia
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/St_Lucia -4:04:00 - LMT 1890 # Castries
+ -4:04:00 - CMT 1912 # Castries Mean Time
+ -4:00 - AST
+
+# St Pierre and Miquelon
+# There are too many St Pierres elsewhere, so we'll use `Miquelon'.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Miquelon -3:44:40 - LMT 1911 May 15 # St Pierre
+ -4:00 - AST 1980 May
+ -3:00 - PMST 1987 # Pierre & Miquelon Time
+ -3:00 Canada PM%sT
+
+# St Vincent and the Grenadines
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/St_Vincent -4:04:56 - LMT 1890 # Kingstown
+ -4:04:56 - KMT 1912 # Kingstown Mean Time
+ -4:00 - AST
+
+# Turks and Caicos
+#
+# From Chris Dunn in
+# <http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=415007>
+# (2007-03-15): In the Turks & Caicos Islands (America/Grand_Turk) the
+# daylight saving dates for time changes have been adjusted to match
+# the recent U.S. change of dates.
+#
+# From Brian Inglis (2007-04-28):
+# http://www.turksandcaicos.tc/calendar/index.htm [2007-04-26]
+# there is an entry for Nov 4 "Daylight Savings Time Ends 2007" and three
+# rows before that there is an out of date entry for Oct:
+# "Eastern Standard Times Begins 2007
+# Clocks are set back one hour at 2:00 a.m. local Daylight Saving Time"
+# indicating that the normal ET rules are followed.
+#
+# From Paul Eggert (2006-05-01):
+# Shanks & Pottenger say they use US DST rules, but IATA SSIM (1991/1998)
+# says they switch at midnight. Go with Shanks & Pottenger.
+#
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule TC 1979 1986 - Apr lastSun 2:00 1:00 D
+Rule TC 1979 2006 - Oct lastSun 2:00 0 S
+Rule TC 1987 2006 - Apr Sun>=1 2:00 1:00 D
+Rule TC 2007 max - Mar Sun>=8 2:00 1:00 D
+Rule TC 2007 max - Nov Sun>=1 2:00 0 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Grand_Turk -4:44:32 - LMT 1890
+ -5:07:12 - KMT 1912 Feb # Kingston Mean Time
+ -5:00 TC E%sT
+
+# British Virgin Is
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Tortola -4:18:28 - LMT 1911 Jul # Road Town
+ -4:00 - AST
+
+# Virgin Is
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/St_Thomas -4:19:44 - LMT 1911 Jul # Charlotte Amalie
+ -4:00 - AST
diff --git a/misc/flot/examples/axes-time-zones/tz/pacificnew b/misc/flot/examples/axes-time-zones/tz/pacificnew
new file mode 100644
index 0000000..bccd852
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/pacificnew
@@ -0,0 +1,28 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# From Arthur David Olson (1989-04-05):
+# On 1989-04-05, the U. S. House of Representatives passed (238-154) a bill
+# establishing "Pacific Presidential Election Time"; it was not acted on
+# by the Senate or signed into law by the President.
+# You might want to change the "PE" (Presidential Election) below to
+# "Q" (Quadrennial) to maintain three-character zone abbreviations.
+# If you're really conservative, you might want to change it to "D".
+# Avoid "L" (Leap Year), which won't be true in 2100.
+
+# If Presidential Election Time is ever established, replace "XXXX" below
+# with the year the law takes effect and uncomment the "##" lines.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+## Rule Twilite XXXX max - Apr Sun>=1 2:00 1:00 D
+## Rule Twilite XXXX max uspres Oct lastSun 2:00 1:00 PE
+## Rule Twilite XXXX max uspres Nov Sun>=7 2:00 0 S
+## Rule Twilite XXXX max nonpres Oct lastSun 2:00 0 S
+
+# Zone NAME GMTOFF RULES/SAVE FORMAT [UNTIL]
+## Zone America/Los_Angeles-PET -8:00 US P%sT XXXX
+## -8:00 Twilite P%sT
+
+# For now...
+Link America/Los_Angeles US/Pacific-New ##
diff --git a/misc/flot/examples/axes-time-zones/tz/solar87 b/misc/flot/examples/axes-time-zones/tz/solar87
new file mode 100644
index 0000000..2299558
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/solar87
@@ -0,0 +1,390 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# So much for footnotes about Saudi Arabia.
+# Apparent noon times below are for Riyadh; your mileage will vary.
+# Times were computed using formulas in the U.S. Naval Observatory's
+# Almanac for Computers 1987; the formulas "will give EqT to an accuracy of
+# [plus or minus two] seconds during the current year."
+#
+# Rounding to the nearest five seconds results in fewer than
+# 256 different "time types"--a limit that's faced because time types are
+# stored on disk as unsigned chars.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule sol87 1987 only - Jan 1 12:03:20s -0:03:20 -
+Rule sol87 1987 only - Jan 2 12:03:50s -0:03:50 -
+Rule sol87 1987 only - Jan 3 12:04:15s -0:04:15 -
+Rule sol87 1987 only - Jan 4 12:04:45s -0:04:45 -
+Rule sol87 1987 only - Jan 5 12:05:10s -0:05:10 -
+Rule sol87 1987 only - Jan 6 12:05:40s -0:05:40 -
+Rule sol87 1987 only - Jan 7 12:06:05s -0:06:05 -
+Rule sol87 1987 only - Jan 8 12:06:30s -0:06:30 -
+Rule sol87 1987 only - Jan 9 12:06:55s -0:06:55 -
+Rule sol87 1987 only - Jan 10 12:07:20s -0:07:20 -
+Rule sol87 1987 only - Jan 11 12:07:45s -0:07:45 -
+Rule sol87 1987 only - Jan 12 12:08:10s -0:08:10 -
+Rule sol87 1987 only - Jan 13 12:08:30s -0:08:30 -
+Rule sol87 1987 only - Jan 14 12:08:55s -0:08:55 -
+Rule sol87 1987 only - Jan 15 12:09:15s -0:09:15 -
+Rule sol87 1987 only - Jan 16 12:09:35s -0:09:35 -
+Rule sol87 1987 only - Jan 17 12:09:55s -0:09:55 -
+Rule sol87 1987 only - Jan 18 12:10:15s -0:10:15 -
+Rule sol87 1987 only - Jan 19 12:10:35s -0:10:35 -
+Rule sol87 1987 only - Jan 20 12:10:55s -0:10:55 -
+Rule sol87 1987 only - Jan 21 12:11:10s -0:11:10 -
+Rule sol87 1987 only - Jan 22 12:11:30s -0:11:30 -
+Rule sol87 1987 only - Jan 23 12:11:45s -0:11:45 -
+Rule sol87 1987 only - Jan 24 12:12:00s -0:12:00 -
+Rule sol87 1987 only - Jan 25 12:12:15s -0:12:15 -
+Rule sol87 1987 only - Jan 26 12:12:30s -0:12:30 -
+Rule sol87 1987 only - Jan 27 12:12:40s -0:12:40 -
+Rule sol87 1987 only - Jan 28 12:12:55s -0:12:55 -
+Rule sol87 1987 only - Jan 29 12:13:05s -0:13:05 -
+Rule sol87 1987 only - Jan 30 12:13:15s -0:13:15 -
+Rule sol87 1987 only - Jan 31 12:13:25s -0:13:25 -
+Rule sol87 1987 only - Feb 1 12:13:35s -0:13:35 -
+Rule sol87 1987 only - Feb 2 12:13:40s -0:13:40 -
+Rule sol87 1987 only - Feb 3 12:13:50s -0:13:50 -
+Rule sol87 1987 only - Feb 4 12:13:55s -0:13:55 -
+Rule sol87 1987 only - Feb 5 12:14:00s -0:14:00 -
+Rule sol87 1987 only - Feb 6 12:14:05s -0:14:05 -
+Rule sol87 1987 only - Feb 7 12:14:10s -0:14:10 -
+Rule sol87 1987 only - Feb 8 12:14:10s -0:14:10 -
+Rule sol87 1987 only - Feb 9 12:14:15s -0:14:15 -
+Rule sol87 1987 only - Feb 10 12:14:15s -0:14:15 -
+Rule sol87 1987 only - Feb 11 12:14:15s -0:14:15 -
+Rule sol87 1987 only - Feb 12 12:14:15s -0:14:15 -
+Rule sol87 1987 only - Feb 13 12:14:15s -0:14:15 -
+Rule sol87 1987 only - Feb 14 12:14:15s -0:14:15 -
+Rule sol87 1987 only - Feb 15 12:14:10s -0:14:10 -
+Rule sol87 1987 only - Feb 16 12:14:10s -0:14:10 -
+Rule sol87 1987 only - Feb 17 12:14:05s -0:14:05 -
+Rule sol87 1987 only - Feb 18 12:14:00s -0:14:00 -
+Rule sol87 1987 only - Feb 19 12:13:55s -0:13:55 -
+Rule sol87 1987 only - Feb 20 12:13:50s -0:13:50 -
+Rule sol87 1987 only - Feb 21 12:13:45s -0:13:45 -
+Rule sol87 1987 only - Feb 22 12:13:35s -0:13:35 -
+Rule sol87 1987 only - Feb 23 12:13:30s -0:13:30 -
+Rule sol87 1987 only - Feb 24 12:13:20s -0:13:20 -
+Rule sol87 1987 only - Feb 25 12:13:10s -0:13:10 -
+Rule sol87 1987 only - Feb 26 12:13:00s -0:13:00 -
+Rule sol87 1987 only - Feb 27 12:12:50s -0:12:50 -
+Rule sol87 1987 only - Feb 28 12:12:40s -0:12:40 -
+Rule sol87 1987 only - Mar 1 12:12:30s -0:12:30 -
+Rule sol87 1987 only - Mar 2 12:12:20s -0:12:20 -
+Rule sol87 1987 only - Mar 3 12:12:05s -0:12:05 -
+Rule sol87 1987 only - Mar 4 12:11:55s -0:11:55 -
+Rule sol87 1987 only - Mar 5 12:11:40s -0:11:40 -
+Rule sol87 1987 only - Mar 6 12:11:25s -0:11:25 -
+Rule sol87 1987 only - Mar 7 12:11:15s -0:11:15 -
+Rule sol87 1987 only - Mar 8 12:11:00s -0:11:00 -
+Rule sol87 1987 only - Mar 9 12:10:45s -0:10:45 -
+Rule sol87 1987 only - Mar 10 12:10:30s -0:10:30 -
+Rule sol87 1987 only - Mar 11 12:10:15s -0:10:15 -
+Rule sol87 1987 only - Mar 12 12:09:55s -0:09:55 -
+Rule sol87 1987 only - Mar 13 12:09:40s -0:09:40 -
+Rule sol87 1987 only - Mar 14 12:09:25s -0:09:25 -
+Rule sol87 1987 only - Mar 15 12:09:10s -0:09:10 -
+Rule sol87 1987 only - Mar 16 12:08:50s -0:08:50 -
+Rule sol87 1987 only - Mar 17 12:08:35s -0:08:35 -
+Rule sol87 1987 only - Mar 18 12:08:15s -0:08:15 -
+Rule sol87 1987 only - Mar 19 12:08:00s -0:08:00 -
+Rule sol87 1987 only - Mar 20 12:07:40s -0:07:40 -
+Rule sol87 1987 only - Mar 21 12:07:25s -0:07:25 -
+Rule sol87 1987 only - Mar 22 12:07:05s -0:07:05 -
+Rule sol87 1987 only - Mar 23 12:06:50s -0:06:50 -
+Rule sol87 1987 only - Mar 24 12:06:30s -0:06:30 -
+Rule sol87 1987 only - Mar 25 12:06:10s -0:06:10 -
+Rule sol87 1987 only - Mar 26 12:05:55s -0:05:55 -
+Rule sol87 1987 only - Mar 27 12:05:35s -0:05:35 -
+Rule sol87 1987 only - Mar 28 12:05:15s -0:05:15 -
+Rule sol87 1987 only - Mar 29 12:05:00s -0:05:00 -
+Rule sol87 1987 only - Mar 30 12:04:40s -0:04:40 -
+Rule sol87 1987 only - Mar 31 12:04:25s -0:04:25 -
+Rule sol87 1987 only - Apr 1 12:04:05s -0:04:05 -
+Rule sol87 1987 only - Apr 2 12:03:45s -0:03:45 -
+Rule sol87 1987 only - Apr 3 12:03:30s -0:03:30 -
+Rule sol87 1987 only - Apr 4 12:03:10s -0:03:10 -
+Rule sol87 1987 only - Apr 5 12:02:55s -0:02:55 -
+Rule sol87 1987 only - Apr 6 12:02:35s -0:02:35 -
+Rule sol87 1987 only - Apr 7 12:02:20s -0:02:20 -
+Rule sol87 1987 only - Apr 8 12:02:05s -0:02:05 -
+Rule sol87 1987 only - Apr 9 12:01:45s -0:01:45 -
+Rule sol87 1987 only - Apr 10 12:01:30s -0:01:30 -
+Rule sol87 1987 only - Apr 11 12:01:15s -0:01:15 -
+Rule sol87 1987 only - Apr 12 12:00:55s -0:00:55 -
+Rule sol87 1987 only - Apr 13 12:00:40s -0:00:40 -
+Rule sol87 1987 only - Apr 14 12:00:25s -0:00:25 -
+Rule sol87 1987 only - Apr 15 12:00:10s -0:00:10 -
+Rule sol87 1987 only - Apr 16 11:59:55s 0:00:05 -
+Rule sol87 1987 only - Apr 17 11:59:45s 0:00:15 -
+Rule sol87 1987 only - Apr 18 11:59:30s 0:00:30 -
+Rule sol87 1987 only - Apr 19 11:59:15s 0:00:45 -
+Rule sol87 1987 only - Apr 20 11:59:05s 0:00:55 -
+Rule sol87 1987 only - Apr 21 11:58:50s 0:01:10 -
+Rule sol87 1987 only - Apr 22 11:58:40s 0:01:20 -
+Rule sol87 1987 only - Apr 23 11:58:25s 0:01:35 -
+Rule sol87 1987 only - Apr 24 11:58:15s 0:01:45 -
+Rule sol87 1987 only - Apr 25 11:58:05s 0:01:55 -
+Rule sol87 1987 only - Apr 26 11:57:55s 0:02:05 -
+Rule sol87 1987 only - Apr 27 11:57:45s 0:02:15 -
+Rule sol87 1987 only - Apr 28 11:57:35s 0:02:25 -
+Rule sol87 1987 only - Apr 29 11:57:25s 0:02:35 -
+Rule sol87 1987 only - Apr 30 11:57:15s 0:02:45 -
+Rule sol87 1987 only - May 1 11:57:10s 0:02:50 -
+Rule sol87 1987 only - May 2 11:57:00s 0:03:00 -
+Rule sol87 1987 only - May 3 11:56:55s 0:03:05 -
+Rule sol87 1987 only - May 4 11:56:50s 0:03:10 -
+Rule sol87 1987 only - May 5 11:56:45s 0:03:15 -
+Rule sol87 1987 only - May 6 11:56:40s 0:03:20 -
+Rule sol87 1987 only - May 7 11:56:35s 0:03:25 -
+Rule sol87 1987 only - May 8 11:56:30s 0:03:30 -
+Rule sol87 1987 only - May 9 11:56:25s 0:03:35 -
+Rule sol87 1987 only - May 10 11:56:25s 0:03:35 -
+Rule sol87 1987 only - May 11 11:56:20s 0:03:40 -
+Rule sol87 1987 only - May 12 11:56:20s 0:03:40 -
+Rule sol87 1987 only - May 13 11:56:20s 0:03:40 -
+Rule sol87 1987 only - May 14 11:56:20s 0:03:40 -
+Rule sol87 1987 only - May 15 11:56:20s 0:03:40 -
+Rule sol87 1987 only - May 16 11:56:20s 0:03:40 -
+Rule sol87 1987 only - May 17 11:56:20s 0:03:40 -
+Rule sol87 1987 only - May 18 11:56:20s 0:03:40 -
+Rule sol87 1987 only - May 19 11:56:25s 0:03:35 -
+Rule sol87 1987 only - May 20 11:56:25s 0:03:35 -
+Rule sol87 1987 only - May 21 11:56:30s 0:03:30 -
+Rule sol87 1987 only - May 22 11:56:35s 0:03:25 -
+Rule sol87 1987 only - May 23 11:56:40s 0:03:20 -
+Rule sol87 1987 only - May 24 11:56:45s 0:03:15 -
+Rule sol87 1987 only - May 25 11:56:50s 0:03:10 -
+Rule sol87 1987 only - May 26 11:56:55s 0:03:05 -
+Rule sol87 1987 only - May 27 11:57:00s 0:03:00 -
+Rule sol87 1987 only - May 28 11:57:10s 0:02:50 -
+Rule sol87 1987 only - May 29 11:57:15s 0:02:45 -
+Rule sol87 1987 only - May 30 11:57:25s 0:02:35 -
+Rule sol87 1987 only - May 31 11:57:30s 0:02:30 -
+Rule sol87 1987 only - Jun 1 11:57:40s 0:02:20 -
+Rule sol87 1987 only - Jun 2 11:57:50s 0:02:10 -
+Rule sol87 1987 only - Jun 3 11:58:00s 0:02:00 -
+Rule sol87 1987 only - Jun 4 11:58:10s 0:01:50 -
+Rule sol87 1987 only - Jun 5 11:58:20s 0:01:40 -
+Rule sol87 1987 only - Jun 6 11:58:30s 0:01:30 -
+Rule sol87 1987 only - Jun 7 11:58:40s 0:01:20 -
+Rule sol87 1987 only - Jun 8 11:58:50s 0:01:10 -
+Rule sol87 1987 only - Jun 9 11:59:05s 0:00:55 -
+Rule sol87 1987 only - Jun 10 11:59:15s 0:00:45 -
+Rule sol87 1987 only - Jun 11 11:59:30s 0:00:30 -
+Rule sol87 1987 only - Jun 12 11:59:40s 0:00:20 -
+Rule sol87 1987 only - Jun 13 11:59:50s 0:00:10 -
+Rule sol87 1987 only - Jun 14 12:00:05s -0:00:05 -
+Rule sol87 1987 only - Jun 15 12:00:15s -0:00:15 -
+Rule sol87 1987 only - Jun 16 12:00:30s -0:00:30 -
+Rule sol87 1987 only - Jun 17 12:00:45s -0:00:45 -
+Rule sol87 1987 only - Jun 18 12:00:55s -0:00:55 -
+Rule sol87 1987 only - Jun 19 12:01:10s -0:01:10 -
+Rule sol87 1987 only - Jun 20 12:01:20s -0:01:20 -
+Rule sol87 1987 only - Jun 21 12:01:35s -0:01:35 -
+Rule sol87 1987 only - Jun 22 12:01:50s -0:01:50 -
+Rule sol87 1987 only - Jun 23 12:02:00s -0:02:00 -
+Rule sol87 1987 only - Jun 24 12:02:15s -0:02:15 -
+Rule sol87 1987 only - Jun 25 12:02:25s -0:02:25 -
+Rule sol87 1987 only - Jun 26 12:02:40s -0:02:40 -
+Rule sol87 1987 only - Jun 27 12:02:50s -0:02:50 -
+Rule sol87 1987 only - Jun 28 12:03:05s -0:03:05 -
+Rule sol87 1987 only - Jun 29 12:03:15s -0:03:15 -
+Rule sol87 1987 only - Jun 30 12:03:30s -0:03:30 -
+Rule sol87 1987 only - Jul 1 12:03:40s -0:03:40 -
+Rule sol87 1987 only - Jul 2 12:03:50s -0:03:50 -
+Rule sol87 1987 only - Jul 3 12:04:05s -0:04:05 -
+Rule sol87 1987 only - Jul 4 12:04:15s -0:04:15 -
+Rule sol87 1987 only - Jul 5 12:04:25s -0:04:25 -
+Rule sol87 1987 only - Jul 6 12:04:35s -0:04:35 -
+Rule sol87 1987 only - Jul 7 12:04:45s -0:04:45 -
+Rule sol87 1987 only - Jul 8 12:04:55s -0:04:55 -
+Rule sol87 1987 only - Jul 9 12:05:05s -0:05:05 -
+Rule sol87 1987 only - Jul 10 12:05:15s -0:05:15 -
+Rule sol87 1987 only - Jul 11 12:05:20s -0:05:20 -
+Rule sol87 1987 only - Jul 12 12:05:30s -0:05:30 -
+Rule sol87 1987 only - Jul 13 12:05:40s -0:05:40 -
+Rule sol87 1987 only - Jul 14 12:05:45s -0:05:45 -
+Rule sol87 1987 only - Jul 15 12:05:50s -0:05:50 -
+Rule sol87 1987 only - Jul 16 12:06:00s -0:06:00 -
+Rule sol87 1987 only - Jul 17 12:06:05s -0:06:05 -
+Rule sol87 1987 only - Jul 18 12:06:10s -0:06:10 -
+Rule sol87 1987 only - Jul 19 12:06:15s -0:06:15 -
+Rule sol87 1987 only - Jul 20 12:06:15s -0:06:15 -
+Rule sol87 1987 only - Jul 21 12:06:20s -0:06:20 -
+Rule sol87 1987 only - Jul 22 12:06:25s -0:06:25 -
+Rule sol87 1987 only - Jul 23 12:06:25s -0:06:25 -
+Rule sol87 1987 only - Jul 24 12:06:25s -0:06:25 -
+Rule sol87 1987 only - Jul 25 12:06:30s -0:06:30 -
+Rule sol87 1987 only - Jul 26 12:06:30s -0:06:30 -
+Rule sol87 1987 only - Jul 27 12:06:30s -0:06:30 -
+Rule sol87 1987 only - Jul 28 12:06:30s -0:06:30 -
+Rule sol87 1987 only - Jul 29 12:06:25s -0:06:25 -
+Rule sol87 1987 only - Jul 30 12:06:25s -0:06:25 -
+Rule sol87 1987 only - Jul 31 12:06:25s -0:06:25 -
+Rule sol87 1987 only - Aug 1 12:06:20s -0:06:20 -
+Rule sol87 1987 only - Aug 2 12:06:15s -0:06:15 -
+Rule sol87 1987 only - Aug 3 12:06:10s -0:06:10 -
+Rule sol87 1987 only - Aug 4 12:06:05s -0:06:05 -
+Rule sol87 1987 only - Aug 5 12:06:00s -0:06:00 -
+Rule sol87 1987 only - Aug 6 12:05:55s -0:05:55 -
+Rule sol87 1987 only - Aug 7 12:05:50s -0:05:50 -
+Rule sol87 1987 only - Aug 8 12:05:40s -0:05:40 -
+Rule sol87 1987 only - Aug 9 12:05:35s -0:05:35 -
+Rule sol87 1987 only - Aug 10 12:05:25s -0:05:25 -
+Rule sol87 1987 only - Aug 11 12:05:15s -0:05:15 -
+Rule sol87 1987 only - Aug 12 12:05:05s -0:05:05 -
+Rule sol87 1987 only - Aug 13 12:04:55s -0:04:55 -
+Rule sol87 1987 only - Aug 14 12:04:45s -0:04:45 -
+Rule sol87 1987 only - Aug 15 12:04:35s -0:04:35 -
+Rule sol87 1987 only - Aug 16 12:04:25s -0:04:25 -
+Rule sol87 1987 only - Aug 17 12:04:10s -0:04:10 -
+Rule sol87 1987 only - Aug 18 12:04:00s -0:04:00 -
+Rule sol87 1987 only - Aug 19 12:03:45s -0:03:45 -
+Rule sol87 1987 only - Aug 20 12:03:30s -0:03:30 -
+Rule sol87 1987 only - Aug 21 12:03:15s -0:03:15 -
+Rule sol87 1987 only - Aug 22 12:03:00s -0:03:00 -
+Rule sol87 1987 only - Aug 23 12:02:45s -0:02:45 -
+Rule sol87 1987 only - Aug 24 12:02:30s -0:02:30 -
+Rule sol87 1987 only - Aug 25 12:02:15s -0:02:15 -
+Rule sol87 1987 only - Aug 26 12:02:00s -0:02:00 -
+Rule sol87 1987 only - Aug 27 12:01:40s -0:01:40 -
+Rule sol87 1987 only - Aug 28 12:01:25s -0:01:25 -
+Rule sol87 1987 only - Aug 29 12:01:05s -0:01:05 -
+Rule sol87 1987 only - Aug 30 12:00:50s -0:00:50 -
+Rule sol87 1987 only - Aug 31 12:00:30s -0:00:30 -
+Rule sol87 1987 only - Sep 1 12:00:10s -0:00:10 -
+Rule sol87 1987 only - Sep 2 11:59:50s 0:00:10 -
+Rule sol87 1987 only - Sep 3 11:59:35s 0:00:25 -
+Rule sol87 1987 only - Sep 4 11:59:15s 0:00:45 -
+Rule sol87 1987 only - Sep 5 11:58:55s 0:01:05 -
+Rule sol87 1987 only - Sep 6 11:58:35s 0:01:25 -
+Rule sol87 1987 only - Sep 7 11:58:15s 0:01:45 -
+Rule sol87 1987 only - Sep 8 11:57:55s 0:02:05 -
+Rule sol87 1987 only - Sep 9 11:57:30s 0:02:30 -
+Rule sol87 1987 only - Sep 10 11:57:10s 0:02:50 -
+Rule sol87 1987 only - Sep 11 11:56:50s 0:03:10 -
+Rule sol87 1987 only - Sep 12 11:56:30s 0:03:30 -
+Rule sol87 1987 only - Sep 13 11:56:10s 0:03:50 -
+Rule sol87 1987 only - Sep 14 11:55:45s 0:04:15 -
+Rule sol87 1987 only - Sep 15 11:55:25s 0:04:35 -
+Rule sol87 1987 only - Sep 16 11:55:05s 0:04:55 -
+Rule sol87 1987 only - Sep 17 11:54:45s 0:05:15 -
+Rule sol87 1987 only - Sep 18 11:54:20s 0:05:40 -
+Rule sol87 1987 only - Sep 19 11:54:00s 0:06:00 -
+Rule sol87 1987 only - Sep 20 11:53:40s 0:06:20 -
+Rule sol87 1987 only - Sep 21 11:53:15s 0:06:45 -
+Rule sol87 1987 only - Sep 22 11:52:55s 0:07:05 -
+Rule sol87 1987 only - Sep 23 11:52:35s 0:07:25 -
+Rule sol87 1987 only - Sep 24 11:52:15s 0:07:45 -
+Rule sol87 1987 only - Sep 25 11:51:55s 0:08:05 -
+Rule sol87 1987 only - Sep 26 11:51:35s 0:08:25 -
+Rule sol87 1987 only - Sep 27 11:51:10s 0:08:50 -
+Rule sol87 1987 only - Sep 28 11:50:50s 0:09:10 -
+Rule sol87 1987 only - Sep 29 11:50:30s 0:09:30 -
+Rule sol87 1987 only - Sep 30 11:50:10s 0:09:50 -
+Rule sol87 1987 only - Oct 1 11:49:50s 0:10:10 -
+Rule sol87 1987 only - Oct 2 11:49:35s 0:10:25 -
+Rule sol87 1987 only - Oct 3 11:49:15s 0:10:45 -
+Rule sol87 1987 only - Oct 4 11:48:55s 0:11:05 -
+Rule sol87 1987 only - Oct 5 11:48:35s 0:11:25 -
+Rule sol87 1987 only - Oct 6 11:48:20s 0:11:40 -
+Rule sol87 1987 only - Oct 7 11:48:00s 0:12:00 -
+Rule sol87 1987 only - Oct 8 11:47:45s 0:12:15 -
+Rule sol87 1987 only - Oct 9 11:47:25s 0:12:35 -
+Rule sol87 1987 only - Oct 10 11:47:10s 0:12:50 -
+Rule sol87 1987 only - Oct 11 11:46:55s 0:13:05 -
+Rule sol87 1987 only - Oct 12 11:46:40s 0:13:20 -
+Rule sol87 1987 only - Oct 13 11:46:25s 0:13:35 -
+Rule sol87 1987 only - Oct 14 11:46:10s 0:13:50 -
+Rule sol87 1987 only - Oct 15 11:45:55s 0:14:05 -
+Rule sol87 1987 only - Oct 16 11:45:45s 0:14:15 -
+Rule sol87 1987 only - Oct 17 11:45:30s 0:14:30 -
+Rule sol87 1987 only - Oct 18 11:45:20s 0:14:40 -
+Rule sol87 1987 only - Oct 19 11:45:05s 0:14:55 -
+Rule sol87 1987 only - Oct 20 11:44:55s 0:15:05 -
+Rule sol87 1987 only - Oct 21 11:44:45s 0:15:15 -
+Rule sol87 1987 only - Oct 22 11:44:35s 0:15:25 -
+Rule sol87 1987 only - Oct 23 11:44:25s 0:15:35 -
+Rule sol87 1987 only - Oct 24 11:44:20s 0:15:40 -
+Rule sol87 1987 only - Oct 25 11:44:10s 0:15:50 -
+Rule sol87 1987 only - Oct 26 11:44:05s 0:15:55 -
+Rule sol87 1987 only - Oct 27 11:43:55s 0:16:05 -
+Rule sol87 1987 only - Oct 28 11:43:50s 0:16:10 -
+Rule sol87 1987 only - Oct 29 11:43:45s 0:16:15 -
+Rule sol87 1987 only - Oct 30 11:43:45s 0:16:15 -
+Rule sol87 1987 only - Oct 31 11:43:40s 0:16:20 -
+Rule sol87 1987 only - Nov 1 11:43:40s 0:16:20 -
+Rule sol87 1987 only - Nov 2 11:43:35s 0:16:25 -
+Rule sol87 1987 only - Nov 3 11:43:35s 0:16:25 -
+Rule sol87 1987 only - Nov 4 11:43:35s 0:16:25 -
+Rule sol87 1987 only - Nov 5 11:43:35s 0:16:25 -
+Rule sol87 1987 only - Nov 6 11:43:40s 0:16:20 -
+Rule sol87 1987 only - Nov 7 11:43:40s 0:16:20 -
+Rule sol87 1987 only - Nov 8 11:43:45s 0:16:15 -
+Rule sol87 1987 only - Nov 9 11:43:50s 0:16:10 -
+Rule sol87 1987 only - Nov 10 11:43:55s 0:16:05 -
+Rule sol87 1987 only - Nov 11 11:44:00s 0:16:00 -
+Rule sol87 1987 only - Nov 12 11:44:05s 0:15:55 -
+Rule sol87 1987 only - Nov 13 11:44:15s 0:15:45 -
+Rule sol87 1987 only - Nov 14 11:44:20s 0:15:40 -
+Rule sol87 1987 only - Nov 15 11:44:30s 0:15:30 -
+Rule sol87 1987 only - Nov 16 11:44:40s 0:15:20 -
+Rule sol87 1987 only - Nov 17 11:44:50s 0:15:10 -
+Rule sol87 1987 only - Nov 18 11:45:05s 0:14:55 -
+Rule sol87 1987 only - Nov 19 11:45:15s 0:14:45 -
+Rule sol87 1987 only - Nov 20 11:45:30s 0:14:30 -
+Rule sol87 1987 only - Nov 21 11:45:45s 0:14:15 -
+Rule sol87 1987 only - Nov 22 11:46:00s 0:14:00 -
+Rule sol87 1987 only - Nov 23 11:46:15s 0:13:45 -
+Rule sol87 1987 only - Nov 24 11:46:30s 0:13:30 -
+Rule sol87 1987 only - Nov 25 11:46:50s 0:13:10 -
+Rule sol87 1987 only - Nov 26 11:47:10s 0:12:50 -
+Rule sol87 1987 only - Nov 27 11:47:25s 0:12:35 -
+Rule sol87 1987 only - Nov 28 11:47:45s 0:12:15 -
+Rule sol87 1987 only - Nov 29 11:48:05s 0:11:55 -
+Rule sol87 1987 only - Nov 30 11:48:30s 0:11:30 -
+Rule sol87 1987 only - Dec 1 11:48:50s 0:11:10 -
+Rule sol87 1987 only - Dec 2 11:49:10s 0:10:50 -
+Rule sol87 1987 only - Dec 3 11:49:35s 0:10:25 -
+Rule sol87 1987 only - Dec 4 11:50:00s 0:10:00 -
+Rule sol87 1987 only - Dec 5 11:50:25s 0:09:35 -
+Rule sol87 1987 only - Dec 6 11:50:50s 0:09:10 -
+Rule sol87 1987 only - Dec 7 11:51:15s 0:08:45 -
+Rule sol87 1987 only - Dec 8 11:51:40s 0:08:20 -
+Rule sol87 1987 only - Dec 9 11:52:05s 0:07:55 -
+Rule sol87 1987 only - Dec 10 11:52:30s 0:07:30 -
+Rule sol87 1987 only - Dec 11 11:53:00s 0:07:00 -
+Rule sol87 1987 only - Dec 12 11:53:25s 0:06:35 -
+Rule sol87 1987 only - Dec 13 11:53:55s 0:06:05 -
+Rule sol87 1987 only - Dec 14 11:54:25s 0:05:35 -
+Rule sol87 1987 only - Dec 15 11:54:50s 0:05:10 -
+Rule sol87 1987 only - Dec 16 11:55:20s 0:04:40 -
+Rule sol87 1987 only - Dec 17 11:55:50s 0:04:10 -
+Rule sol87 1987 only - Dec 18 11:56:20s 0:03:40 -
+Rule sol87 1987 only - Dec 19 11:56:50s 0:03:10 -
+Rule sol87 1987 only - Dec 20 11:57:20s 0:02:40 -
+Rule sol87 1987 only - Dec 21 11:57:50s 0:02:10 -
+Rule sol87 1987 only - Dec 22 11:58:20s 0:01:40 -
+Rule sol87 1987 only - Dec 23 11:58:50s 0:01:10 -
+Rule sol87 1987 only - Dec 24 11:59:20s 0:00:40 -
+Rule sol87 1987 only - Dec 25 11:59:50s 0:00:10 -
+Rule sol87 1987 only - Dec 26 12:00:20s -0:00:20 -
+Rule sol87 1987 only - Dec 27 12:00:45s -0:00:45 -
+Rule sol87 1987 only - Dec 28 12:01:15s -0:01:15 -
+Rule sol87 1987 only - Dec 29 12:01:45s -0:01:45 -
+Rule sol87 1987 only - Dec 30 12:02:15s -0:02:15 -
+Rule sol87 1987 only - Dec 31 12:02:45s -0:02:45 -
+
+# Riyadh is at about 46 degrees 46 minutes East: 3 hrs, 7 mins, 4 secs
+# Before and after 1987, we'll operate on local mean solar time.
+
+# Zone NAME GMTOFF RULES/SAVE FORMAT [UNTIL]
+Zone Asia/Riyadh87 3:07:04 - zzz 1987
+ 3:07:04 sol87 zzz 1988
+ 3:07:04 - zzz
+# For backward compatibility...
+Link Asia/Riyadh87 Mideast/Riyadh87
diff --git a/misc/flot/examples/axes-time-zones/tz/solar88 b/misc/flot/examples/axes-time-zones/tz/solar88
new file mode 100644
index 0000000..bb1d6ca
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/solar88
@@ -0,0 +1,390 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# Apparent noon times below are for Riyadh; they're a bit off for other places.
+# Times were computed using formulas in the U.S. Naval Observatory's
+# Almanac for Computers 1988; the formulas "will give EqT to an accuracy of
+# [plus or minus two] seconds during the current year."
+#
+# Rounding to the nearest five seconds results in fewer than
+# 256 different "time types"--a limit that's faced because time types are
+# stored on disk as unsigned chars.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule sol88 1988 only - Jan 1 12:03:15s -0:03:15 -
+Rule sol88 1988 only - Jan 2 12:03:40s -0:03:40 -
+Rule sol88 1988 only - Jan 3 12:04:10s -0:04:10 -
+Rule sol88 1988 only - Jan 4 12:04:40s -0:04:40 -
+Rule sol88 1988 only - Jan 5 12:05:05s -0:05:05 -
+Rule sol88 1988 only - Jan 6 12:05:30s -0:05:30 -
+Rule sol88 1988 only - Jan 7 12:06:00s -0:06:00 -
+Rule sol88 1988 only - Jan 8 12:06:25s -0:06:25 -
+Rule sol88 1988 only - Jan 9 12:06:50s -0:06:50 -
+Rule sol88 1988 only - Jan 10 12:07:15s -0:07:15 -
+Rule sol88 1988 only - Jan 11 12:07:40s -0:07:40 -
+Rule sol88 1988 only - Jan 12 12:08:05s -0:08:05 -
+Rule sol88 1988 only - Jan 13 12:08:25s -0:08:25 -
+Rule sol88 1988 only - Jan 14 12:08:50s -0:08:50 -
+Rule sol88 1988 only - Jan 15 12:09:10s -0:09:10 -
+Rule sol88 1988 only - Jan 16 12:09:30s -0:09:30 -
+Rule sol88 1988 only - Jan 17 12:09:50s -0:09:50 -
+Rule sol88 1988 only - Jan 18 12:10:10s -0:10:10 -
+Rule sol88 1988 only - Jan 19 12:10:30s -0:10:30 -
+Rule sol88 1988 only - Jan 20 12:10:50s -0:10:50 -
+Rule sol88 1988 only - Jan 21 12:11:05s -0:11:05 -
+Rule sol88 1988 only - Jan 22 12:11:25s -0:11:25 -
+Rule sol88 1988 only - Jan 23 12:11:40s -0:11:40 -
+Rule sol88 1988 only - Jan 24 12:11:55s -0:11:55 -
+Rule sol88 1988 only - Jan 25 12:12:10s -0:12:10 -
+Rule sol88 1988 only - Jan 26 12:12:25s -0:12:25 -
+Rule sol88 1988 only - Jan 27 12:12:40s -0:12:40 -
+Rule sol88 1988 only - Jan 28 12:12:50s -0:12:50 -
+Rule sol88 1988 only - Jan 29 12:13:00s -0:13:00 -
+Rule sol88 1988 only - Jan 30 12:13:10s -0:13:10 -
+Rule sol88 1988 only - Jan 31 12:13:20s -0:13:20 -
+Rule sol88 1988 only - Feb 1 12:13:30s -0:13:30 -
+Rule sol88 1988 only - Feb 2 12:13:40s -0:13:40 -
+Rule sol88 1988 only - Feb 3 12:13:45s -0:13:45 -
+Rule sol88 1988 only - Feb 4 12:13:55s -0:13:55 -
+Rule sol88 1988 only - Feb 5 12:14:00s -0:14:00 -
+Rule sol88 1988 only - Feb 6 12:14:05s -0:14:05 -
+Rule sol88 1988 only - Feb 7 12:14:10s -0:14:10 -
+Rule sol88 1988 only - Feb 8 12:14:10s -0:14:10 -
+Rule sol88 1988 only - Feb 9 12:14:15s -0:14:15 -
+Rule sol88 1988 only - Feb 10 12:14:15s -0:14:15 -
+Rule sol88 1988 only - Feb 11 12:14:15s -0:14:15 -
+Rule sol88 1988 only - Feb 12 12:14:15s -0:14:15 -
+Rule sol88 1988 only - Feb 13 12:14:15s -0:14:15 -
+Rule sol88 1988 only - Feb 14 12:14:15s -0:14:15 -
+Rule sol88 1988 only - Feb 15 12:14:10s -0:14:10 -
+Rule sol88 1988 only - Feb 16 12:14:10s -0:14:10 -
+Rule sol88 1988 only - Feb 17 12:14:05s -0:14:05 -
+Rule sol88 1988 only - Feb 18 12:14:00s -0:14:00 -
+Rule sol88 1988 only - Feb 19 12:13:55s -0:13:55 -
+Rule sol88 1988 only - Feb 20 12:13:50s -0:13:50 -
+Rule sol88 1988 only - Feb 21 12:13:45s -0:13:45 -
+Rule sol88 1988 only - Feb 22 12:13:40s -0:13:40 -
+Rule sol88 1988 only - Feb 23 12:13:30s -0:13:30 -
+Rule sol88 1988 only - Feb 24 12:13:20s -0:13:20 -
+Rule sol88 1988 only - Feb 25 12:13:15s -0:13:15 -
+Rule sol88 1988 only - Feb 26 12:13:05s -0:13:05 -
+Rule sol88 1988 only - Feb 27 12:12:55s -0:12:55 -
+Rule sol88 1988 only - Feb 28 12:12:45s -0:12:45 -
+Rule sol88 1988 only - Feb 29 12:12:30s -0:12:30 -
+Rule sol88 1988 only - Mar 1 12:12:20s -0:12:20 -
+Rule sol88 1988 only - Mar 2 12:12:10s -0:12:10 -
+Rule sol88 1988 only - Mar 3 12:11:55s -0:11:55 -
+Rule sol88 1988 only - Mar 4 12:11:45s -0:11:45 -
+Rule sol88 1988 only - Mar 5 12:11:30s -0:11:30 -
+Rule sol88 1988 only - Mar 6 12:11:15s -0:11:15 -
+Rule sol88 1988 only - Mar 7 12:11:00s -0:11:00 -
+Rule sol88 1988 only - Mar 8 12:10:45s -0:10:45 -
+Rule sol88 1988 only - Mar 9 12:10:30s -0:10:30 -
+Rule sol88 1988 only - Mar 10 12:10:15s -0:10:15 -
+Rule sol88 1988 only - Mar 11 12:10:00s -0:10:00 -
+Rule sol88 1988 only - Mar 12 12:09:45s -0:09:45 -
+Rule sol88 1988 only - Mar 13 12:09:30s -0:09:30 -
+Rule sol88 1988 only - Mar 14 12:09:10s -0:09:10 -
+Rule sol88 1988 only - Mar 15 12:08:55s -0:08:55 -
+Rule sol88 1988 only - Mar 16 12:08:40s -0:08:40 -
+Rule sol88 1988 only - Mar 17 12:08:20s -0:08:20 -
+Rule sol88 1988 only - Mar 18 12:08:05s -0:08:05 -
+Rule sol88 1988 only - Mar 19 12:07:45s -0:07:45 -
+Rule sol88 1988 only - Mar 20 12:07:30s -0:07:30 -
+Rule sol88 1988 only - Mar 21 12:07:10s -0:07:10 -
+Rule sol88 1988 only - Mar 22 12:06:50s -0:06:50 -
+Rule sol88 1988 only - Mar 23 12:06:35s -0:06:35 -
+Rule sol88 1988 only - Mar 24 12:06:15s -0:06:15 -
+Rule sol88 1988 only - Mar 25 12:06:00s -0:06:00 -
+Rule sol88 1988 only - Mar 26 12:05:40s -0:05:40 -
+Rule sol88 1988 only - Mar 27 12:05:20s -0:05:20 -
+Rule sol88 1988 only - Mar 28 12:05:05s -0:05:05 -
+Rule sol88 1988 only - Mar 29 12:04:45s -0:04:45 -
+Rule sol88 1988 only - Mar 30 12:04:25s -0:04:25 -
+Rule sol88 1988 only - Mar 31 12:04:10s -0:04:10 -
+Rule sol88 1988 only - Apr 1 12:03:50s -0:03:50 -
+Rule sol88 1988 only - Apr 2 12:03:35s -0:03:35 -
+Rule sol88 1988 only - Apr 3 12:03:15s -0:03:15 -
+Rule sol88 1988 only - Apr 4 12:03:00s -0:03:00 -
+Rule sol88 1988 only - Apr 5 12:02:40s -0:02:40 -
+Rule sol88 1988 only - Apr 6 12:02:25s -0:02:25 -
+Rule sol88 1988 only - Apr 7 12:02:05s -0:02:05 -
+Rule sol88 1988 only - Apr 8 12:01:50s -0:01:50 -
+Rule sol88 1988 only - Apr 9 12:01:35s -0:01:35 -
+Rule sol88 1988 only - Apr 10 12:01:15s -0:01:15 -
+Rule sol88 1988 only - Apr 11 12:01:00s -0:01:00 -
+Rule sol88 1988 only - Apr 12 12:00:45s -0:00:45 -
+Rule sol88 1988 only - Apr 13 12:00:30s -0:00:30 -
+Rule sol88 1988 only - Apr 14 12:00:15s -0:00:15 -
+Rule sol88 1988 only - Apr 15 12:00:00s 0:00:00 -
+Rule sol88 1988 only - Apr 16 11:59:45s 0:00:15 -
+Rule sol88 1988 only - Apr 17 11:59:30s 0:00:30 -
+Rule sol88 1988 only - Apr 18 11:59:20s 0:00:40 -
+Rule sol88 1988 only - Apr 19 11:59:05s 0:00:55 -
+Rule sol88 1988 only - Apr 20 11:58:55s 0:01:05 -
+Rule sol88 1988 only - Apr 21 11:58:40s 0:01:20 -
+Rule sol88 1988 only - Apr 22 11:58:30s 0:01:30 -
+Rule sol88 1988 only - Apr 23 11:58:15s 0:01:45 -
+Rule sol88 1988 only - Apr 24 11:58:05s 0:01:55 -
+Rule sol88 1988 only - Apr 25 11:57:55s 0:02:05 -
+Rule sol88 1988 only - Apr 26 11:57:45s 0:02:15 -
+Rule sol88 1988 only - Apr 27 11:57:35s 0:02:25 -
+Rule sol88 1988 only - Apr 28 11:57:30s 0:02:30 -
+Rule sol88 1988 only - Apr 29 11:57:20s 0:02:40 -
+Rule sol88 1988 only - Apr 30 11:57:10s 0:02:50 -
+Rule sol88 1988 only - May 1 11:57:05s 0:02:55 -
+Rule sol88 1988 only - May 2 11:56:55s 0:03:05 -
+Rule sol88 1988 only - May 3 11:56:50s 0:03:10 -
+Rule sol88 1988 only - May 4 11:56:45s 0:03:15 -
+Rule sol88 1988 only - May 5 11:56:40s 0:03:20 -
+Rule sol88 1988 only - May 6 11:56:35s 0:03:25 -
+Rule sol88 1988 only - May 7 11:56:30s 0:03:30 -
+Rule sol88 1988 only - May 8 11:56:25s 0:03:35 -
+Rule sol88 1988 only - May 9 11:56:25s 0:03:35 -
+Rule sol88 1988 only - May 10 11:56:20s 0:03:40 -
+Rule sol88 1988 only - May 11 11:56:20s 0:03:40 -
+Rule sol88 1988 only - May 12 11:56:20s 0:03:40 -
+Rule sol88 1988 only - May 13 11:56:20s 0:03:40 -
+Rule sol88 1988 only - May 14 11:56:20s 0:03:40 -
+Rule sol88 1988 only - May 15 11:56:20s 0:03:40 -
+Rule sol88 1988 only - May 16 11:56:20s 0:03:40 -
+Rule sol88 1988 only - May 17 11:56:20s 0:03:40 -
+Rule sol88 1988 only - May 18 11:56:25s 0:03:35 -
+Rule sol88 1988 only - May 19 11:56:25s 0:03:35 -
+Rule sol88 1988 only - May 20 11:56:30s 0:03:30 -
+Rule sol88 1988 only - May 21 11:56:35s 0:03:25 -
+Rule sol88 1988 only - May 22 11:56:40s 0:03:20 -
+Rule sol88 1988 only - May 23 11:56:45s 0:03:15 -
+Rule sol88 1988 only - May 24 11:56:50s 0:03:10 -
+Rule sol88 1988 only - May 25 11:56:55s 0:03:05 -
+Rule sol88 1988 only - May 26 11:57:00s 0:03:00 -
+Rule sol88 1988 only - May 27 11:57:05s 0:02:55 -
+Rule sol88 1988 only - May 28 11:57:15s 0:02:45 -
+Rule sol88 1988 only - May 29 11:57:20s 0:02:40 -
+Rule sol88 1988 only - May 30 11:57:30s 0:02:30 -
+Rule sol88 1988 only - May 31 11:57:40s 0:02:20 -
+Rule sol88 1988 only - Jun 1 11:57:50s 0:02:10 -
+Rule sol88 1988 only - Jun 2 11:57:55s 0:02:05 -
+Rule sol88 1988 only - Jun 3 11:58:05s 0:01:55 -
+Rule sol88 1988 only - Jun 4 11:58:15s 0:01:45 -
+Rule sol88 1988 only - Jun 5 11:58:30s 0:01:30 -
+Rule sol88 1988 only - Jun 6 11:58:40s 0:01:20 -
+Rule sol88 1988 only - Jun 7 11:58:50s 0:01:10 -
+Rule sol88 1988 only - Jun 8 11:59:00s 0:01:00 -
+Rule sol88 1988 only - Jun 9 11:59:15s 0:00:45 -
+Rule sol88 1988 only - Jun 10 11:59:25s 0:00:35 -
+Rule sol88 1988 only - Jun 11 11:59:35s 0:00:25 -
+Rule sol88 1988 only - Jun 12 11:59:50s 0:00:10 -
+Rule sol88 1988 only - Jun 13 12:00:00s 0:00:00 -
+Rule sol88 1988 only - Jun 14 12:00:15s -0:00:15 -
+Rule sol88 1988 only - Jun 15 12:00:25s -0:00:25 -
+Rule sol88 1988 only - Jun 16 12:00:40s -0:00:40 -
+Rule sol88 1988 only - Jun 17 12:00:55s -0:00:55 -
+Rule sol88 1988 only - Jun 18 12:01:05s -0:01:05 -
+Rule sol88 1988 only - Jun 19 12:01:20s -0:01:20 -
+Rule sol88 1988 only - Jun 20 12:01:30s -0:01:30 -
+Rule sol88 1988 only - Jun 21 12:01:45s -0:01:45 -
+Rule sol88 1988 only - Jun 22 12:02:00s -0:02:00 -
+Rule sol88 1988 only - Jun 23 12:02:10s -0:02:10 -
+Rule sol88 1988 only - Jun 24 12:02:25s -0:02:25 -
+Rule sol88 1988 only - Jun 25 12:02:35s -0:02:35 -
+Rule sol88 1988 only - Jun 26 12:02:50s -0:02:50 -
+Rule sol88 1988 only - Jun 27 12:03:00s -0:03:00 -
+Rule sol88 1988 only - Jun 28 12:03:15s -0:03:15 -
+Rule sol88 1988 only - Jun 29 12:03:25s -0:03:25 -
+Rule sol88 1988 only - Jun 30 12:03:40s -0:03:40 -
+Rule sol88 1988 only - Jul 1 12:03:50s -0:03:50 -
+Rule sol88 1988 only - Jul 2 12:04:00s -0:04:00 -
+Rule sol88 1988 only - Jul 3 12:04:10s -0:04:10 -
+Rule sol88 1988 only - Jul 4 12:04:25s -0:04:25 -
+Rule sol88 1988 only - Jul 5 12:04:35s -0:04:35 -
+Rule sol88 1988 only - Jul 6 12:04:45s -0:04:45 -
+Rule sol88 1988 only - Jul 7 12:04:55s -0:04:55 -
+Rule sol88 1988 only - Jul 8 12:05:05s -0:05:05 -
+Rule sol88 1988 only - Jul 9 12:05:10s -0:05:10 -
+Rule sol88 1988 only - Jul 10 12:05:20s -0:05:20 -
+Rule sol88 1988 only - Jul 11 12:05:30s -0:05:30 -
+Rule sol88 1988 only - Jul 12 12:05:35s -0:05:35 -
+Rule sol88 1988 only - Jul 13 12:05:45s -0:05:45 -
+Rule sol88 1988 only - Jul 14 12:05:50s -0:05:50 -
+Rule sol88 1988 only - Jul 15 12:05:55s -0:05:55 -
+Rule sol88 1988 only - Jul 16 12:06:00s -0:06:00 -
+Rule sol88 1988 only - Jul 17 12:06:05s -0:06:05 -
+Rule sol88 1988 only - Jul 18 12:06:10s -0:06:10 -
+Rule sol88 1988 only - Jul 19 12:06:15s -0:06:15 -
+Rule sol88 1988 only - Jul 20 12:06:20s -0:06:20 -
+Rule sol88 1988 only - Jul 21 12:06:25s -0:06:25 -
+Rule sol88 1988 only - Jul 22 12:06:25s -0:06:25 -
+Rule sol88 1988 only - Jul 23 12:06:25s -0:06:25 -
+Rule sol88 1988 only - Jul 24 12:06:30s -0:06:30 -
+Rule sol88 1988 only - Jul 25 12:06:30s -0:06:30 -
+Rule sol88 1988 only - Jul 26 12:06:30s -0:06:30 -
+Rule sol88 1988 only - Jul 27 12:06:30s -0:06:30 -
+Rule sol88 1988 only - Jul 28 12:06:30s -0:06:30 -
+Rule sol88 1988 only - Jul 29 12:06:25s -0:06:25 -
+Rule sol88 1988 only - Jul 30 12:06:25s -0:06:25 -
+Rule sol88 1988 only - Jul 31 12:06:20s -0:06:20 -
+Rule sol88 1988 only - Aug 1 12:06:15s -0:06:15 -
+Rule sol88 1988 only - Aug 2 12:06:15s -0:06:15 -
+Rule sol88 1988 only - Aug 3 12:06:10s -0:06:10 -
+Rule sol88 1988 only - Aug 4 12:06:05s -0:06:05 -
+Rule sol88 1988 only - Aug 5 12:05:55s -0:05:55 -
+Rule sol88 1988 only - Aug 6 12:05:50s -0:05:50 -
+Rule sol88 1988 only - Aug 7 12:05:45s -0:05:45 -
+Rule sol88 1988 only - Aug 8 12:05:35s -0:05:35 -
+Rule sol88 1988 only - Aug 9 12:05:25s -0:05:25 -
+Rule sol88 1988 only - Aug 10 12:05:20s -0:05:20 -
+Rule sol88 1988 only - Aug 11 12:05:10s -0:05:10 -
+Rule sol88 1988 only - Aug 12 12:05:00s -0:05:00 -
+Rule sol88 1988 only - Aug 13 12:04:50s -0:04:50 -
+Rule sol88 1988 only - Aug 14 12:04:35s -0:04:35 -
+Rule sol88 1988 only - Aug 15 12:04:25s -0:04:25 -
+Rule sol88 1988 only - Aug 16 12:04:15s -0:04:15 -
+Rule sol88 1988 only - Aug 17 12:04:00s -0:04:00 -
+Rule sol88 1988 only - Aug 18 12:03:50s -0:03:50 -
+Rule sol88 1988 only - Aug 19 12:03:35s -0:03:35 -
+Rule sol88 1988 only - Aug 20 12:03:20s -0:03:20 -
+Rule sol88 1988 only - Aug 21 12:03:05s -0:03:05 -
+Rule sol88 1988 only - Aug 22 12:02:50s -0:02:50 -
+Rule sol88 1988 only - Aug 23 12:02:35s -0:02:35 -
+Rule sol88 1988 only - Aug 24 12:02:20s -0:02:20 -
+Rule sol88 1988 only - Aug 25 12:02:00s -0:02:00 -
+Rule sol88 1988 only - Aug 26 12:01:45s -0:01:45 -
+Rule sol88 1988 only - Aug 27 12:01:30s -0:01:30 -
+Rule sol88 1988 only - Aug 28 12:01:10s -0:01:10 -
+Rule sol88 1988 only - Aug 29 12:00:50s -0:00:50 -
+Rule sol88 1988 only - Aug 30 12:00:35s -0:00:35 -
+Rule sol88 1988 only - Aug 31 12:00:15s -0:00:15 -
+Rule sol88 1988 only - Sep 1 11:59:55s 0:00:05 -
+Rule sol88 1988 only - Sep 2 11:59:35s 0:00:25 -
+Rule sol88 1988 only - Sep 3 11:59:20s 0:00:40 -
+Rule sol88 1988 only - Sep 4 11:59:00s 0:01:00 -
+Rule sol88 1988 only - Sep 5 11:58:40s 0:01:20 -
+Rule sol88 1988 only - Sep 6 11:58:20s 0:01:40 -
+Rule sol88 1988 only - Sep 7 11:58:00s 0:02:00 -
+Rule sol88 1988 only - Sep 8 11:57:35s 0:02:25 -
+Rule sol88 1988 only - Sep 9 11:57:15s 0:02:45 -
+Rule sol88 1988 only - Sep 10 11:56:55s 0:03:05 -
+Rule sol88 1988 only - Sep 11 11:56:35s 0:03:25 -
+Rule sol88 1988 only - Sep 12 11:56:15s 0:03:45 -
+Rule sol88 1988 only - Sep 13 11:55:50s 0:04:10 -
+Rule sol88 1988 only - Sep 14 11:55:30s 0:04:30 -
+Rule sol88 1988 only - Sep 15 11:55:10s 0:04:50 -
+Rule sol88 1988 only - Sep 16 11:54:50s 0:05:10 -
+Rule sol88 1988 only - Sep 17 11:54:25s 0:05:35 -
+Rule sol88 1988 only - Sep 18 11:54:05s 0:05:55 -
+Rule sol88 1988 only - Sep 19 11:53:45s 0:06:15 -
+Rule sol88 1988 only - Sep 20 11:53:25s 0:06:35 -
+Rule sol88 1988 only - Sep 21 11:53:00s 0:07:00 -
+Rule sol88 1988 only - Sep 22 11:52:40s 0:07:20 -
+Rule sol88 1988 only - Sep 23 11:52:20s 0:07:40 -
+Rule sol88 1988 only - Sep 24 11:52:00s 0:08:00 -
+Rule sol88 1988 only - Sep 25 11:51:40s 0:08:20 -
+Rule sol88 1988 only - Sep 26 11:51:15s 0:08:45 -
+Rule sol88 1988 only - Sep 27 11:50:55s 0:09:05 -
+Rule sol88 1988 only - Sep 28 11:50:35s 0:09:25 -
+Rule sol88 1988 only - Sep 29 11:50:15s 0:09:45 -
+Rule sol88 1988 only - Sep 30 11:49:55s 0:10:05 -
+Rule sol88 1988 only - Oct 1 11:49:35s 0:10:25 -
+Rule sol88 1988 only - Oct 2 11:49:20s 0:10:40 -
+Rule sol88 1988 only - Oct 3 11:49:00s 0:11:00 -
+Rule sol88 1988 only - Oct 4 11:48:40s 0:11:20 -
+Rule sol88 1988 only - Oct 5 11:48:25s 0:11:35 -
+Rule sol88 1988 only - Oct 6 11:48:05s 0:11:55 -
+Rule sol88 1988 only - Oct 7 11:47:50s 0:12:10 -
+Rule sol88 1988 only - Oct 8 11:47:30s 0:12:30 -
+Rule sol88 1988 only - Oct 9 11:47:15s 0:12:45 -
+Rule sol88 1988 only - Oct 10 11:47:00s 0:13:00 -
+Rule sol88 1988 only - Oct 11 11:46:45s 0:13:15 -
+Rule sol88 1988 only - Oct 12 11:46:30s 0:13:30 -
+Rule sol88 1988 only - Oct 13 11:46:15s 0:13:45 -
+Rule sol88 1988 only - Oct 14 11:46:00s 0:14:00 -
+Rule sol88 1988 only - Oct 15 11:45:45s 0:14:15 -
+Rule sol88 1988 only - Oct 16 11:45:35s 0:14:25 -
+Rule sol88 1988 only - Oct 17 11:45:20s 0:14:40 -
+Rule sol88 1988 only - Oct 18 11:45:10s 0:14:50 -
+Rule sol88 1988 only - Oct 19 11:45:00s 0:15:00 -
+Rule sol88 1988 only - Oct 20 11:44:45s 0:15:15 -
+Rule sol88 1988 only - Oct 21 11:44:40s 0:15:20 -
+Rule sol88 1988 only - Oct 22 11:44:30s 0:15:30 -
+Rule sol88 1988 only - Oct 23 11:44:20s 0:15:40 -
+Rule sol88 1988 only - Oct 24 11:44:10s 0:15:50 -
+Rule sol88 1988 only - Oct 25 11:44:05s 0:15:55 -
+Rule sol88 1988 only - Oct 26 11:44:00s 0:16:00 -
+Rule sol88 1988 only - Oct 27 11:43:55s 0:16:05 -
+Rule sol88 1988 only - Oct 28 11:43:50s 0:16:10 -
+Rule sol88 1988 only - Oct 29 11:43:45s 0:16:15 -
+Rule sol88 1988 only - Oct 30 11:43:40s 0:16:20 -
+Rule sol88 1988 only - Oct 31 11:43:40s 0:16:20 -
+Rule sol88 1988 only - Nov 1 11:43:35s 0:16:25 -
+Rule sol88 1988 only - Nov 2 11:43:35s 0:16:25 -
+Rule sol88 1988 only - Nov 3 11:43:35s 0:16:25 -
+Rule sol88 1988 only - Nov 4 11:43:35s 0:16:25 -
+Rule sol88 1988 only - Nov 5 11:43:40s 0:16:20 -
+Rule sol88 1988 only - Nov 6 11:43:40s 0:16:20 -
+Rule sol88 1988 only - Nov 7 11:43:45s 0:16:15 -
+Rule sol88 1988 only - Nov 8 11:43:45s 0:16:15 -
+Rule sol88 1988 only - Nov 9 11:43:50s 0:16:10 -
+Rule sol88 1988 only - Nov 10 11:44:00s 0:16:00 -
+Rule sol88 1988 only - Nov 11 11:44:05s 0:15:55 -
+Rule sol88 1988 only - Nov 12 11:44:10s 0:15:50 -
+Rule sol88 1988 only - Nov 13 11:44:20s 0:15:40 -
+Rule sol88 1988 only - Nov 14 11:44:30s 0:15:30 -
+Rule sol88 1988 only - Nov 15 11:44:40s 0:15:20 -
+Rule sol88 1988 only - Nov 16 11:44:50s 0:15:10 -
+Rule sol88 1988 only - Nov 17 11:45:00s 0:15:00 -
+Rule sol88 1988 only - Nov 18 11:45:15s 0:14:45 -
+Rule sol88 1988 only - Nov 19 11:45:25s 0:14:35 -
+Rule sol88 1988 only - Nov 20 11:45:40s 0:14:20 -
+Rule sol88 1988 only - Nov 21 11:45:55s 0:14:05 -
+Rule sol88 1988 only - Nov 22 11:46:10s 0:13:50 -
+Rule sol88 1988 only - Nov 23 11:46:30s 0:13:30 -
+Rule sol88 1988 only - Nov 24 11:46:45s 0:13:15 -
+Rule sol88 1988 only - Nov 25 11:47:05s 0:12:55 -
+Rule sol88 1988 only - Nov 26 11:47:20s 0:12:40 -
+Rule sol88 1988 only - Nov 27 11:47:40s 0:12:20 -
+Rule sol88 1988 only - Nov 28 11:48:00s 0:12:00 -
+Rule sol88 1988 only - Nov 29 11:48:25s 0:11:35 -
+Rule sol88 1988 only - Nov 30 11:48:45s 0:11:15 -
+Rule sol88 1988 only - Dec 1 11:49:05s 0:10:55 -
+Rule sol88 1988 only - Dec 2 11:49:30s 0:10:30 -
+Rule sol88 1988 only - Dec 3 11:49:55s 0:10:05 -
+Rule sol88 1988 only - Dec 4 11:50:15s 0:09:45 -
+Rule sol88 1988 only - Dec 5 11:50:40s 0:09:20 -
+Rule sol88 1988 only - Dec 6 11:51:05s 0:08:55 -
+Rule sol88 1988 only - Dec 7 11:51:35s 0:08:25 -
+Rule sol88 1988 only - Dec 8 11:52:00s 0:08:00 -
+Rule sol88 1988 only - Dec 9 11:52:25s 0:07:35 -
+Rule sol88 1988 only - Dec 10 11:52:55s 0:07:05 -
+Rule sol88 1988 only - Dec 11 11:53:20s 0:06:40 -
+Rule sol88 1988 only - Dec 12 11:53:50s 0:06:10 -
+Rule sol88 1988 only - Dec 13 11:54:15s 0:05:45 -
+Rule sol88 1988 only - Dec 14 11:54:45s 0:05:15 -
+Rule sol88 1988 only - Dec 15 11:55:15s 0:04:45 -
+Rule sol88 1988 only - Dec 16 11:55:45s 0:04:15 -
+Rule sol88 1988 only - Dec 17 11:56:15s 0:03:45 -
+Rule sol88 1988 only - Dec 18 11:56:40s 0:03:20 -
+Rule sol88 1988 only - Dec 19 11:57:10s 0:02:50 -
+Rule sol88 1988 only - Dec 20 11:57:40s 0:02:20 -
+Rule sol88 1988 only - Dec 21 11:58:10s 0:01:50 -
+Rule sol88 1988 only - Dec 22 11:58:40s 0:01:20 -
+Rule sol88 1988 only - Dec 23 11:59:10s 0:00:50 -
+Rule sol88 1988 only - Dec 24 11:59:40s 0:00:20 -
+Rule sol88 1988 only - Dec 25 12:00:10s -0:00:10 -
+Rule sol88 1988 only - Dec 26 12:00:40s -0:00:40 -
+Rule sol88 1988 only - Dec 27 12:01:10s -0:01:10 -
+Rule sol88 1988 only - Dec 28 12:01:40s -0:01:40 -
+Rule sol88 1988 only - Dec 29 12:02:10s -0:02:10 -
+Rule sol88 1988 only - Dec 30 12:02:35s -0:02:35 -
+Rule sol88 1988 only - Dec 31 12:03:05s -0:03:05 -
+
+# Riyadh is at about 46 degrees 46 minutes East: 3 hrs, 7 mins, 4 secs
+# Before and after 1988, we'll operate on local mean solar time.
+
+# Zone NAME GMTOFF RULES/SAVE FORMAT [UNTIL]
+Zone Asia/Riyadh88 3:07:04 - zzz 1988
+ 3:07:04 sol88 zzz 1989
+ 3:07:04 - zzz
+# For backward compatibility...
+Link Asia/Riyadh88 Mideast/Riyadh88
diff --git a/misc/flot/examples/axes-time-zones/tz/solar89 b/misc/flot/examples/axes-time-zones/tz/solar89
new file mode 100644
index 0000000..af93235
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/solar89
@@ -0,0 +1,395 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# Apparent noon times below are for Riyadh; they're a bit off for other places.
+# Times were computed using a formula provided by the U. S. Naval Observatory:
+# eqt = -105.8 * sin(l) + 596.2 * sin(2 * l) + 4.4 * sin(3 * l)
+# -12.7 * sin(4 * l) - 429.0 * cos(l) - 2.1 * cos (2 * l)
+# + 19.3 * cos(3 * l);
+# where l is the "mean longitude of the Sun" given by
+# l = 279.642 degrees + 0.985647 * d
+# and d is the interval in days from January 0, 0 hours Universal Time
+# (equaling the day of the year plus the fraction of a day from zero hours).
+# The accuracy of the formula is plus or minus three seconds.
+#
+# Rounding to the nearest five seconds results in fewer than
+# 256 different "time types"--a limit that's faced because time types are
+# stored on disk as unsigned chars.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule sol89 1989 only - Jan 1 12:03:35s -0:03:35 -
+Rule sol89 1989 only - Jan 2 12:04:05s -0:04:05 -
+Rule sol89 1989 only - Jan 3 12:04:30s -0:04:30 -
+Rule sol89 1989 only - Jan 4 12:05:00s -0:05:00 -
+Rule sol89 1989 only - Jan 5 12:05:25s -0:05:25 -
+Rule sol89 1989 only - Jan 6 12:05:50s -0:05:50 -
+Rule sol89 1989 only - Jan 7 12:06:15s -0:06:15 -
+Rule sol89 1989 only - Jan 8 12:06:45s -0:06:45 -
+Rule sol89 1989 only - Jan 9 12:07:10s -0:07:10 -
+Rule sol89 1989 only - Jan 10 12:07:35s -0:07:35 -
+Rule sol89 1989 only - Jan 11 12:07:55s -0:07:55 -
+Rule sol89 1989 only - Jan 12 12:08:20s -0:08:20 -
+Rule sol89 1989 only - Jan 13 12:08:45s -0:08:45 -
+Rule sol89 1989 only - Jan 14 12:09:05s -0:09:05 -
+Rule sol89 1989 only - Jan 15 12:09:25s -0:09:25 -
+Rule sol89 1989 only - Jan 16 12:09:45s -0:09:45 -
+Rule sol89 1989 only - Jan 17 12:10:05s -0:10:05 -
+Rule sol89 1989 only - Jan 18 12:10:25s -0:10:25 -
+Rule sol89 1989 only - Jan 19 12:10:45s -0:10:45 -
+Rule sol89 1989 only - Jan 20 12:11:05s -0:11:05 -
+Rule sol89 1989 only - Jan 21 12:11:20s -0:11:20 -
+Rule sol89 1989 only - Jan 22 12:11:35s -0:11:35 -
+Rule sol89 1989 only - Jan 23 12:11:55s -0:11:55 -
+Rule sol89 1989 only - Jan 24 12:12:10s -0:12:10 -
+Rule sol89 1989 only - Jan 25 12:12:20s -0:12:20 -
+Rule sol89 1989 only - Jan 26 12:12:35s -0:12:35 -
+Rule sol89 1989 only - Jan 27 12:12:50s -0:12:50 -
+Rule sol89 1989 only - Jan 28 12:13:00s -0:13:00 -
+Rule sol89 1989 only - Jan 29 12:13:10s -0:13:10 -
+Rule sol89 1989 only - Jan 30 12:13:20s -0:13:20 -
+Rule sol89 1989 only - Jan 31 12:13:30s -0:13:30 -
+Rule sol89 1989 only - Feb 1 12:13:40s -0:13:40 -
+Rule sol89 1989 only - Feb 2 12:13:45s -0:13:45 -
+Rule sol89 1989 only - Feb 3 12:13:55s -0:13:55 -
+Rule sol89 1989 only - Feb 4 12:14:00s -0:14:00 -
+Rule sol89 1989 only - Feb 5 12:14:05s -0:14:05 -
+Rule sol89 1989 only - Feb 6 12:14:10s -0:14:10 -
+Rule sol89 1989 only - Feb 7 12:14:10s -0:14:10 -
+Rule sol89 1989 only - Feb 8 12:14:15s -0:14:15 -
+Rule sol89 1989 only - Feb 9 12:14:15s -0:14:15 -
+Rule sol89 1989 only - Feb 10 12:14:20s -0:14:20 -
+Rule sol89 1989 only - Feb 11 12:14:20s -0:14:20 -
+Rule sol89 1989 only - Feb 12 12:14:20s -0:14:20 -
+Rule sol89 1989 only - Feb 13 12:14:15s -0:14:15 -
+Rule sol89 1989 only - Feb 14 12:14:15s -0:14:15 -
+Rule sol89 1989 only - Feb 15 12:14:10s -0:14:10 -
+Rule sol89 1989 only - Feb 16 12:14:10s -0:14:10 -
+Rule sol89 1989 only - Feb 17 12:14:05s -0:14:05 -
+Rule sol89 1989 only - Feb 18 12:14:00s -0:14:00 -
+Rule sol89 1989 only - Feb 19 12:13:55s -0:13:55 -
+Rule sol89 1989 only - Feb 20 12:13:50s -0:13:50 -
+Rule sol89 1989 only - Feb 21 12:13:40s -0:13:40 -
+Rule sol89 1989 only - Feb 22 12:13:35s -0:13:35 -
+Rule sol89 1989 only - Feb 23 12:13:25s -0:13:25 -
+Rule sol89 1989 only - Feb 24 12:13:15s -0:13:15 -
+Rule sol89 1989 only - Feb 25 12:13:05s -0:13:05 -
+Rule sol89 1989 only - Feb 26 12:12:55s -0:12:55 -
+Rule sol89 1989 only - Feb 27 12:12:45s -0:12:45 -
+Rule sol89 1989 only - Feb 28 12:12:35s -0:12:35 -
+Rule sol89 1989 only - Mar 1 12:12:25s -0:12:25 -
+Rule sol89 1989 only - Mar 2 12:12:10s -0:12:10 -
+Rule sol89 1989 only - Mar 3 12:12:00s -0:12:00 -
+Rule sol89 1989 only - Mar 4 12:11:45s -0:11:45 -
+Rule sol89 1989 only - Mar 5 12:11:35s -0:11:35 -
+Rule sol89 1989 only - Mar 6 12:11:20s -0:11:20 -
+Rule sol89 1989 only - Mar 7 12:11:05s -0:11:05 -
+Rule sol89 1989 only - Mar 8 12:10:50s -0:10:50 -
+Rule sol89 1989 only - Mar 9 12:10:35s -0:10:35 -
+Rule sol89 1989 only - Mar 10 12:10:20s -0:10:20 -
+Rule sol89 1989 only - Mar 11 12:10:05s -0:10:05 -
+Rule sol89 1989 only - Mar 12 12:09:50s -0:09:50 -
+Rule sol89 1989 only - Mar 13 12:09:30s -0:09:30 -
+Rule sol89 1989 only - Mar 14 12:09:15s -0:09:15 -
+Rule sol89 1989 only - Mar 15 12:09:00s -0:09:00 -
+Rule sol89 1989 only - Mar 16 12:08:40s -0:08:40 -
+Rule sol89 1989 only - Mar 17 12:08:25s -0:08:25 -
+Rule sol89 1989 only - Mar 18 12:08:05s -0:08:05 -
+Rule sol89 1989 only - Mar 19 12:07:50s -0:07:50 -
+Rule sol89 1989 only - Mar 20 12:07:30s -0:07:30 -
+Rule sol89 1989 only - Mar 21 12:07:15s -0:07:15 -
+Rule sol89 1989 only - Mar 22 12:06:55s -0:06:55 -
+Rule sol89 1989 only - Mar 23 12:06:35s -0:06:35 -
+Rule sol89 1989 only - Mar 24 12:06:20s -0:06:20 -
+Rule sol89 1989 only - Mar 25 12:06:00s -0:06:00 -
+Rule sol89 1989 only - Mar 26 12:05:40s -0:05:40 -
+Rule sol89 1989 only - Mar 27 12:05:25s -0:05:25 -
+Rule sol89 1989 only - Mar 28 12:05:05s -0:05:05 -
+Rule sol89 1989 only - Mar 29 12:04:50s -0:04:50 -
+Rule sol89 1989 only - Mar 30 12:04:30s -0:04:30 -
+Rule sol89 1989 only - Mar 31 12:04:10s -0:04:10 -
+Rule sol89 1989 only - Apr 1 12:03:55s -0:03:55 -
+Rule sol89 1989 only - Apr 2 12:03:35s -0:03:35 -
+Rule sol89 1989 only - Apr 3 12:03:20s -0:03:20 -
+Rule sol89 1989 only - Apr 4 12:03:00s -0:03:00 -
+Rule sol89 1989 only - Apr 5 12:02:45s -0:02:45 -
+Rule sol89 1989 only - Apr 6 12:02:25s -0:02:25 -
+Rule sol89 1989 only - Apr 7 12:02:10s -0:02:10 -
+Rule sol89 1989 only - Apr 8 12:01:50s -0:01:50 -
+Rule sol89 1989 only - Apr 9 12:01:35s -0:01:35 -
+Rule sol89 1989 only - Apr 10 12:01:20s -0:01:20 -
+Rule sol89 1989 only - Apr 11 12:01:05s -0:01:05 -
+Rule sol89 1989 only - Apr 12 12:00:50s -0:00:50 -
+Rule sol89 1989 only - Apr 13 12:00:35s -0:00:35 -
+Rule sol89 1989 only - Apr 14 12:00:20s -0:00:20 -
+Rule sol89 1989 only - Apr 15 12:00:05s -0:00:05 -
+Rule sol89 1989 only - Apr 16 11:59:50s 0:00:10 -
+Rule sol89 1989 only - Apr 17 11:59:35s 0:00:25 -
+Rule sol89 1989 only - Apr 18 11:59:20s 0:00:40 -
+Rule sol89 1989 only - Apr 19 11:59:10s 0:00:50 -
+Rule sol89 1989 only - Apr 20 11:58:55s 0:01:05 -
+Rule sol89 1989 only - Apr 21 11:58:45s 0:01:15 -
+Rule sol89 1989 only - Apr 22 11:58:30s 0:01:30 -
+Rule sol89 1989 only - Apr 23 11:58:20s 0:01:40 -
+Rule sol89 1989 only - Apr 24 11:58:10s 0:01:50 -
+Rule sol89 1989 only - Apr 25 11:58:00s 0:02:00 -
+Rule sol89 1989 only - Apr 26 11:57:50s 0:02:10 -
+Rule sol89 1989 only - Apr 27 11:57:40s 0:02:20 -
+Rule sol89 1989 only - Apr 28 11:57:30s 0:02:30 -
+Rule sol89 1989 only - Apr 29 11:57:20s 0:02:40 -
+Rule sol89 1989 only - Apr 30 11:57:15s 0:02:45 -
+Rule sol89 1989 only - May 1 11:57:05s 0:02:55 -
+Rule sol89 1989 only - May 2 11:57:00s 0:03:00 -
+Rule sol89 1989 only - May 3 11:56:50s 0:03:10 -
+Rule sol89 1989 only - May 4 11:56:45s 0:03:15 -
+Rule sol89 1989 only - May 5 11:56:40s 0:03:20 -
+Rule sol89 1989 only - May 6 11:56:35s 0:03:25 -
+Rule sol89 1989 only - May 7 11:56:30s 0:03:30 -
+Rule sol89 1989 only - May 8 11:56:30s 0:03:30 -
+Rule sol89 1989 only - May 9 11:56:25s 0:03:35 -
+Rule sol89 1989 only - May 10 11:56:25s 0:03:35 -
+Rule sol89 1989 only - May 11 11:56:20s 0:03:40 -
+Rule sol89 1989 only - May 12 11:56:20s 0:03:40 -
+Rule sol89 1989 only - May 13 11:56:20s 0:03:40 -
+Rule sol89 1989 only - May 14 11:56:20s 0:03:40 -
+Rule sol89 1989 only - May 15 11:56:20s 0:03:40 -
+Rule sol89 1989 only - May 16 11:56:20s 0:03:40 -
+Rule sol89 1989 only - May 17 11:56:20s 0:03:40 -
+Rule sol89 1989 only - May 18 11:56:25s 0:03:35 -
+Rule sol89 1989 only - May 19 11:56:25s 0:03:35 -
+Rule sol89 1989 only - May 20 11:56:30s 0:03:30 -
+Rule sol89 1989 only - May 21 11:56:35s 0:03:25 -
+Rule sol89 1989 only - May 22 11:56:35s 0:03:25 -
+Rule sol89 1989 only - May 23 11:56:40s 0:03:20 -
+Rule sol89 1989 only - May 24 11:56:45s 0:03:15 -
+Rule sol89 1989 only - May 25 11:56:55s 0:03:05 -
+Rule sol89 1989 only - May 26 11:57:00s 0:03:00 -
+Rule sol89 1989 only - May 27 11:57:05s 0:02:55 -
+Rule sol89 1989 only - May 28 11:57:15s 0:02:45 -
+Rule sol89 1989 only - May 29 11:57:20s 0:02:40 -
+Rule sol89 1989 only - May 30 11:57:30s 0:02:30 -
+Rule sol89 1989 only - May 31 11:57:35s 0:02:25 -
+Rule sol89 1989 only - Jun 1 11:57:45s 0:02:15 -
+Rule sol89 1989 only - Jun 2 11:57:55s 0:02:05 -
+Rule sol89 1989 only - Jun 3 11:58:05s 0:01:55 -
+Rule sol89 1989 only - Jun 4 11:58:15s 0:01:45 -
+Rule sol89 1989 only - Jun 5 11:58:25s 0:01:35 -
+Rule sol89 1989 only - Jun 6 11:58:35s 0:01:25 -
+Rule sol89 1989 only - Jun 7 11:58:45s 0:01:15 -
+Rule sol89 1989 only - Jun 8 11:59:00s 0:01:00 -
+Rule sol89 1989 only - Jun 9 11:59:10s 0:00:50 -
+Rule sol89 1989 only - Jun 10 11:59:20s 0:00:40 -
+Rule sol89 1989 only - Jun 11 11:59:35s 0:00:25 -
+Rule sol89 1989 only - Jun 12 11:59:45s 0:00:15 -
+Rule sol89 1989 only - Jun 13 12:00:00s 0:00:00 -
+Rule sol89 1989 only - Jun 14 12:00:10s -0:00:10 -
+Rule sol89 1989 only - Jun 15 12:00:25s -0:00:25 -
+Rule sol89 1989 only - Jun 16 12:00:35s -0:00:35 -
+Rule sol89 1989 only - Jun 17 12:00:50s -0:00:50 -
+Rule sol89 1989 only - Jun 18 12:01:05s -0:01:05 -
+Rule sol89 1989 only - Jun 19 12:01:15s -0:01:15 -
+Rule sol89 1989 only - Jun 20 12:01:30s -0:01:30 -
+Rule sol89 1989 only - Jun 21 12:01:40s -0:01:40 -
+Rule sol89 1989 only - Jun 22 12:01:55s -0:01:55 -
+Rule sol89 1989 only - Jun 23 12:02:10s -0:02:10 -
+Rule sol89 1989 only - Jun 24 12:02:20s -0:02:20 -
+Rule sol89 1989 only - Jun 25 12:02:35s -0:02:35 -
+Rule sol89 1989 only - Jun 26 12:02:45s -0:02:45 -
+Rule sol89 1989 only - Jun 27 12:03:00s -0:03:00 -
+Rule sol89 1989 only - Jun 28 12:03:10s -0:03:10 -
+Rule sol89 1989 only - Jun 29 12:03:25s -0:03:25 -
+Rule sol89 1989 only - Jun 30 12:03:35s -0:03:35 -
+Rule sol89 1989 only - Jul 1 12:03:45s -0:03:45 -
+Rule sol89 1989 only - Jul 2 12:04:00s -0:04:00 -
+Rule sol89 1989 only - Jul 3 12:04:10s -0:04:10 -
+Rule sol89 1989 only - Jul 4 12:04:20s -0:04:20 -
+Rule sol89 1989 only - Jul 5 12:04:30s -0:04:30 -
+Rule sol89 1989 only - Jul 6 12:04:40s -0:04:40 -
+Rule sol89 1989 only - Jul 7 12:04:50s -0:04:50 -
+Rule sol89 1989 only - Jul 8 12:05:00s -0:05:00 -
+Rule sol89 1989 only - Jul 9 12:05:10s -0:05:10 -
+Rule sol89 1989 only - Jul 10 12:05:20s -0:05:20 -
+Rule sol89 1989 only - Jul 11 12:05:25s -0:05:25 -
+Rule sol89 1989 only - Jul 12 12:05:35s -0:05:35 -
+Rule sol89 1989 only - Jul 13 12:05:40s -0:05:40 -
+Rule sol89 1989 only - Jul 14 12:05:50s -0:05:50 -
+Rule sol89 1989 only - Jul 15 12:05:55s -0:05:55 -
+Rule sol89 1989 only - Jul 16 12:06:00s -0:06:00 -
+Rule sol89 1989 only - Jul 17 12:06:05s -0:06:05 -
+Rule sol89 1989 only - Jul 18 12:06:10s -0:06:10 -
+Rule sol89 1989 only - Jul 19 12:06:15s -0:06:15 -
+Rule sol89 1989 only - Jul 20 12:06:20s -0:06:20 -
+Rule sol89 1989 only - Jul 21 12:06:20s -0:06:20 -
+Rule sol89 1989 only - Jul 22 12:06:25s -0:06:25 -
+Rule sol89 1989 only - Jul 23 12:06:25s -0:06:25 -
+Rule sol89 1989 only - Jul 24 12:06:30s -0:06:30 -
+Rule sol89 1989 only - Jul 25 12:06:30s -0:06:30 -
+Rule sol89 1989 only - Jul 26 12:06:30s -0:06:30 -
+Rule sol89 1989 only - Jul 27 12:06:30s -0:06:30 -
+Rule sol89 1989 only - Jul 28 12:06:30s -0:06:30 -
+Rule sol89 1989 only - Jul 29 12:06:25s -0:06:25 -
+Rule sol89 1989 only - Jul 30 12:06:25s -0:06:25 -
+Rule sol89 1989 only - Jul 31 12:06:20s -0:06:20 -
+Rule sol89 1989 only - Aug 1 12:06:20s -0:06:20 -
+Rule sol89 1989 only - Aug 2 12:06:15s -0:06:15 -
+Rule sol89 1989 only - Aug 3 12:06:10s -0:06:10 -
+Rule sol89 1989 only - Aug 4 12:06:05s -0:06:05 -
+Rule sol89 1989 only - Aug 5 12:06:00s -0:06:00 -
+Rule sol89 1989 only - Aug 6 12:05:50s -0:05:50 -
+Rule sol89 1989 only - Aug 7 12:05:45s -0:05:45 -
+Rule sol89 1989 only - Aug 8 12:05:35s -0:05:35 -
+Rule sol89 1989 only - Aug 9 12:05:30s -0:05:30 -
+Rule sol89 1989 only - Aug 10 12:05:20s -0:05:20 -
+Rule sol89 1989 only - Aug 11 12:05:10s -0:05:10 -
+Rule sol89 1989 only - Aug 12 12:05:00s -0:05:00 -
+Rule sol89 1989 only - Aug 13 12:04:50s -0:04:50 -
+Rule sol89 1989 only - Aug 14 12:04:40s -0:04:40 -
+Rule sol89 1989 only - Aug 15 12:04:30s -0:04:30 -
+Rule sol89 1989 only - Aug 16 12:04:15s -0:04:15 -
+Rule sol89 1989 only - Aug 17 12:04:05s -0:04:05 -
+Rule sol89 1989 only - Aug 18 12:03:50s -0:03:50 -
+Rule sol89 1989 only - Aug 19 12:03:35s -0:03:35 -
+Rule sol89 1989 only - Aug 20 12:03:25s -0:03:25 -
+Rule sol89 1989 only - Aug 21 12:03:10s -0:03:10 -
+Rule sol89 1989 only - Aug 22 12:02:55s -0:02:55 -
+Rule sol89 1989 only - Aug 23 12:02:40s -0:02:40 -
+Rule sol89 1989 only - Aug 24 12:02:20s -0:02:20 -
+Rule sol89 1989 only - Aug 25 12:02:05s -0:02:05 -
+Rule sol89 1989 only - Aug 26 12:01:50s -0:01:50 -
+Rule sol89 1989 only - Aug 27 12:01:30s -0:01:30 -
+Rule sol89 1989 only - Aug 28 12:01:15s -0:01:15 -
+Rule sol89 1989 only - Aug 29 12:00:55s -0:00:55 -
+Rule sol89 1989 only - Aug 30 12:00:40s -0:00:40 -
+Rule sol89 1989 only - Aug 31 12:00:20s -0:00:20 -
+Rule sol89 1989 only - Sep 1 12:00:00s 0:00:00 -
+Rule sol89 1989 only - Sep 2 11:59:45s 0:00:15 -
+Rule sol89 1989 only - Sep 3 11:59:25s 0:00:35 -
+Rule sol89 1989 only - Sep 4 11:59:05s 0:00:55 -
+Rule sol89 1989 only - Sep 5 11:58:45s 0:01:15 -
+Rule sol89 1989 only - Sep 6 11:58:25s 0:01:35 -
+Rule sol89 1989 only - Sep 7 11:58:05s 0:01:55 -
+Rule sol89 1989 only - Sep 8 11:57:45s 0:02:15 -
+Rule sol89 1989 only - Sep 9 11:57:20s 0:02:40 -
+Rule sol89 1989 only - Sep 10 11:57:00s 0:03:00 -
+Rule sol89 1989 only - Sep 11 11:56:40s 0:03:20 -
+Rule sol89 1989 only - Sep 12 11:56:20s 0:03:40 -
+Rule sol89 1989 only - Sep 13 11:56:00s 0:04:00 -
+Rule sol89 1989 only - Sep 14 11:55:35s 0:04:25 -
+Rule sol89 1989 only - Sep 15 11:55:15s 0:04:45 -
+Rule sol89 1989 only - Sep 16 11:54:55s 0:05:05 -
+Rule sol89 1989 only - Sep 17 11:54:35s 0:05:25 -
+Rule sol89 1989 only - Sep 18 11:54:10s 0:05:50 -
+Rule sol89 1989 only - Sep 19 11:53:50s 0:06:10 -
+Rule sol89 1989 only - Sep 20 11:53:30s 0:06:30 -
+Rule sol89 1989 only - Sep 21 11:53:10s 0:06:50 -
+Rule sol89 1989 only - Sep 22 11:52:45s 0:07:15 -
+Rule sol89 1989 only - Sep 23 11:52:25s 0:07:35 -
+Rule sol89 1989 only - Sep 24 11:52:05s 0:07:55 -
+Rule sol89 1989 only - Sep 25 11:51:45s 0:08:15 -
+Rule sol89 1989 only - Sep 26 11:51:25s 0:08:35 -
+Rule sol89 1989 only - Sep 27 11:51:05s 0:08:55 -
+Rule sol89 1989 only - Sep 28 11:50:40s 0:09:20 -
+Rule sol89 1989 only - Sep 29 11:50:20s 0:09:40 -
+Rule sol89 1989 only - Sep 30 11:50:00s 0:10:00 -
+Rule sol89 1989 only - Oct 1 11:49:45s 0:10:15 -
+Rule sol89 1989 only - Oct 2 11:49:25s 0:10:35 -
+Rule sol89 1989 only - Oct 3 11:49:05s 0:10:55 -
+Rule sol89 1989 only - Oct 4 11:48:45s 0:11:15 -
+Rule sol89 1989 only - Oct 5 11:48:30s 0:11:30 -
+Rule sol89 1989 only - Oct 6 11:48:10s 0:11:50 -
+Rule sol89 1989 only - Oct 7 11:47:50s 0:12:10 -
+Rule sol89 1989 only - Oct 8 11:47:35s 0:12:25 -
+Rule sol89 1989 only - Oct 9 11:47:20s 0:12:40 -
+Rule sol89 1989 only - Oct 10 11:47:00s 0:13:00 -
+Rule sol89 1989 only - Oct 11 11:46:45s 0:13:15 -
+Rule sol89 1989 only - Oct 12 11:46:30s 0:13:30 -
+Rule sol89 1989 only - Oct 13 11:46:15s 0:13:45 -
+Rule sol89 1989 only - Oct 14 11:46:00s 0:14:00 -
+Rule sol89 1989 only - Oct 15 11:45:50s 0:14:10 -
+Rule sol89 1989 only - Oct 16 11:45:35s 0:14:25 -
+Rule sol89 1989 only - Oct 17 11:45:20s 0:14:40 -
+Rule sol89 1989 only - Oct 18 11:45:10s 0:14:50 -
+Rule sol89 1989 only - Oct 19 11:45:00s 0:15:00 -
+Rule sol89 1989 only - Oct 20 11:44:50s 0:15:10 -
+Rule sol89 1989 only - Oct 21 11:44:40s 0:15:20 -
+Rule sol89 1989 only - Oct 22 11:44:30s 0:15:30 -
+Rule sol89 1989 only - Oct 23 11:44:20s 0:15:40 -
+Rule sol89 1989 only - Oct 24 11:44:10s 0:15:50 -
+Rule sol89 1989 only - Oct 25 11:44:05s 0:15:55 -
+Rule sol89 1989 only - Oct 26 11:44:00s 0:16:00 -
+Rule sol89 1989 only - Oct 27 11:43:50s 0:16:10 -
+Rule sol89 1989 only - Oct 28 11:43:45s 0:16:15 -
+Rule sol89 1989 only - Oct 29 11:43:40s 0:16:20 -
+Rule sol89 1989 only - Oct 30 11:43:40s 0:16:20 -
+Rule sol89 1989 only - Oct 31 11:43:35s 0:16:25 -
+Rule sol89 1989 only - Nov 1 11:43:35s 0:16:25 -
+Rule sol89 1989 only - Nov 2 11:43:35s 0:16:25 -
+Rule sol89 1989 only - Nov 3 11:43:30s 0:16:30 -
+Rule sol89 1989 only - Nov 4 11:43:35s 0:16:25 -
+Rule sol89 1989 only - Nov 5 11:43:35s 0:16:25 -
+Rule sol89 1989 only - Nov 6 11:43:35s 0:16:25 -
+Rule sol89 1989 only - Nov 7 11:43:40s 0:16:20 -
+Rule sol89 1989 only - Nov 8 11:43:45s 0:16:15 -
+Rule sol89 1989 only - Nov 9 11:43:50s 0:16:10 -
+Rule sol89 1989 only - Nov 10 11:43:55s 0:16:05 -
+Rule sol89 1989 only - Nov 11 11:44:00s 0:16:00 -
+Rule sol89 1989 only - Nov 12 11:44:05s 0:15:55 -
+Rule sol89 1989 only - Nov 13 11:44:15s 0:15:45 -
+Rule sol89 1989 only - Nov 14 11:44:25s 0:15:35 -
+Rule sol89 1989 only - Nov 15 11:44:35s 0:15:25 -
+Rule sol89 1989 only - Nov 16 11:44:45s 0:15:15 -
+Rule sol89 1989 only - Nov 17 11:44:55s 0:15:05 -
+Rule sol89 1989 only - Nov 18 11:45:10s 0:14:50 -
+Rule sol89 1989 only - Nov 19 11:45:20s 0:14:40 -
+Rule sol89 1989 only - Nov 20 11:45:35s 0:14:25 -
+Rule sol89 1989 only - Nov 21 11:45:50s 0:14:10 -
+Rule sol89 1989 only - Nov 22 11:46:05s 0:13:55 -
+Rule sol89 1989 only - Nov 23 11:46:25s 0:13:35 -
+Rule sol89 1989 only - Nov 24 11:46:40s 0:13:20 -
+Rule sol89 1989 only - Nov 25 11:47:00s 0:13:00 -
+Rule sol89 1989 only - Nov 26 11:47:20s 0:12:40 -
+Rule sol89 1989 only - Nov 27 11:47:35s 0:12:25 -
+Rule sol89 1989 only - Nov 28 11:47:55s 0:12:05 -
+Rule sol89 1989 only - Nov 29 11:48:20s 0:11:40 -
+Rule sol89 1989 only - Nov 30 11:48:40s 0:11:20 -
+Rule sol89 1989 only - Dec 1 11:49:00s 0:11:00 -
+Rule sol89 1989 only - Dec 2 11:49:25s 0:10:35 -
+Rule sol89 1989 only - Dec 3 11:49:50s 0:10:10 -
+Rule sol89 1989 only - Dec 4 11:50:15s 0:09:45 -
+Rule sol89 1989 only - Dec 5 11:50:35s 0:09:25 -
+Rule sol89 1989 only - Dec 6 11:51:00s 0:09:00 -
+Rule sol89 1989 only - Dec 7 11:51:30s 0:08:30 -
+Rule sol89 1989 only - Dec 8 11:51:55s 0:08:05 -
+Rule sol89 1989 only - Dec 9 11:52:20s 0:07:40 -
+Rule sol89 1989 only - Dec 10 11:52:50s 0:07:10 -
+Rule sol89 1989 only - Dec 11 11:53:15s 0:06:45 -
+Rule sol89 1989 only - Dec 12 11:53:45s 0:06:15 -
+Rule sol89 1989 only - Dec 13 11:54:10s 0:05:50 -
+Rule sol89 1989 only - Dec 14 11:54:40s 0:05:20 -
+Rule sol89 1989 only - Dec 15 11:55:10s 0:04:50 -
+Rule sol89 1989 only - Dec 16 11:55:40s 0:04:20 -
+Rule sol89 1989 only - Dec 17 11:56:05s 0:03:55 -
+Rule sol89 1989 only - Dec 18 11:56:35s 0:03:25 -
+Rule sol89 1989 only - Dec 19 11:57:05s 0:02:55 -
+Rule sol89 1989 only - Dec 20 11:57:35s 0:02:25 -
+Rule sol89 1989 only - Dec 21 11:58:05s 0:01:55 -
+Rule sol89 1989 only - Dec 22 11:58:35s 0:01:25 -
+Rule sol89 1989 only - Dec 23 11:59:05s 0:00:55 -
+Rule sol89 1989 only - Dec 24 11:59:35s 0:00:25 -
+Rule sol89 1989 only - Dec 25 12:00:05s -0:00:05 -
+Rule sol89 1989 only - Dec 26 12:00:35s -0:00:35 -
+Rule sol89 1989 only - Dec 27 12:01:05s -0:01:05 -
+Rule sol89 1989 only - Dec 28 12:01:35s -0:01:35 -
+Rule sol89 1989 only - Dec 29 12:02:00s -0:02:00 -
+Rule sol89 1989 only - Dec 30 12:02:30s -0:02:30 -
+Rule sol89 1989 only - Dec 31 12:03:00s -0:03:00 -
+
+# Riyadh is at about 46 degrees 46 minutes East: 3 hrs, 7 mins, 4 secs
+# Before and after 1989, we'll operate on local mean solar time.
+
+# Zone NAME GMTOFF RULES/SAVE FORMAT [UNTIL]
+Zone Asia/Riyadh89 3:07:04 - zzz 1989
+ 3:07:04 sol89 zzz 1990
+ 3:07:04 - zzz
+# For backward compatibility...
+Link Asia/Riyadh89 Mideast/Riyadh89
diff --git a/misc/flot/examples/axes-time-zones/tz/southamerica b/misc/flot/examples/axes-time-zones/tz/southamerica
new file mode 100644
index 0000000..3301a43
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/southamerica
@@ -0,0 +1,1711 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# This data is by no means authoritative; if you think you know better,
+# go ahead and edit the file (and please send any changes to
+# tz@iana.org for general use in the future).
+
+# From Paul Eggert (2006-03-22):
+# A good source for time zone historical data outside the U.S. is
+# Thomas G. Shanks and Rique Pottenger, The International Atlas (6th edition),
+# San Diego: ACS Publications, Inc. (2003).
+#
+# Gwillim Law writes that a good source
+# for recent time zone data is the International Air Transport
+# Association's Standard Schedules Information Manual (IATA SSIM),
+# published semiannually. Law sent in several helpful summaries
+# of the IATA's data after 1990.
+#
+# Except where otherwise noted, Shanks & Pottenger is the source for
+# entries through 1990, and IATA SSIM is the source for entries afterwards.
+#
+# Earlier editions of these tables used the North American style (e.g. ARST and
+# ARDT for Argentine Standard and Daylight Time), but the following quote
+# suggests that it's better to use European style (e.g. ART and ARST).
+# I suggest the use of _Summer time_ instead of the more cumbersome
+# _daylight-saving time_. _Summer time_ seems to be in general use
+# in Europe and South America.
+# -- E O Cutler, _New York Times_ (1937-02-14), quoted in
+# H L Mencken, _The American Language: Supplement I_ (1960), p 466
+#
+# Earlier editions of these tables also used the North American style
+# for time zones in Brazil, but this was incorrect, as Brazilians say
+# "summer time". Reinaldo Goulart, a Sao Paulo businessman active in
+# the railroad sector, writes (1999-07-06):
+# The subject of time zones is currently a matter of discussion/debate in
+# Brazil. Let's say that "the Brasilia time" is considered the
+# "official time" because Brasilia is the capital city.
+# The other three time zones are called "Brasilia time "minus one" or
+# "plus one" or "plus two". As far as I know there is no such
+# name/designation as "Eastern Time" or "Central Time".
+# So I invented the following (English-language) abbreviations for now.
+# Corrections are welcome!
+# std dst
+# -2:00 FNT FNST Fernando de Noronha
+# -3:00 BRT BRST Brasilia
+# -4:00 AMT AMST Amazon
+# -5:00 ACT ACST Acre
+
+###############################################################################
+
+###############################################################################
+
+# Argentina
+
+# From Bob Devine (1988-01-28):
+# Argentina: first Sunday in October to first Sunday in April since 1976.
+# Double Summer time from 1969 to 1974. Switches at midnight.
+
+# From U. S. Naval Observatory (1988-01-199):
+# ARGENTINA 3 H BEHIND UTC
+
+# From Hernan G. Otero (1995-06-26):
+# I am sending modifications to the Argentine time zone table...
+# AR was chosen because they are the ISO letters that represent Argentina.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Arg 1930 only - Dec 1 0:00 1:00 S
+Rule Arg 1931 only - Apr 1 0:00 0 -
+Rule Arg 1931 only - Oct 15 0:00 1:00 S
+Rule Arg 1932 1940 - Mar 1 0:00 0 -
+Rule Arg 1932 1939 - Nov 1 0:00 1:00 S
+Rule Arg 1940 only - Jul 1 0:00 1:00 S
+Rule Arg 1941 only - Jun 15 0:00 0 -
+Rule Arg 1941 only - Oct 15 0:00 1:00 S
+Rule Arg 1943 only - Aug 1 0:00 0 -
+Rule Arg 1943 only - Oct 15 0:00 1:00 S
+Rule Arg 1946 only - Mar 1 0:00 0 -
+Rule Arg 1946 only - Oct 1 0:00 1:00 S
+Rule Arg 1963 only - Oct 1 0:00 0 -
+Rule Arg 1963 only - Dec 15 0:00 1:00 S
+Rule Arg 1964 1966 - Mar 1 0:00 0 -
+Rule Arg 1964 1966 - Oct 15 0:00 1:00 S
+Rule Arg 1967 only - Apr 2 0:00 0 -
+Rule Arg 1967 1968 - Oct Sun>=1 0:00 1:00 S
+Rule Arg 1968 1969 - Apr Sun>=1 0:00 0 -
+Rule Arg 1974 only - Jan 23 0:00 1:00 S
+Rule Arg 1974 only - May 1 0:00 0 -
+Rule Arg 1988 only - Dec 1 0:00 1:00 S
+#
+# From Hernan G. Otero (1995-06-26):
+# These corrections were contributed by InterSoft Argentina S.A.,
+# obtaining the data from the:
+# Talleres de Hidrografia Naval Argentina
+# (Argentine Naval Hydrography Institute)
+Rule Arg 1989 1993 - Mar Sun>=1 0:00 0 -
+Rule Arg 1989 1992 - Oct Sun>=15 0:00 1:00 S
+#
+# From Hernan G. Otero (1995-06-26):
+# From this moment on, the law that mandated the daylight saving
+# time corrections was derogated and no more modifications
+# to the time zones (for daylight saving) are now made.
+#
+# From Rives McDow (2000-01-10):
+# On October 3, 1999, 0:00 local, Argentina implemented daylight savings time,
+# which did not result in the switch of a time zone, as they stayed 9 hours
+# from the International Date Line.
+Rule Arg 1999 only - Oct Sun>=1 0:00 1:00 S
+# From Paul Eggert (2007-12-28):
+# DST was set to expire on March 5, not March 3, but since it was converted
+# to standard time on March 3 it's more convenient for us to pretend that
+# it ended on March 3.
+Rule Arg 2000 only - Mar 3 0:00 0 -
+#
+# From Peter Gradelski via Steffen Thorsen (2000-03-01):
+# We just checked with our Sao Paulo office and they say the government of
+# Argentina decided not to become one of the countries that go on or off DST.
+# So Buenos Aires should be -3 hours from GMT at all times.
+#
+# From Fabian L. Arce Jofre (2000-04-04):
+# The law that claimed DST for Argentina was derogated by President Fernando
+# de la Rua on March 2, 2000, because it would make people spend more energy
+# in the winter time, rather than less. The change took effect on March 3.
+#
+# From Mariano Absatz (2001-06-06):
+# one of the major newspapers here in Argentina said that the 1999
+# Timezone Law (which never was effectively applied) will (would?) be
+# in effect.... The article is at
+# http://ar.clarin.com/diario/2001-06-06/e-01701.htm
+# ... The Law itself is "Ley No 25155", sanctioned on 1999-08-25, enacted
+# 1999-09-17, and published 1999-09-21. The official publication is at:
+# http://www.boletin.jus.gov.ar/BON/Primera/1999/09-Septiembre/21/PDF/BO21-09-99LEG.PDF
+# Regretfully, you have to subscribe (and pay) for the on-line version....
+#
+# (2001-06-12):
+# the timezone for Argentina will not change next Sunday.
+# Apparently it will do so on Sunday 24th....
+# http://ar.clarin.com/diario/2001-06-12/s-03501.htm
+#
+# (2001-06-25):
+# Last Friday (yes, the last working day before the date of the change), the
+# Senate annulled the 1999 law that introduced the changes later postponed.
+# http://www.clarin.com.ar/diario/2001-06-22/s-03601.htm
+# It remains the vote of the Deputies..., but it will be the same....
+# This kind of things had always been done this way in Argentina.
+# We are still -03:00 all year round in all of the country.
+#
+# From Steffen Thorsen (2007-12-21):
+# A user (Leonardo Chaim) reported that Argentina will adopt DST....
+# all of the country (all Zone-entries) are affected. News reports like
+# http://www.lanacion.com.ar/opinion/nota.asp?nota_id=973037 indicate
+# that Argentina will use DST next year as well, from October to
+# March, although exact rules are not given.
+#
+# From Jesper Norgaard Welen (2007-12-26)
+# The last hurdle of Argentina DST is over, the proposal was approved in
+# the lower chamber too (Deputados) with a vote 192 for and 2 against.
+# By the way thanks to Mariano Absatz and Daniel Mario Vega for the link to
+# the original scanned proposal, where the dates and the zero hours are
+# clear and unambiguous...This is the article about final approval:
+# <a href="http://www.lanacion.com.ar/politica/nota.asp?nota_id=973996">
+# http://www.lanacion.com.ar/politica/nota.asp?nota_id=973996
+# </a>
+#
+# From Paul Eggert (2007-12-22):
+# For dates after mid-2008, the following rules are my guesses and
+# are quite possibly wrong, but are more likely than no DST at all.
+
+# From Alexander Krivenyshev (2008-09-05):
+# As per message from Carlos Alberto Fonseca Arauz (Nicaragua),
+# Argentina will start DST on Sunday October 19, 2008.
+#
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_argentina03.html">
+# http://www.worldtimezone.com/dst_news/dst_news_argentina03.html
+# </a>
+# OR
+# <a href="http://www.impulsobaires.com.ar/nota.php?id=57832 (in spanish)">
+# http://www.impulsobaires.com.ar/nota.php?id=57832 (in spanish)
+# </a>
+
+# From Rodrigo Severo (2008-10-06):
+# Here is some info available at a Gentoo bug related to TZ on Argentina's DST:
+# ...
+# ------- Comment #1 from [jmdocile] 2008-10-06 16:28 0000 -------
+# Hi, there is a problem with timezone-data-2008e and maybe with
+# timezone-data-2008f
+# Argentinian law [Number] 25.155 is no longer valid.
+# <a href="http://www.infoleg.gov.ar/infolegInternet/anexos/60000-64999/60036/norma.htm">
+# http://www.infoleg.gov.ar/infolegInternet/anexos/60000-64999/60036/norma.htm
+# </a>
+# The new one is law [Number] 26.350
+# <a href="http://www.infoleg.gov.ar/infolegInternet/anexos/135000-139999/136191/norma.htm">
+# http://www.infoleg.gov.ar/infolegInternet/anexos/135000-139999/136191/norma.htm
+# </a>
+# So there is no summer time in Argentina for now.
+
+# From Mariano Absatz (2008-10-20):
+# Decree 1693/2008 applies Law 26.350 for the summer 2008/2009 establishing DST in Argentina
+# From 2008-10-19 until 2009-03-15
+# <a href="http://www.boletinoficial.gov.ar/Bora.Portal/CustomControls/PdfContent.aspx?fp=16102008&pi=3&pf=4&s=0&sec=01">
+# http://www.boletinoficial.gov.ar/Bora.Portal/CustomControls/PdfContent.aspx?fp=16102008&pi=3&pf=4&s=0&sec=01
+# </a>
+#
+# Decree 1705/2008 excepting 12 Provinces from applying DST in the summer 2008/2009:
+# Catamarca, La Rioja, Mendoza, Salta, San Juan, San Luis, La Pampa, Neuquen, Rio Negro, Chubut, Santa Cruz
+# and Tierra del Fuego
+# <a href="http://www.boletinoficial.gov.ar/Bora.Portal/CustomControls/PdfContent.aspx?fp=17102008&pi=1&pf=1&s=0&sec=01">
+# http://www.boletinoficial.gov.ar/Bora.Portal/CustomControls/PdfContent.aspx?fp=17102008&pi=1&pf=1&s=0&sec=01
+# </a>
+#
+# Press release 235 dated Saturday October 18th, from the Government of the Province of Jujuy saying
+# it will not apply DST either (even when it was not included in Decree 1705/2008)
+# <a href="http://www.jujuy.gov.ar/index2/partes_prensa/18_10_08/235-181008.doc">
+# http://www.jujuy.gov.ar/index2/partes_prensa/18_10_08/235-181008.doc
+# </a>
+
+# From fullinet (2009-10-18):
+# As announced in
+# <a hef="http://www.argentina.gob.ar/argentina/portal/paginas.dhtml?pagina=356">
+# http://www.argentina.gob.ar/argentina/portal/paginas.dhtml?pagina=356
+# </a>
+# (an official .gob.ar) under title: "Sin Cambio de Hora" (english: "No hour change")
+#
+# "Por el momento, el Gobierno Nacional resolvio no modificar la hora
+# oficial, decision que estaba en estudio para su implementacion el
+# domingo 18 de octubre. Desde el Ministerio de Planificacion se anuncio
+# que la Argentina hoy, en estas condiciones meteorologicas, no necesita
+# la modificacion del huso horario, ya que 2009 nos encuentra con
+# crecimiento en la produccion y distribucion energetica."
+
+Rule Arg 2007 only - Dec 30 0:00 1:00 S
+Rule Arg 2008 2009 - Mar Sun>=15 0:00 0 -
+Rule Arg 2008 only - Oct Sun>=15 0:00 1:00 S
+
+# From Mariano Absatz (2004-05-21):
+# Today it was officially published that the Province of Mendoza is changing
+# its timezone this winter... starting tomorrow night....
+# http://www.gobernac.mendoza.gov.ar/boletin/pdf/20040521-27158-normas.pdf
+# From Paul Eggert (2004-05-24):
+# It's Law No. 7,210. This change is due to a public power emergency, so for
+# now we'll assume it's for this year only.
+#
+# From Paul Eggert (2006-03-22):
+# <a href="http://www.spicasc.net/horvera.html">
+# Hora de verano para la Republica Argentina (2003-06-08)
+# </a> says that standard time in Argentina from 1894-10-31
+# to 1920-05-01 was -4:16:48.25. Go with this more-precise value
+# over Shanks & Pottenger.
+#
+# From Mariano Absatz (2004-06-05):
+# These media articles from a major newspaper mostly cover the current state:
+# http://www.lanacion.com.ar/04/05/27/de_604825.asp
+# http://www.lanacion.com.ar/04/05/28/de_605203.asp
+#
+# The following eight (8) provinces pulled clocks back to UTC-04:00 at
+# midnight Monday May 31st. (that is, the night between 05/31 and 06/01).
+# Apparently, all nine provinces would go back to UTC-03:00 at the same
+# time in October 17th.
+#
+# Catamarca, Chubut, La Rioja, San Juan, San Luis, Santa Cruz,
+# Tierra del Fuego, Tucuman.
+#
+# From Mariano Absatz (2004-06-14):
+# ... this weekend, the Province of Tucuman decided it'd go back to UTC-03:00
+# yesterday midnight (that is, at 24:00 Saturday 12th), since the people's
+# annoyance with the change is much higher than the power savings obtained....
+#
+# From Gwillim Law (2004-06-14):
+# http://www.lanacion.com.ar/04/06/10/de_609078.asp ...
+# "The time change in Tierra del Fuego was a conflicted decision from
+# the start. The government had decreed that the measure would take
+# effect on June 1, but a normative error forced the new time to begin
+# three days earlier, from a Saturday to a Sunday....
+# Our understanding was that the change was originally scheduled to take place
+# on June 1 at 00:00 in Chubut, Santa Cruz, Tierra del Fuego (and some other
+# provinces). Sunday was May 30, only two days earlier. So the article
+# contains a contradiction. I would give more credence to the Saturday/Sunday
+# date than the "three days earlier" phrase, and conclude that Tierra del
+# Fuego set its clocks back at 2004-05-30 00:00.
+#
+# From Steffen Thorsen (2004-10-05):
+# The previous law 7210 which changed the province of Mendoza's time zone
+# back in May have been modified slightly in a new law 7277, which set the
+# new end date to 2004-09-26 (original date was 2004-10-17).
+# http://www.gobernac.mendoza.gov.ar/boletin/pdf/20040924-27244-normas.pdf
+#
+# From Mariano Absatz (2004-10-05):
+# San Juan changed from UTC-03:00 to UTC-04:00 at midnight between
+# Sunday, May 30th and Monday, May 31st. It changed back to UTC-03:00
+# at midnight between Saturday, July 24th and Sunday, July 25th....
+# http://www.sanjuan.gov.ar/prensa/archivo/000329.html
+# http://www.sanjuan.gov.ar/prensa/archivo/000426.html
+# http://www.sanjuan.gov.ar/prensa/archivo/000441.html
+
+# From Alex Krivenyshev (2008-01-17):
+# Here are articles that Argentina Province San Luis is planning to end DST
+# as earlier as upcoming Monday January 21, 2008 or February 2008:
+#
+# Provincia argentina retrasa reloj y marca diferencia con resto del pais
+# (Argentine Province delayed clock and mark difference with the rest of the
+# country)
+# <a href="http://cl.invertia.com/noticias/noticia.aspx?idNoticia=200801171849_EFE_ET4373&idtel">
+# http://cl.invertia.com/noticias/noticia.aspx?idNoticia=200801171849_EFE_ET4373&idtel
+# </a>
+#
+# Es inminente que en San Luis atrasen una hora los relojes
+# (It is imminent in San Luis clocks one hour delay)
+# <a href="http://www.lagaceta.com.ar/vernotae.asp?id_nota=253414">
+# http://www.lagaceta.com.ar/vernotae.asp?id_nota=253414
+# </a>
+#
+# <a href="http://www.worldtimezone.net/dst_news/dst_news_argentina02.html">
+# http://www.worldtimezone.net/dst_news/dst_news_argentina02.html
+# </a>
+
+# From Jesper Norgaard Welen (2008-01-18):
+# The page of the San Luis provincial government
+# <a href="http://www.sanluis.gov.ar/notas.asp?idCanal=0&id=22812">
+# http://www.sanluis.gov.ar/notas.asp?idCanal=0&id=22812
+# </a>
+# confirms what Alex Krivenyshev has earlier sent to the tz
+# emailing list about that San Luis plans to return to standard
+# time much earlier than the rest of the country. It also
+# confirms that upon request the provinces San Juan and Mendoza
+# refused to follow San Luis in this change.
+#
+# The change is supposed to take place Monday the 21.st at 0:00
+# hours. As far as I understand it if this goes ahead, we need
+# a new timezone for San Luis (although there are also documented
+# independent changes in the southamerica file of San Luis in
+# 1990 and 1991 which has not been confirmed).
+
+# From Jesper Norgaard Welen (2008-01-25):
+# Unfortunately the below page has become defunct, about the San Luis
+# time change. Perhaps because it now is part of a group of pages "Most
+# important pages of 2008."
+#
+# You can use
+# <a href="http://www.sanluis.gov.ar/notas.asp?idCanal=8141&id=22834">
+# http://www.sanluis.gov.ar/notas.asp?idCanal=8141&id=22834
+# </a>
+# instead it seems. Or use "Buscador" from the main page of the San Luis
+# government, and fill in "huso" and click OK, and you will get 3 pages
+# from which the first one is identical to the above.
+
+# From Mariano Absatz (2008-01-28):
+# I can confirm that the Province of San Luis (and so far only that
+# province) decided to go back to UTC-3 effective midnight Jan 20th 2008
+# (that is, Monday 21st at 0:00 is the time the clocks were delayed back
+# 1 hour), and they intend to keep UTC-3 as their timezone all year round
+# (that is, unless they change their mind any minute now).
+#
+# So we'll have to add yet another city to 'southamerica' (I think San
+# Luis city is the mos populated city in the Province, so it'd be
+# America/Argentina/San_Luis... of course I can't remember if San Luis's
+# history of particular changes goes along with Mendoza or San Juan :-(
+# (I only remember not being able to collect hard facts about San Luis
+# back in 2004, when these provinces changed to UTC-4 for a few days, I
+# mailed them personally and never got an answer).
+
+# From Paul Eggert (2008-06-30):
+# Unless otherwise specified, data are from Shanks & Pottenger through 1992,
+# from the IATA otherwise. As noted below, Shanks & Pottenger say that
+# America/Cordoba split into 6 subregions during 1991/1992, one of which
+# was America/San_Luis, but we haven't verified this yet so for now we'll
+# keep America/Cordoba a single region rather than splitting it into the
+# other 5 subregions.
+
+# From Mariano Absatz (2009-03-13):
+# Yesterday (with our usual 2-day notice) the Province of San Luis
+# decided that next Sunday instead of "staying" @utc-03:00 they will go
+# to utc-04:00 until the second Saturday in October...
+#
+# The press release is at
+# <a href="http://www.sanluis.gov.ar/SL/Paginas/NoticiaDetalle.asp?TemaId=1&InfoPrensaId=3102">
+# http://www.sanluis.gov.ar/SL/Paginas/NoticiaDetalle.asp?TemaId=1&InfoPrensaId=3102
+# </a>
+# (I couldn't find the decree, but
+# <a href="http://www.sanluis.gov.ar">
+# www.sanluis.gov.ar
+# <a/>
+# is the official page for the Province Government).
+#
+# There's also a note in only one of the major national papers (La Nación) at
+# <a href="http://www.lanacion.com.ar/nota.asp?nota_id=1107912">
+# http://www.lanacion.com.ar/nota.asp?nota_id=1107912
+# </a>
+#
+# The press release says:
+# (...) anunció que el próximo domingo a las 00:00 los puntanos deberán
+# atrasar una hora sus relojes.
+#
+# A partir de entonces, San Luis establecerá el huso horario propio de
+# la Provincia. De esta manera, durante el periodo del calendario anual
+# 2009, el cambio horario quedará comprendido entre las 00:00 del tercer
+# domingo de marzo y las 24:00 del segundo sábado de octubre.
+# Quick&dirty translation
+# (...) announced that next Sunday, at 00:00, Puntanos (the San Luis
+# inhabitants) will have to turn back one hour their clocks
+#
+# Since then, San Luis will establish its own Province timezone. Thus,
+# during 2009, this timezone change will run from 00:00 the third Sunday
+# in March until 24:00 of the second Saturday in October.
+
+# From Mariano Absatz (2009-10-16):
+# ...the Province of San Luis is a case in itself.
+#
+# The Law at
+# <a href="http://www.diputadossanluis.gov.ar/diputadosasp/paginas/verNorma.asp?NormaID=276>"
+# http://www.diputadossanluis.gov.ar/diputadosasp/paginas/verNorma.asp?NormaID=276
+# </a>
+# is ambiguous because establishes a calendar from the 2nd Sunday in
+# October at 0:00 thru the 2nd Saturday in March at 24:00 and the
+# complement of that starting on the 2nd Sunday of March at 0:00 and
+# ending on the 2nd Saturday of March at 24:00.
+#
+# This clearly breaks every time the 1st of March or October is a Sunday.
+#
+# IMHO, the "spirit of the Law" is to make the changes at 0:00 on the 2nd
+# Sunday of October and March.
+#
+# The problem is that the changes in the rest of the Provinces that did
+# change in 2007/2008, were made according to the Federal Law and Decrees
+# that did so on the 3rd Sunday of October and March.
+#
+# In fact, San Luis actually switched from UTC-4 to UTC-3 last Sunday
+# (October 11th) at 0:00.
+#
+# So I guess a new set of rules, besides "Arg", must be made and the last
+# America/Argentina/San_Luis entries should change to use these...
+#
+# I'm enclosing a patch that does what I say... regretfully, the San Luis
+# timezone must be called "WART/WARST" even when most of the time (like,
+# right now) WARST == ART... that is, since last Sunday, all the country
+# is using UTC-3, but in my patch, San Luis calls it "WARST" and the rest
+# of the country calls it "ART".
+# ...
+
+# From Alexander Krivenyshev (2010-04-09):
+# According to news reports from El Diario de la Republica Province San
+# Luis, Argentina (standard time UTC-04) will keep Daylight Saving Time
+# after April 11, 2010--will continue to have same time as rest of
+# Argentina (UTC-3) (no DST).
+#
+# Confirmaron la pr&oacute;rroga del huso horario de verano (Spanish)
+# <a href="http://www.eldiariodelarepublica.com/index.php?option=com_content&task=view&id=29383&Itemid=9">
+# http://www.eldiariodelarepublica.com/index.php?option=com_content&task=view&id=29383&Itemid=9
+# </a>
+# or (some English translation):
+# <a href="http://www.worldtimezone.com/dst_news/dst_news_argentina08.html">
+# http://www.worldtimezone.com/dst_news/dst_news_argentina08.html
+# </a>
+
+# From Mariano Absatz (2010-04-12):
+# yes...I can confirm this...and given that San Luis keeps calling
+# UTC-03:00 "summer time", we should't just let San Luis go back to "Arg"
+# rules...San Luis is still using "Western ARgentina Time" and it got
+# stuck on Summer daylight savings time even though the summer is over.
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+#
+# Buenos Aires (BA), Capital Federal (CF),
+Zone America/Argentina/Buenos_Aires -3:53:48 - LMT 1894 Oct 31
+ -4:16:48 - CMT 1920 May # Cordoba Mean Time
+ -4:00 - ART 1930 Dec
+ -4:00 Arg AR%sT 1969 Oct 5
+ -3:00 Arg AR%sT 1999 Oct 3
+ -4:00 Arg AR%sT 2000 Mar 3
+ -3:00 Arg AR%sT
+#
+# Cordoba (CB), Santa Fe (SF), Entre Rios (ER), Corrientes (CN), Misiones (MN),
+# Chaco (CC), Formosa (FM), Santiago del Estero (SE)
+#
+# Shanks & Pottenger also make the following claims, which we haven't verified:
+# - Formosa switched to -3:00 on 1991-01-07.
+# - Misiones switched to -3:00 on 1990-12-29.
+# - Chaco switched to -3:00 on 1991-01-04.
+# - Santiago del Estero switched to -4:00 on 1991-04-01,
+# then to -3:00 on 1991-04-26.
+#
+Zone America/Argentina/Cordoba -4:16:48 - LMT 1894 Oct 31
+ -4:16:48 - CMT 1920 May
+ -4:00 - ART 1930 Dec
+ -4:00 Arg AR%sT 1969 Oct 5
+ -3:00 Arg AR%sT 1991 Mar 3
+ -4:00 - WART 1991 Oct 20
+ -3:00 Arg AR%sT 1999 Oct 3
+ -4:00 Arg AR%sT 2000 Mar 3
+ -3:00 Arg AR%sT
+#
+# Salta (SA), La Pampa (LP), Neuquen (NQ), Rio Negro (RN)
+Zone America/Argentina/Salta -4:21:40 - LMT 1894 Oct 31
+ -4:16:48 - CMT 1920 May
+ -4:00 - ART 1930 Dec
+ -4:00 Arg AR%sT 1969 Oct 5
+ -3:00 Arg AR%sT 1991 Mar 3
+ -4:00 - WART 1991 Oct 20
+ -3:00 Arg AR%sT 1999 Oct 3
+ -4:00 Arg AR%sT 2000 Mar 3
+ -3:00 Arg AR%sT 2008 Oct 18
+ -3:00 - ART
+#
+# Tucuman (TM)
+Zone America/Argentina/Tucuman -4:20:52 - LMT 1894 Oct 31
+ -4:16:48 - CMT 1920 May
+ -4:00 - ART 1930 Dec
+ -4:00 Arg AR%sT 1969 Oct 5
+ -3:00 Arg AR%sT 1991 Mar 3
+ -4:00 - WART 1991 Oct 20
+ -3:00 Arg AR%sT 1999 Oct 3
+ -4:00 Arg AR%sT 2000 Mar 3
+ -3:00 - ART 2004 Jun 1
+ -4:00 - WART 2004 Jun 13
+ -3:00 Arg AR%sT
+#
+# La Rioja (LR)
+Zone America/Argentina/La_Rioja -4:27:24 - LMT 1894 Oct 31
+ -4:16:48 - CMT 1920 May
+ -4:00 - ART 1930 Dec
+ -4:00 Arg AR%sT 1969 Oct 5
+ -3:00 Arg AR%sT 1991 Mar 1
+ -4:00 - WART 1991 May 7
+ -3:00 Arg AR%sT 1999 Oct 3
+ -4:00 Arg AR%sT 2000 Mar 3
+ -3:00 - ART 2004 Jun 1
+ -4:00 - WART 2004 Jun 20
+ -3:00 Arg AR%sT 2008 Oct 18
+ -3:00 - ART
+#
+# San Juan (SJ)
+Zone America/Argentina/San_Juan -4:34:04 - LMT 1894 Oct 31
+ -4:16:48 - CMT 1920 May
+ -4:00 - ART 1930 Dec
+ -4:00 Arg AR%sT 1969 Oct 5
+ -3:00 Arg AR%sT 1991 Mar 1
+ -4:00 - WART 1991 May 7
+ -3:00 Arg AR%sT 1999 Oct 3
+ -4:00 Arg AR%sT 2000 Mar 3
+ -3:00 - ART 2004 May 31
+ -4:00 - WART 2004 Jul 25
+ -3:00 Arg AR%sT 2008 Oct 18
+ -3:00 - ART
+#
+# Jujuy (JY)
+Zone America/Argentina/Jujuy -4:21:12 - LMT 1894 Oct 31
+ -4:16:48 - CMT 1920 May
+ -4:00 - ART 1930 Dec
+ -4:00 Arg AR%sT 1969 Oct 5
+ -3:00 Arg AR%sT 1990 Mar 4
+ -4:00 - WART 1990 Oct 28
+ -4:00 1:00 WARST 1991 Mar 17
+ -4:00 - WART 1991 Oct 6
+ -3:00 1:00 ARST 1992
+ -3:00 Arg AR%sT 1999 Oct 3
+ -4:00 Arg AR%sT 2000 Mar 3
+ -3:00 Arg AR%sT 2008 Oct 18
+ -3:00 - ART
+#
+# Catamarca (CT), Chubut (CH)
+Zone America/Argentina/Catamarca -4:23:08 - LMT 1894 Oct 31
+ -4:16:48 - CMT 1920 May
+ -4:00 - ART 1930 Dec
+ -4:00 Arg AR%sT 1969 Oct 5
+ -3:00 Arg AR%sT 1991 Mar 3
+ -4:00 - WART 1991 Oct 20
+ -3:00 Arg AR%sT 1999 Oct 3
+ -4:00 Arg AR%sT 2000 Mar 3
+ -3:00 - ART 2004 Jun 1
+ -4:00 - WART 2004 Jun 20
+ -3:00 Arg AR%sT 2008 Oct 18
+ -3:00 - ART
+#
+# Mendoza (MZ)
+Zone America/Argentina/Mendoza -4:35:16 - LMT 1894 Oct 31
+ -4:16:48 - CMT 1920 May
+ -4:00 - ART 1930 Dec
+ -4:00 Arg AR%sT 1969 Oct 5
+ -3:00 Arg AR%sT 1990 Mar 4
+ -4:00 - WART 1990 Oct 15
+ -4:00 1:00 WARST 1991 Mar 1
+ -4:00 - WART 1991 Oct 15
+ -4:00 1:00 WARST 1992 Mar 1
+ -4:00 - WART 1992 Oct 18
+ -3:00 Arg AR%sT 1999 Oct 3
+ -4:00 Arg AR%sT 2000 Mar 3
+ -3:00 - ART 2004 May 23
+ -4:00 - WART 2004 Sep 26
+ -3:00 Arg AR%sT 2008 Oct 18
+ -3:00 - ART
+#
+# San Luis (SL)
+
+Rule SanLuis 2008 2009 - Mar Sun>=8 0:00 0 -
+Rule SanLuis 2007 2009 - Oct Sun>=8 0:00 1:00 S
+
+Zone America/Argentina/San_Luis -4:25:24 - LMT 1894 Oct 31
+ -4:16:48 - CMT 1920 May
+ -4:00 - ART 1930 Dec
+ -4:00 Arg AR%sT 1969 Oct 5
+ -3:00 Arg AR%sT 1990
+ -3:00 1:00 ARST 1990 Mar 14
+ -4:00 - WART 1990 Oct 15
+ -4:00 1:00 WARST 1991 Mar 1
+ -4:00 - WART 1991 Jun 1
+ -3:00 - ART 1999 Oct 3
+ -4:00 1:00 WARST 2000 Mar 3
+ -3:00 - ART 2004 May 31
+ -4:00 - WART 2004 Jul 25
+ -3:00 Arg AR%sT 2008 Jan 21
+ -4:00 SanLuis WAR%sT
+#
+# Santa Cruz (SC)
+Zone America/Argentina/Rio_Gallegos -4:36:52 - LMT 1894 Oct 31
+ -4:16:48 - CMT 1920 May # Cordoba Mean Time
+ -4:00 - ART 1930 Dec
+ -4:00 Arg AR%sT 1969 Oct 5
+ -3:00 Arg AR%sT 1999 Oct 3
+ -4:00 Arg AR%sT 2000 Mar 3
+ -3:00 - ART 2004 Jun 1
+ -4:00 - WART 2004 Jun 20
+ -3:00 Arg AR%sT 2008 Oct 18
+ -3:00 - ART
+#
+# Tierra del Fuego, Antartida e Islas del Atlantico Sur (TF)
+Zone America/Argentina/Ushuaia -4:33:12 - LMT 1894 Oct 31
+ -4:16:48 - CMT 1920 May # Cordoba Mean Time
+ -4:00 - ART 1930 Dec
+ -4:00 Arg AR%sT 1969 Oct 5
+ -3:00 Arg AR%sT 1999 Oct 3
+ -4:00 Arg AR%sT 2000 Mar 3
+ -3:00 - ART 2004 May 30
+ -4:00 - WART 2004 Jun 20
+ -3:00 Arg AR%sT 2008 Oct 18
+ -3:00 - ART
+
+# Aruba
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Aruba -4:40:24 - LMT 1912 Feb 12 # Oranjestad
+ -4:30 - ANT 1965 # Netherlands Antilles Time
+ -4:00 - AST
+
+# Bolivia
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/La_Paz -4:32:36 - LMT 1890
+ -4:32:36 - CMT 1931 Oct 15 # Calamarca MT
+ -4:32:36 1:00 BOST 1932 Mar 21 # Bolivia ST
+ -4:00 - BOT # Bolivia Time
+
+# Brazil
+
+# From Paul Eggert (1993-11-18):
+# The mayor of Rio recently attempted to change the time zone rules
+# just in his city, in order to leave more summer time for the tourist trade.
+# The rule change lasted only part of the day;
+# the federal government refused to follow the city's rules, and business
+# was in a chaos, so the mayor backed down that afternoon.
+
+# From IATA SSIM (1996-02):
+# _Only_ the following states in BR1 observe DST: Rio Grande do Sul (RS),
+# Santa Catarina (SC), Parana (PR), Sao Paulo (SP), Rio de Janeiro (RJ),
+# Espirito Santo (ES), Minas Gerais (MG), Bahia (BA), Goias (GO),
+# Distrito Federal (DF), Tocantins (TO), Sergipe [SE] and Alagoas [AL].
+# [The last three states are new to this issue of the IATA SSIM.]
+
+# From Gwillim Law (1996-10-07):
+# Geography, history (Tocantins was part of Goias until 1989), and other
+# sources of time zone information lead me to believe that AL, SE, and TO were
+# always in BR1, and so the only change was whether or not they observed DST....
+# The earliest issue of the SSIM I have is 2/91. Each issue from then until
+# 9/95 says that DST is observed only in the ten states I quoted from 9/95,
+# along with Mato Grosso (MT) and Mato Grosso do Sul (MS), which are in BR2
+# (UTC-4).... The other two time zones given for Brazil are BR3, which is
+# UTC-5, no DST, and applies only in the state of Acre (AC); and BR4, which is
+# UTC-2, and applies to Fernando de Noronha (formerly FN, but I believe it's
+# become part of the state of Pernambuco). The boundary between BR1 and BR2
+# has never been clearly stated. They've simply been called East and West.
+# However, some conclusions can be drawn from another IATA manual: the Airline
+# Coding Directory, which lists close to 400 airports in Brazil. For each
+# airport it gives a time zone which is coded to the SSIM. From that
+# information, I'm led to conclude that the states of Amapa (AP), Ceara (CE),
+# Maranhao (MA), Paraiba (PR), Pernambuco (PE), Piaui (PI), and Rio Grande do
+# Norte (RN), and the eastern part of Para (PA) are all in BR1 without DST.
+
+# From Marcos Tadeu (1998-09-27):
+# <a href="http://pcdsh01.on.br/verao1.html">
+# Brazilian official page
+# </a>
+
+# From Jesper Norgaard (2000-11-03):
+# [For an official list of which regions in Brazil use which time zones, see:]
+# http://pcdsh01.on.br/Fusbr.htm
+# http://pcdsh01.on.br/Fusbrhv.htm
+
+# From Celso Doria via David Madeo (2002-10-09):
+# The reason for the delay this year has to do with elections in Brazil.
+#
+# Unlike in the United States, elections in Brazil are 100% computerized and
+# the results are known almost immediately. Yesterday, it was the first
+# round of the elections when 115 million Brazilians voted for President,
+# Governor, Senators, Federal Deputies, and State Deputies. Nobody is
+# counting (or re-counting) votes anymore and we know there will be a second
+# round for the Presidency and also for some Governors. The 2nd round will
+# take place on October 27th.
+#
+# The reason why the DST will only begin November 3rd is that the thousands
+# of electoral machines used cannot have their time changed, and since the
+# Constitution says the elections must begin at 8:00 AM and end at 5:00 PM,
+# the Government decided to postpone DST, instead of changing the Constitution
+# (maybe, for the next elections, it will be possible to change the clock)...
+
+# From Rodrigo Severo (2004-10-04):
+# It's just the biannual change made necessary by the much hyped, supposedly
+# modern Brazilian eletronic voting machines which, apparently, can't deal
+# with a time change between the first and the second rounds of the elections.
+
+# From Steffen Thorsen (2007-09-20):
+# Brazil will start DST on 2007-10-14 00:00 and end on 2008-02-17 00:00:
+# http://www.mme.gov.br/site/news/detail.do;jsessionid=BBA06811AFCAAC28F0285210913513DA?newsId=13975
+
+# From Paul Schulze (2008-06-24):
+# ...by law number 11.662 of April 24, 2008 (published in the "Diario
+# Oficial da Uniao"...) in Brazil there are changes in the timezones,
+# effective today (00:00am at June 24, 2008) as follows:
+#
+# a) The timezone UTC+5 is e[x]tinguished, with all the Acre state and the
+# part of the Amazonas state that had this timezone now being put to the
+# timezone UTC+4
+# b) The whole Para state now is put at timezone UTC+3, instead of just
+# part of it, as was before.
+#
+# This change follows a proposal of senator Tiao Viana of Acre state, that
+# proposed it due to concerns about open television channels displaying
+# programs inappropriate to youths in the states that had the timezone
+# UTC+5 too early in the night. In the occasion, some more corrections
+# were proposed, trying to unify the timezones of any given state. This
+# change modifies timezone rules defined in decree 2.784 of 18 June,
+# 1913.
+
+# From Rodrigo Severo (2008-06-24):
+# Just correcting the URL:
+# <a href="https://www.in.gov.br/imprensa/visualiza/index.jsp?jornal=do&secao=1&pagina=1&data=25/04/2008">
+# https://www.in.gov.br/imprensa/visualiza/index.jsp?jornal=do&secao=1&pagina=1&data=25/04/2008
+# </a>
+#
+# As a result of the above Decree I believe the America/Rio_Branco
+# timezone shall be modified from UTC-5 to UTC-4 and a new timezone shall
+# be created to represent the...west side of the Para State. I
+# suggest this new timezone be called Santarem as the most
+# important/populated city in the affected area.
+#
+# This new timezone would be the same as the Rio_Branco timezone up to
+# the 2008/06/24 change which would be to UTC-3 instead of UTC-4.
+
+# From Alex Krivenyshev (2008-06-24):
+# This is a quick reference page for New and Old Brazil Time Zones map.
+# <a href="http://www.worldtimezone.com/brazil-time-new-old.php">
+# http://www.worldtimezone.com/brazil-time-new-old.php
+# </a>
+#
+# - 4 time zones replaced by 3 time zones-eliminating time zone UTC- 05
+# (state Acre and the part of the Amazonas will be UTC/GMT- 04) - western
+# part of Par state is moving to one timezone UTC- 03 (from UTC -04).
+
+# From Paul Eggert (2002-10-10):
+# The official decrees referenced below are mostly taken from
+# <a href="http://pcdsh01.on.br/DecHV.html">
+# Decretos sobre o Horario de Verao no Brasil
+# </a>.
+
+# From Steffen Thorsen (2008-08-29):
+# As announced by the government and many newspapers in Brazil late
+# yesterday, Brazil will start DST on 2008-10-19 (need to change rule) and
+# it will end on 2009-02-15 (current rule for Brazil is fine). Based on
+# past years experience with the elections, there was a good chance that
+# the start was postponed to November, but it did not happen this year.
+#
+# It has not yet been posted to http://pcdsh01.on.br/DecHV.html
+#
+# An official page about it:
+# <a href="http://www.mme.gov.br/site/news/detail.do?newsId=16722">
+# http://www.mme.gov.br/site/news/detail.do?newsId=16722
+# </a>
+# Note that this link does not always work directly, but must be accessed
+# by going to
+# <a href="http://www.mme.gov.br/first">
+# http://www.mme.gov.br/first
+# </a>
+#
+# One example link that works directly:
+# <a href="http://jornale.com.br/index.php?option=com_content&task=view&id=13530&Itemid=54">
+# http://jornale.com.br/index.php?option=com_content&task=view&id=13530&Itemid=54
+# (Portuguese)
+# </a>
+#
+# We have a written a short article about it as well:
+# <a href="http://www.timeanddate.com/news/time/brazil-dst-2008-2009.html">
+# http://www.timeanddate.com/news/time/brazil-dst-2008-2009.html
+# </a>
+#
+# From Alexander Krivenyshev (2011-10-04):
+# State Bahia will return to Daylight savings time this year after 8 years off.
+# The announcement was made by Governor Jaques Wagner in an interview to a
+# television station in Salvador.
+
+# In Portuguese:
+# <a href="http://g1.globo.com/bahia/noticia/2011/10/governador-jaques-wagner-confirma-horario-de-verao-na-bahia.html">
+# http://g1.globo.com/bahia/noticia/2011/10/governador-jaques-wagner-confirma-horario-de-verao-na-bahia.html
+# </a> and
+# <a href="http://noticias.terra.com.br/brasil/noticias/0,,OI5390887-EI8139,00-Bahia+volta+a+ter+horario+de+verao+apos+oito+anos.html">
+# http://noticias.terra.com.br/brasil/noticias/0,,OI5390887-EI8139,00-Bahia+volta+a+ter+horario+de+verao+apos+oito+anos.html
+# </a>
+
+# From Guilherme Bernardes Rodrigues (2011-10-07):
+# There is news in the media, however there is still no decree about it.
+# I just send a e-mail to Zulmira Brandão at
+# <a href="http://pcdsh01.on.br/">http://pcdsh01.on.br/</a> the
+# oficial agency about time in Brazil, and she confirmed that the old rule is
+# still in force.
+
+# From Guilherme Bernardes Rodrigues (2011-10-14)
+# It's official, the President signed a decree that includes Bahia in summer
+# time.
+# [ and in a second message (same day): ]
+# I found the decree.
+#
+# DECRETO No- 7.584, DE 13 DE OUTUBRO DE 2011
+# Link :
+# <a href="http://www.in.gov.br/visualiza/index.jsp?data=13/10/2011&jornal=1000&pagina=6&totalArquivos=6">
+# http://www.in.gov.br/visualiza/index.jsp?data=13/10/2011&jornal=1000&pagina=6&totalArquivos=6
+# </a>
+
+# From Kelley Cook (2012-10-16):
+# The governor of state of Bahia in Brazil announced on Thursday that
+# due to public pressure, he is reversing the DST policy they implemented
+# last year and will not be going to Summer Time on October 21st....
+# http://www.correio24horas.com.br/r/artigo/apos-pressoes-wagner-suspende-horario-de-verao-na-bahia
+
+# From Rodrigo Severo (2012-10-16):
+# Tocantins state will have DST.
+# http://noticias.terra.com.br/brasil/noticias/0,,OI6232536-EI306.html
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+# Decree <a href="http://pcdsh01.on.br/HV20466.htm">20,466</a> (1931-10-01)
+# Decree <a href="http://pcdsh01.on.br/HV21896.htm">21,896</a> (1932-01-10)
+Rule Brazil 1931 only - Oct 3 11:00 1:00 S
+Rule Brazil 1932 1933 - Apr 1 0:00 0 -
+Rule Brazil 1932 only - Oct 3 0:00 1:00 S
+# Decree <a href="http://pcdsh01.on.br/HV23195.htm">23,195</a> (1933-10-10)
+# revoked DST.
+# Decree <a href="http://pcdsh01.on.br/HV27496.htm">27,496</a> (1949-11-24)
+# Decree <a href="http://pcdsh01.on.br/HV27998.htm">27,998</a> (1950-04-13)
+Rule Brazil 1949 1952 - Dec 1 0:00 1:00 S
+Rule Brazil 1950 only - Apr 16 1:00 0 -
+Rule Brazil 1951 1952 - Apr 1 0:00 0 -
+# Decree <a href="http://pcdsh01.on.br/HV32308.htm">32,308</a> (1953-02-24)
+Rule Brazil 1953 only - Mar 1 0:00 0 -
+# Decree <a href="http://pcdsh01.on.br/HV34724.htm">34,724</a> (1953-11-30)
+# revoked DST.
+# Decree <a href="http://pcdsh01.on.br/HV52700.htm">52,700</a> (1963-10-18)
+# established DST from 1963-10-23 00:00 to 1964-02-29 00:00
+# in SP, RJ, GB, MG, ES, due to the prolongation of the drought.
+# Decree <a href="http://pcdsh01.on.br/HV53071.htm">53,071</a> (1963-12-03)
+# extended the above decree to all of the national territory on 12-09.
+Rule Brazil 1963 only - Dec 9 0:00 1:00 S
+# Decree <a href="http://pcdsh01.on.br/HV53604.htm">53,604</a> (1964-02-25)
+# extended summer time by one day to 1964-03-01 00:00 (start of school).
+Rule Brazil 1964 only - Mar 1 0:00 0 -
+# Decree <a href="http://pcdsh01.on.br/HV55639.htm">55,639</a> (1965-01-27)
+Rule Brazil 1965 only - Jan 31 0:00 1:00 S
+Rule Brazil 1965 only - Mar 31 0:00 0 -
+# Decree <a href="http://pcdsh01.on.br/HV57303.htm">57,303</a> (1965-11-22)
+Rule Brazil 1965 only - Dec 1 0:00 1:00 S
+# Decree <a href="http://pcdsh01.on.br/HV57843.htm">57,843</a> (1966-02-18)
+Rule Brazil 1966 1968 - Mar 1 0:00 0 -
+Rule Brazil 1966 1967 - Nov 1 0:00 1:00 S
+# Decree <a href="http://pcdsh01.on.br/HV63429.htm">63,429</a> (1968-10-15)
+# revoked DST.
+# Decree <a href="http://pcdsh01.on.br/HV91698.htm">91,698</a> (1985-09-27)
+Rule Brazil 1985 only - Nov 2 0:00 1:00 S
+# Decree 92,310 (1986-01-21)
+# Decree 92,463 (1986-03-13)
+Rule Brazil 1986 only - Mar 15 0:00 0 -
+# Decree 93,316 (1986-10-01)
+Rule Brazil 1986 only - Oct 25 0:00 1:00 S
+Rule Brazil 1987 only - Feb 14 0:00 0 -
+# Decree <a href="http://pcdsh01.on.br/HV94922.htm">94,922</a> (1987-09-22)
+Rule Brazil 1987 only - Oct 25 0:00 1:00 S
+Rule Brazil 1988 only - Feb 7 0:00 0 -
+# Decree <a href="http://pcdsh01.on.br/HV96676.htm">96,676</a> (1988-09-12)
+# except for the states of AC, AM, PA, RR, RO, and AP (then a territory)
+Rule Brazil 1988 only - Oct 16 0:00 1:00 S
+Rule Brazil 1989 only - Jan 29 0:00 0 -
+# Decree <a href="http://pcdsh01.on.br/HV98077.htm">98,077</a> (1989-08-21)
+# with the same exceptions
+Rule Brazil 1989 only - Oct 15 0:00 1:00 S
+Rule Brazil 1990 only - Feb 11 0:00 0 -
+# Decree <a href="http://pcdsh01.on.br/HV99530.htm">99,530</a> (1990-09-17)
+# adopted by RS, SC, PR, SP, RJ, ES, MG, GO, MS, DF.
+# Decree 99,629 (1990-10-19) adds BA, MT.
+Rule Brazil 1990 only - Oct 21 0:00 1:00 S
+Rule Brazil 1991 only - Feb 17 0:00 0 -
+# <a href="http://pcdsh01.on.br/HV1991.htm">Unnumbered decree</a> (1991-09-25)
+# adopted by RS, SC, PR, SP, RJ, ES, MG, BA, GO, MT, MS, DF.
+Rule Brazil 1991 only - Oct 20 0:00 1:00 S
+Rule Brazil 1992 only - Feb 9 0:00 0 -
+# <a href="http://pcdsh01.on.br/HV1992.htm">Unnumbered decree</a> (1992-10-16)
+# adopted by same states.
+Rule Brazil 1992 only - Oct 25 0:00 1:00 S
+Rule Brazil 1993 only - Jan 31 0:00 0 -
+# Decree <a href="http://pcdsh01.on.br/HV942.htm">942</a> (1993-09-28)
+# adopted by same states, plus AM.
+# Decree <a href="http://pcdsh01.on.br/HV1252.htm">1,252</a> (1994-09-22;
+# web page corrected 2004-01-07) adopted by same states, minus AM.
+# Decree <a href="http://pcdsh01.on.br/HV1636.htm">1,636</a> (1995-09-14)
+# adopted by same states, plus MT and TO.
+# Decree <a href="http://pcdsh01.on.br/HV1674.htm">1,674</a> (1995-10-13)
+# adds AL, SE.
+Rule Brazil 1993 1995 - Oct Sun>=11 0:00 1:00 S
+Rule Brazil 1994 1995 - Feb Sun>=15 0:00 0 -
+Rule Brazil 1996 only - Feb 11 0:00 0 -
+# Decree <a href="http://pcdsh01.on.br/HV2000.htm">2,000</a> (1996-09-04)
+# adopted by same states, minus AL, SE.
+Rule Brazil 1996 only - Oct 6 0:00 1:00 S
+Rule Brazil 1997 only - Feb 16 0:00 0 -
+# From Daniel C. Sobral (1998-02-12):
+# In 1997, the DS began on October 6. The stated reason was that
+# because international television networks ignored Brazil's policy on DS,
+# they bought the wrong times on satellite for coverage of Pope's visit.
+# This year, the ending date of DS was postponed to March 1
+# to help dealing with the shortages of electric power.
+#
+# Decree 2,317 (1997-09-04), adopted by same states.
+Rule Brazil 1997 only - Oct 6 0:00 1:00 S
+# Decree <a href="http://pcdsh01.on.br/figuras/HV2495.JPG">2,495</a>
+# (1998-02-10)
+Rule Brazil 1998 only - Mar 1 0:00 0 -
+# Decree <a href="http://pcdsh01.on.br/figuras/Hv98.jpg">2,780</a> (1998-09-11)
+# adopted by the same states as before.
+Rule Brazil 1998 only - Oct 11 0:00 1:00 S
+Rule Brazil 1999 only - Feb 21 0:00 0 -
+# Decree <a href="http://pcdsh01.on.br/figuras/HV3150.gif">3,150</a>
+# (1999-08-23) adopted by same states.
+# Decree <a href="http://pcdsh01.on.br/DecHV99.gif">3,188</a> (1999-09-30)
+# adds SE, AL, PB, PE, RN, CE, PI, MA and RR.
+Rule Brazil 1999 only - Oct 3 0:00 1:00 S
+Rule Brazil 2000 only - Feb 27 0:00 0 -
+# Decree <a href="http://pcdsh01.on.br/DEC3592.htm">3,592</a> (2000-09-06)
+# adopted by the same states as before.
+# Decree <a href="http://pcdsh01.on.br/Dec3630.jpg">3,630</a> (2000-10-13)
+# repeals DST in PE and RR, effective 2000-10-15 00:00.
+# Decree <a href="http://pcdsh01.on.br/Dec3632.jpg">3,632</a> (2000-10-17)
+# repeals DST in SE, AL, PB, RN, CE, PI and MA, effective 2000-10-22 00:00.
+# Decree <a href="http://pcdsh01.on.br/figuras/HV3916.gif">3,916</a>
+# (2001-09-13) reestablishes DST in AL, CE, MA, PB, PE, PI, RN, SE.
+Rule Brazil 2000 2001 - Oct Sun>=8 0:00 1:00 S
+Rule Brazil 2001 2006 - Feb Sun>=15 0:00 0 -
+# Decree 4,399 (2002-10-01) repeals DST in AL, CE, MA, PB, PE, PI, RN, SE.
+# <a href="http://www.presidencia.gov.br/CCIVIL/decreto/2002/D4399.htm">4,399</a>
+Rule Brazil 2002 only - Nov 3 0:00 1:00 S
+# Decree 4,844 (2003-09-24; corrected 2003-09-26) repeals DST in BA, MT, TO.
+# <a href="http://www.presidencia.gov.br/CCIVIL/decreto/2003/D4844.htm">4,844</a>
+Rule Brazil 2003 only - Oct 19 0:00 1:00 S
+# Decree 5,223 (2004-10-01) reestablishes DST in MT.
+# <a href="http://www.planalto.gov.br/ccivil_03/_Ato2004-2006/2004/Decreto/D5223.htm">5,223</a>
+Rule Brazil 2004 only - Nov 2 0:00 1:00 S
+# Decree <a href="http://pcdsh01.on.br/DecHV5539.gif">5,539</a> (2005-09-19),
+# adopted by the same states as before.
+Rule Brazil 2005 only - Oct 16 0:00 1:00 S
+# Decree <a href="http://pcdsh01.on.br/DecHV5920.gif">5,920</a> (2006-10-03),
+# adopted by the same states as before.
+Rule Brazil 2006 only - Nov 5 0:00 1:00 S
+Rule Brazil 2007 only - Feb 25 0:00 0 -
+# Decree <a href="http://pcdsh01.on.br/DecHV6212.gif">6,212</a> (2007-09-26),
+# adopted by the same states as before.
+Rule Brazil 2007 only - Oct Sun>=8 0:00 1:00 S
+# From Frederico A. C. Neves (2008-09-10):
+# Acording to this decree
+# <a href="http://www.planalto.gov.br/ccivil_03/_Ato2007-2010/2008/Decreto/D6558.htm">
+# http://www.planalto.gov.br/ccivil_03/_Ato2007-2010/2008/Decreto/D6558.htm
+# </a>
+# [t]he DST period in Brazil now on will be from the 3rd Oct Sunday to the
+# 3rd Feb Sunday. There is an exception on the return date when this is
+# the Carnival Sunday then the return date will be the next Sunday...
+Rule Brazil 2008 max - Oct Sun>=15 0:00 1:00 S
+Rule Brazil 2008 2011 - Feb Sun>=15 0:00 0 -
+Rule Brazil 2012 only - Feb Sun>=22 0:00 0 -
+Rule Brazil 2013 2014 - Feb Sun>=15 0:00 0 -
+Rule Brazil 2015 only - Feb Sun>=22 0:00 0 -
+Rule Brazil 2016 2022 - Feb Sun>=15 0:00 0 -
+Rule Brazil 2023 only - Feb Sun>=22 0:00 0 -
+Rule Brazil 2024 2025 - Feb Sun>=15 0:00 0 -
+Rule Brazil 2026 only - Feb Sun>=22 0:00 0 -
+Rule Brazil 2027 2033 - Feb Sun>=15 0:00 0 -
+Rule Brazil 2034 only - Feb Sun>=22 0:00 0 -
+Rule Brazil 2035 2036 - Feb Sun>=15 0:00 0 -
+Rule Brazil 2037 only - Feb Sun>=22 0:00 0 -
+# From Arthur David Olson (2008-09-29):
+# The next is wrong in some years but is better than nothing.
+Rule Brazil 2038 max - Feb Sun>=15 0:00 0 -
+
+# The latest ruleset listed above says that the following states observe DST:
+# DF, ES, GO, MG, MS, MT, PR, RJ, RS, SC, SP.
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+#
+# Fernando de Noronha (administratively part of PE)
+Zone America/Noronha -2:09:40 - LMT 1914
+ -2:00 Brazil FN%sT 1990 Sep 17
+ -2:00 - FNT 1999 Sep 30
+ -2:00 Brazil FN%sT 2000 Oct 15
+ -2:00 - FNT 2001 Sep 13
+ -2:00 Brazil FN%sT 2002 Oct 1
+ -2:00 - FNT
+# Other Atlantic islands have no permanent settlement.
+# These include Trindade and Martin Vaz (administratively part of ES),
+# Atol das Rocas (RN), and Penedos de Sao Pedro e Sao Paulo (PE).
+# Fernando de Noronha was a separate territory from 1942-09-02 to 1989-01-01;
+# it also included the Penedos.
+#
+# Amapa (AP), east Para (PA)
+# East Para includes Belem, Maraba, Serra Norte, and Sao Felix do Xingu.
+# The division between east and west Para is the river Xingu.
+# In the north a very small part from the river Javary (now Jari I guess,
+# the border with Amapa) to the Amazon, then to the Xingu.
+Zone America/Belem -3:13:56 - LMT 1914
+ -3:00 Brazil BR%sT 1988 Sep 12
+ -3:00 - BRT
+#
+# west Para (PA)
+# West Para includes Altamira, Oribidos, Prainha, Oriximina, and Santarem.
+Zone America/Santarem -3:38:48 - LMT 1914
+ -4:00 Brazil AM%sT 1988 Sep 12
+ -4:00 - AMT 2008 Jun 24 00:00
+ -3:00 - BRT
+#
+# Maranhao (MA), Piaui (PI), Ceara (CE), Rio Grande do Norte (RN),
+# Paraiba (PB)
+Zone America/Fortaleza -2:34:00 - LMT 1914
+ -3:00 Brazil BR%sT 1990 Sep 17
+ -3:00 - BRT 1999 Sep 30
+ -3:00 Brazil BR%sT 2000 Oct 22
+ -3:00 - BRT 2001 Sep 13
+ -3:00 Brazil BR%sT 2002 Oct 1
+ -3:00 - BRT
+#
+# Pernambuco (PE) (except Atlantic islands)
+Zone America/Recife -2:19:36 - LMT 1914
+ -3:00 Brazil BR%sT 1990 Sep 17
+ -3:00 - BRT 1999 Sep 30
+ -3:00 Brazil BR%sT 2000 Oct 15
+ -3:00 - BRT 2001 Sep 13
+ -3:00 Brazil BR%sT 2002 Oct 1
+ -3:00 - BRT
+#
+# Tocantins (TO)
+Zone America/Araguaina -3:12:48 - LMT 1914
+ -3:00 Brazil BR%sT 1990 Sep 17
+ -3:00 - BRT 1995 Sep 14
+ -3:00 Brazil BR%sT 2003 Sep 24
+ -3:00 - BRT 2012 Oct 21
+ -3:00 Brazil BR%sT
+#
+# Alagoas (AL), Sergipe (SE)
+Zone America/Maceio -2:22:52 - LMT 1914
+ -3:00 Brazil BR%sT 1990 Sep 17
+ -3:00 - BRT 1995 Oct 13
+ -3:00 Brazil BR%sT 1996 Sep 4
+ -3:00 - BRT 1999 Sep 30
+ -3:00 Brazil BR%sT 2000 Oct 22
+ -3:00 - BRT 2001 Sep 13
+ -3:00 Brazil BR%sT 2002 Oct 1
+ -3:00 - BRT
+#
+# Bahia (BA)
+# There are too many Salvadors elsewhere, so use America/Bahia instead
+# of America/Salvador.
+Zone America/Bahia -2:34:04 - LMT 1914
+ -3:00 Brazil BR%sT 2003 Sep 24
+ -3:00 - BRT 2011 Oct 16
+ -3:00 Brazil BR%sT 2012 Oct 21
+ -3:00 - BRT
+#
+# Goias (GO), Distrito Federal (DF), Minas Gerais (MG),
+# Espirito Santo (ES), Rio de Janeiro (RJ), Sao Paulo (SP), Parana (PR),
+# Santa Catarina (SC), Rio Grande do Sul (RS)
+Zone America/Sao_Paulo -3:06:28 - LMT 1914
+ -3:00 Brazil BR%sT 1963 Oct 23 00:00
+ -3:00 1:00 BRST 1964
+ -3:00 Brazil BR%sT
+#
+# Mato Grosso do Sul (MS)
+Zone America/Campo_Grande -3:38:28 - LMT 1914
+ -4:00 Brazil AM%sT
+#
+# Mato Grosso (MT)
+Zone America/Cuiaba -3:44:20 - LMT 1914
+ -4:00 Brazil AM%sT 2003 Sep 24
+ -4:00 - AMT 2004 Oct 1
+ -4:00 Brazil AM%sT
+#
+# Rondonia (RO)
+Zone America/Porto_Velho -4:15:36 - LMT 1914
+ -4:00 Brazil AM%sT 1988 Sep 12
+ -4:00 - AMT
+#
+# Roraima (RR)
+Zone America/Boa_Vista -4:02:40 - LMT 1914
+ -4:00 Brazil AM%sT 1988 Sep 12
+ -4:00 - AMT 1999 Sep 30
+ -4:00 Brazil AM%sT 2000 Oct 15
+ -4:00 - AMT
+#
+# east Amazonas (AM): Boca do Acre, Jutai, Manaus, Floriano Peixoto
+# The great circle line from Tabatinga to Porto Acre divides
+# east from west Amazonas.
+Zone America/Manaus -4:00:04 - LMT 1914
+ -4:00 Brazil AM%sT 1988 Sep 12
+ -4:00 - AMT 1993 Sep 28
+ -4:00 Brazil AM%sT 1994 Sep 22
+ -4:00 - AMT
+#
+# west Amazonas (AM): Atalaia do Norte, Boca do Maoco, Benjamin Constant,
+# Eirunepe, Envira, Ipixuna
+Zone America/Eirunepe -4:39:28 - LMT 1914
+ -5:00 Brazil AC%sT 1988 Sep 12
+ -5:00 - ACT 1993 Sep 28
+ -5:00 Brazil AC%sT 1994 Sep 22
+ -5:00 - ACT 2008 Jun 24 00:00
+ -4:00 - AMT
+#
+# Acre (AC)
+Zone America/Rio_Branco -4:31:12 - LMT 1914
+ -5:00 Brazil AC%sT 1988 Sep 12
+ -5:00 - ACT 2008 Jun 24 00:00
+ -4:00 - AMT
+
+# Chile
+
+# From Eduardo Krell (1995-10-19):
+# The law says to switch to DST at midnight [24:00] on the second SATURDAY
+# of October.... The law is the same for March and October.
+# (1998-09-29):
+# Because of the drought this year, the government decided to go into
+# DST earlier (saturday 9/26 at 24:00). This is a one-time change only ...
+# (unless there's another dry season next year, I guess).
+
+# From Julio I. Pacheco Troncoso (1999-03-18):
+# Because of the same drought, the government decided to end DST later,
+# on April 3, (one-time change).
+
+# From Oscar van Vlijmen (2006-10-08):
+# http://www.horaoficial.cl/cambio.htm
+
+# From Jesper Norgaard Welen (2006-10-08):
+# I think that there are some obvious mistakes in the suggested link
+# from Oscar van Vlijmen,... for instance entry 66 says that GMT-4
+# ended 1990-09-12 while entry 67 only begins GMT-3 at 1990-09-15
+# (they should have been 1990-09-15 and 1990-09-16 respectively), but
+# anyhow it clears up some doubts too.
+
+# From Paul Eggert (2006-12-27):
+# The following data for Chile and America/Santiago are from
+# <http://www.horaoficial.cl/horaof.htm> (2006-09-20), transcribed by
+# Jesper Norgaard Welen. The data for Pacific/Easter are from Shanks
+# & Pottenger, except with DST transitions after 1932 cloned from
+# America/Santiago. The pre-1980 Pacific/Easter data are dubious,
+# but we have no other source.
+
+# From German Poo-Caaman~o (2008-03-03):
+# Due to drought, Chile extends Daylight Time in three weeks. This
+# is one-time change (Saturday 3/29 at 24:00 for America/Santiago
+# and Saturday 3/29 at 22:00 for Pacific/Easter)
+# The Supreme Decree is located at
+# <a href="http://www.shoa.cl/servicios/supremo316.pdf">
+# http://www.shoa.cl/servicios/supremo316.pdf
+# </a>
+# and the instructions for 2008 are located in:
+# <a href="http://www.horaoficial.cl/cambio.htm">
+# http://www.horaoficial.cl/cambio.htm
+# </a>.
+
+# From Jose Miguel Garrido (2008-03-05):
+# ...
+# You could see the announces of the change on
+# <a href="http://www.shoa.cl/noticias/2008/04hora/hora.htm">
+# http://www.shoa.cl/noticias/2008/04hora/hora.htm
+# </a>.
+
+# From Angel Chiang (2010-03-04):
+# Subject: DST in Chile exceptionally extended to 3 April due to earthquake
+# <a href="http://www.gobiernodechile.cl/viewNoticia.aspx?idArticulo=30098">
+# http://www.gobiernodechile.cl/viewNoticia.aspx?idArticulo=30098
+# </a>
+# (in Spanish, last paragraph).
+#
+# This is breaking news. There should be more information available later.
+
+# From Arthur Daivd Olson (2010-03-06):
+# Angel Chiang's message confirmed by Julio Pacheco; Julio provided a patch.
+
+# From Glenn Eychaner (2011-03-02): [geychaner@mac.com]
+# It appears that the Chilean government has decided to postpone the
+# change from summer time to winter time again, by three weeks to April
+# 2nd:
+# <a href="http://www.emol.com/noticias/nacional/detalle/detallenoticias.asp?idnoticia=467651">
+# http://www.emol.com/noticias/nacional/detalle/detallenoticias.asp?idnoticia=467651
+# </a>
+#
+# This is not yet reflected in the offical "cambio de hora" site, but
+# probably will be soon:
+# <a href="http://www.horaoficial.cl/cambio.htm">
+# http://www.horaoficial.cl/cambio.htm
+# </a>
+
+# From Arthur David Olson (2011-03-02):
+# The emol.com article mentions a water shortage as the cause of the
+# postponement, which may mean that it's not a permanent change.
+
+# From Glenn Eychaner (2011-03-28):
+# The article:
+# <a href="http://diario.elmercurio.com/2011/03/28/_portada/_portada/noticias/7565897A-CA86-49E6-9E03-660B21A4883E.htm?id=3D{7565897A-CA86-49E6-9E03-660B21A4883E}">
+# http://diario.elmercurio.com/2011/03/28/_portada/_portada/noticias/7565897A-CA86-49E6-9E03-660B21A4883E.htm?id=3D{7565897A-CA86-49E6-9E03-660B21A4883E}
+# </a>
+#
+# In English:
+# Chile's clocks will go back an hour this year on the 7th of May instead
+# of this Saturday. They will go forward again the 3rd Saturday in
+# August, not in October as they have since 1968. This is a pilot plan
+# which will be reevaluated in 2012.
+
+# From Mauricio Parada (2012-02-22), translated by Glenn Eychaner (2012-02-23):
+# As stated in the website of the Chilean Energy Ministry
+# http://www.minenergia.cl/ministerio/noticias/generales/gobierno-anuncia-fechas-de-cambio-de.html
+# The Chilean Government has decided to postpone the entrance into winter time
+# (to leave DST) from March 11 2012 to April 28th 2012. The decision has not
+# been yet formalized but it will within the next days.
+# Quote from the website communication:
+#
+# 6. For the year 2012, the dates of entry into winter time will be as follows:
+# a. Saturday April 28, 2012, clocks should go back 60 minutes; that is, at
+# 23:59:59, instead of passing to 0:00, the time should be adjusted to be 23:00
+# of the same day.
+# b. Saturday, September 1, 2012, clocks should go forward 60 minutes; that is,
+# at 23:59:59, instead of passing to 0:00, the time should be adjusted to be
+# 01:00 on September 2.
+#
+# Note that...this is yet another "temporary" change that will be reevaluated
+# AGAIN in 2013.
+
+# NOTE: ChileAQ rules for Antarctic bases are stored separately in the
+# 'antarctica' file.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Chile 1927 1932 - Sep 1 0:00 1:00 S
+Rule Chile 1928 1932 - Apr 1 0:00 0 -
+Rule Chile 1942 only - Jun 1 4:00u 0 -
+Rule Chile 1942 only - Aug 1 5:00u 1:00 S
+Rule Chile 1946 only - Jul 15 4:00u 1:00 S
+Rule Chile 1946 only - Sep 1 3:00u 0:00 -
+Rule Chile 1947 only - Apr 1 4:00u 0 -
+Rule Chile 1968 only - Nov 3 4:00u 1:00 S
+Rule Chile 1969 only - Mar 30 3:00u 0 -
+Rule Chile 1969 only - Nov 23 4:00u 1:00 S
+Rule Chile 1970 only - Mar 29 3:00u 0 -
+Rule Chile 1971 only - Mar 14 3:00u 0 -
+Rule Chile 1970 1972 - Oct Sun>=9 4:00u 1:00 S
+Rule Chile 1972 1986 - Mar Sun>=9 3:00u 0 -
+Rule Chile 1973 only - Sep 30 4:00u 1:00 S
+Rule Chile 1974 1987 - Oct Sun>=9 4:00u 1:00 S
+Rule Chile 1987 only - Apr 12 3:00u 0 -
+Rule Chile 1988 1989 - Mar Sun>=9 3:00u 0 -
+Rule Chile 1988 only - Oct Sun>=1 4:00u 1:00 S
+Rule Chile 1989 only - Oct Sun>=9 4:00u 1:00 S
+Rule Chile 1990 only - Mar 18 3:00u 0 -
+Rule Chile 1990 only - Sep 16 4:00u 1:00 S
+Rule Chile 1991 1996 - Mar Sun>=9 3:00u 0 -
+Rule Chile 1991 1997 - Oct Sun>=9 4:00u 1:00 S
+Rule Chile 1997 only - Mar 30 3:00u 0 -
+Rule Chile 1998 only - Mar Sun>=9 3:00u 0 -
+Rule Chile 1998 only - Sep 27 4:00u 1:00 S
+Rule Chile 1999 only - Apr 4 3:00u 0 -
+Rule Chile 1999 2010 - Oct Sun>=9 4:00u 1:00 S
+Rule Chile 2000 2007 - Mar Sun>=9 3:00u 0 -
+# N.B.: the end of March 29 in Chile is March 30 in Universal time,
+# which is used below in specifying the transition.
+Rule Chile 2008 only - Mar 30 3:00u 0 -
+Rule Chile 2009 only - Mar Sun>=9 3:00u 0 -
+Rule Chile 2010 only - Apr Sun>=1 3:00u 0 -
+Rule Chile 2011 only - May Sun>=2 3:00u 0 -
+Rule Chile 2011 only - Aug Sun>=16 4:00u 1:00 S
+Rule Chile 2012 only - Apr Sun>=23 3:00u 0 -
+Rule Chile 2012 only - Sep Sun>=2 4:00u 1:00 S
+Rule Chile 2013 max - Mar Sun>=9 3:00u 0 -
+Rule Chile 2013 max - Oct Sun>=9 4:00u 1:00 S
+# IATA SSIM anomalies: (1992-02) says 1992-03-14;
+# (1996-09) says 1998-03-08. Ignore these.
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Santiago -4:42:46 - LMT 1890
+ -4:42:46 - SMT 1910 # Santiago Mean Time
+ -5:00 - CLT 1916 Jul 1 # Chile Time
+ -4:42:46 - SMT 1918 Sep 1 # Santiago Mean Time
+ -4:00 - CLT 1919 Jul 1 # Chile Time
+ -4:42:46 - SMT 1927 Sep 1 # Santiago Mean Time
+ -5:00 Chile CL%sT 1947 May 22 # Chile Time
+ -4:00 Chile CL%sT
+Zone Pacific/Easter -7:17:44 - LMT 1890
+ -7:17:28 - EMT 1932 Sep # Easter Mean Time
+ -7:00 Chile EAS%sT 1982 Mar 13 21:00 # Easter I Time
+ -6:00 Chile EAS%sT
+#
+# Sala y Gomez Island is like Pacific/Easter.
+# Other Chilean locations, including Juan Fernandez Is, San Ambrosio,
+# San Felix, and Antarctic bases, are like America/Santiago.
+
+# Colombia
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule CO 1992 only - May 3 0:00 1:00 S
+Rule CO 1993 only - Apr 4 0:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Bogota -4:56:20 - LMT 1884 Mar 13
+ -4:56:20 - BMT 1914 Nov 23 # Bogota Mean Time
+ -5:00 CO CO%sT # Colombia Time
+# Malpelo, Providencia, San Andres
+# no information; probably like America/Bogota
+
+# Curacao
+#
+# From Paul Eggert (2006-03-22):
+# Shanks & Pottenger say that The Bottom and Philipsburg have been at
+# -4:00 since standard time was introduced on 1912-03-02; and that
+# Kralendijk and Rincon used Kralendijk Mean Time (-4:33:08) from
+# 1912-02-02 to 1965-01-01. The former is dubious, since S&P also say
+# Saba Island has been like Curacao.
+# This all predates our 1970 cutoff, though.
+#
+# By July 2007 Curacao and St Maarten are planned to become
+# associated states within the Netherlands, much like Aruba;
+# Bonaire, Saba and St Eustatius would become directly part of the
+# Netherlands as Kingdom Islands. This won't affect their time zones
+# though, as far as we know.
+#
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Curacao -4:35:44 - LMT 1912 Feb 12 # Willemstad
+ -4:30 - ANT 1965 # Netherlands Antilles Time
+ -4:00 - AST
+
+# From Arthur David Olson (2011-06-15):
+# At least for now, use links for places with new iso3166 codes.
+# The name "Lower Prince's Quarter" is both longer than fourteen charaters
+# and contains an apostrophe; use "Lower_Princes" below.
+
+Link America/Curacao America/Lower_Princes # Sint Maarten
+Link America/Curacao America/Kralendijk # Bonaire, Sint Estatius and Saba
+
+# Ecuador
+#
+# From Paul Eggert (2007-03-04):
+# Apparently Ecuador had a failed experiment with DST in 1992.
+# <http://midena.gov.ec/content/view/1261/208/> (2007-02-27) and
+# <http://www.hoy.com.ec/NoticiaNue.asp?row_id=249856> (2006-11-06) both
+# talk about "hora Sixto". Leave this alone for now, as we have no data.
+#
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Guayaquil -5:19:20 - LMT 1890
+ -5:14:00 - QMT 1931 # Quito Mean Time
+ -5:00 - ECT # Ecuador Time
+Zone Pacific/Galapagos -5:58:24 - LMT 1931 # Puerto Baquerizo Moreno
+ -5:00 - ECT 1986
+ -6:00 - GALT # Galapagos Time
+
+# Falklands
+
+# From Paul Eggert (2006-03-22):
+# Between 1990 and 2000 inclusive, Shanks & Pottenger and the IATA agree except
+# the IATA gives 1996-09-08. Go with Shanks & Pottenger.
+
+# From Falkland Islands Government Office, London (2001-01-22)
+# via Jesper Norgaard:
+# ... the clocks revert back to Local Mean Time at 2 am on Sunday 15
+# April 2001 and advance one hour to summer time at 2 am on Sunday 2
+# September. It is anticipated that the clocks will revert back at 2
+# am on Sunday 21 April 2002 and advance to summer time at 2 am on
+# Sunday 1 September.
+
+# From Rives McDow (2001-02-13):
+#
+# I have communicated several times with people there, and the last
+# time I had communications that was helpful was in 1998. Here is
+# what was said then:
+#
+# "The general rule was that Stanley used daylight saving and the Camp
+# did not. However for various reasons many people in the Camp have
+# started to use daylight saving (known locally as 'Stanley Time')
+# There is no rule as to who uses daylight saving - it is a matter of
+# personal choice and so it is impossible to draw a map showing who
+# uses it and who does not. Any list would be out of date as soon as
+# it was produced. This year daylight saving ended on April 18/19th
+# and started again on September 12/13th. I do not know what the rule
+# is, but can find out if you like. We do not change at the same time
+# as UK or Chile."
+#
+# I did have in my notes that the rule was "Second Saturday in Sep at
+# 0:00 until third Saturday in Apr at 0:00". I think that this does
+# not agree in some cases with Shanks; is this true?
+#
+# Also, there is no mention in the list that some areas in the
+# Falklands do not use DST. I have found in my communications there
+# that these areas are on the western half of East Falkland and all of
+# West Falkland. Stanley is the only place that consistently observes
+# DST. Again, as in other places in the world, the farmers don't like
+# it. West Falkland is almost entirely sheep farmers.
+#
+# I know one lady there that keeps a list of which farm keeps DST and
+# which doesn't each year. She runs a shop in Stanley, and says that
+# the list changes each year. She uses it to communicate to her
+# customers, catching them when they are home for lunch or dinner.
+
+# From Paul Eggert (2001-03-05):
+# For now, we'll just record the time in Stanley, since we have no
+# better info.
+
+# From Steffen Thorsen (2011-04-01):
+# The Falkland Islands will not turn back clocks this winter, but stay on
+# daylight saving time.
+#
+# One source:
+# <a href="http://www.falklandnews.com/public/story.cfm?get=5914&source=3">
+# http://www.falklandnews.com/public/story.cfm?get=5914&source=3
+# </a>
+#
+# We have gotten this confirmed by a clerk of the legislative assembly:
+# Normally the clocks revert to Local Mean Time (UTC/GMT -4 hours) on the
+# third Sunday of April at 0200hrs and advance to Summer Time (UTC/GMT -3
+# hours) on the first Sunday of September at 0200hrs.
+#
+# IMPORTANT NOTE: During 2011, on a trial basis, the Falkland Islands
+# will not revert to local mean time, but clocks will remain on Summer
+# time (UTC/GMT - 3 hours) throughout the whole of 2011. Any long term
+# change to local time following the trial period will be notified.
+#
+# From Andrew Newman (2012-02-24)
+# A letter from Justin McPhee, Chief Executive,
+# Cable & Wireless Falkland Islands (dated 2012-02-22)
+# states...
+# The current Atlantic/Stanley entry under South America expects the
+# clocks to go back to standard Falklands Time (FKT) on the 15th April.
+# The database entry states that in 2011 Stanley was staying on fixed
+# summer time on a trial basis only. FIG need to contact IANA and/or
+# the maintainers of the database to inform them we're adopting
+# the same policy this year and suggest recommendations for future years.
+#
+# For now we will assume permanent summer time for the Falklands
+# until advised differently (to apply for 2012 and beyond, after the 2011
+# experiment was apparently successful.)
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Falk 1937 1938 - Sep lastSun 0:00 1:00 S
+Rule Falk 1938 1942 - Mar Sun>=19 0:00 0 -
+Rule Falk 1939 only - Oct 1 0:00 1:00 S
+Rule Falk 1940 1942 - Sep lastSun 0:00 1:00 S
+Rule Falk 1943 only - Jan 1 0:00 0 -
+Rule Falk 1983 only - Sep lastSun 0:00 1:00 S
+Rule Falk 1984 1985 - Apr lastSun 0:00 0 -
+Rule Falk 1984 only - Sep 16 0:00 1:00 S
+Rule Falk 1985 2000 - Sep Sun>=9 0:00 1:00 S
+Rule Falk 1986 2000 - Apr Sun>=16 0:00 0 -
+Rule Falk 2001 2010 - Apr Sun>=15 2:00 0 -
+Rule Falk 2001 2010 - Sep Sun>=1 2:00 1:00 S
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Atlantic/Stanley -3:51:24 - LMT 1890
+ -3:51:24 - SMT 1912 Mar 12 # Stanley Mean Time
+ -4:00 Falk FK%sT 1983 May # Falkland Is Time
+ -3:00 Falk FK%sT 1985 Sep 15
+ -4:00 Falk FK%sT 2010 Sep 5 02:00
+ -3:00 - FKST
+
+# French Guiana
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Cayenne -3:29:20 - LMT 1911 Jul
+ -4:00 - GFT 1967 Oct # French Guiana Time
+ -3:00 - GFT
+
+# Guyana
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Guyana -3:52:40 - LMT 1915 Mar # Georgetown
+ -3:45 - GBGT 1966 May 26 # Br Guiana Time
+ -3:45 - GYT 1975 Jul 31 # Guyana Time
+ -3:00 - GYT 1991
+# IATA SSIM (1996-06) says -4:00. Assume a 1991 switch.
+ -4:00 - GYT
+
+# Paraguay
+# From Paul Eggert (2006-03-22):
+# Shanks & Pottenger say that spring transitions are from 01:00 -> 02:00,
+# and autumn transitions are from 00:00 -> 23:00. Go with pre-1999
+# editions of Shanks, and with the IATA, who say transitions occur at 00:00.
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Para 1975 1988 - Oct 1 0:00 1:00 S
+Rule Para 1975 1978 - Mar 1 0:00 0 -
+Rule Para 1979 1991 - Apr 1 0:00 0 -
+Rule Para 1989 only - Oct 22 0:00 1:00 S
+Rule Para 1990 only - Oct 1 0:00 1:00 S
+Rule Para 1991 only - Oct 6 0:00 1:00 S
+Rule Para 1992 only - Mar 1 0:00 0 -
+Rule Para 1992 only - Oct 5 0:00 1:00 S
+Rule Para 1993 only - Mar 31 0:00 0 -
+Rule Para 1993 1995 - Oct 1 0:00 1:00 S
+Rule Para 1994 1995 - Feb lastSun 0:00 0 -
+Rule Para 1996 only - Mar 1 0:00 0 -
+# IATA SSIM (2000-02) says 1999-10-10; ignore this for now.
+# From Steffen Thorsen (2000-10-02):
+# I have three independent reports that Paraguay changed to DST this Sunday
+# (10-01).
+#
+# Translated by Gwillim Law (2001-02-27) from
+# <a href="http://www.diarionoticias.com.py/011000/nacional/naciona1.htm">
+# Noticias, a daily paper in Asuncion, Paraguay (2000-10-01)
+# </a>:
+# Starting at 0:00 today, the clock will be set forward 60 minutes, in
+# fulfillment of Decree No. 7,273 of the Executive Power.... The time change
+# system has been operating for several years. Formerly there was a separate
+# decree each year; the new law has the same effect, but permanently. Every
+# year, the time will change on the first Sunday of October; likewise, the
+# clock will be set back on the first Sunday of March.
+#
+Rule Para 1996 2001 - Oct Sun>=1 0:00 1:00 S
+# IATA SSIM (1997-09) says Mar 1; go with Shanks & Pottenger.
+Rule Para 1997 only - Feb lastSun 0:00 0 -
+# Shanks & Pottenger say 1999-02-28; IATA SSIM (1999-02) says 1999-02-27, but
+# (1999-09) reports no date; go with above sources and Gerd Knops (2001-02-27).
+Rule Para 1998 2001 - Mar Sun>=1 0:00 0 -
+# From Rives McDow (2002-02-28):
+# A decree was issued in Paraguay (no. 16350) on 2002-02-26 that changed the
+# dst method to be from the first Sunday in September to the first Sunday in
+# April.
+Rule Para 2002 2004 - Apr Sun>=1 0:00 0 -
+Rule Para 2002 2003 - Sep Sun>=1 0:00 1:00 S
+#
+# From Jesper Norgaard Welen (2005-01-02):
+# There are several sources that claim that Paraguay made
+# a timezone rule change in autumn 2004.
+# From Steffen Thorsen (2005-01-05):
+# Decree 1,867 (2004-03-05)
+# From Carlos Raul Perasso via Jesper Norgaard Welen (2006-10-13)
+# <http://www.presidencia.gov.py/decretos/D1867.pdf>
+Rule Para 2004 2009 - Oct Sun>=15 0:00 1:00 S
+Rule Para 2005 2009 - Mar Sun>=8 0:00 0 -
+# From Carlos Raul Perasso (2010-02-18):
+# By decree number 3958 issued yesterday (
+# <a href="http://www.presidencia.gov.py/v1/wp-content/uploads/2010/02/decreto3958.pdf">
+# http://www.presidencia.gov.py/v1/wp-content/uploads/2010/02/decreto3958.pdf
+# </a>
+# )
+# Paraguay changes its DST schedule, postponing the March rule to April and
+# modifying the October date. The decree reads:
+# ...
+# Art. 1. It is hereby established that from the second Sunday of the month of
+# April of this year (2010), the official time is to be set back 60 minutes,
+# and that on the first Sunday of the month of October, it is to be set
+# forward 60 minutes, in all the territory of the Paraguayan Republic.
+# ...
+Rule Para 2010 max - Oct Sun>=1 0:00 1:00 S
+Rule Para 2010 max - Apr Sun>=8 0:00 0 -
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Asuncion -3:50:40 - LMT 1890
+ -3:50:40 - AMT 1931 Oct 10 # Asuncion Mean Time
+ -4:00 - PYT 1972 Oct # Paraguay Time
+ -3:00 - PYT 1974 Apr
+ -4:00 Para PY%sT
+
+# Peru
+#
+# <a href="news:xrGmb.39935$gA1.13896113@news4.srv.hcvlny.cv.net">
+# From Evelyn C. Leeper via Mark Brader (2003-10-26):</a>
+# When we were in Peru in 1985-1986, they apparently switched over
+# sometime between December 29 and January 3 while we were on the Amazon.
+#
+# From Paul Eggert (2006-03-22):
+# Shanks & Pottenger don't have this transition. Assume 1986 was like 1987.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule Peru 1938 only - Jan 1 0:00 1:00 S
+Rule Peru 1938 only - Apr 1 0:00 0 -
+Rule Peru 1938 1939 - Sep lastSun 0:00 1:00 S
+Rule Peru 1939 1940 - Mar Sun>=24 0:00 0 -
+Rule Peru 1986 1987 - Jan 1 0:00 1:00 S
+Rule Peru 1986 1987 - Apr 1 0:00 0 -
+Rule Peru 1990 only - Jan 1 0:00 1:00 S
+Rule Peru 1990 only - Apr 1 0:00 0 -
+# IATA is ambiguous for 1993/1995; go with Shanks & Pottenger.
+Rule Peru 1994 only - Jan 1 0:00 1:00 S
+Rule Peru 1994 only - Apr 1 0:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Lima -5:08:12 - LMT 1890
+ -5:08:36 - LMT 1908 Jul 28 # Lima Mean Time?
+ -5:00 Peru PE%sT # Peru Time
+
+# South Georgia
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone Atlantic/South_Georgia -2:26:08 - LMT 1890 # Grytviken
+ -2:00 - GST # South Georgia Time
+
+# South Sandwich Is
+# uninhabited; scientific personnel have wintered
+
+# Suriname
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Paramaribo -3:40:40 - LMT 1911
+ -3:40:52 - PMT 1935 # Paramaribo Mean Time
+ -3:40:36 - PMT 1945 Oct # The capital moved?
+ -3:30 - NEGT 1975 Nov 20 # Dutch Guiana Time
+ -3:30 - SRT 1984 Oct # Suriname Time
+ -3:00 - SRT
+
+# Trinidad and Tobago
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Port_of_Spain -4:06:04 - LMT 1912 Mar 2
+ -4:00 - AST
+
+# Uruguay
+# From Paul Eggert (1993-11-18):
+# Uruguay wins the prize for the strangest peacetime manipulation of the rules.
+# From Shanks & Pottenger:
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+# Whitman gives 1923 Oct 1; go with Shanks & Pottenger.
+Rule Uruguay 1923 only - Oct 2 0:00 0:30 HS
+Rule Uruguay 1924 1926 - Apr 1 0:00 0 -
+Rule Uruguay 1924 1925 - Oct 1 0:00 0:30 HS
+Rule Uruguay 1933 1935 - Oct lastSun 0:00 0:30 HS
+# Shanks & Pottenger give 1935 Apr 1 0:00 & 1936 Mar 30 0:00; go with Whitman.
+Rule Uruguay 1934 1936 - Mar Sat>=25 23:30s 0 -
+Rule Uruguay 1936 only - Nov 1 0:00 0:30 HS
+Rule Uruguay 1937 1941 - Mar lastSun 0:00 0 -
+# Whitman gives 1937 Oct 3; go with Shanks & Pottenger.
+Rule Uruguay 1937 1940 - Oct lastSun 0:00 0:30 HS
+# Whitman gives 1941 Oct 24 - 1942 Mar 27, 1942 Dec 14 - 1943 Apr 13,
+# and 1943 Apr 13 ``to present time''; go with Shanks & Pottenger.
+Rule Uruguay 1941 only - Aug 1 0:00 0:30 HS
+Rule Uruguay 1942 only - Jan 1 0:00 0 -
+Rule Uruguay 1942 only - Dec 14 0:00 1:00 S
+Rule Uruguay 1943 only - Mar 14 0:00 0 -
+Rule Uruguay 1959 only - May 24 0:00 1:00 S
+Rule Uruguay 1959 only - Nov 15 0:00 0 -
+Rule Uruguay 1960 only - Jan 17 0:00 1:00 S
+Rule Uruguay 1960 only - Mar 6 0:00 0 -
+Rule Uruguay 1965 1967 - Apr Sun>=1 0:00 1:00 S
+Rule Uruguay 1965 only - Sep 26 0:00 0 -
+Rule Uruguay 1966 1967 - Oct 31 0:00 0 -
+Rule Uruguay 1968 1970 - May 27 0:00 0:30 HS
+Rule Uruguay 1968 1970 - Dec 2 0:00 0 -
+Rule Uruguay 1972 only - Apr 24 0:00 1:00 S
+Rule Uruguay 1972 only - Aug 15 0:00 0 -
+Rule Uruguay 1974 only - Mar 10 0:00 0:30 HS
+Rule Uruguay 1974 only - Dec 22 0:00 1:00 S
+Rule Uruguay 1976 only - Oct 1 0:00 0 -
+Rule Uruguay 1977 only - Dec 4 0:00 1:00 S
+Rule Uruguay 1978 only - Apr 1 0:00 0 -
+Rule Uruguay 1979 only - Oct 1 0:00 1:00 S
+Rule Uruguay 1980 only - May 1 0:00 0 -
+Rule Uruguay 1987 only - Dec 14 0:00 1:00 S
+Rule Uruguay 1988 only - Mar 14 0:00 0 -
+Rule Uruguay 1988 only - Dec 11 0:00 1:00 S
+Rule Uruguay 1989 only - Mar 12 0:00 0 -
+Rule Uruguay 1989 only - Oct 29 0:00 1:00 S
+# Shanks & Pottenger say no DST was observed in 1990/1 and 1991/2,
+# and that 1992/3's DST was from 10-25 to 03-01. Go with IATA.
+Rule Uruguay 1990 1992 - Mar Sun>=1 0:00 0 -
+Rule Uruguay 1990 1991 - Oct Sun>=21 0:00 1:00 S
+Rule Uruguay 1992 only - Oct 18 0:00 1:00 S
+Rule Uruguay 1993 only - Feb 28 0:00 0 -
+# From Eduardo Cota (2004-09-20):
+# The uruguayan government has decreed a change in the local time....
+# http://www.presidencia.gub.uy/decretos/2004091502.htm
+Rule Uruguay 2004 only - Sep 19 0:00 1:00 S
+# From Steffen Thorsen (2005-03-11):
+# Uruguay's DST was scheduled to end on Sunday, 2005-03-13, but in order to
+# save energy ... it was postponed two weeks....
+# http://www.presidencia.gub.uy/_Web/noticias/2005/03/2005031005.htm
+Rule Uruguay 2005 only - Mar 27 2:00 0 -
+# From Eduardo Cota (2005-09-27):
+# http://www.presidencia.gub.uy/_Web/decretos/2005/09/CM%20119_09%2009%202005_00001.PDF
+# This means that from 2005-10-09 at 02:00 local time, until 2006-03-12 at
+# 02:00 local time, official time in Uruguay will be at GMT -2.
+Rule Uruguay 2005 only - Oct 9 2:00 1:00 S
+Rule Uruguay 2006 only - Mar 12 2:00 0 -
+# From Jesper Norgaard Welen (2006-09-06):
+# http://www.presidencia.gub.uy/_web/decretos/2006/09/CM%20210_08%2006%202006_00001.PDF
+Rule Uruguay 2006 max - Oct Sun>=1 2:00 1:00 S
+Rule Uruguay 2007 max - Mar Sun>=8 2:00 0 -
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Montevideo -3:44:44 - LMT 1898 Jun 28
+ -3:44:44 - MMT 1920 May 1 # Montevideo MT
+ -3:30 Uruguay UY%sT 1942 Dec 14 # Uruguay Time
+ -3:00 Uruguay UY%sT
+
+# Venezuela
+#
+# From John Stainforth (2007-11-28):
+# ... the change for Venezuela originally expected for 2007-12-31 has
+# been brought forward to 2007-12-09. The official announcement was
+# published today in the "Gaceta Oficial de la Republica Bolivariana
+# de Venezuela, numero 38.819" (official document for all laws or
+# resolution publication)
+# http://www.globovision.com/news.php?nid=72208
+
+# Zone NAME GMTOFF RULES FORMAT [UNTIL]
+Zone America/Caracas -4:27:44 - LMT 1890
+ -4:27:40 - CMT 1912 Feb 12 # Caracas Mean Time?
+ -4:30 - VET 1965 # Venezuela Time
+ -4:00 - VET 2007 Dec 9 03:00
+ -4:30 - VET
diff --git a/misc/flot/examples/axes-time-zones/tz/systemv b/misc/flot/examples/axes-time-zones/tz/systemv
new file mode 100644
index 0000000..e651e85
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/systemv
@@ -0,0 +1,38 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# Old rules, should the need arise.
+# No attempt is made to handle Newfoundland, since it cannot be expressed
+# using the System V "TZ" scheme (half-hour offset), or anything outside
+# North America (no support for non-standard DST start/end dates), nor
+# the changes in the DST rules in the US after 1976 (which occurred after
+# the old rules were written).
+#
+# If you need the old rules, uncomment ## lines.
+# Compile this *without* leap second correction for true conformance.
+
+# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
+Rule SystemV min 1973 - Apr lastSun 2:00 1:00 D
+Rule SystemV min 1973 - Oct lastSun 2:00 0 S
+Rule SystemV 1974 only - Jan 6 2:00 1:00 D
+Rule SystemV 1974 only - Nov lastSun 2:00 0 S
+Rule SystemV 1975 only - Feb 23 2:00 1:00 D
+Rule SystemV 1975 only - Oct lastSun 2:00 0 S
+Rule SystemV 1976 max - Apr lastSun 2:00 1:00 D
+Rule SystemV 1976 max - Oct lastSun 2:00 0 S
+
+# Zone NAME GMTOFF RULES/SAVE FORMAT [UNTIL]
+## Zone SystemV/AST4ADT -4:00 SystemV A%sT
+## Zone SystemV/EST5EDT -5:00 SystemV E%sT
+## Zone SystemV/CST6CDT -6:00 SystemV C%sT
+## Zone SystemV/MST7MDT -7:00 SystemV M%sT
+## Zone SystemV/PST8PDT -8:00 SystemV P%sT
+## Zone SystemV/YST9YDT -9:00 SystemV Y%sT
+## Zone SystemV/AST4 -4:00 - AST
+## Zone SystemV/EST5 -5:00 - EST
+## Zone SystemV/CST6 -6:00 - CST
+## Zone SystemV/MST7 -7:00 - MST
+## Zone SystemV/PST8 -8:00 - PST
+## Zone SystemV/YST9 -9:00 - YST
+## Zone SystemV/HST10 -10:00 - HST
diff --git a/misc/flot/examples/axes-time-zones/tz/yearistype.sh b/misc/flot/examples/axes-time-zones/tz/yearistype.sh
new file mode 100644
index 0000000..bdc6e58
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/yearistype.sh
@@ -0,0 +1,38 @@
+#! /bin/sh
+
+: 'This file is in the public domain, so clarified as of'
+: '2006-07-17 by Arthur David Olson.'
+
+case $#-$1 in
+ 2-|2-0*|2-*[!0-9]*)
+ echo "$0: wild year - $1" >&2
+ exit 1 ;;
+esac
+
+case $#-$2 in
+ 2-even)
+ case $1 in
+ *[24680]) exit 0 ;;
+ *) exit 1 ;;
+ esac ;;
+ 2-nonpres|2-nonuspres)
+ case $1 in
+ *[02468][048]|*[13579][26]) exit 1 ;;
+ *) exit 0 ;;
+ esac ;;
+ 2-odd)
+ case $1 in
+ *[13579]) exit 0 ;;
+ *) exit 1 ;;
+ esac ;;
+ 2-uspres)
+ case $1 in
+ *[02468][048]|*[13579][26]) exit 0 ;;
+ *) exit 1 ;;
+ esac ;;
+ 2-*)
+ echo "$0: wild type - $2" >&2 ;;
+esac
+
+echo "$0: usage is $0 year even|odd|uspres|nonpres|nonuspres" >&2
+exit 1
diff --git a/misc/flot/examples/axes-time-zones/tz/zone.tab b/misc/flot/examples/axes-time-zones/tz/zone.tab
new file mode 100644
index 0000000..6bda826
--- /dev/null
+++ b/misc/flot/examples/axes-time-zones/tz/zone.tab
@@ -0,0 +1,441 @@
+# <pre>
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+#
+# TZ zone descriptions
+#
+# From Paul Eggert (1996-08-05):
+#
+# This file contains a table with the following columns:
+# 1. ISO 3166 2-character country code. See the file `iso3166.tab'.
+# 2. Latitude and longitude of the zone's principal location
+# in ISO 6709 sign-degrees-minutes-seconds format,
+# either +-DDMM+-DDDMM or +-DDMMSS+-DDDMMSS,
+# first latitude (+ is north), then longitude (+ is east).
+# 3. Zone name used in value of TZ environment variable.
+# 4. Comments; present if and only if the country has multiple rows.
+#
+# Columns are separated by a single tab.
+# The table is sorted first by country, then an order within the country that
+# (1) makes some geographical sense, and
+# (2) puts the most populous zones first, where that does not contradict (1).
+#
+# Lines beginning with `#' are comments.
+#
+#country-
+#code coordinates TZ comments
+AD +4230+00131 Europe/Andorra
+AE +2518+05518 Asia/Dubai
+AF +3431+06912 Asia/Kabul
+AG +1703-06148 America/Antigua
+AI +1812-06304 America/Anguilla
+AL +4120+01950 Europe/Tirane
+AM +4011+04430 Asia/Yerevan
+AO -0848+01314 Africa/Luanda
+AQ -7750+16636 Antarctica/McMurdo McMurdo Station, Ross Island
+AQ -9000+00000 Antarctica/South_Pole Amundsen-Scott Station, South Pole
+AQ -6734-06808 Antarctica/Rothera Rothera Station, Adelaide Island
+AQ -6448-06406 Antarctica/Palmer Palmer Station, Anvers Island
+AQ -6736+06253 Antarctica/Mawson Mawson Station, Holme Bay
+AQ -6835+07758 Antarctica/Davis Davis Station, Vestfold Hills
+AQ -6617+11031 Antarctica/Casey Casey Station, Bailey Peninsula
+AQ -7824+10654 Antarctica/Vostok Vostok Station, Lake Vostok
+AQ -6640+14001 Antarctica/DumontDUrville Dumont-d'Urville Station, Terre Adelie
+AQ -690022+0393524 Antarctica/Syowa Syowa Station, E Ongul I
+AQ -5430+15857 Antarctica/Macquarie Macquarie Island Station, Macquarie Island
+AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF)
+AR -3124-06411 America/Argentina/Cordoba most locations (CB, CC, CN, ER, FM, MN, SE, SF)
+AR -2447-06525 America/Argentina/Salta (SA, LP, NQ, RN)
+AR -2411-06518 America/Argentina/Jujuy Jujuy (JY)
+AR -2649-06513 America/Argentina/Tucuman Tucuman (TM)
+AR -2828-06547 America/Argentina/Catamarca Catamarca (CT), Chubut (CH)
+AR -2926-06651 America/Argentina/La_Rioja La Rioja (LR)
+AR -3132-06831 America/Argentina/San_Juan San Juan (SJ)
+AR -3253-06849 America/Argentina/Mendoza Mendoza (MZ)
+AR -3319-06621 America/Argentina/San_Luis San Luis (SL)
+AR -5138-06913 America/Argentina/Rio_Gallegos Santa Cruz (SC)
+AR -5448-06818 America/Argentina/Ushuaia Tierra del Fuego (TF)
+AS -1416-17042 Pacific/Pago_Pago
+AT +4813+01620 Europe/Vienna
+AU -3133+15905 Australia/Lord_Howe Lord Howe Island
+AU -4253+14719 Australia/Hobart Tasmania - most locations
+AU -3956+14352 Australia/Currie Tasmania - King Island
+AU -3749+14458 Australia/Melbourne Victoria
+AU -3352+15113 Australia/Sydney New South Wales - most locations
+AU -3157+14127 Australia/Broken_Hill New South Wales - Yancowinna
+AU -2728+15302 Australia/Brisbane Queensland - most locations
+AU -2016+14900 Australia/Lindeman Queensland - Holiday Islands
+AU -3455+13835 Australia/Adelaide South Australia
+AU -1228+13050 Australia/Darwin Northern Territory
+AU -3157+11551 Australia/Perth Western Australia - most locations
+AU -3143+12852 Australia/Eucla Western Australia - Eucla area
+AW +1230-06958 America/Aruba
+AX +6006+01957 Europe/Mariehamn
+AZ +4023+04951 Asia/Baku
+BA +4352+01825 Europe/Sarajevo
+BB +1306-05937 America/Barbados
+BD +2343+09025 Asia/Dhaka
+BE +5050+00420 Europe/Brussels
+BF +1222-00131 Africa/Ouagadougou
+BG +4241+02319 Europe/Sofia
+BH +2623+05035 Asia/Bahrain
+BI -0323+02922 Africa/Bujumbura
+BJ +0629+00237 Africa/Porto-Novo
+BL +1753-06251 America/St_Barthelemy
+BM +3217-06446 Atlantic/Bermuda
+BN +0456+11455 Asia/Brunei
+BO -1630-06809 America/La_Paz
+BQ +120903-0681636 America/Kralendijk
+BR -0351-03225 America/Noronha Atlantic islands
+BR -0127-04829 America/Belem Amapa, E Para
+BR -0343-03830 America/Fortaleza NE Brazil (MA, PI, CE, RN, PB)
+BR -0803-03454 America/Recife Pernambuco
+BR -0712-04812 America/Araguaina Tocantins
+BR -0940-03543 America/Maceio Alagoas, Sergipe
+BR -1259-03831 America/Bahia Bahia
+BR -2332-04637 America/Sao_Paulo S & SE Brazil (GO, DF, MG, ES, RJ, SP, PR, SC, RS)
+BR -2027-05437 America/Campo_Grande Mato Grosso do Sul
+BR -1535-05605 America/Cuiaba Mato Grosso
+BR -0226-05452 America/Santarem W Para
+BR -0846-06354 America/Porto_Velho Rondonia
+BR +0249-06040 America/Boa_Vista Roraima
+BR -0308-06001 America/Manaus E Amazonas
+BR -0640-06952 America/Eirunepe W Amazonas
+BR -0958-06748 America/Rio_Branco Acre
+BS +2505-07721 America/Nassau
+BT +2728+08939 Asia/Thimphu
+BW -2439+02555 Africa/Gaborone
+BY +5354+02734 Europe/Minsk
+BZ +1730-08812 America/Belize
+CA +4734-05243 America/St_Johns Newfoundland Time, including SE Labrador
+CA +4439-06336 America/Halifax Atlantic Time - Nova Scotia (most places), PEI
+CA +4612-05957 America/Glace_Bay Atlantic Time - Nova Scotia - places that did not observe DST 1966-1971
+CA +4606-06447 America/Moncton Atlantic Time - New Brunswick
+CA +5320-06025 America/Goose_Bay Atlantic Time - Labrador - most locations
+CA +5125-05707 America/Blanc-Sablon Atlantic Standard Time - Quebec - Lower North Shore
+CA +4531-07334 America/Montreal Eastern Time - Quebec - most locations
+CA +4339-07923 America/Toronto Eastern Time - Ontario - most locations
+CA +4901-08816 America/Nipigon Eastern Time - Ontario & Quebec - places that did not observe DST 1967-1973
+CA +4823-08915 America/Thunder_Bay Eastern Time - Thunder Bay, Ontario
+CA +6344-06828 America/Iqaluit Eastern Time - east Nunavut - most locations
+CA +6608-06544 America/Pangnirtung Eastern Time - Pangnirtung, Nunavut
+CA +744144-0944945 America/Resolute Central Standard Time - Resolute, Nunavut
+CA +484531-0913718 America/Atikokan Eastern Standard Time - Atikokan, Ontario and Southampton I, Nunavut
+CA +624900-0920459 America/Rankin_Inlet Central Time - central Nunavut
+CA +4953-09709 America/Winnipeg Central Time - Manitoba & west Ontario
+CA +4843-09434 America/Rainy_River Central Time - Rainy River & Fort Frances, Ontario
+CA +5024-10439 America/Regina Central Standard Time - Saskatchewan - most locations
+CA +5017-10750 America/Swift_Current Central Standard Time - Saskatchewan - midwest
+CA +5333-11328 America/Edmonton Mountain Time - Alberta, east British Columbia & west Saskatchewan
+CA +690650-1050310 America/Cambridge_Bay Mountain Time - west Nunavut
+CA +6227-11421 America/Yellowknife Mountain Time - central Northwest Territories
+CA +682059-1334300 America/Inuvik Mountain Time - west Northwest Territories
+CA +4906-11631 America/Creston Mountain Standard Time - Creston, British Columbia
+CA +5946-12014 America/Dawson_Creek Mountain Standard Time - Dawson Creek & Fort Saint John, British Columbia
+CA +4916-12307 America/Vancouver Pacific Time - west British Columbia
+CA +6043-13503 America/Whitehorse Pacific Time - south Yukon
+CA +6404-13925 America/Dawson Pacific Time - north Yukon
+CC -1210+09655 Indian/Cocos
+CD -0418+01518 Africa/Kinshasa west Dem. Rep. of Congo
+CD -1140+02728 Africa/Lubumbashi east Dem. Rep. of Congo
+CF +0422+01835 Africa/Bangui
+CG -0416+01517 Africa/Brazzaville
+CH +4723+00832 Europe/Zurich
+CI +0519-00402 Africa/Abidjan
+CK -2114-15946 Pacific/Rarotonga
+CL -3327-07040 America/Santiago most locations
+CL -2709-10926 Pacific/Easter Easter Island & Sala y Gomez
+CM +0403+00942 Africa/Douala
+CN +3114+12128 Asia/Shanghai east China - Beijing, Guangdong, Shanghai, etc.
+CN +4545+12641 Asia/Harbin Heilongjiang (except Mohe), Jilin
+CN +2934+10635 Asia/Chongqing central China - Sichuan, Yunnan, Guangxi, Shaanxi, Guizhou, etc.
+CN +4348+08735 Asia/Urumqi most of Tibet & Xinjiang
+CN +3929+07559 Asia/Kashgar west Tibet & Xinjiang
+CO +0436-07405 America/Bogota
+CR +0956-08405 America/Costa_Rica
+CU +2308-08222 America/Havana
+CV +1455-02331 Atlantic/Cape_Verde
+CW +1211-06900 America/Curacao
+CX -1025+10543 Indian/Christmas
+CY +3510+03322 Asia/Nicosia
+CZ +5005+01426 Europe/Prague
+DE +5230+01322 Europe/Berlin
+DJ +1136+04309 Africa/Djibouti
+DK +5540+01235 Europe/Copenhagen
+DM +1518-06124 America/Dominica
+DO +1828-06954 America/Santo_Domingo
+DZ +3647+00303 Africa/Algiers
+EC -0210-07950 America/Guayaquil mainland
+EC -0054-08936 Pacific/Galapagos Galapagos Islands
+EE +5925+02445 Europe/Tallinn
+EG +3003+03115 Africa/Cairo
+EH +2709-01312 Africa/El_Aaiun
+ER +1520+03853 Africa/Asmara
+ES +4024-00341 Europe/Madrid mainland
+ES +3553-00519 Africa/Ceuta Ceuta & Melilla
+ES +2806-01524 Atlantic/Canary Canary Islands
+ET +0902+03842 Africa/Addis_Ababa
+FI +6010+02458 Europe/Helsinki
+FJ -1808+17825 Pacific/Fiji
+FK -5142-05751 Atlantic/Stanley
+FM +0725+15147 Pacific/Chuuk Chuuk (Truk) and Yap
+FM +0658+15813 Pacific/Pohnpei Pohnpei (Ponape)
+FM +0519+16259 Pacific/Kosrae Kosrae
+FO +6201-00646 Atlantic/Faroe
+FR +4852+00220 Europe/Paris
+GA +0023+00927 Africa/Libreville
+GB +513030-0000731 Europe/London
+GD +1203-06145 America/Grenada
+GE +4143+04449 Asia/Tbilisi
+GF +0456-05220 America/Cayenne
+GG +4927-00232 Europe/Guernsey
+GH +0533-00013 Africa/Accra
+GI +3608-00521 Europe/Gibraltar
+GL +6411-05144 America/Godthab most locations
+GL +7646-01840 America/Danmarkshavn east coast, north of Scoresbysund
+GL +7029-02158 America/Scoresbysund Scoresbysund / Ittoqqortoormiit
+GL +7634-06847 America/Thule Thule / Pituffik
+GM +1328-01639 Africa/Banjul
+GN +0931-01343 Africa/Conakry
+GP +1614-06132 America/Guadeloupe
+GQ +0345+00847 Africa/Malabo
+GR +3758+02343 Europe/Athens
+GS -5416-03632 Atlantic/South_Georgia
+GT +1438-09031 America/Guatemala
+GU +1328+14445 Pacific/Guam
+GW +1151-01535 Africa/Bissau
+GY +0648-05810 America/Guyana
+HK +2217+11409 Asia/Hong_Kong
+HN +1406-08713 America/Tegucigalpa
+HR +4548+01558 Europe/Zagreb
+HT +1832-07220 America/Port-au-Prince
+HU +4730+01905 Europe/Budapest
+ID -0610+10648 Asia/Jakarta Java & Sumatra
+ID -0002+10920 Asia/Pontianak west & central Borneo
+ID -0507+11924 Asia/Makassar east & south Borneo, Sulawesi (Celebes), Bali, Nusa Tengarra, west Timor
+ID -0232+14042 Asia/Jayapura west New Guinea (Irian Jaya) & Malukus (Moluccas)
+IE +5320-00615 Europe/Dublin
+IL +3146+03514 Asia/Jerusalem
+IM +5409-00428 Europe/Isle_of_Man
+IN +2232+08822 Asia/Kolkata
+IO -0720+07225 Indian/Chagos
+IQ +3321+04425 Asia/Baghdad
+IR +3540+05126 Asia/Tehran
+IS +6409-02151 Atlantic/Reykjavik
+IT +4154+01229 Europe/Rome
+JE +4912-00207 Europe/Jersey
+JM +1800-07648 America/Jamaica
+JO +3157+03556 Asia/Amman
+JP +353916+1394441 Asia/Tokyo
+KE -0117+03649 Africa/Nairobi
+KG +4254+07436 Asia/Bishkek
+KH +1133+10455 Asia/Phnom_Penh
+KI +0125+17300 Pacific/Tarawa Gilbert Islands
+KI -0308-17105 Pacific/Enderbury Phoenix Islands
+KI +0152-15720 Pacific/Kiritimati Line Islands
+KM -1141+04316 Indian/Comoro
+KN +1718-06243 America/St_Kitts
+KP +3901+12545 Asia/Pyongyang
+KR +3733+12658 Asia/Seoul
+KW +2920+04759 Asia/Kuwait
+KY +1918-08123 America/Cayman
+KZ +4315+07657 Asia/Almaty most locations
+KZ +4448+06528 Asia/Qyzylorda Qyzylorda (Kyzylorda, Kzyl-Orda)
+KZ +5017+05710 Asia/Aqtobe Aqtobe (Aktobe)
+KZ +4431+05016 Asia/Aqtau Atyrau (Atirau, Gur'yev), Mangghystau (Mankistau)
+KZ +5113+05121 Asia/Oral West Kazakhstan
+LA +1758+10236 Asia/Vientiane
+LB +3353+03530 Asia/Beirut
+LC +1401-06100 America/St_Lucia
+LI +4709+00931 Europe/Vaduz
+LK +0656+07951 Asia/Colombo
+LR +0618-01047 Africa/Monrovia
+LS -2928+02730 Africa/Maseru
+LT +5441+02519 Europe/Vilnius
+LU +4936+00609 Europe/Luxembourg
+LV +5657+02406 Europe/Riga
+LY +3254+01311 Africa/Tripoli
+MA +3339-00735 Africa/Casablanca
+MC +4342+00723 Europe/Monaco
+MD +4700+02850 Europe/Chisinau
+ME +4226+01916 Europe/Podgorica
+MF +1804-06305 America/Marigot
+MG -1855+04731 Indian/Antananarivo
+MH +0709+17112 Pacific/Majuro most locations
+MH +0905+16720 Pacific/Kwajalein Kwajalein
+MK +4159+02126 Europe/Skopje
+ML +1239-00800 Africa/Bamako
+MM +1647+09610 Asia/Rangoon
+MN +4755+10653 Asia/Ulaanbaatar most locations
+MN +4801+09139 Asia/Hovd Bayan-Olgiy, Govi-Altai, Hovd, Uvs, Zavkhan
+MN +4804+11430 Asia/Choibalsan Dornod, Sukhbaatar
+MO +2214+11335 Asia/Macau
+MP +1512+14545 Pacific/Saipan
+MQ +1436-06105 America/Martinique
+MR +1806-01557 Africa/Nouakchott
+MS +1643-06213 America/Montserrat
+MT +3554+01431 Europe/Malta
+MU -2010+05730 Indian/Mauritius
+MV +0410+07330 Indian/Maldives
+MW -1547+03500 Africa/Blantyre
+MX +1924-09909 America/Mexico_City Central Time - most locations
+MX +2105-08646 America/Cancun Central Time - Quintana Roo
+MX +2058-08937 America/Merida Central Time - Campeche, Yucatan
+MX +2540-10019 America/Monterrey Mexican Central Time - Coahuila, Durango, Nuevo Leon, Tamaulipas away from US border
+MX +2550-09730 America/Matamoros US Central Time - Coahuila, Durango, Nuevo Leon, Tamaulipas near US border
+MX +2313-10625 America/Mazatlan Mountain Time - S Baja, Nayarit, Sinaloa
+MX +2838-10605 America/Chihuahua Mexican Mountain Time - Chihuahua away from US border
+MX +2934-10425 America/Ojinaga US Mountain Time - Chihuahua near US border
+MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora
+MX +3232-11701 America/Tijuana US Pacific Time - Baja California near US border
+MX +3018-11452 America/Santa_Isabel Mexican Pacific Time - Baja California away from US border
+MX +2048-10515 America/Bahia_Banderas Mexican Central Time - Bahia de Banderas
+MY +0310+10142 Asia/Kuala_Lumpur peninsular Malaysia
+MY +0133+11020 Asia/Kuching Sabah & Sarawak
+MZ -2558+03235 Africa/Maputo
+NA -2234+01706 Africa/Windhoek
+NC -2216+16627 Pacific/Noumea
+NE +1331+00207 Africa/Niamey
+NF -2903+16758 Pacific/Norfolk
+NG +0627+00324 Africa/Lagos
+NI +1209-08617 America/Managua
+NL +5222+00454 Europe/Amsterdam
+NO +5955+01045 Europe/Oslo
+NP +2743+08519 Asia/Kathmandu
+NR -0031+16655 Pacific/Nauru
+NU -1901-16955 Pacific/Niue
+NZ -3652+17446 Pacific/Auckland most locations
+NZ -4357-17633 Pacific/Chatham Chatham Islands
+OM +2336+05835 Asia/Muscat
+PA +0858-07932 America/Panama
+PE -1203-07703 America/Lima
+PF -1732-14934 Pacific/Tahiti Society Islands
+PF -0900-13930 Pacific/Marquesas Marquesas Islands
+PF -2308-13457 Pacific/Gambier Gambier Islands
+PG -0930+14710 Pacific/Port_Moresby
+PH +1435+12100 Asia/Manila
+PK +2452+06703 Asia/Karachi
+PL +5215+02100 Europe/Warsaw
+PM +4703-05620 America/Miquelon
+PN -2504-13005 Pacific/Pitcairn
+PR +182806-0660622 America/Puerto_Rico
+PS +3130+03428 Asia/Gaza Gaza Strip
+PS +313200+0350542 Asia/Hebron West Bank
+PT +3843-00908 Europe/Lisbon mainland
+PT +3238-01654 Atlantic/Madeira Madeira Islands
+PT +3744-02540 Atlantic/Azores Azores
+PW +0720+13429 Pacific/Palau
+PY -2516-05740 America/Asuncion
+QA +2517+05132 Asia/Qatar
+RE -2052+05528 Indian/Reunion
+RO +4426+02606 Europe/Bucharest
+RS +4450+02030 Europe/Belgrade
+RU +5443+02030 Europe/Kaliningrad Moscow-01 - Kaliningrad
+RU +5545+03735 Europe/Moscow Moscow+00 - west Russia
+RU +4844+04425 Europe/Volgograd Moscow+00 - Caspian Sea
+RU +5312+05009 Europe/Samara Moscow+00 - Samara, Udmurtia
+RU +5651+06036 Asia/Yekaterinburg Moscow+02 - Urals
+RU +5500+07324 Asia/Omsk Moscow+03 - west Siberia
+RU +5502+08255 Asia/Novosibirsk Moscow+03 - Novosibirsk
+RU +5345+08707 Asia/Novokuznetsk Moscow+03 - Novokuznetsk
+RU +5601+09250 Asia/Krasnoyarsk Moscow+04 - Yenisei River
+RU +5216+10420 Asia/Irkutsk Moscow+05 - Lake Baikal
+RU +6200+12940 Asia/Yakutsk Moscow+06 - Lena River
+RU +4310+13156 Asia/Vladivostok Moscow+07 - Amur River
+RU +4658+14242 Asia/Sakhalin Moscow+07 - Sakhalin Island
+RU +5934+15048 Asia/Magadan Moscow+08 - Magadan
+RU +5301+15839 Asia/Kamchatka Moscow+08 - Kamchatka
+RU +6445+17729 Asia/Anadyr Moscow+08 - Bering Sea
+RW -0157+03004 Africa/Kigali
+SA +2438+04643 Asia/Riyadh
+SB -0932+16012 Pacific/Guadalcanal
+SC -0440+05528 Indian/Mahe
+SD +1536+03232 Africa/Khartoum
+SE +5920+01803 Europe/Stockholm
+SG +0117+10351 Asia/Singapore
+SH -1555-00542 Atlantic/St_Helena
+SI +4603+01431 Europe/Ljubljana
+SJ +7800+01600 Arctic/Longyearbyen
+SK +4809+01707 Europe/Bratislava
+SL +0830-01315 Africa/Freetown
+SM +4355+01228 Europe/San_Marino
+SN +1440-01726 Africa/Dakar
+SO +0204+04522 Africa/Mogadishu
+SR +0550-05510 America/Paramaribo
+SS +0451+03136 Africa/Juba
+ST +0020+00644 Africa/Sao_Tome
+SV +1342-08912 America/El_Salvador
+SX +180305-0630250 America/Lower_Princes
+SY +3330+03618 Asia/Damascus
+SZ -2618+03106 Africa/Mbabane
+TC +2128-07108 America/Grand_Turk
+TD +1207+01503 Africa/Ndjamena
+TF -492110+0701303 Indian/Kerguelen
+TG +0608+00113 Africa/Lome
+TH +1345+10031 Asia/Bangkok
+TJ +3835+06848 Asia/Dushanbe
+TK -0922-17114 Pacific/Fakaofo
+TL -0833+12535 Asia/Dili
+TM +3757+05823 Asia/Ashgabat
+TN +3648+01011 Africa/Tunis
+TO -2110-17510 Pacific/Tongatapu
+TR +4101+02858 Europe/Istanbul
+TT +1039-06131 America/Port_of_Spain
+TV -0831+17913 Pacific/Funafuti
+TW +2503+12130 Asia/Taipei
+TZ -0648+03917 Africa/Dar_es_Salaam
+UA +5026+03031 Europe/Kiev most locations
+UA +4837+02218 Europe/Uzhgorod Ruthenia
+UA +4750+03510 Europe/Zaporozhye Zaporozh'ye, E Lugansk / Zaporizhia, E Luhansk
+UA +4457+03406 Europe/Simferopol central Crimea
+UG +0019+03225 Africa/Kampala
+UM +1645-16931 Pacific/Johnston Johnston Atoll
+UM +2813-17722 Pacific/Midway Midway Islands
+UM +1917+16637 Pacific/Wake Wake Island
+US +404251-0740023 America/New_York Eastern Time
+US +421953-0830245 America/Detroit Eastern Time - Michigan - most locations
+US +381515-0854534 America/Kentucky/Louisville Eastern Time - Kentucky - Louisville area
+US +364947-0845057 America/Kentucky/Monticello Eastern Time - Kentucky - Wayne County
+US +394606-0860929 America/Indiana/Indianapolis Eastern Time - Indiana - most locations
+US +384038-0873143 America/Indiana/Vincennes Eastern Time - Indiana - Daviess, Dubois, Knox & Martin Counties
+US +410305-0863611 America/Indiana/Winamac Eastern Time - Indiana - Pulaski County
+US +382232-0862041 America/Indiana/Marengo Eastern Time - Indiana - Crawford County
+US +382931-0871643 America/Indiana/Petersburg Eastern Time - Indiana - Pike County
+US +384452-0850402 America/Indiana/Vevay Eastern Time - Indiana - Switzerland County
+US +415100-0873900 America/Chicago Central Time
+US +375711-0864541 America/Indiana/Tell_City Central Time - Indiana - Perry County
+US +411745-0863730 America/Indiana/Knox Central Time - Indiana - Starke County
+US +450628-0873651 America/Menominee Central Time - Michigan - Dickinson, Gogebic, Iron & Menominee Counties
+US +470659-1011757 America/North_Dakota/Center Central Time - North Dakota - Oliver County
+US +465042-1012439 America/North_Dakota/New_Salem Central Time - North Dakota - Morton County (except Mandan area)
+US +471551-1014640 America/North_Dakota/Beulah Central Time - North Dakota - Mercer County
+US +394421-1045903 America/Denver Mountain Time
+US +433649-1161209 America/Boise Mountain Time - south Idaho & east Oregon
+US +364708-1084111 America/Shiprock Mountain Time - Navajo
+US +332654-1120424 America/Phoenix Mountain Standard Time - Arizona
+US +340308-1181434 America/Los_Angeles Pacific Time
+US +611305-1495401 America/Anchorage Alaska Time
+US +581807-1342511 America/Juneau Alaska Time - Alaska panhandle
+US +571035-1351807 America/Sitka Alaska Time - southeast Alaska panhandle
+US +593249-1394338 America/Yakutat Alaska Time - Alaska panhandle neck
+US +643004-1652423 America/Nome Alaska Time - west Alaska
+US +515248-1763929 America/Adak Aleutian Islands
+US +550737-1313435 America/Metlakatla Metlakatla Time - Annette Island
+US +211825-1575130 Pacific/Honolulu Hawaii
+UY -3453-05611 America/Montevideo
+UZ +3940+06648 Asia/Samarkand west Uzbekistan
+UZ +4120+06918 Asia/Tashkent east Uzbekistan
+VA +415408+0122711 Europe/Vatican
+VC +1309-06114 America/St_Vincent
+VE +1030-06656 America/Caracas
+VG +1827-06437 America/Tortola
+VI +1821-06456 America/St_Thomas
+VN +1045+10640 Asia/Ho_Chi_Minh
+VU -1740+16825 Pacific/Efate
+WF -1318-17610 Pacific/Wallis
+WS -1350-17144 Pacific/Apia
+YE +1245+04512 Asia/Aden
+YT -1247+04514 Indian/Mayotte
+ZA -2615+02800 Africa/Johannesburg
+ZM -1525+02817 Africa/Lusaka
+ZW -1750+03103 Africa/Harare
diff --git a/misc/flot/examples/axes-time/index.html b/misc/flot/examples/axes-time/index.html
new file mode 100644
index 0000000..ac3b2dc
--- /dev/null
+++ b/misc/flot/examples/axes-time/index.html
@@ -0,0 +1,137 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Time Axes</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.time.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var d = [[-373597200000, 315.71], [-370918800000, 317.45], [-368326800000, 317.50], [-363056400000, 315.86], [-360378000000, 314.93], [-357699600000, 313.19], [-352429200000, 313.34], [-349837200000, 314.67], [-347158800000, 315.58], [-344480400000, 316.47], [-342061200000, 316.65], [-339382800000, 317.71], [-336790800000, 318.29], [-334112400000, 318.16], [-331520400000, 316.55], [-328842000000, 314.80], [-326163600000, 313.84], [-323571600000, 313.34], [-320893200000, 314.81], [-318301200000, 315.59], [-315622800000, 316.43], [-312944400000, 316.97], [-310438800000, 317.58], [-307760400000, 319.03], [-305168400000, 320.03], [-302490000000, 319.59], [-299898000000, 318.18], [-297219600000, 315.91], [-294541200000, 314.16], [-291949200000, 313.83], [-289270800000, 315.00], [-286678800000, 316.19], [-284000400000, 316.89], [-281322000000, 317.70], [-278902800000, 318.54], [-276224400000, 319.48], [-273632400000, 320.58], [-270954000000, 319.78], [-268362000000, 318.58], [-265683600000, 316.79], [-263005200000, 314.99], [-260413200000, 315.31], [-257734800000, 316.10], [-255142800000, 317.01], [-252464400000, 317.94], [-249786000000, 318.56], [-247366800000, 319.69], [-244688400000, 320.58], [-242096400000, 321.01], [-239418000000, 320.61], [-236826000000, 319.61], [-234147600000, 317.40], [-231469200000, 316.26], [-228877200000, 315.42], [-226198800000, 316.69], [-223606800000, 317.69], [-220928400000, 318.74], [-218250000000, 319.08], [-215830800000, 319.86], [-213152400000, 321.39], [-210560400000, 322.24], [-207882000000, 321.47], [-205290000000, 319.74], [-202611600000, 317.77], [-199933200000, 316.21], [-197341200000, 315.99], [-194662800000, 317.07], [-192070800000, 318.36], [-189392400000, 319.57], [-178938000000, 322.23], [-176259600000, 321.89], [-173667600000, 320.44], [-170989200000, 318.70], [-168310800000, 316.70], [-165718800000, 316.87], [-163040400000, 317.68], [-160448400000, 318.71], [-157770000000, 319.44], [-155091600000, 320.44], [-152672400000, 320.89], [-149994000000, 322.13], [-147402000000, 322.16], [-144723600000, 321.87], [-142131600000, 321.21], [-139453200000, 318.87], [-136774800000, 317.81], [-134182800000, 317.30], [-131504400000, 318.87], [-128912400000, 319.42], [-126234000000, 320.62], [-123555600000, 321.59], [-121136400000, 322.39], [-118458000000, 323.70], [-115866000000, 324.07], [-113187600000, 323.75], [-110595600000, 322.40], [-107917200000, 320.37], [-105238800000, 318.64], [-102646800000, 318.10], [-99968400000, 319.79], [-97376400000, 321.03], [-94698000000, 322.33], [-92019600000, 322.50], [-89600400000, 323.04], [-86922000000, 324.42], [-84330000000, 325.00], [-81651600000, 324.09], [-79059600000, 322.55], [-76381200000, 320.92], [-73702800000, 319.26], [-71110800000, 319.39], [-68432400000, 320.72], [-65840400000, 321.96], [-63162000000, 322.57], [-60483600000, 323.15], [-57978000000, 323.89], [-55299600000, 325.02], [-52707600000, 325.57], [-50029200000, 325.36], [-47437200000, 324.14], [-44758800000, 322.11], [-42080400000, 320.33], [-39488400000, 320.25], [-36810000000, 321.32], [-34218000000, 322.90], [-31539600000, 324.00], [-28861200000, 324.42], [-26442000000, 325.64], [-23763600000, 326.66], [-21171600000, 327.38], [-18493200000, 326.70], [-15901200000, 325.89], [-13222800000, 323.67], [-10544400000, 322.38], [-7952400000, 321.78], [-5274000000, 322.85], [-2682000000, 324.12], [-3600000, 325.06], [2674800000, 325.98], [5094000000, 326.93], [7772400000, 328.13], [10364400000, 328.07], [13042800000, 327.66], [15634800000, 326.35], [18313200000, 324.69], [20991600000, 323.10], [23583600000, 323.07], [26262000000, 324.01], [28854000000, 325.13], [31532400000, 326.17], [34210800000, 326.68], [36630000000, 327.18], [39308400000, 327.78], [41900400000, 328.92], [44578800000, 328.57], [47170800000, 327.37], [49849200000, 325.43], [52527600000, 323.36], [55119600000, 323.56], [57798000000, 324.80], [60390000000, 326.01], [63068400000, 326.77], [65746800000, 327.63], [68252400000, 327.75], [70930800000, 329.72], [73522800000, 330.07], [76201200000, 329.09], [78793200000, 328.05], [81471600000, 326.32], [84150000000, 324.84], [86742000000, 325.20], [89420400000, 326.50], [92012400000, 327.55], [94690800000, 328.54], [97369200000, 329.56], [99788400000, 330.30], [102466800000, 331.50], [105058800000, 332.48], [107737200000, 332.07], [110329200000, 330.87], [113007600000, 329.31], [115686000000, 327.51], [118278000000, 327.18], [120956400000, 328.16], [123548400000, 328.64], [126226800000, 329.35], [128905200000, 330.71], [131324400000, 331.48], [134002800000, 332.65], [136594800000, 333.16], [139273200000, 332.06], [141865200000, 330.99], [144543600000, 329.17], [147222000000, 327.41], [149814000000, 327.20], [152492400000, 328.33], [155084400000, 329.50], [157762800000, 330.68], [160441200000, 331.41], [162860400000, 331.85], [165538800000, 333.29], [168130800000, 333.91], [170809200000, 333.40], [173401200000, 331.78], [176079600000, 329.88], [178758000000, 328.57], [181350000000, 328.46], [184028400000, 329.26], [189298800000, 331.71], [191977200000, 332.76], [194482800000, 333.48], [197161200000, 334.78], [199753200000, 334.78], [202431600000, 334.17], [205023600000, 332.78], [207702000000, 330.64], [210380400000, 328.95], [212972400000, 328.77], [215650800000, 330.23], [218242800000, 331.69], [220921200000, 332.70], [223599600000, 333.24], [226018800000, 334.96], [228697200000, 336.04], [231289200000, 336.82], [233967600000, 336.13], [236559600000, 334.73], [239238000000, 332.52], [241916400000, 331.19], [244508400000, 331.19], [247186800000, 332.35], [249778800000, 333.47], [252457200000, 335.11], [255135600000, 335.26], [257554800000, 336.60], [260233200000, 337.77], [262825200000, 338.00], [265503600000, 337.99], [268095600000, 336.48], [270774000000, 334.37], [273452400000, 332.27], [276044400000, 332.41], [278722800000, 333.76], [281314800000, 334.83], [283993200000, 336.21], [286671600000, 336.64], [289090800000, 338.12], [291769200000, 339.02], [294361200000, 339.02], [297039600000, 339.20], [299631600000, 337.58], [302310000000, 335.55], [304988400000, 333.89], [307580400000, 334.14], [310258800000, 335.26], [312850800000, 336.71], [315529200000, 337.81], [318207600000, 338.29], [320713200000, 340.04], [323391600000, 340.86], [325980000000, 341.47], [328658400000, 341.26], [331250400000, 339.29], [333928800000, 337.60], [336607200000, 336.12], [339202800000, 336.08], [341881200000, 337.22], [344473200000, 338.34], [347151600000, 339.36], [349830000000, 340.51], [352249200000, 341.57], [354924000000, 342.56], [357516000000, 343.01], [360194400000, 342.47], [362786400000, 340.71], [365464800000, 338.52], [368143200000, 336.96], [370738800000, 337.13], [373417200000, 338.58], [376009200000, 339.89], [378687600000, 340.93], [381366000000, 341.69], [383785200000, 342.69], [389052000000, 344.30], [391730400000, 343.43], [394322400000, 341.88], [397000800000, 339.89], [399679200000, 337.95], [402274800000, 338.10], [404953200000, 339.27], [407545200000, 340.67], [410223600000, 341.42], [412902000000, 342.68], [415321200000, 343.46], [417996000000, 345.10], [420588000000, 345.76], [423266400000, 345.36], [425858400000, 343.91], [428536800000, 342.05], [431215200000, 340.00], [433810800000, 340.12], [436489200000, 341.33], [439081200000, 342.94], [441759600000, 343.87], [444438000000, 344.60], [446943600000, 345.20], [452210400000, 347.36], [454888800000, 346.74], [457480800000, 345.41], [460159200000, 343.01], [462837600000, 341.23], [465433200000, 341.52], [468111600000, 342.86], [470703600000, 344.41], [473382000000, 345.09], [476060400000, 345.89], [478479600000, 347.49], [481154400000, 348.00], [483746400000, 348.75], [486424800000, 348.19], [489016800000, 346.54], [491695200000, 344.63], [494373600000, 343.03], [496969200000, 342.92], [499647600000, 344.24], [502239600000, 345.62], [504918000000, 346.43], [507596400000, 346.94], [510015600000, 347.88], [512690400000, 349.57], [515282400000, 350.35], [517960800000, 349.72], [520552800000, 347.78], [523231200000, 345.86], [525909600000, 344.84], [528505200000, 344.32], [531183600000, 345.67], [533775600000, 346.88], [536454000000, 348.19], [539132400000, 348.55], [541551600000, 349.52], [544226400000, 351.12], [546818400000, 351.84], [549496800000, 351.49], [552088800000, 349.82], [554767200000, 347.63], [557445600000, 346.38], [560041200000, 346.49], [562719600000, 347.75], [565311600000, 349.03], [567990000000, 350.20], [570668400000, 351.61], [573174000000, 352.22], [575848800000, 353.53], [578440800000, 354.14], [581119200000, 353.62], [583711200000, 352.53], [586389600000, 350.41], [589068000000, 348.84], [591663600000, 348.94], [594342000000, 350.04], [596934000000, 351.29], [599612400000, 352.72], [602290800000, 353.10], [604710000000, 353.65], [607384800000, 355.43], [609976800000, 355.70], [612655200000, 355.11], [615247200000, 353.79], [617925600000, 351.42], [620604000000, 349.81], [623199600000, 350.11], [625878000000, 351.26], [628470000000, 352.63], [631148400000, 353.64], [633826800000, 354.72], [636246000000, 355.49], [638920800000, 356.09], [641512800000, 357.08], [644191200000, 356.11], [646783200000, 354.70], [649461600000, 352.68], [652140000000, 351.05], [654735600000, 351.36], [657414000000, 352.81], [660006000000, 354.22], [662684400000, 354.85], [665362800000, 355.66], [667782000000, 357.04], [670456800000, 358.40], [673048800000, 359.00], [675727200000, 357.99], [678319200000, 356.00], [680997600000, 353.78], [683676000000, 352.20], [686271600000, 352.22], [688950000000, 353.70], [691542000000, 354.98], [694220400000, 356.09], [696898800000, 356.85], [699404400000, 357.73], [702079200000, 358.91], [704671200000, 359.45], [707349600000, 359.19], [709941600000, 356.72], [712620000000, 354.79], [715298400000, 352.79], [717894000000, 353.20], [720572400000, 354.15], [723164400000, 355.39], [725842800000, 356.77], [728521200000, 357.17], [730940400000, 358.26], [733615200000, 359.16], [736207200000, 360.07], [738885600000, 359.41], [741477600000, 357.44], [744156000000, 355.30], [746834400000, 353.87], [749430000000, 354.04], [752108400000, 355.27], [754700400000, 356.70], [757378800000, 358.00], [760057200000, 358.81], [762476400000, 359.68], [765151200000, 361.13], [767743200000, 361.48], [770421600000, 360.60], [773013600000, 359.20], [775692000000, 357.23], [778370400000, 355.42], [780966000000, 355.89], [783644400000, 357.41], [786236400000, 358.74], [788914800000, 359.73], [791593200000, 360.61], [794012400000, 361.58], [796687200000, 363.05], [799279200000, 363.62], [801957600000, 363.03], [804549600000, 361.55], [807228000000, 358.94], [809906400000, 357.93], [812502000000, 357.80], [815180400000, 359.22], [817772400000, 360.44], [820450800000, 361.83], [823129200000, 362.95], [825634800000, 363.91], [828309600000, 364.28], [830901600000, 364.94], [833580000000, 364.70], [836172000000, 363.31], [838850400000, 361.15], [841528800000, 359.40], [844120800000, 359.34], [846802800000, 360.62], [849394800000, 361.96], [852073200000, 362.81], [854751600000, 363.87], [857170800000, 364.25], [859845600000, 366.02], [862437600000, 366.46], [865116000000, 365.32], [867708000000, 364.07], [870386400000, 361.95], [873064800000, 360.06], [875656800000, 360.49], [878338800000, 362.19], [880930800000, 364.12], [883609200000, 364.99], [886287600000, 365.82], [888706800000, 366.95], [891381600000, 368.42], [893973600000, 369.33], [896652000000, 368.78], [899244000000, 367.59], [901922400000, 365.84], [904600800000, 363.83], [907192800000, 364.18], [909874800000, 365.34], [912466800000, 366.93], [915145200000, 367.94], [917823600000, 368.82], [920242800000, 369.46], [922917600000, 370.77], [925509600000, 370.66], [928188000000, 370.10], [930780000000, 369.08], [933458400000, 366.66], [936136800000, 364.60], [938728800000, 365.17], [941410800000, 366.51], [944002800000, 367.89], [946681200000, 369.04], [949359600000, 369.35], [951865200000, 370.38], [954540000000, 371.63], [957132000000, 371.32], [959810400000, 371.53], [962402400000, 369.75], [965080800000, 368.23], [967759200000, 366.87], [970351200000, 366.94], [973033200000, 368.27], [975625200000, 369.64], [978303600000, 370.46], [980982000000, 371.44], [983401200000, 372.37], [986076000000, 373.33], [988668000000, 373.77], [991346400000, 373.09], [993938400000, 371.51], [996616800000, 369.55], [999295200000, 368.12], [1001887200000, 368.38], [1004569200000, 369.66], [1007161200000, 371.11], [1009839600000, 372.36], [1012518000000, 373.09], [1014937200000, 373.81], [1017612000000, 374.93], [1020204000000, 375.58], [1022882400000, 375.44], [1025474400000, 373.86], [1028152800000, 371.77], [1030831200000, 370.73], [1033423200000, 370.50], [1036105200000, 372.18], [1038697200000, 373.70], [1041375600000, 374.92], [1044054000000, 375.62], [1046473200000, 376.51], [1049148000000, 377.75], [1051740000000, 378.54], [1054418400000, 378.20], [1057010400000, 376.68], [1059688800000, 374.43], [1062367200000, 373.11], [1064959200000, 373.10], [1067641200000, 374.77], [1070233200000, 375.97], [1072911600000, 377.03], [1075590000000, 377.87], [1078095600000, 378.88], [1080770400000, 380.42], [1083362400000, 380.62], [1086040800000, 379.70], [1088632800000, 377.43], [1091311200000, 376.32], [1093989600000, 374.19], [1096581600000, 374.47], [1099263600000, 376.15], [1101855600000, 377.51], [1104534000000, 378.43], [1107212400000, 379.70], [1109631600000, 380.92], [1112306400000, 382.18], [1114898400000, 382.45], [1117576800000, 382.14], [1120168800000, 380.60], [1122847200000, 378.64], [1125525600000, 376.73], [1128117600000, 376.84], [1130799600000, 378.29], [1133391600000, 380.06], [1136070000000, 381.40], [1138748400000, 382.20], [1141167600000, 382.66], [1143842400000, 384.69], [1146434400000, 384.94], [1149112800000, 384.01], [1151704800000, 382.14], [1154383200000, 380.31], [1157061600000, 378.81], [1159653600000, 379.03], [1162335600000, 380.17], [1164927600000, 381.85], [1167606000000, 382.94], [1170284400000, 383.86], [1172703600000, 384.49], [1175378400000, 386.37], [1177970400000, 386.54], [1180648800000, 385.98], [1183240800000, 384.36], [1185919200000, 381.85], [1188597600000, 380.74], [1191189600000, 381.15], [1193871600000, 382.38], [1196463600000, 383.94], [1199142000000, 385.44]];
+
+ $.plot("#placeholder", [d], {
+ xaxis: { mode: "time" }
+ });
+
+ $("#whole").click(function () {
+ $.plot("#placeholder", [d], {
+ xaxis: { mode: "time" }
+ });
+ });
+
+ $("#nineties").click(function () {
+ $.plot("#placeholder", [d], {
+ xaxis: {
+ mode: "time",
+ min: (new Date(1990, 0, 1)).getTime(),
+ max: (new Date(2000, 0, 1)).getTime()
+ }
+ });
+ });
+
+ $("#latenineties").click(function () {
+ $.plot("#placeholder", [d], {
+ xaxis: {
+ mode: "time",
+ minTickSize: [1, "year"],
+ min: (new Date(1996, 0, 1)).getTime(),
+ max: (new Date(2000, 0, 1)).getTime()
+ }
+ });
+ });
+
+ $("#ninetyninequarters").click(function () {
+ $.plot("#placeholder", [d], {
+ xaxis: {
+ mode: "time",
+ minTickSize: [1, "quarter"],
+ min: (new Date(1999, 0, 1)).getTime(),
+ max: (new Date(2000, 0, 1)).getTime()
+ }
+ });
+ });
+
+ $("#ninetynine").click(function () {
+ $.plot("#placeholder", [d], {
+ xaxis: {
+ mode: "time",
+ minTickSize: [1, "month"],
+ min: (new Date(1999, 0, 1)).getTime(),
+ max: (new Date(2000, 0, 1)).getTime()
+ }
+ });
+ });
+
+ $("#lastweekninetynine").click(function () {
+ $.plot("#placeholder", [d], {
+ xaxis: {
+ mode: "time",
+ minTickSize: [1, "day"],
+ min: (new Date(1999, 11, 25)).getTime(),
+ max: (new Date(2000, 0, 1)).getTime(),
+ timeformat: "%a"
+ }
+ });
+ });
+
+ $("#lastdayninetynine").click(function () {
+ $.plot("#placeholder", [d], {
+ xaxis: {
+ mode: "time",
+ minTickSize: [1, "hour"],
+ min: (new Date(1999, 11, 31)).getTime(),
+ max: (new Date(2000, 0, 1)).getTime(),
+ twelveHourClock: true
+ }
+ });
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Time Axes</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>Monthly mean atmospheric CO<sub>2</sub> in PPM at Mauna Loa, Hawaii (source: <a href="http://www.esrl.noaa.gov/gmd/ccgg/trends/">NOAA/ESRL</a>).</p>
+
+ <p>If you tell Flot that an axis represents time, the data will be interpreted as timestamps and the ticks adjusted and formatted accordingly.</p>
+
+ <p>Zoom to: <button id="whole">Whole period</button>
+ <button id="nineties">1990-2000</button>
+ <button id="latenineties">1996-2000</button></p>
+
+ <p>Zoom to: <button id="ninetyninequarters">1999 by quarter</button>
+ <button id="ninetynine">1999 by month</button>
+ <button id="lastweekninetynine">Last week of 1999</button>
+ <button id="lastdayninetynine">Dec. 31, 1999</button></p>
+
+ <p>The timestamps must be specified as Javascript timestamps, as milliseconds since January 1, 1970 00:00. This is like Unix timestamps, but in milliseconds instead of seconds (remember to multiply with 1000!).</p>
+
+ <p>As an extra caveat, the timestamps are interpreted according to UTC and, by default, displayed as such. You can set the axis "timezone" option to "browser" to display the timestamps in the user's timezone, or, if you use timezoneJS, you can specify a time zone.</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/background.png b/misc/flot/examples/background.png
new file mode 100644
index 0000000..47a4a4c
--- /dev/null
+++ b/misc/flot/examples/background.png
Binary files differ
diff --git a/misc/flot/examples/basic-options/index.html b/misc/flot/examples/basic-options/index.html
new file mode 100644
index 0000000..8d7f98d
--- /dev/null
+++ b/misc/flot/examples/basic-options/index.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Basic Options</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script type="text/javascript">
+
+ $(function () {
+
+ var d1 = [];
+ for (var i = 0; i < Math.PI * 2; i += 0.25) {
+ d1.push([i, Math.sin(i)]);
+ }
+
+ var d2 = [];
+ for (var i = 0; i < Math.PI * 2; i += 0.25) {
+ d2.push([i, Math.cos(i)]);
+ }
+
+ var d3 = [];
+ for (var i = 0; i < Math.PI * 2; i += 0.1) {
+ d3.push([i, Math.tan(i)]);
+ }
+
+ $.plot("#placeholder", [
+ { label: "sin(x)", data: d1 },
+ { label: "cos(x)", data: d2 },
+ { label: "tan(x)", data: d3 }
+ ], {
+ series: {
+ lines: { show: true },
+ points: { show: true }
+ },
+ xaxis: {
+ ticks: [
+ 0, [ Math.PI/2, "\u03c0/2" ], [ Math.PI, "\u03c0" ],
+ [ Math.PI * 3/2, "3\u03c0/2" ], [ Math.PI * 2, "2\u03c0" ]
+ ]
+ },
+ yaxis: {
+ ticks: 10,
+ min: -2,
+ max: 2,
+ tickDecimals: 3
+ },
+ grid: {
+ backgroundColor: { colors: [ "#fff", "#eee" ] },
+ borderWidth: {
+ top: 1,
+ right: 1,
+ bottom: 2,
+ left: 2
+ }
+ }
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Basic Options</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>There are plenty of options you can set to control the precise looks of your plot. You can control the ticks on the axes, the legend, the graph type, etc.</p>
+
+ <p>Flot goes to great lengths to provide sensible defaults so that you don't have to customize much for a good-looking result.</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/basic-usage/index.html b/misc/flot/examples/basic-usage/index.html
new file mode 100644
index 0000000..4ace696
--- /dev/null
+++ b/misc/flot/examples/basic-usage/index.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Basic Usage</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var d1 = [];
+ for (var i = 0; i < 14; i += 0.5) {
+ d1.push([i, Math.sin(i)]);
+ }
+
+ var d2 = [[0, 3], [4, 8], [8, 5], [9, 13]];
+
+ // A null signifies separate line segments
+
+ var d3 = [[0, 12], [7, 12], null, [7, 2.5], [12, 2.5]];
+
+ $.plot("#placeholder", [ d1, d2, d3 ]);
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Basic Usage</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>You don't have to do much to get an attractive plot. Create a placeholder, make sure it has dimensions (so Flot knows at what size to draw the plot), then call the plot function with your data.</p>
+
+ <p>The axes are automatically scaled.</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/canvas/index.html b/misc/flot/examples/canvas/index.html
new file mode 100644
index 0000000..f51362a
--- /dev/null
+++ b/misc/flot/examples/canvas/index.html
@@ -0,0 +1,75 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Canvas text</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.time.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.canvas.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var oilPrices = [[1167692400000,61.05], [1167778800000,58.32], [1167865200000,57.35], [1167951600000,56.31], [1168210800000,55.55], [1168297200000,55.64], [1168383600000,54.02], [1168470000000,51.88], [1168556400000,52.99], [1168815600000,52.99], [1168902000000,51.21], [1168988400000,52.24], [1169074800000,50.48], [1169161200000,51.99], [1169420400000,51.13], [1169506800000,55.04], [1169593200000,55.37], [1169679600000,54.23], [1169766000000,55.42], [1170025200000,54.01], [1170111600000,56.97], [1170198000000,58.14], [1170284400000,58.14], [1170370800000,59.02], [1170630000000,58.74], [1170716400000,58.88], [1170802800000,57.71], [1170889200000,59.71], [1170975600000,59.89], [1171234800000,57.81], [1171321200000,59.06], [1171407600000,58.00], [1171494000000,57.99], [1171580400000,59.39], [1171839600000,59.39], [1171926000000,58.07], [1172012400000,60.07], [1172098800000,61.14], [1172444400000,61.39], [1172530800000,61.46], [1172617200000,61.79], [1172703600000,62.00], [1172790000000,60.07], [1173135600000,60.69], [1173222000000,61.82], [1173308400000,60.05], [1173654000000,58.91], [1173740400000,57.93], [1173826800000,58.16], [1173913200000,57.55], [1173999600000,57.11], [1174258800000,56.59], [1174345200000,59.61], [1174518000000,61.69], [1174604400000,62.28], [1174860000000,62.91], [1174946400000,62.93], [1175032800000,64.03], [1175119200000,66.03], [1175205600000,65.87], [1175464800000,64.64], [1175637600000,64.38], [1175724000000,64.28], [1175810400000,64.28], [1176069600000,61.51], [1176156000000,61.89], [1176242400000,62.01], [1176328800000,63.85], [1176415200000,63.63], [1176674400000,63.61], [1176760800000,63.10], [1176847200000,63.13], [1176933600000,61.83], [1177020000000,63.38], [1177279200000,64.58], [1177452000000,65.84], [1177538400000,65.06], [1177624800000,66.46], [1177884000000,64.40], [1178056800000,63.68], [1178143200000,63.19], [1178229600000,61.93], [1178488800000,61.47], [1178575200000,61.55], [1178748000000,61.81], [1178834400000,62.37], [1179093600000,62.46], [1179180000000,63.17], [1179266400000,62.55], [1179352800000,64.94], [1179698400000,66.27], [1179784800000,65.50], [1179871200000,65.77], [1179957600000,64.18], [1180044000000,65.20], [1180389600000,63.15], [1180476000000,63.49], [1180562400000,65.08], [1180908000000,66.30], [1180994400000,65.96], [1181167200000,66.93], [1181253600000,65.98], [1181599200000,65.35], [1181685600000,66.26], [1181858400000,68.00], [1182117600000,69.09], [1182204000000,69.10], [1182290400000,68.19], [1182376800000,68.19], [1182463200000,69.14], [1182722400000,68.19], [1182808800000,67.77], [1182895200000,68.97], [1182981600000,69.57], [1183068000000,70.68], [1183327200000,71.09], [1183413600000,70.92], [1183586400000,71.81], [1183672800000,72.81], [1183932000000,72.19], [1184018400000,72.56], [1184191200000,72.50], [1184277600000,74.15], [1184623200000,75.05], [1184796000000,75.92], [1184882400000,75.57], [1185141600000,74.89], [1185228000000,73.56], [1185314400000,75.57], [1185400800000,74.95], [1185487200000,76.83], [1185832800000,78.21], [1185919200000,76.53], [1186005600000,76.86], [1186092000000,76.00], [1186437600000,71.59], [1186696800000,71.47], [1186956000000,71.62], [1187042400000,71.00], [1187301600000,71.98], [1187560800000,71.12], [1187647200000,69.47], [1187733600000,69.26], [1187820000000,69.83], [1187906400000,71.09], [1188165600000,71.73], [1188338400000,73.36], [1188511200000,74.04], [1188856800000,76.30], [1189116000000,77.49], [1189461600000,78.23], [1189548000000,79.91], [1189634400000,80.09], [1189720800000,79.10], [1189980000000,80.57], [1190066400000,81.93], [1190239200000,83.32], [1190325600000,81.62], [1190584800000,80.95], [1190671200000,79.53], [1190757600000,80.30], [1190844000000,82.88], [1190930400000,81.66], [1191189600000,80.24], [1191276000000,80.05], [1191362400000,79.94], [1191448800000,81.44], [1191535200000,81.22], [1191794400000,79.02], [1191880800000,80.26], [1191967200000,80.30], [1192053600000,83.08], [1192140000000,83.69], [1192399200000,86.13], [1192485600000,87.61], [1192572000000,87.40], [1192658400000,89.47], [1192744800000,88.60], [1193004000000,87.56], [1193090400000,87.56], [1193176800000,87.10], [1193263200000,91.86], [1193612400000,93.53], [1193698800000,94.53], [1193871600000,95.93], [1194217200000,93.98], [1194303600000,96.37], [1194476400000,95.46], [1194562800000,96.32], [1195081200000,93.43], [1195167600000,95.10], [1195426800000,94.64], [1195513200000,95.10], [1196031600000,97.70], [1196118000000,94.42], [1196204400000,90.62], [1196290800000,91.01], [1196377200000,88.71], [1196636400000,88.32], [1196809200000,90.23], [1196982000000,88.28], [1197241200000,87.86], [1197327600000,90.02], [1197414000000,92.25], [1197586800000,90.63], [1197846000000,90.63], [1197932400000,90.49], [1198018800000,91.24], [1198105200000,91.06], [1198191600000,90.49], [1198710000000,96.62], [1198796400000,96.00], [1199142000000,99.62], [1199314800000,99.18], [1199401200000,95.09], [1199660400000,96.33], [1199833200000,95.67], [1200351600000,91.90], [1200438000000,90.84], [1200524400000,90.13], [1200610800000,90.57], [1200956400000,89.21], [1201042800000,86.99], [1201129200000,89.85], [1201474800000,90.99], [1201561200000,91.64], [1201647600000,92.33], [1201734000000,91.75], [1202079600000,90.02], [1202166000000,88.41], [1202252400000,87.14], [1202338800000,88.11], [1202425200000,91.77], [1202770800000,92.78], [1202857200000,93.27], [1202943600000,95.46], [1203030000000,95.46], [1203289200000,101.74], [1203462000000,98.81], [1203894000000,100.88], [1204066800000,99.64], [1204153200000,102.59], [1204239600000,101.84], [1204498800000,99.52], [1204585200000,99.52], [1204671600000,104.52], [1204758000000,105.47], [1204844400000,105.15], [1205103600000,108.75], [1205276400000,109.92], [1205362800000,110.33], [1205449200000,110.21], [1205708400000,105.68], [1205967600000,101.84], [1206313200000,100.86], [1206399600000,101.22], [1206486000000,105.90], [1206572400000,107.58], [1206658800000,105.62], [1206914400000,101.58], [1207000800000,100.98], [1207173600000,103.83], [1207260000000,106.23], [1207605600000,108.50], [1207778400000,110.11], [1207864800000,110.14], [1208210400000,113.79], [1208296800000,114.93], [1208383200000,114.86], [1208728800000,117.48], [1208815200000,118.30], [1208988000000,116.06], [1209074400000,118.52], [1209333600000,118.75], [1209420000000,113.46], [1209592800000,112.52], [1210024800000,121.84], [1210111200000,123.53], [1210197600000,123.69], [1210543200000,124.23], [1210629600000,125.80], [1210716000000,126.29], [1211148000000,127.05], [1211320800000,129.07], [1211493600000,132.19], [1211839200000,128.85], [1212357600000,127.76], [1212703200000,138.54], [1212962400000,136.80], [1213135200000,136.38], [1213308000000,134.86], [1213653600000,134.01], [1213740000000,136.68], [1213912800000,135.65], [1214172000000,134.62], [1214258400000,134.62], [1214344800000,134.62], [1214431200000,139.64], [1214517600000,140.21], [1214776800000,140.00], [1214863200000,140.97], [1214949600000,143.57], [1215036000000,145.29], [1215381600000,141.37], [1215468000000,136.04], [1215727200000,146.40], [1215986400000,145.18], [1216072800000,138.74], [1216159200000,134.60], [1216245600000,129.29], [1216332000000,130.65], [1216677600000,127.95], [1216850400000,127.95], [1217282400000,122.19], [1217455200000,124.08], [1217541600000,125.10], [1217800800000,121.41], [1217887200000,119.17], [1217973600000,118.58], [1218060000000,120.02], [1218405600000,114.45], [1218492000000,113.01], [1218578400000,116.00], [1218751200000,113.77], [1219010400000,112.87], [1219096800000,114.53], [1219269600000,114.98], [1219356000000,114.98], [1219701600000,116.27], [1219788000000,118.15], [1219874400000,115.59], [1219960800000,115.46], [1220306400000,109.71], [1220392800000,109.35], [1220565600000,106.23], [1220824800000,106.34]];
+
+ var exchangeRates = [[1167606000000,0.7580], [1167692400000,0.7580], [1167778800000,0.75470], [1167865200000,0.75490], [1167951600000,0.76130], [1168038000000,0.76550], [1168124400000,0.76930], [1168210800000,0.76940], [1168297200000,0.76880], [1168383600000,0.76780], [1168470000000,0.77080], [1168556400000,0.77270], [1168642800000,0.77490], [1168729200000,0.77410], [1168815600000,0.77410], [1168902000000,0.77320], [1168988400000,0.77270], [1169074800000,0.77370], [1169161200000,0.77240], [1169247600000,0.77120], [1169334000000,0.7720], [1169420400000,0.77210], [1169506800000,0.77170], [1169593200000,0.77040], [1169679600000,0.7690], [1169766000000,0.77110], [1169852400000,0.7740], [1169938800000,0.77450], [1170025200000,0.77450], [1170111600000,0.7740], [1170198000000,0.77160], [1170284400000,0.77130], [1170370800000,0.76780], [1170457200000,0.76880], [1170543600000,0.77180], [1170630000000,0.77180], [1170716400000,0.77280], [1170802800000,0.77290], [1170889200000,0.76980], [1170975600000,0.76850], [1171062000000,0.76810], [1171148400000,0.7690], [1171234800000,0.7690], [1171321200000,0.76980], [1171407600000,0.76990], [1171494000000,0.76510], [1171580400000,0.76130], [1171666800000,0.76160], [1171753200000,0.76140], [1171839600000,0.76140], [1171926000000,0.76070], [1172012400000,0.76020], [1172098800000,0.76110], [1172185200000,0.76220], [1172271600000,0.76150], [1172358000000,0.75980], [1172444400000,0.75980], [1172530800000,0.75920], [1172617200000,0.75730], [1172703600000,0.75660], [1172790000000,0.75670], [1172876400000,0.75910], [1172962800000,0.75820], [1173049200000,0.75850], [1173135600000,0.76130], [1173222000000,0.76310], [1173308400000,0.76150], [1173394800000,0.760], [1173481200000,0.76130], [1173567600000,0.76270], [1173654000000,0.76270], [1173740400000,0.76080], [1173826800000,0.75830], [1173913200000,0.75750], [1173999600000,0.75620], [1174086000000,0.7520], [1174172400000,0.75120], [1174258800000,0.75120], [1174345200000,0.75170], [1174431600000,0.7520], [1174518000000,0.75110], [1174604400000,0.7480], [1174690800000,0.75090], [1174777200000,0.75310], [1174860000000,0.75310], [1174946400000,0.75270], [1175032800000,0.74980], [1175119200000,0.74930], [1175205600000,0.75040], [1175292000000,0.750], [1175378400000,0.74910], [1175464800000,0.74910], [1175551200000,0.74850], [1175637600000,0.74840], [1175724000000,0.74920], [1175810400000,0.74710], [1175896800000,0.74590], [1175983200000,0.74770], [1176069600000,0.74770], [1176156000000,0.74830], [1176242400000,0.74580], [1176328800000,0.74480], [1176415200000,0.7430], [1176501600000,0.73990], [1176588000000,0.73950], [1176674400000,0.73950], [1176760800000,0.73780], [1176847200000,0.73820], [1176933600000,0.73620], [1177020000000,0.73550], [1177106400000,0.73480], [1177192800000,0.73610], [1177279200000,0.73610], [1177365600000,0.73650], [1177452000000,0.73620], [1177538400000,0.73310], [1177624800000,0.73390], [1177711200000,0.73440], [1177797600000,0.73270], [1177884000000,0.73270], [1177970400000,0.73360], [1178056800000,0.73330], [1178143200000,0.73590], [1178229600000,0.73590], [1178316000000,0.73720], [1178402400000,0.7360], [1178488800000,0.7360], [1178575200000,0.7350], [1178661600000,0.73650], [1178748000000,0.73840], [1178834400000,0.73950], [1178920800000,0.74130], [1179007200000,0.73970], [1179093600000,0.73960], [1179180000000,0.73850], [1179266400000,0.73780], [1179352800000,0.73660], [1179439200000,0.740], [1179525600000,0.74110], [1179612000000,0.74060], [1179698400000,0.74050], [1179784800000,0.74140], [1179871200000,0.74310], [1179957600000,0.74310], [1180044000000,0.74380], [1180130400000,0.74430], [1180216800000,0.74430], [1180303200000,0.74430], [1180389600000,0.74340], [1180476000000,0.74290], [1180562400000,0.74420], [1180648800000,0.7440], [1180735200000,0.74390], [1180821600000,0.74370], [1180908000000,0.74370], [1180994400000,0.74290], [1181080800000,0.74030], [1181167200000,0.73990], [1181253600000,0.74180], [1181340000000,0.74680], [1181426400000,0.7480], [1181512800000,0.7480], [1181599200000,0.7490], [1181685600000,0.74940], [1181772000000,0.75220], [1181858400000,0.75150], [1181944800000,0.75020], [1182031200000,0.74720], [1182117600000,0.74720], [1182204000000,0.74620], [1182290400000,0.74550], [1182376800000,0.74490], [1182463200000,0.74670], [1182549600000,0.74580], [1182636000000,0.74270], [1182722400000,0.74270], [1182808800000,0.7430], [1182895200000,0.74290], [1182981600000,0.7440], [1183068000000,0.7430], [1183154400000,0.74220], [1183240800000,0.73880], [1183327200000,0.73880], [1183413600000,0.73690], [1183500000000,0.73450], [1183586400000,0.73450], [1183672800000,0.73450], [1183759200000,0.73520], [1183845600000,0.73410], [1183932000000,0.73410], [1184018400000,0.7340], [1184104800000,0.73240], [1184191200000,0.72720], [1184277600000,0.72640], [1184364000000,0.72550], [1184450400000,0.72580], [1184536800000,0.72580], [1184623200000,0.72560], [1184709600000,0.72570], [1184796000000,0.72470], [1184882400000,0.72430], [1184968800000,0.72440], [1185055200000,0.72350], [1185141600000,0.72350], [1185228000000,0.72350], [1185314400000,0.72350], [1185400800000,0.72620], [1185487200000,0.72880], [1185573600000,0.73010], [1185660000000,0.73370], [1185746400000,0.73370], [1185832800000,0.73240], [1185919200000,0.72970], [1186005600000,0.73170], [1186092000000,0.73150], [1186178400000,0.72880], [1186264800000,0.72630], [1186351200000,0.72630], [1186437600000,0.72420], [1186524000000,0.72530], [1186610400000,0.72640], [1186696800000,0.7270], [1186783200000,0.73120], [1186869600000,0.73050], [1186956000000,0.73050], [1187042400000,0.73180], [1187128800000,0.73580], [1187215200000,0.74090], [1187301600000,0.74540], [1187388000000,0.74370], [1187474400000,0.74240], [1187560800000,0.74240], [1187647200000,0.74150], [1187733600000,0.74190], [1187820000000,0.74140], [1187906400000,0.73770], [1187992800000,0.73550], [1188079200000,0.73150], [1188165600000,0.73150], [1188252000000,0.7320], [1188338400000,0.73320], [1188424800000,0.73460], [1188511200000,0.73280], [1188597600000,0.73230], [1188684000000,0.7340], [1188770400000,0.7340], [1188856800000,0.73360], [1188943200000,0.73510], [1189029600000,0.73460], [1189116000000,0.73210], [1189202400000,0.72940], [1189288800000,0.72660], [1189375200000,0.72660], [1189461600000,0.72540], [1189548000000,0.72420], [1189634400000,0.72130], [1189720800000,0.71970], [1189807200000,0.72090], [1189893600000,0.7210], [1189980000000,0.7210], [1190066400000,0.7210], [1190152800000,0.72090], [1190239200000,0.71590], [1190325600000,0.71330], [1190412000000,0.71050], [1190498400000,0.70990], [1190584800000,0.70990], [1190671200000,0.70930], [1190757600000,0.70930], [1190844000000,0.70760], [1190930400000,0.7070], [1191016800000,0.70490], [1191103200000,0.70120], [1191189600000,0.70110], [1191276000000,0.70190], [1191362400000,0.70460], [1191448800000,0.70630], [1191535200000,0.70890], [1191621600000,0.70770], [1191708000000,0.70770], [1191794400000,0.70770], [1191880800000,0.70910], [1191967200000,0.71180], [1192053600000,0.70790], [1192140000000,0.70530], [1192226400000,0.7050], [1192312800000,0.70550], [1192399200000,0.70550], [1192485600000,0.70450], [1192572000000,0.70510], [1192658400000,0.70510], [1192744800000,0.70170], [1192831200000,0.70], [1192917600000,0.69950], [1193004000000,0.69940], [1193090400000,0.70140], [1193176800000,0.70360], [1193263200000,0.70210], [1193349600000,0.70020], [1193436000000,0.69670], [1193522400000,0.6950], [1193612400000,0.6950], [1193698800000,0.69390], [1193785200000,0.6940], [1193871600000,0.69220], [1193958000000,0.69190], [1194044400000,0.69140], [1194130800000,0.68940], [1194217200000,0.68910], [1194303600000,0.69040], [1194390000000,0.6890], [1194476400000,0.68340], [1194562800000,0.68230], [1194649200000,0.68070], [1194735600000,0.68150], [1194822000000,0.68150], [1194908400000,0.68470], [1194994800000,0.68590], [1195081200000,0.68220], [1195167600000,0.68270], [1195254000000,0.68370], [1195340400000,0.68230], [1195426800000,0.68220], [1195513200000,0.68220], [1195599600000,0.67920], [1195686000000,0.67460], [1195772400000,0.67350], [1195858800000,0.67310], [1195945200000,0.67420], [1196031600000,0.67440], [1196118000000,0.67390], [1196204400000,0.67310], [1196290800000,0.67610], [1196377200000,0.67610], [1196463600000,0.67850], [1196550000000,0.68180], [1196636400000,0.68360], [1196722800000,0.68230], [1196809200000,0.68050], [1196895600000,0.67930], [1196982000000,0.68490], [1197068400000,0.68330], [1197154800000,0.68250], [1197241200000,0.68250], [1197327600000,0.68160], [1197414000000,0.67990], [1197500400000,0.68130], [1197586800000,0.68090], [1197673200000,0.68680], [1197759600000,0.69330], [1197846000000,0.69330], [1197932400000,0.69450], [1198018800000,0.69440], [1198105200000,0.69460], [1198191600000,0.69640], [1198278000000,0.69650], [1198364400000,0.69560], [1198450800000,0.69560], [1198537200000,0.6950], [1198623600000,0.69480], [1198710000000,0.69280], [1198796400000,0.68870], [1198882800000,0.68240], [1198969200000,0.67940], [1199055600000,0.67940], [1199142000000,0.68030], [1199228400000,0.68550], [1199314800000,0.68240], [1199401200000,0.67910], [1199487600000,0.67830], [1199574000000,0.67850], [1199660400000,0.67850], [1199746800000,0.67970], [1199833200000,0.680], [1199919600000,0.68030], [1200006000000,0.68050], [1200092400000,0.6760], [1200178800000,0.6770], [1200265200000,0.6770], [1200351600000,0.67360], [1200438000000,0.67260], [1200524400000,0.67640], [1200610800000,0.68210], [1200697200000,0.68310], [1200783600000,0.68420], [1200870000000,0.68420], [1200956400000,0.68870], [1201042800000,0.69030], [1201129200000,0.68480], [1201215600000,0.68240], [1201302000000,0.67880], [1201388400000,0.68140], [1201474800000,0.68140], [1201561200000,0.67970], [1201647600000,0.67690], [1201734000000,0.67650], [1201820400000,0.67330], [1201906800000,0.67290], [1201993200000,0.67580], [1202079600000,0.67580], [1202166000000,0.6750], [1202252400000,0.6780], [1202338800000,0.68330], [1202425200000,0.68560], [1202511600000,0.69030], [1202598000000,0.68960], [1202684400000,0.68960], [1202770800000,0.68820], [1202857200000,0.68790], [1202943600000,0.68620], [1203030000000,0.68520], [1203116400000,0.68230], [1203202800000,0.68130], [1203289200000,0.68130], [1203375600000,0.68220], [1203462000000,0.68020], [1203548400000,0.68020], [1203634800000,0.67840], [1203721200000,0.67480], [1203807600000,0.67470], [1203894000000,0.67470], [1203980400000,0.67480], [1204066800000,0.67330], [1204153200000,0.6650], [1204239600000,0.66110], [1204326000000,0.65830], [1204412400000,0.6590], [1204498800000,0.6590], [1204585200000,0.65810], [1204671600000,0.65780], [1204758000000,0.65740], [1204844400000,0.65320], [1204930800000,0.65020], [1205017200000,0.65140], [1205103600000,0.65140], [1205190000000,0.65070], [1205276400000,0.6510], [1205362800000,0.64890], [1205449200000,0.64240], [1205535600000,0.64060], [1205622000000,0.63820], [1205708400000,0.63820], [1205794800000,0.63410], [1205881200000,0.63440], [1205967600000,0.63780], [1206054000000,0.64390], [1206140400000,0.64780], [1206226800000,0.64810], [1206313200000,0.64810], [1206399600000,0.64940], [1206486000000,0.64380], [1206572400000,0.63770], [1206658800000,0.63290], [1206745200000,0.63360], [1206831600000,0.63330], [1206914400000,0.63330], [1207000800000,0.6330], [1207087200000,0.63710], [1207173600000,0.64030], [1207260000000,0.63960], [1207346400000,0.63640], [1207432800000,0.63560], [1207519200000,0.63560], [1207605600000,0.63680], [1207692000000,0.63570], [1207778400000,0.63540], [1207864800000,0.6320], [1207951200000,0.63320], [1208037600000,0.63280], [1208124000000,0.63310], [1208210400000,0.63420], [1208296800000,0.63210], [1208383200000,0.63020], [1208469600000,0.62780], [1208556000000,0.63080], [1208642400000,0.63240], [1208728800000,0.63240], [1208815200000,0.63070], [1208901600000,0.62770], [1208988000000,0.62690], [1209074400000,0.63350], [1209160800000,0.63920], [1209247200000,0.640], [1209333600000,0.64010], [1209420000000,0.63960], [1209506400000,0.64070], [1209592800000,0.64230], [1209679200000,0.64290], [1209765600000,0.64720], [1209852000000,0.64850], [1209938400000,0.64860], [1210024800000,0.64670], [1210111200000,0.64440], [1210197600000,0.64670], [1210284000000,0.65090], [1210370400000,0.64780], [1210456800000,0.64610], [1210543200000,0.64610], [1210629600000,0.64680], [1210716000000,0.64490], [1210802400000,0.6470], [1210888800000,0.64610], [1210975200000,0.64520], [1211061600000,0.64220], [1211148000000,0.64220], [1211234400000,0.64250], [1211320800000,0.64140], [1211407200000,0.63660], [1211493600000,0.63460], [1211580000000,0.6350], [1211666400000,0.63460], [1211752800000,0.63460], [1211839200000,0.63430], [1211925600000,0.63460], [1212012000000,0.63790], [1212098400000,0.64160], [1212184800000,0.64420], [1212271200000,0.64310], [1212357600000,0.64310], [1212444000000,0.64350], [1212530400000,0.6440], [1212616800000,0.64730], [1212703200000,0.64690], [1212789600000,0.63860], [1212876000000,0.63560], [1212962400000,0.6340], [1213048800000,0.63460], [1213135200000,0.6430], [1213221600000,0.64520], [1213308000000,0.64670], [1213394400000,0.65060], [1213480800000,0.65040], [1213567200000,0.65030], [1213653600000,0.64810], [1213740000000,0.64510], [1213826400000,0.6450], [1213912800000,0.64410], [1213999200000,0.64140], [1214085600000,0.64090], [1214172000000,0.64090], [1214258400000,0.64280], [1214344800000,0.64310], [1214431200000,0.64180], [1214517600000,0.63710], [1214604000000,0.63490], [1214690400000,0.63330], [1214776800000,0.63340], [1214863200000,0.63380], [1214949600000,0.63420], [1215036000000,0.6320], [1215122400000,0.63180], [1215208800000,0.6370], [1215295200000,0.63680], [1215381600000,0.63680], [1215468000000,0.63830], [1215554400000,0.63710], [1215640800000,0.63710], [1215727200000,0.63550], [1215813600000,0.6320], [1215900000000,0.62770], [1215986400000,0.62760], [1216072800000,0.62910], [1216159200000,0.62740], [1216245600000,0.62930], [1216332000000,0.63110], [1216418400000,0.6310], [1216504800000,0.63120], [1216591200000,0.63120], [1216677600000,0.63040], [1216764000000,0.62940], [1216850400000,0.63480], [1216936800000,0.63780], [1217023200000,0.63680], [1217109600000,0.63680], [1217196000000,0.63680], [1217282400000,0.6360], [1217368800000,0.6370], [1217455200000,0.64180], [1217541600000,0.64110], [1217628000000,0.64350], [1217714400000,0.64270], [1217800800000,0.64270], [1217887200000,0.64190], [1217973600000,0.64460], [1218060000000,0.64680], [1218146400000,0.64870], [1218232800000,0.65940], [1218319200000,0.66660], [1218405600000,0.66660], [1218492000000,0.66780], [1218578400000,0.67120], [1218664800000,0.67050], [1218751200000,0.67180], [1218837600000,0.67840], [1218924000000,0.68110], [1219010400000,0.68110], [1219096800000,0.67940], [1219183200000,0.68040], [1219269600000,0.67810], [1219356000000,0.67560], [1219442400000,0.67350], [1219528800000,0.67630], [1219615200000,0.67620], [1219701600000,0.67770], [1219788000000,0.68150], [1219874400000,0.68020], [1219960800000,0.6780], [1220047200000,0.67960], [1220133600000,0.68170], [1220220000000,0.68170], [1220306400000,0.68320], [1220392800000,0.68770], [1220479200000,0.69120], [1220565600000,0.69140], [1220652000000,0.70090], [1220738400000,0.70120], [1220824800000,0.7010], [1220911200000,0.70050]];
+
+ var data = [
+ { data: oilPrices, label: "Oil price ($)" },
+ { data: exchangeRates, label: "USD/EUR exchange rate", yaxis: 2 }
+ ];
+
+ var options = {
+ canvas: true,
+ xaxes: [ { mode: "time" } ],
+ yaxes: [ { min: 0 }, {
+ position: "right",
+ alignTicksWithAxis: 1,
+ tickFormatter: function(value, axis) {
+ return value.toFixed(axis.tickDecimals) + "€";
+ }
+ } ],
+ legend: { position: "sw" }
+ }
+
+ $.plot("#placeholder", data, options);
+
+ $("input").change(function () {
+ options.canvas = $(this).is(":checked");
+ $.plot("#placeholder", data, options);
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Canvas text</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>This example uses the same dataset (raw oil price in US $/barrel of crude oil vs. the exchange rate from US $ to €) as the multiple-axes example, but uses the canvas plugin to render axis tick labels using canvas text.</p>
+
+ <p><input type="checkbox" checked="checked">Enable canvas text</input></p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/categories/index.html b/misc/flot/examples/categories/index.html
new file mode 100644
index 0000000..5841676
--- /dev/null
+++ b/misc/flot/examples/categories/index.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Categories</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.categories.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var data = [ ["January", 10], ["February", 8], ["March", 4], ["April", 13], ["May", 17], ["June", 9] ];
+
+ $.plot("#placeholder", [ data ], {
+ series: {
+ bars: {
+ show: true,
+ barWidth: 0.6,
+ align: "center"
+ }
+ },
+ xaxis: {
+ mode: "categories",
+ tickLength: 0
+ }
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Categories</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>With the categories plugin you can plot categories/textual data easily.</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
+
+
+
+
+
diff --git a/misc/flot/examples/examples.css b/misc/flot/examples/examples.css
new file mode 100644
index 0000000..ee47247
--- /dev/null
+++ b/misc/flot/examples/examples.css
@@ -0,0 +1,97 @@
+* { padding: 0; margin: 0; vertical-align: top; }
+
+body {
+ background: url(background.png) repeat-x;
+ font: 18px/1.5em "proxima-nova", Helvetica, Arial, sans-serif;
+}
+
+a { color: #069; }
+a:hover { color: #28b; }
+
+h2 {
+ margin-top: 15px;
+ font: normal 32px "omnes-pro", Helvetica, Arial, sans-serif;
+}
+
+h3 {
+ margin-left: 30px;
+ font: normal 26px "omnes-pro", Helvetica, Arial, sans-serif;
+ color: #666;
+}
+
+p {
+ margin-top: 10px;
+}
+
+button {
+ font-size: 18px;
+ padding: 1px 7px;
+}
+
+input {
+ font-size: 18px;
+}
+
+input[type=checkbox] {
+ margin: 7px;
+}
+
+#header {
+ position: relative;
+ width: 900px;
+ margin: auto;
+}
+
+#header h2 {
+ margin-left: 10px;
+ vertical-align: middle;
+ font-size: 42px;
+ font-weight: bold;
+ text-decoration: none;
+ color: #000;
+}
+
+#content {
+ width: 880px;
+ margin: 0 auto;
+ padding: 10px;
+}
+
+#footer {
+ margin-top: 25px;
+ margin-bottom: 10px;
+ text-align: center;
+ font-size: 12px;
+ color: #999;
+}
+
+.demo-container {
+ box-sizing: border-box;
+ width: 850px;
+ height: 450px;
+ padding: 20px 15px 15px 15px;
+ margin: 15px auto 30px auto;
+ border: 1px solid #ddd;
+ background: #fff;
+ background: linear-gradient(#f6f6f6 0, #fff 50px);
+ background: -o-linear-gradient(#f6f6f6 0, #fff 50px);
+ background: -ms-linear-gradient(#f6f6f6 0, #fff 50px);
+ background: -moz-linear-gradient(#f6f6f6 0, #fff 50px);
+ background: -webkit-linear-gradient(#f6f6f6 0, #fff 50px);
+ box-shadow: 0 3px 10px rgba(0,0,0,0.15);
+ -o-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
+ -ms-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
+ -moz-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
+ -webkit-box-shadow: 0 3px 10px rgba(0,0,0,0.1);
+}
+
+.demo-placeholder {
+ width: 100%;
+ height: 100%;
+ font-size: 14px;
+ line-height: 1.2em;
+}
+
+.legend table {
+ border-spacing: 5px;
+} \ No newline at end of file
diff --git a/misc/flot/examples/image/hs-2004-27-a-large-web.jpg b/misc/flot/examples/image/hs-2004-27-a-large-web.jpg
new file mode 100644
index 0000000..a1d5c05
--- /dev/null
+++ b/misc/flot/examples/image/hs-2004-27-a-large-web.jpg
Binary files differ
diff --git a/misc/flot/examples/image/index.html b/misc/flot/examples/image/index.html
new file mode 100644
index 0000000..450101c
--- /dev/null
+++ b/misc/flot/examples/image/index.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Image Plots</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.image.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var data = [[["hs-2004-27-a-large-web.jpg", -10, -10, 10, 10]]];
+
+ var options = {
+ series: {
+ images: {
+ show: true
+ }
+ },
+ xaxis: {
+ min: -8,
+ max: 4
+ },
+ yaxis: {
+ min: -8,
+ max: 4
+ }
+ };
+
+ $.plot.image.loadDataImages(data, options, function () {
+ $.plot("#placeholder", data, options);
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Image Plots</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container" style="width:600px;height:600px;">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>The Cat's Eye Nebula (<a href="http://hubblesite.org/gallery/album/nebula/pr2004027a/">picture from Hubble</a>).</p>
+
+ <p>With the image plugin, you can plot static images against a set of axes. This is for useful for adding ticks to complex prerendered visualizations. Instead of inputting data points, you specify the images and where their two opposite corners are supposed to be in plot space.</p>
+
+ <p>Images represent a little further complication because you need to make sure they are loaded before you can use them (Flot skips incomplete images). The plugin comes with a couple of helpers for doing that.</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/index.html b/misc/flot/examples/index.html
new file mode 100644
index 0000000..6975d29
--- /dev/null
+++ b/misc/flot/examples/index.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples</title>
+ <link href="examples.css" rel="stylesheet" type="text/css">
+ <style>
+
+ h3 {
+ margin-top: 30px;
+ margin-bottom: 5px;
+ }
+
+ </style>
+ <script language="javascript" type="text/javascript" src="../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../jquery.flot.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Flot Examples</h2>
+ </div>
+
+ <div id="content">
+
+ <p>Here are some examples for <a href="http://www.flotcharts.org">Flot</a>, the Javascript charting library for jQuery:</p>
+
+ <h3>Basic Usage</h3>
+
+ <ul>
+ <li><a href="basic-usage/index.html">Basic example</a></li>
+ <li><a href="series-types/index.html">Different graph types</a> and <a href="categories/index.html">simple categories/textual data</a></li>
+ <li><a href="basic-options/index.html">Setting various options</a> and <a href="annotating/index.html">annotating a chart</a></li>
+ <li><a href="ajax/index.html">Updating graphs with AJAX</a> and <a href="realtime/index.html">real-time updates</a></li>
+ </ul>
+
+ <h3>Interactivity</h3>
+
+ <ul>
+ <li><a href="series-toggle/index.html">Turning series on/off</a></li>
+ <li><a href="selection/index.html">Rectangular selection support and zooming</a> and <a href="zooming/index.html">zooming with overview</a> (both with selection plugin)</li>
+ <li><a href="interacting/index.html">Interacting with the data points</a></li>
+ <li><a href="navigate/index.html">Panning and zooming</a> (with navigation plugin)</li>
+ <li><a href="resize/index.html">Automatically redraw when window is resized</a> (with resize plugin)</li>
+ </ul>
+
+ <h3>Additional Features</h3>
+
+ <ul>
+ <li><a href="symbols/index.html">Using other symbols than circles for points</a> (with symbol plugin)</li>
+ <li><a href="axes-time/index.html">Plotting time series</a>, <a href="visitors/index.html">visitors per day with zooming and weekends</a> (with selection plugin) and <a href="axes-time-zones/index.html">time zone support</a></li>
+ <li><a href="axes-multiple/index.html">Multiple axes</a> and <a href="axes-interacting/index.html">interacting with the axes</a></li>
+ <li><a href="threshold/index.html">Thresholding the data</a> (with threshold plugin)</li>
+ <li><a href="stacking/index.html">Stacked charts</a> (with stacking plugin)</li>
+ <li><a href="percentiles/index.html">Using filled areas to plot percentiles</a> (with fillbetween plugin)</li>
+ <li><a href="tracking/index.html">Tracking curves with crosshair</a> (with crosshair plugin)</li>
+ <li><a href="image/index.html">Plotting prerendered images</a> (with image plugin)</li>
+ <li><a href="series-errorbars/index.html">Plotting error bars</a> (with errorbars plugin)</li>
+ <li><a href="series-pie/index.html">Pie charts</a> (with pie plugin)</li>
+ <li><a href="canvas/index.html">Rendering text with canvas instead of HTML</a> (with canvas plugin)</li>
+ </ul>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2013 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/interacting/index.html b/misc/flot/examples/interacting/index.html
new file mode 100644
index 0000000..31169db
--- /dev/null
+++ b/misc/flot/examples/interacting/index.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Interactivity</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var sin = [],
+ cos = [];
+
+ for (var i = 0; i < 14; i += 0.5) {
+ sin.push([i, Math.sin(i)]);
+ cos.push([i, Math.cos(i)]);
+ }
+
+ var plot = $.plot("#placeholder", [
+ { data: sin, label: "sin(x)"},
+ { data: cos, label: "cos(x)"}
+ ], {
+ series: {
+ lines: {
+ show: true
+ },
+ points: {
+ show: true
+ }
+ },
+ grid: {
+ hoverable: true,
+ clickable: true
+ },
+ yaxis: {
+ min: -1.2,
+ max: 1.2
+ }
+ });
+
+ $("<div id='tooltip'></div>").css({
+ position: "absolute",
+ display: "none",
+ border: "1px solid #fdd",
+ padding: "2px",
+ "background-color": "#fee",
+ opacity: 0.80
+ }).appendTo("body");
+
+ $("#placeholder").bind("plothover", function (event, pos, item) {
+
+ if ($("#enablePosition:checked").length > 0) {
+ var str = "(" + pos.x.toFixed(2) + ", " + pos.y.toFixed(2) + ")";
+ $("#hoverdata").text(str);
+ }
+
+ if ($("#enableTooltip:checked").length > 0) {
+ if (item) {
+ var x = item.datapoint[0].toFixed(2),
+ y = item.datapoint[1].toFixed(2);
+
+ $("#tooltip").html(item.series.label + " of " + x + " = " + y)
+ .css({top: item.pageY+5, left: item.pageX+5})
+ .fadeIn(200);
+ } else {
+ $("#tooltip").hide();
+ }
+ }
+ });
+
+ $("#placeholder").bind("plotclick", function (event, pos, item) {
+ if (item) {
+ $("#clickdata").text(" - click point " + item.dataIndex + " in " + item.series.label);
+ plot.highlight(item.series, item.datapoint);
+ }
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+ <div id="header">
+ <h2>Interactivity</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>One of the goals of Flot is to support user interactions. Try pointing and clicking on the points.</p>
+
+ <p>
+ <label><input id="enablePosition" type="checkbox" checked="checked"></input>Show mouse position</label>
+ <span id="hoverdata"></span>
+ <span id="clickdata"></span>
+ </p>
+
+ <p>A tooltip is easy to build with a bit of jQuery code and the data returned from the plot.</p>
+
+ <p><label><input id="enableTooltip" type="checkbox" checked="checked"></input>Enable tooltip</label></p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/navigate/arrow-down.gif b/misc/flot/examples/navigate/arrow-down.gif
new file mode 100644
index 0000000..e239d11
--- /dev/null
+++ b/misc/flot/examples/navigate/arrow-down.gif
Binary files differ
diff --git a/misc/flot/examples/navigate/arrow-left.gif b/misc/flot/examples/navigate/arrow-left.gif
new file mode 100644
index 0000000..93ffd5a
--- /dev/null
+++ b/misc/flot/examples/navigate/arrow-left.gif
Binary files differ
diff --git a/misc/flot/examples/navigate/arrow-right.gif b/misc/flot/examples/navigate/arrow-right.gif
new file mode 100644
index 0000000..5fd0530
--- /dev/null
+++ b/misc/flot/examples/navigate/arrow-right.gif
Binary files differ
diff --git a/misc/flot/examples/navigate/arrow-up.gif b/misc/flot/examples/navigate/arrow-up.gif
new file mode 100644
index 0000000..7d19626
--- /dev/null
+++ b/misc/flot/examples/navigate/arrow-up.gif
Binary files differ
diff --git a/misc/flot/examples/navigate/index.html b/misc/flot/examples/navigate/index.html
new file mode 100644
index 0000000..671692e
--- /dev/null
+++ b/misc/flot/examples/navigate/index.html
@@ -0,0 +1,153 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Navigation</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <style type="text/css">
+
+ #placeholder .button {
+ position: absolute;
+ cursor: pointer;
+ }
+
+ #placeholder div.button {
+ font-size: smaller;
+ color: #999;
+ background-color: #eee;
+ padding: 2px;
+ }
+ .message {
+ padding-left: 50px;
+ font-size: smaller;
+ }
+
+ </style>
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.navigate.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ // generate data set from a parametric function with a fractal look
+
+ function sumf(f, t, m) {
+ var res = 0;
+ for (var i = 1; i < m; ++i) {
+ res += f(i * i * t) / (i * i);
+ }
+ return res;
+ }
+
+ var d1 = [];
+ for (var t = 0; t <= 2 * Math.PI; t += 0.01) {
+ d1.push([sumf(Math.cos, t, 10), sumf(Math.sin, t, 10)]);
+ }
+
+ var data = [ d1 ],
+ placeholder = $("#placeholder");
+
+ var plot = $.plot(placeholder, data, {
+ series: {
+ lines: {
+ show: true
+ },
+ shadowSize: 0
+ },
+ xaxis: {
+ zoomRange: [0.1, 10],
+ panRange: [-10, 10]
+ },
+ yaxis: {
+ zoomRange: [0.1, 10],
+ panRange: [-10, 10]
+ },
+ zoom: {
+ interactive: true
+ },
+ pan: {
+ interactive: true
+ }
+ });
+
+ // show pan/zoom messages to illustrate events
+
+ placeholder.bind("plotpan", function (event, plot) {
+ var axes = plot.getAxes();
+ $(".message").html("Panning to x: " + axes.xaxis.min.toFixed(2)
+ + " &ndash; " + axes.xaxis.max.toFixed(2)
+ + " and y: " + axes.yaxis.min.toFixed(2)
+ + " &ndash; " + axes.yaxis.max.toFixed(2));
+ });
+
+ placeholder.bind("plotzoom", function (event, plot) {
+ var axes = plot.getAxes();
+ $(".message").html("Zooming to x: " + axes.xaxis.min.toFixed(2)
+ + " &ndash; " + axes.xaxis.max.toFixed(2)
+ + " and y: " + axes.yaxis.min.toFixed(2)
+ + " &ndash; " + axes.yaxis.max.toFixed(2));
+ });
+
+ // add zoom out button
+
+ $("<div class='button' style='right:20px;top:20px'>zoom out</div>")
+ .appendTo(placeholder)
+ .click(function (event) {
+ event.preventDefault();
+ plot.zoomOut();
+ });
+
+ // and add panning buttons
+
+ // little helper for taking the repetitive work out of placing
+ // panning arrows
+
+ function addArrow(dir, right, top, offset) {
+ $("<img class='button' src='arrow-" + dir + ".gif' style='right:" + right + "px;top:" + top + "px'>")
+ .appendTo(placeholder)
+ .click(function (e) {
+ e.preventDefault();
+ plot.pan(offset);
+ });
+ }
+
+ addArrow("left", 55, 60, { left: -100 });
+ addArrow("right", 25, 60, { left: 100 });
+ addArrow("up", 40, 45, { top: -100 });
+ addArrow("down", 40, 75, { top: 100 });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Navigation</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p class="message"></p>
+
+ <p>With the navigate plugin it is easy to add panning and zooming. Drag to pan, double click to zoom (or use the mouse scrollwheel).</p>
+
+ <p>The plugin fires events (useful for synchronizing several plots) and adds a couple of public methods so you can easily build a little user interface around it, like the little buttons at the top right in the plot.</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/percentiles/index.html b/misc/flot/examples/percentiles/index.html
new file mode 100644
index 0000000..57df2a5
--- /dev/null
+++ b/misc/flot/examples/percentiles/index.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Percentiles</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.fillbetween.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var males = {"15%": [[2, 88.0], [3, 93.3], [4, 102.0], [5, 108.5], [6, 115.7], [7, 115.6], [8, 124.6], [9, 130.3], [10, 134.3], [11, 141.4], [12, 146.5], [13, 151.7], [14, 159.9], [15, 165.4], [16, 167.8], [17, 168.7], [18, 169.5], [19, 168.0]], "90%": [[2, 96.8], [3, 105.2], [4, 113.9], [5, 120.8], [6, 127.0], [7, 133.1], [8, 139.1], [9, 143.9], [10, 151.3], [11, 161.1], [12, 164.8], [13, 173.5], [14, 179.0], [15, 182.0], [16, 186.9], [17, 185.2], [18, 186.3], [19, 186.6]], "25%": [[2, 89.2], [3, 94.9], [4, 104.4], [5, 111.4], [6, 117.5], [7, 120.2], [8, 127.1], [9, 132.9], [10, 136.8], [11, 144.4], [12, 149.5], [13, 154.1], [14, 163.1], [15, 169.2], [16, 170.4], [17, 171.2], [18, 172.4], [19, 170.8]], "10%": [[2, 86.9], [3, 92.6], [4, 99.9], [5, 107.0], [6, 114.0], [7, 113.5], [8, 123.6], [9, 129.2], [10, 133.0], [11, 140.6], [12, 145.2], [13, 149.7], [14, 158.4], [15, 163.5], [16, 166.9], [17, 167.5], [18, 167.1], [19, 165.3]], "mean": [[2, 91.9], [3, 98.5], [4, 107.1], [5, 114.4], [6, 120.6], [7, 124.7], [8, 131.1], [9, 136.8], [10, 142.3], [11, 150.0], [12, 154.7], [13, 161.9], [14, 168.7], [15, 173.6], [16, 175.9], [17, 176.6], [18, 176.8], [19, 176.7]], "75%": [[2, 94.5], [3, 102.1], [4, 110.8], [5, 117.9], [6, 124.0], [7, 129.3], [8, 134.6], [9, 141.4], [10, 147.0], [11, 156.1], [12, 160.3], [13, 168.3], [14, 174.7], [15, 178.0], [16, 180.2], [17, 181.7], [18, 181.3], [19, 182.5]], "85%": [[2, 96.2], [3, 103.8], [4, 111.8], [5, 119.6], [6, 125.6], [7, 131.5], [8, 138.0], [9, 143.3], [10, 149.3], [11, 159.8], [12, 162.5], [13, 171.3], [14, 177.5], [15, 180.2], [16, 183.8], [17, 183.4], [18, 183.5], [19, 185.5]], "50%": [[2, 91.9], [3, 98.2], [4, 106.8], [5, 114.6], [6, 120.8], [7, 125.2], [8, 130.3], [9, 137.1], [10, 141.5], [11, 149.4], [12, 153.9], [13, 162.2], [14, 169.0], [15, 174.8], [16, 176.0], [17, 176.8], [18, 176.4], [19, 177.4]]};
+
+ var females = {"15%": [[2, 84.8], [3, 93.7], [4, 100.6], [5, 105.8], [6, 113.3], [7, 119.3], [8, 124.3], [9, 131.4], [10, 136.9], [11, 143.8], [12, 149.4], [13, 151.2], [14, 152.3], [15, 155.9], [16, 154.7], [17, 157.0], [18, 156.1], [19, 155.4]], "90%": [[2, 95.6], [3, 104.1], [4, 111.9], [5, 119.6], [6, 127.6], [7, 133.1], [8, 138.7], [9, 147.1], [10, 152.8], [11, 161.3], [12, 166.6], [13, 167.9], [14, 169.3], [15, 170.1], [16, 172.4], [17, 169.2], [18, 171.1], [19, 172.4]], "25%": [[2, 87.2], [3, 95.9], [4, 101.9], [5, 107.4], [6, 114.8], [7, 121.4], [8, 126.8], [9, 133.4], [10, 138.6], [11, 146.2], [12, 152.0], [13, 153.8], [14, 155.7], [15, 158.4], [16, 157.0], [17, 158.5], [18, 158.4], [19, 158.1]], "10%": [[2, 84.0], [3, 91.9], [4, 99.2], [5, 105.2], [6, 112.7], [7, 118.0], [8, 123.3], [9, 130.2], [10, 135.0], [11, 141.1], [12, 148.3], [13, 150.0], [14, 150.7], [15, 154.3], [16, 153.6], [17, 155.6], [18, 154.7], [19, 153.1]], "mean": [[2, 90.2], [3, 98.3], [4, 105.2], [5, 112.2], [6, 119.0], [7, 125.8], [8, 131.3], [9, 138.6], [10, 144.2], [11, 151.3], [12, 156.7], [13, 158.6], [14, 160.5], [15, 162.1], [16, 162.9], [17, 162.2], [18, 163.0], [19, 163.1]], "75%": [[2, 93.2], [3, 101.5], [4, 107.9], [5, 116.6], [6, 122.8], [7, 129.3], [8, 135.2], [9, 143.7], [10, 148.7], [11, 156.9], [12, 160.8], [13, 163.0], [14, 165.0], [15, 165.8], [16, 168.7], [17, 166.2], [18, 167.6], [19, 168.0]], "85%": [[2, 94.5], [3, 102.8], [4, 110.4], [5, 119.0], [6, 125.7], [7, 131.5], [8, 137.9], [9, 146.0], [10, 151.3], [11, 159.9], [12, 164.0], [13, 166.5], [14, 167.5], [15, 168.5], [16, 171.5], [17, 168.0], [18, 169.8], [19, 170.3]], "50%": [[2, 90.2], [3, 98.1], [4, 105.2], [5, 111.7], [6, 118.2], [7, 125.6], [8, 130.5], [9, 138.3], [10, 143.7], [11, 151.4], [12, 156.7], [13, 157.7], [14, 161.0], [15, 162.0], [16, 162.8], [17, 162.2], [18, 162.8], [19, 163.3]]};
+
+ var dataset = [
+ { label: "Female mean", data: females["mean"], lines: { show: true }, color: "rgb(255,50,50)" },
+ { id: "f15%", data: females["15%"], lines: { show: true, lineWidth: 0, fill: false }, color: "rgb(255,50,50)" },
+ { id: "f25%", data: females["25%"], lines: { show: true, lineWidth: 0, fill: 0.2 }, color: "rgb(255,50,50)", fillBetween: "f15%" },
+ { id: "f50%", data: females["50%"], lines: { show: true, lineWidth: 0.5, fill: 0.4, shadowSize: 0 }, color: "rgb(255,50,50)", fillBetween: "f25%" },
+ { id: "f75%", data: females["75%"], lines: { show: true, lineWidth: 0, fill: 0.4 }, color: "rgb(255,50,50)", fillBetween: "f50%" },
+ { id: "f85%", data: females["85%"], lines: { show: true, lineWidth: 0, fill: 0.2 }, color: "rgb(255,50,50)", fillBetween: "f75%" },
+
+ { label: "Male mean", data: males["mean"], lines: { show: true }, color: "rgb(50,50,255)" },
+ { id: "m15%", data: males["15%"], lines: { show: true, lineWidth: 0, fill: false }, color: "rgb(50,50,255)" },
+ { id: "m25%", data: males["25%"], lines: { show: true, lineWidth: 0, fill: 0.2 }, color: "rgb(50,50,255)", fillBetween: "m15%" },
+ { id: "m50%", data: males["50%"], lines: { show: true, lineWidth: 0.5, fill: 0.4, shadowSize: 0 }, color: "rgb(50,50,255)", fillBetween: "m25%" },
+ { id: "m75%", data: males["75%"], lines: { show: true, lineWidth: 0, fill: 0.4 }, color: "rgb(50,50,255)", fillBetween: "m50%" },
+ { id: "m85%", data: males["85%"], lines: { show: true, lineWidth: 0, fill: 0.2 }, color: "rgb(50,50,255)", fillBetween: "m75%" }
+ ];
+
+ $.plot($("#placeholder"), dataset, {
+ xaxis: {
+ tickDecimals: 0
+ },
+ yaxis: {
+ tickFormatter: function (v) {
+ return v + " cm";
+ }
+ },
+ legend: {
+ position: "se"
+ }
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Percentiles</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>Height in centimeters of individuals from the US (2003-2006) as function of age in years (source: <a href="http://www.cdc.gov/nchs/data/nhsr/nhsr010.pdf">CDC</a>). The 15%-85%, 25%-75% and 50% percentiles are indicated.</p>
+
+ <p>For each point of a filled curve, you can specify an arbitrary bottom. As this example illustrates, this can be useful for plotting percentiles. If you have the data sets available without appropriate fill bottoms, you can use the fillbetween plugin to compute the data point bottoms automatically.</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/realtime/index.html b/misc/flot/examples/realtime/index.html
new file mode 100644
index 0000000..8742a29
--- /dev/null
+++ b/misc/flot/examples/realtime/index.html
@@ -0,0 +1,122 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Real-time updates</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ // We use an inline data source in the example, usually data would
+ // be fetched from a server
+
+ var data = [],
+ totalPoints = 300;
+
+ function getRandomData() {
+
+ if (data.length > 0)
+ data = data.slice(1);
+
+ // Do a random walk
+
+ while (data.length < totalPoints) {
+
+ var prev = data.length > 0 ? data[data.length - 1] : 50,
+ y = prev + Math.random() * 10 - 5;
+
+ if (y < 0) {
+ y = 0;
+ } else if (y > 100) {
+ y = 100;
+ }
+
+ data.push(y);
+ }
+
+ // Zip the generated y values with the x values
+
+ var res = [];
+ for (var i = 0; i < data.length; ++i) {
+ res.push([i, data[i]])
+ }
+
+ return res;
+ }
+
+ // Set up the control widget
+
+ var updateInterval = 30;
+ $("#updateInterval").val(updateInterval).change(function () {
+ var v = $(this).val();
+ if (v && !isNaN(+v)) {
+ updateInterval = +v;
+ if (updateInterval < 1) {
+ updateInterval = 1;
+ } else if (updateInterval > 2000) {
+ updateInterval = 2000;
+ }
+ $(this).val("" + updateInterval);
+ }
+ });
+
+ var plot = $.plot("#placeholder", [ getRandomData() ], {
+ series: {
+ shadowSize: 0 // Drawing is faster without shadows
+ },
+ yaxis: {
+ min: 0,
+ max: 100
+ },
+ xaxis: {
+ show: false
+ }
+ });
+
+ function update() {
+
+ plot.setData([getRandomData()]);
+
+ // Since the axes don't change, we don't need to call plot.setupGrid()
+
+ plot.draw();
+ setTimeout(update, updateInterval);
+ }
+
+ update();
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Real-time updates</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>You can update a chart periodically to get a real-time effect by using a timer to insert the new data in the plot and redraw it.</p>
+
+ <p>Time between updates: <input id="updateInterval" type="text" value="" style="text-align: right; width:5em"> milliseconds</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/resize/index.html b/misc/flot/examples/resize/index.html
new file mode 100644
index 0000000..46b815c
--- /dev/null
+++ b/misc/flot/examples/resize/index.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Resizing</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <link href="../shared/jquery-ui/jquery-ui.min.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../shared/jquery-ui/jquery-ui.min.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.resize.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var d1 = [];
+ for (var i = 0; i < 14; i += 0.5) {
+ d1.push([i, Math.sin(i)]);
+ }
+
+ var d2 = [[0, 3], [4, 8], [8, 5], [9, 13]];
+ var d3 = [[0, 12], [7, 12], null, [7, 2.5], [12, 2.5]];
+
+ var placeholder = $("#placeholder");
+ var plot = $.plot(placeholder, [d1, d2, d3]);
+
+ // The plugin includes a jQuery plugin for adding resize events to any
+ // element. Add a callback so we can display the placeholder size.
+
+ placeholder.resize(function () {
+ $(".message").text("Placeholder is now "
+ + $(this).width() + "x" + $(this).height()
+ + " pixels");
+ });
+
+ $(".demo-container").resizable({
+ maxWidth: 900,
+ maxHeight: 500,
+ minWidth: 450,
+ minHeight: 250
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Resizing</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p class="message"></p>
+
+ <p>Sometimes it makes more sense to just let the plot take up the available space. In that case, we need to redraw the plot each time the placeholder changes its size. If you include the resize plugin, this is handled automatically.</p>
+
+ <p>Drag the bottom and right sides of the plot to resize it.</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/selection/index.html b/misc/flot/examples/selection/index.html
new file mode 100644
index 0000000..48db7d3
--- /dev/null
+++ b/misc/flot/examples/selection/index.html
@@ -0,0 +1,152 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Selection</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.selection.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ // Shim allowing us to get the state of the check-box on jQuery versions
+ // prior to 1.6, when prop was added. The reason we don't just use attr
+ // is because it doesn't work in jQuery versions 1.9.x and later.
+
+ // TODO: Remove this once Flot's minimum supported jQuery reaches 1.6.
+ if (typeof $.fn.prop != 'function') {
+ $.fn.prop = $.fn.attr;
+ }
+
+ var data = [{
+ label: "United States",
+ data: [[1990, 18.9], [1991, 18.7], [1992, 18.4], [1993, 19.3], [1994, 19.5], [1995, 19.3], [1996, 19.4], [1997, 20.2], [1998, 19.8], [1999, 19.9], [2000, 20.4], [2001, 20.1], [2002, 20.0], [2003, 19.8], [2004, 20.4]]
+ }, {
+ label: "Russia",
+ data: [[1992, 13.4], [1993, 12.2], [1994, 10.6], [1995, 10.2], [1996, 10.1], [1997, 9.7], [1998, 9.5], [1999, 9.7], [2000, 9.9], [2001, 9.9], [2002, 9.9], [2003, 10.3], [2004, 10.5]]
+ }, {
+ label: "United Kingdom",
+ data: [[1990, 10.0], [1991, 11.3], [1992, 9.9], [1993, 9.6], [1994, 9.5], [1995, 9.5], [1996, 9.9], [1997, 9.3], [1998, 9.2], [1999, 9.2], [2000, 9.5], [2001, 9.6], [2002, 9.3], [2003, 9.4], [2004, 9.79]]
+ }, {
+ label: "Germany",
+ data: [[1990, 12.4], [1991, 11.2], [1992, 10.8], [1993, 10.5], [1994, 10.4], [1995, 10.2], [1996, 10.5], [1997, 10.2], [1998, 10.1], [1999, 9.6], [2000, 9.7], [2001, 10.0], [2002, 9.7], [2003, 9.8], [2004, 9.79]]
+ }, {
+ label: "Denmark",
+ data: [[1990, 9.7], [1991, 12.1], [1992, 10.3], [1993, 11.3], [1994, 11.7], [1995, 10.6], [1996, 12.8], [1997, 10.8], [1998, 10.3], [1999, 9.4], [2000, 8.7], [2001, 9.0], [2002, 8.9], [2003, 10.1], [2004, 9.80]]
+ }, {
+ label: "Sweden",
+ data: [[1990, 5.8], [1991, 6.0], [1992, 5.9], [1993, 5.5], [1994, 5.7], [1995, 5.3], [1996, 6.1], [1997, 5.4], [1998, 5.4], [1999, 5.1], [2000, 5.2], [2001, 5.4], [2002, 6.2], [2003, 5.9], [2004, 5.89]]
+ }, {
+ label: "Norway",
+ data: [[1990, 8.3], [1991, 8.3], [1992, 7.8], [1993, 8.3], [1994, 8.4], [1995, 5.9], [1996, 6.4], [1997, 6.7], [1998, 6.9], [1999, 7.6], [2000, 7.4], [2001, 8.1], [2002, 12.5], [2003, 9.9], [2004, 19.0]]
+ }];
+
+ var options = {
+ series: {
+ lines: {
+ show: true
+ },
+ points: {
+ show: true
+ }
+ },
+ legend: {
+ noColumns: 2
+ },
+ xaxis: {
+ tickDecimals: 0
+ },
+ yaxis: {
+ min: 0
+ },
+ selection: {
+ mode: "x"
+ }
+ };
+
+ var placeholder = $("#placeholder");
+
+ placeholder.bind("plotselected", function (event, ranges) {
+
+ $("#selection").text(ranges.xaxis.from.toFixed(1) + " to " + ranges.xaxis.to.toFixed(1));
+
+ var zoom = $("#zoom").prop("checked");
+
+ if (zoom) {
+ $.each(plot.getXAxes(), function(_, axis) {
+ var opts = axis.options;
+ opts.min = ranges.xaxis.from;
+ opts.max = ranges.xaxis.to;
+ });
+ plot.setupGrid();
+ plot.draw();
+ plot.clearSelection();
+ }
+ });
+
+ placeholder.bind("plotunselected", function (event) {
+ $("#selection").text("");
+ });
+
+ var plot = $.plot(placeholder, data, options);
+
+ $("#clearSelection").click(function () {
+ plot.clearSelection();
+ });
+
+ $("#setSelection").click(function () {
+ plot.setSelection({
+ xaxis: {
+ from: 1994,
+ to: 1995
+ }
+ });
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Selection</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>1000 kg. CO<sub>2</sub> emissions per year per capita for various countries (source: <a href="http://en.wikipedia.org/wiki/List_of_countries_by_carbon_dioxide_emissions_per_capita">Wikipedia</a>).</p>
+
+ <p>Flot supports selections through the selection plugin. You can enable rectangular selection or one-dimensional selection if the user should only be able to select on one axis. Try left-click and drag on the plot above where selection on the x axis is enabled.</p>
+
+ <p>You selected: <span id="selection"></span></p>
+
+ <p>The plot command returns a plot object you can use to control the selection. Click the buttons below.</p>
+
+ <p>
+ <button id="clearSelection">Clear selection</button>
+ <button id="setSelection">Select year 1994</button>
+ </p>
+
+ <p>Selections are really useful for zooming. Just replot the chart with min and max values for the axes set to the values in the "plotselected" event triggered. Enable the checkbox below and select a region again.</p>
+
+ <p><label><input id="zoom" type="checkbox"></input>Zoom to selection.</label></p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/series-errorbars/index.html b/misc/flot/examples/series-errorbars/index.html
new file mode 100644
index 0000000..23a5a87
--- /dev/null
+++ b/misc/flot/examples/series-errorbars/index.html
@@ -0,0 +1,150 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Error Bars</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.errorbars.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.navigate.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ function drawArrow(ctx, x, y, radius){
+ ctx.beginPath();
+ ctx.moveTo(x + radius, y + radius);
+ ctx.lineTo(x, y);
+ ctx.lineTo(x - radius, y + radius);
+ ctx.stroke();
+ }
+
+ function drawSemiCircle(ctx, x, y, radius){
+ ctx.beginPath();
+ ctx.arc(x, y, radius, 0, Math.PI, false);
+ ctx.moveTo(x - radius, y);
+ ctx.lineTo(x + radius, y);
+ ctx.stroke();
+ }
+
+ var data1 = [
+ [1,1,.5,.1,.3],
+ [2,2,.3,.5,.2],
+ [3,3,.9,.5,.2],
+ [1.5,-.05,.5,.1,.3],
+ [3.15,1.,.5,.1,.3],
+ [2.5,-1.,.5,.1,.3]
+ ];
+
+ var data1_points = {
+ show: true,
+ radius: 5,
+ fillColor: "blue",
+ errorbars: "xy",
+ xerr: {show: true, asymmetric: true, upperCap: "-", lowerCap: "-"},
+ yerr: {show: true, color: "red", upperCap: "-"}
+ };
+
+ var data2 = [
+ [.7,3,.2,.4],
+ [1.5,2.2,.3,.4],
+ [2.3,1,.5,.2]
+ ];
+
+ var data2_points = {
+ show: true,
+ radius: 5,
+ errorbars: "y",
+ yerr: {show:true, asymmetric:true, upperCap: drawArrow, lowerCap: drawSemiCircle}
+ };
+
+ var data3 = [
+ [1,2,.4],
+ [2,0.5,.3],
+ [2.7,2,.5]
+ ];
+
+ var data3_points = {
+ //do not show points
+ radius: 0,
+ errorbars: "y",
+ yerr: {show:true, upperCap: "-", lowerCap: "-", radius: 5}
+ };
+
+ var data4 = [
+ [1.3, 1],
+ [1.75, 2.5],
+ [2.5, 0.5]
+ ];
+
+ var data4_errors = [0.1, 0.4, 0.2];
+ for (var i = 0; i < data4.length; i++) {
+ data4_errors[i] = data4[i].concat(data4_errors[i])
+ }
+
+ var data = [
+ {color: "blue", points: data1_points, data: data1, label: "data1"},
+ {color: "red", points: data2_points, data: data2, label: "data2"},
+ {color: "green", lines: {show: true}, points: data3_points, data: data3, label: "data3"},
+ // bars with errors
+ {color: "orange", bars: {show: true, align: "center", barWidth: 0.25}, data: data4, label: "data4"},
+ {color: "orange", points: data3_points, data: data4_errors}
+ ];
+
+ $.plot($("#placeholder"), data , {
+ legend: {
+ position: "sw",
+ show: true
+ },
+ series: {
+ lines: {
+ show: false
+ }
+ },
+ xaxis: {
+ min: 0.6,
+ max: 3.1
+ },
+ yaxis: {
+ min: 0,
+ max: 3.5
+ },
+ zoom: {
+ interactive: true
+ },
+ pan: {
+ interactive: true
+ }
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Error Bars</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>With the errorbars plugin you can plot error bars to show standard deviation and other useful statistical properties.</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/series-pie/index.html b/misc/flot/examples/series-pie/index.html
new file mode 100644
index 0000000..342636f
--- /dev/null
+++ b/misc/flot/examples/series-pie/index.html
@@ -0,0 +1,818 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Pie Charts</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <style type="text/css">
+
+ .demo-container {
+ position: relative;
+ height: 400px;
+ }
+
+ #placeholder {
+ width: 550px;
+ }
+
+ #menu {
+ position: absolute;
+ top: 20px;
+ left: 625px;
+ bottom: 20px;
+ right: 20px;
+ width: 200px;
+ }
+
+ #menu button {
+ display: inline-block;
+ width: 200px;
+ padding: 3px 0 2px 0;
+ margin-bottom: 4px;
+ background: #eee;
+ border: 1px solid #999;
+ border-radius: 2px;
+ font-size: 16px;
+ -o-box-shadow: 0 1px 2px rgba(0,0,0,0.15);
+ -ms-box-shadow: 0 1px 2px rgba(0,0,0,0.15);
+ -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.15);
+ -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.15);
+ box-shadow: 0 1px 2px rgba(0,0,0,0.15);
+ cursor: pointer;
+ }
+
+ #description {
+ margin: 15px 10px 20px 10px;
+ }
+
+ #code {
+ display: block;
+ width: 870px;
+ padding: 15px;
+ margin: 10px auto;
+ border: 1px dashed #999;
+ background-color: #f8f8f8;
+ font-size: 16px;
+ line-height: 20px;
+ color: #666;
+ }
+
+ ul {
+ font-size: 10pt;
+ }
+
+ ul li {
+ margin-bottom: 0.5em;
+ }
+
+ ul.options li {
+ list-style: none;
+ margin-bottom: 1em;
+ }
+
+ ul li i {
+ color: #999;
+ }
+
+ </style>
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.pie.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ // Example Data
+
+ //var data = [
+ // { label: "Series1", data: 10},
+ // { label: "Series2", data: 30},
+ // { label: "Series3", data: 90},
+ // { label: "Series4", data: 70},
+ // { label: "Series5", data: 80},
+ // { label: "Series6", data: 110}
+ //];
+
+ //var data = [
+ // { label: "Series1", data: [[1,10]]},
+ // { label: "Series2", data: [[1,30]]},
+ // { label: "Series3", data: [[1,90]]},
+ // { label: "Series4", data: [[1,70]]},
+ // { label: "Series5", data: [[1,80]]},
+ // { label: "Series6", data: [[1,0]]}
+ //];
+
+ //var data = [
+ // { label: "Series A", data: 0.2063},
+ // { label: "Series B", data: 38888}
+ //];
+
+ // Randomly Generated Data
+
+ var data = [],
+ series = Math.floor(Math.random() * 6) + 3;
+
+ for (var i = 0; i < series; i++) {
+ data[i] = {
+ label: "Series" + (i + 1),
+ data: Math.floor(Math.random() * 100) + 1
+ }
+ }
+
+ var placeholder = $("#placeholder");
+
+ $("#example-1").click(function() {
+
+ placeholder.unbind();
+
+ $("#title").text("Default pie chart");
+ $("#description").text("The default pie chart with no options set.");
+
+ $.plot(placeholder, data, {
+ series: {
+ pie: {
+ show: true
+ }
+ }
+ });
+
+ setCode([
+ "$.plot('#placeholder', data, {",
+ " series: {",
+ " pie: {",
+ " show: true",
+ " }",
+ " }",
+ "});"
+ ]);
+ });
+
+ $("#example-2").click(function() {
+
+ placeholder.unbind();
+
+ $("#title").text("Default without legend");
+ $("#description").text("The default pie chart when the legend is disabled. Since the labels would normally be outside the container, the chart is resized to fit.");
+
+ $.plot(placeholder, data, {
+ series: {
+ pie: {
+ show: true
+ }
+ },
+ legend: {
+ show: false
+ }
+ });
+
+ setCode([
+ "$.plot('#placeholder', data, {",
+ " series: {",
+ " pie: {",
+ " show: true",
+ " }",
+ " },",
+ " legend: {",
+ " show: false",
+ " }",
+ "});"
+ ]);
+ });
+
+ $("#example-3").click(function() {
+
+ placeholder.unbind();
+
+ $("#title").text("Custom Label Formatter");
+ $("#description").text("Added a semi-transparent background to the labels and a custom labelFormatter function.");
+
+ $.plot(placeholder, data, {
+ series: {
+ pie: {
+ show: true,
+ radius: 1,
+ label: {
+ show: true,
+ radius: 1,
+ formatter: labelFormatter,
+ background: {
+ opacity: 0.8
+ }
+ }
+ }
+ },
+ legend: {
+ show: false
+ }
+ });
+
+ setCode([
+ "$.plot('#placeholder', data, {",
+ " series: {",
+ " pie: {",
+ " show: true,",
+ " radius: 1,",
+ " label: {",
+ " show: true,",
+ " radius: 1,",
+ " formatter: labelFormatter,",
+ " background: {",
+ " opacity: 0.8",
+ " }",
+ " }",
+ " }",
+ " },",
+ " legend: {",
+ " show: false",
+ " }",
+ "});"
+ ]);
+ });
+
+ $("#example-4").click(function() {
+
+ placeholder.unbind();
+
+ $("#title").text("Label Radius");
+ $("#description").text("Slightly more transparent label backgrounds and adjusted the radius values to place them within the pie.");
+
+ $.plot(placeholder, data, {
+ series: {
+ pie: {
+ show: true,
+ radius: 1,
+ label: {
+ show: true,
+ radius: 3/4,
+ formatter: labelFormatter,
+ background: {
+ opacity: 0.5
+ }
+ }
+ }
+ },
+ legend: {
+ show: false
+ }
+ });
+
+ setCode([
+ "$.plot('#placeholder', data, {",
+ " series: {",
+ " pie: {",
+ " show: true,",
+ " radius: 1,",
+ " label: {",
+ " show: true,",
+ " radius: 3/4,",
+ " formatter: labelFormatter,",
+ " background: {",
+ " opacity: 0.5",
+ " }",
+ " }",
+ " }",
+ " },",
+ " legend: {",
+ " show: false",
+ " }",
+ "});"
+ ]);
+ });
+
+ $("#example-5").click(function() {
+
+ placeholder.unbind();
+
+ $("#title").text("Label Styles #1");
+ $("#description").text("Semi-transparent, black-colored label background.");
+
+ $.plot(placeholder, data, {
+ series: {
+ pie: {
+ show: true,
+ radius: 1,
+ label: {
+ show: true,
+ radius: 3/4,
+ formatter: labelFormatter,
+ background: {
+ opacity: 0.5,
+ color: "#000"
+ }
+ }
+ }
+ },
+ legend: {
+ show: false
+ }
+ });
+
+ setCode([
+ "$.plot('#placeholder', data, {",
+ " series: {",
+ " pie: { ",
+ " show: true,",
+ " radius: 1,",
+ " label: {",
+ " show: true,",
+ " radius: 3/4,",
+ " formatter: labelFormatter,",
+ " background: { ",
+ " opacity: 0.5,",
+ " color: '#000'",
+ " }",
+ " }",
+ " }",
+ " },",
+ " legend: {",
+ " show: false",
+ " }",
+ "});"
+ ]);
+ });
+
+ $("#example-6").click(function() {
+
+ placeholder.unbind();
+
+ $("#title").text("Label Styles #2");
+ $("#description").text("Semi-transparent, black-colored label background placed at pie edge.");
+
+ $.plot(placeholder, data, {
+ series: {
+ pie: {
+ show: true,
+ radius: 3/4,
+ label: {
+ show: true,
+ radius: 3/4,
+ formatter: labelFormatter,
+ background: {
+ opacity: 0.5,
+ color: "#000"
+ }
+ }
+ }
+ },
+ legend: {
+ show: false
+ }
+ });
+
+ setCode([
+ "$.plot('#placeholder', data, {",
+ " series: {",
+ " pie: {",
+ " show: true,",
+ " radius: 3/4,",
+ " label: {",
+ " show: true,",
+ " radius: 3/4,",
+ " formatter: labelFormatter,",
+ " background: {",
+ " opacity: 0.5,",
+ " color: '#000'",
+ " }",
+ " }",
+ " }",
+ " },",
+ " legend: {",
+ " show: false",
+ " }",
+ "});"
+ ]);
+ });
+
+ $("#example-7").click(function() {
+
+ placeholder.unbind();
+
+ $("#title").text("Hidden Labels");
+ $("#description").text("Labels can be hidden if the slice is less than a given percentage of the pie (10% in this case).");
+
+ $.plot(placeholder, data, {
+ series: {
+ pie: {
+ show: true,
+ radius: 1,
+ label: {
+ show: true,
+ radius: 2/3,
+ formatter: labelFormatter,
+ threshold: 0.1
+ }
+ }
+ },
+ legend: {
+ show: false
+ }
+ });
+
+ setCode([
+ "$.plot('#placeholder', data, {",
+ " series: {",
+ " pie: {",
+ " show: true,",
+ " radius: 1,",
+ " label: {",
+ " show: true,",
+ " radius: 2/3,",
+ " formatter: labelFormatter,",
+ " threshold: 0.1",
+ " }",
+ " }",
+ " },",
+ " legend: {",
+ " show: false",
+ " }",
+ "});"
+ ]);
+ });
+
+ $("#example-8").click(function() {
+
+ placeholder.unbind();
+
+ $("#title").text("Combined Slice");
+ $("#description").text("Multiple slices less than a given percentage (5% in this case) of the pie can be combined into a single, larger slice.");
+
+ $.plot(placeholder, data, {
+ series: {
+ pie: {
+ show: true,
+ combine: {
+ color: "#999",
+ threshold: 0.05
+ }
+ }
+ },
+ legend: {
+ show: false
+ }
+ });
+
+ setCode([
+ "$.plot('#placeholder', data, {",
+ " series: {",
+ " pie: {",
+ " show: true,",
+ " combine: {",
+ " color: '#999',",
+ " threshold: 0.1",
+ " }",
+ " }",
+ " },",
+ " legend: {",
+ " show: false",
+ " }",
+ "});"
+ ]);
+ });
+
+ $("#example-9").click(function() {
+
+ placeholder.unbind();
+
+ $("#title").text("Rectangular Pie");
+ $("#description").text("The radius can also be set to a specific size (even larger than the container itself).");
+
+ $.plot(placeholder, data, {
+ series: {
+ pie: {
+ show: true,
+ radius: 500,
+ label: {
+ show: true,
+ formatter: labelFormatter,
+ threshold: 0.1
+ }
+ }
+ },
+ legend: {
+ show: false
+ }
+ });
+
+ setCode([
+ "$.plot('#placeholder', data, {",
+ " series: {",
+ " pie: {",
+ " show: true,",
+ " radius: 500,",
+ " label: {",
+ " show: true,",
+ " formatter: labelFormatter,",
+ " threshold: 0.1",
+ " }",
+ " }",
+ " },",
+ " legend: {",
+ " show: false",
+ " }",
+ "});"
+ ]);
+ });
+
+ $("#example-10").click(function() {
+
+ placeholder.unbind();
+
+ $("#title").text("Tilted Pie");
+ $("#description").text("The pie can be tilted at an angle.");
+
+ $.plot(placeholder, data, {
+ series: {
+ pie: {
+ show: true,
+ radius: 1,
+ tilt: 0.5,
+ label: {
+ show: true,
+ radius: 1,
+ formatter: labelFormatter,
+ background: {
+ opacity: 0.8
+ }
+ },
+ combine: {
+ color: "#999",
+ threshold: 0.1
+ }
+ }
+ },
+ legend: {
+ show: false
+ }
+ });
+
+ setCode([
+ "$.plot('#placeholder', data, {",
+ " series: {",
+ " pie: {",
+ " show: true,",
+ " radius: 1,",
+ " tilt: 0.5,",
+ " label: {",
+ " show: true,",
+ " radius: 1,",
+ " formatter: labelFormatter,",
+ " background: {",
+ " opacity: 0.8",
+ " }",
+ " },",
+ " combine: {",
+ " color: '#999',",
+ " threshold: 0.1",
+ " }",
+ " }",
+ " },",
+ " legend: {",
+ " show: false",
+ " }",
+ "});",
+ ]);
+ });
+
+ $("#example-11").click(function() {
+
+ placeholder.unbind();
+
+ $("#title").text("Donut Hole");
+ $("#description").text("A donut hole can be added.");
+
+ $.plot(placeholder, data, {
+ series: {
+ pie: {
+ innerRadius: 0.5,
+ show: true
+ }
+ }
+ });
+
+ setCode([
+ "$.plot('#placeholder', data, {",
+ " series: {",
+ " pie: {",
+ " innerRadius: 0.5,",
+ " show: true",
+ " }",
+ " }",
+ "});"
+ ]);
+ });
+
+ $("#example-12").click(function() {
+
+ placeholder.unbind();
+
+ $("#title").text("Interactivity");
+ $("#description").text("The pie can be made interactive with hover and click events.");
+
+ $.plot(placeholder, data, {
+ series: {
+ pie: {
+ show: true
+ }
+ },
+ grid: {
+ hoverable: true,
+ clickable: true
+ }
+ });
+
+ setCode([
+ "$.plot('#placeholder', data, {",
+ " series: {",
+ " pie: {",
+ " show: true",
+ " }",
+ " },",
+ " grid: {",
+ " hoverable: true,",
+ " clickable: true",
+ " }",
+ "});"
+ ]);
+
+ placeholder.bind("plothover", function(event, pos, obj) {
+
+ if (!obj) {
+ return;
+ }
+
+ var percent = parseFloat(obj.series.percent).toFixed(2);
+ $("#hover").html("<span style='font-weight:bold; color:" + obj.series.color + "'>" + obj.series.label + " (" + percent + "%)</span>");
+ });
+
+ placeholder.bind("plotclick", function(event, pos, obj) {
+
+ if (!obj) {
+ return;
+ }
+
+ percent = parseFloat(obj.series.percent).toFixed(2);
+ alert("" + obj.series.label + ": " + percent + "%");
+ });
+ });
+
+ // Show the initial default chart
+
+ $("#example-1").click();
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ // A custom label formatter used by several of the plots
+
+ function labelFormatter(label, series) {
+ return "<div style='font-size:8pt; text-align:center; padding:2px; color:white;'>" + label + "<br/>" + Math.round(series.percent) + "%</div>";
+ }
+
+ //
+
+ function setCode(lines) {
+ $("#code").text(lines.join("\n"));
+ }
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Pie Charts</h2>
+ </div>
+
+ <div id="content">
+
+ <h3 id="title"></h3>
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ <div id="menu">
+ <button id="example-1">Default Options</button>
+ <button id="example-2">Without Legend</button>
+ <button id="example-3">Label Formatter</button>
+ <button id="example-4">Label Radius</button>
+ <button id="example-5">Label Styles #1</button>
+ <button id="example-6">Label Styles #2</button>
+ <button id="example-7">Hidden Labels</button>
+ <button id="example-8">Combined Slice</button>
+ <button id="example-9">Rectangular Pie</button>
+ <button id="example-10">Tilted Pie</button>
+ <button id="example-11">Donut Hole</button>
+ <button id="example-12">Interactivity</button>
+ </div>
+ </div>
+
+ <p id="description"></p>
+
+ <h3>Source Code</h3>
+ <pre><code id="code"></code></pre>
+
+ <br/>
+
+ <h2>Pie Options</h2>
+
+ <ul class="options">
+ <li style="border-bottom: 1px dotted #ccc;"><b>option:</b> <i>default value</i> - Description of option</li>
+ <li><b>show:</b> <i>false</i> - Enable the plugin and draw as a pie.</li>
+ <li><b>radius:</b> <i>'auto'</i> - Sets the radius of the pie. If value is between 0 and 1 (inclusive) then it will use that as a percentage of the available space (size of the container), otherwise it will use the value as a direct pixel length. If set to 'auto', it will be set to 1 if the legend is enabled and 3/4 if not.</li>
+ <li><b>innerRadius:</b> <i>0</i> - Sets the radius of the donut hole. If value is between 0 and 1 (inclusive) then it will use that as a percentage of the radius, otherwise it will use the value as a direct pixel length.</li>
+ <li><b>startAngle:</b> <i>3/2</i> - Factor of PI used for the starting angle (in radians) It can range between 0 and 2 (where 0 and 2 have the same result).</li>
+ <li><b>tilt:</b> <i>1</i> - Percentage of tilt ranging from 0 and 1, where 1 has no change (fully vertical) and 0 is completely flat (fully horizontal -- in which case nothing actually gets drawn).</li>
+ <li><b>shadow:</b> <ul>
+ <li><b>top:</b> <i>5</i> - Vertical distance in pixel of the tilted pie shadow.</li>
+ <li><b>left:</b> <i>15</i> - Horizontal distance in pixel of the tilted pie shadow.</li>
+ <li><b>alpha:</b> <i>0.02</i> - Alpha value of the tilted pie shadow.</li>
+ </ul>
+ <li><b>offset:</b> <ul>
+ <li><b>top:</b> <i>0</i> - Pixel distance to move the pie up and down (relative to the center).</li>
+ <li><b>left:</b> <i>'auto'</i> - Pixel distance to move the pie left and right (relative to the center).</li>
+ </ul>
+ <li><b>stroke:</b> <ul>
+ <li><b>color:</b> <i>'#FFF'</i> - Color of the border of each slice. Hexadecimal color definitions are prefered (other formats may or may not work).</li>
+ <li><b>width:</b> <i>1</i> - Pixel width of the border of each slice.</li>
+ </ul>
+ <li><b>label:</b> <ul>
+ <li><b>show:</b> <i>'auto'</i> - Enable/Disable the labels. This can be set to true, false, or 'auto'. When set to 'auto', it will be set to false if the legend is enabled and true if not.</li>
+ <li><b>radius:</b> <i>1</i> - Sets the radius at which to place the labels. If value is between 0 and 1 (inclusive) then it will use that as a percentage of the available space (size of the container), otherwise it will use the value as a direct pixel length.</li>
+ <li><b>threshold:</b> <i>0</i> - Hides the labels of any pie slice that is smaller than the specified percentage (ranging from 0 to 1) i.e. a value of '0.03' will hide all slices 3% or less of the total.</li>
+ <li><b>formatter:</b> <i>[function]</i> - This function specifies how the positioned labels should be formatted, and is applied after the legend's labelFormatter function. The labels can also still be styled using the class "pieLabel" (i.e. ".pieLabel" or "#graph1 .pieLabel").</li>
+ <li><b>radius:</b> <i>1</i> - Sets the radius at which to place the labels. If value is between 0 and 1 (inclusive) then it will use that as a percentage of the available space (size of the container), otherwise it will use the value as a direct pixel length.</li>
+ <li><b>background:</b> <ul>
+ <li><b>color:</b> <i>null</i> - Backgound color of the positioned labels. If null, the plugin will automatically use the color of the slice.</li>
+ <li><b>opacity:</b> <i>0</i> - Opacity of the background for the positioned labels. Acceptable values range from 0 to 1, where 0 is completely transparent and 1 is completely opaque.</li>
+ </ul>
+ </ul>
+ <li><b>combine:</b> <ul>
+ <li><b>threshold:</b> <i>0</i> - Combines all slices that are smaller than the specified percentage (ranging from 0 to 1) i.e. a value of '0.03' will combine all slices 3% or less into one slice).</li>
+ <li><b>color:</b> <i>null</i> - Backgound color of the positioned labels. If null, the plugin will automatically use the color of the first slice to be combined.</li>
+ <li><b>label:</b> <i>'Other'</i> - Label text for the combined slice.</li>
+ </ul>
+ <li><b>highlight:</b> <ul>
+ <li><b>opacity:</b> <i>0.5</i> - Opacity of the highlight overlay on top of the current pie slice. Currently this just uses a white overlay, but support for changing the color of the overlay will also be added at a later date.
+ </ul>
+ </ul>
+
+ <h2>Changes/Features</h2>
+ <ul>
+ <li style="list-style: none;"><i>v1.0 - November 20th, 2009 - Brian Medendorp</i></li>
+ <li>The pie plug-in is now part of the Flot repository! This should make it a lot easier to deal with.</li>
+ <li>Added a new option (innerRadius) to add a "donut hole" to the center of the pie, based on comtributions from Anthony Aragues. I was a little reluctant to add this feature because it doesn't work very well with the shadow created for the tilted pie, but figured it was worthwhile for non-tilted pies. Also, excanvas apparently doesn't support compositing, so it will fall back to using the stroke color to fill in the center (but I recommend setting the stroke color to the background color anyway).</li>
+ <li>Changed the lineJoin for the border of the pie slices to use the 'round' option. This should make the center of the pie look better, particularly when there are numerous thin slices.</li>
+ <li>Included a bug fix submitted by btburnett3 to display a slightly smaller slice in the event that the slice is 100% and being rendered with Internet Explorer. I haven't experienced this bug myself, but it doesn't seem to hurt anything so I've included it.</li>
+ <li>The tilt value is now used when calculating the maximum radius of the pie in relation to the height of the container. This should prevent the pie from being smaller than it needed to in some cases, as well as reducing the amount of extra white space generated above and below the pie.</li>
+ <li><b>Hover and Click functionality are now availabe!</b><ul>
+ <li>Thanks to btburnett3 for the original hover functionality and Anthony Aragues for the modification that makes it compatable with excanvas, this was a huge help!</li>
+ <li>Added a new option (highlight opacity) to modify the highlight created when mousing over a slice. Currently this just uses a white overlay, but an option to change the hightlight color will be added when the appropriate functionality becomes available.
+ <li>I had a major setback that required me to practically rebuild the hover/click events from scratch one piece at a time (I discovered that it only worked with a single pie on a page at a time), but the end result ended up being virtually identical to the original, so I'm not quite sure what exactly made it work.</li>
+ <li><span style="color: red;">Warning:</span> There are some minor issues with using this functionality in conjuction with some of the other more advanced features (tilt and donut). When using a donut hole, the inner portion still triggers the events even though that portion of the pie is no longer visible. When tilted, the interactive portions still use the original, untilted version of the pie when determining mouse position (this is because the isPointInPath function apparently doesn't work with transformations), however hover and click both work this way, so the appropriate slice is still highlighted when clicking, and it isn't as noticable of a problem.</li>
+ </ul></li>
+ <li>Included a bug fix submitted by Xavi Ivars to fix array issues when other javascript libraries are included in addition to jQuery</li>
+ <br/>
+ <li style="list-style: none;"><i>v0.4 - July 1st, 2009 - Brian Medendorp</i></li>
+ <li>Each series will now be shown in the legend, even if it's value is zero. The series will not get a positioned label because it will overlap with the other labels present and often makes them unreadable.</li>
+ <li>Data can now be passed in using the standard Flot method using an array of datapoints, the pie plugin will simply use the first y-value that it finds for each series in this case. The plugin uses this datastructure internally, but you can still use the old method of passing in a single numerical value for each series (the plugin will convert it as necessary). This should make it easier to transition from other types of graphs (such as a stacked bar graph) to a pie.</li>
+ <li>The pie can now be tilted at an angle with a new "tilt" option. Acceptable values range from 0-1, where 1 has no change (fully vertical) and 0 is completely flat (fully horizontal -- in which case nothing actually gets drawn). If the plugin determines that it will fit within the canvas, a drop shadow will be drawn under the tilted pie (this also requires a tilt value of 0.8 or less).</li>
+ <br/>
+ <li style="list-style: none;"><i>v0.3.2 - June 25th, 2009 - Brian Medendorp</i></li>
+ <li>Fixed a bug that was causing the pie to be shifted too far left or right when the legend is showing in some cases.</li>
+ <br/>
+ <li style="list-style: none;"><i>v0.3.1 - June 24th, 2009 - Brian Medendorp</i></li>
+ <li>Fixed a bug that was causing nothing to be drawn and generating a javascript error if any of the data values were set to zero.</li>
+ <br/>
+ <li style="list-style: none;"><i>v0.3 - June 23rd, 2009 - Brian Medendorp</i></li>
+ <li>The legend now works without any modifications! Because of changes made to flot and the plugin system (thanks Ole Laursen!) I was able to simplify a number of things and am now able to use the legend without the direct access hack that was required in the previous version.</li>
+ <br/>
+ <li style="list-style: none;"><i>v0.2 - June 22nd, 2009 - Brian Medendorp</i></li>
+ <li>The legend now works but only if you make the necessary changes to jquery.flot.js. Because of this, I changed the default values for pie.radius and pie.label.show to new 'auto' settings that change the default behavior of the size and labels depending on whether the legend functionality is available or not.</li>
+ <br/>
+ <li style="list-style: none;"><i>v0.1 - June 18th, 2009 - Brian Medendorp</i></li>
+ <li>Rewrote the entire pie code into a flot plugin (since that is now an option), so it should be much easier to use and the code is cleaned up a bit. However, the (standard flot) legend is no longer available because the only way to prevent the grid lines from being displayed also prevents the legend from being displayed. Hopefully this can be fixed at a later date.</li>
+ <li>Restructured and combined some of the options. It should be much easier to deal with now.</li>
+ <li>Added the ability to change the starting point of the pie (still defaults to the top).</li>
+ <li>Modified the default options to show the labels to compensate for the lack of a legend.</li>
+ <li>Modified this page to use a random dataset. <span style="color: red">Note: you may need to refresh the page to see the effects of some of the examples.</span></li>
+ <br/>
+ <li style="list-style: none;"><i>May 21st, 2009 - Brian Medendorp</i></li>
+ <li>Merged original pie modifications by Sergey Nosenko into the latest SVN version <i>(as of May 15th, 2009)</i> so that it will work with ie8.</li>
+ <li>Pie graph will now be centered in the canvas unless moved because of the legend or manually via the options. Additionally it prevents the pie from being moved beyond the edge of the canvas.</li>
+ <li>Modified the code related to the labelFormatter option to apply flot's legend labelFormatter first. This is so that the labels will be consistent, but still provide extra formatting for the positioned labels (such as adding the percentage value).</li>
+ <li>Positioned labels now have their backgrounds applied as a seperate element (much like the legend background) so that the opacity value can be set independently from the label itself (foreground). Additionally, the background color defaults to that of the matching slice.</li>
+ <li>As long as the labelOffset and radiusLimit are not set to hard values, the pie will be shrunk if the labels will extend outside the edge of the canvas</li>
+ <li>Added new options "radiusLimitFactor" and "radiusLimit" which limits how large the (visual) radius of the pie is in relation to the full radius (as calculated from the canvas dimensions) or a hard-pixel value (respectively). This allows for pushing the labels "outside" the pie.</li>
+ <li>Added a new option "labelHidePercent" that does not show the positioned labels of slices smaller than the specified percentage. This is to help prevent a bunch of overlapping labels from small slices.</li>
+ <li>Added a new option "sliceCombinePercent" that combines all slices smaller than the specified percentage into one larger slice. This is to help make the pie more attractive when there are a number of tiny slices. The options "sliceCombineColor" and "sliceCombineLabel" have also been added to change the color and name of the new slice if desired.</li>
+ <li>Tested in Firefox (3.0.10, 3.5b4), Internet Explorer (6.0.2900, 7.0.5730, 8.0.6001), Chrome (1.0.154), Opera (9.64), and Safari (3.1.1, 4 beta 5528.16).
+ </ul>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/series-toggle/index.html b/misc/flot/examples/series-toggle/index.html
new file mode 100644
index 0000000..4c06290
--- /dev/null
+++ b/misc/flot/examples/series-toggle/index.html
@@ -0,0 +1,121 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Toggling Series</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var datasets = {
+ "usa": {
+ label: "USA",
+ data: [[1988, 483994], [1989, 479060], [1990, 457648], [1991, 401949], [1992, 424705], [1993, 402375], [1994, 377867], [1995, 357382], [1996, 337946], [1997, 336185], [1998, 328611], [1999, 329421], [2000, 342172], [2001, 344932], [2002, 387303], [2003, 440813], [2004, 480451], [2005, 504638], [2006, 528692]]
+ },
+ "russia": {
+ label: "Russia",
+ data: [[1988, 218000], [1989, 203000], [1990, 171000], [1992, 42500], [1993, 37600], [1994, 36600], [1995, 21700], [1996, 19200], [1997, 21300], [1998, 13600], [1999, 14000], [2000, 19100], [2001, 21300], [2002, 23600], [2003, 25100], [2004, 26100], [2005, 31100], [2006, 34700]]
+ },
+ "uk": {
+ label: "UK",
+ data: [[1988, 62982], [1989, 62027], [1990, 60696], [1991, 62348], [1992, 58560], [1993, 56393], [1994, 54579], [1995, 50818], [1996, 50554], [1997, 48276], [1998, 47691], [1999, 47529], [2000, 47778], [2001, 48760], [2002, 50949], [2003, 57452], [2004, 60234], [2005, 60076], [2006, 59213]]
+ },
+ "germany": {
+ label: "Germany",
+ data: [[1988, 55627], [1989, 55475], [1990, 58464], [1991, 55134], [1992, 52436], [1993, 47139], [1994, 43962], [1995, 43238], [1996, 42395], [1997, 40854], [1998, 40993], [1999, 41822], [2000, 41147], [2001, 40474], [2002, 40604], [2003, 40044], [2004, 38816], [2005, 38060], [2006, 36984]]
+ },
+ "denmark": {
+ label: "Denmark",
+ data: [[1988, 3813], [1989, 3719], [1990, 3722], [1991, 3789], [1992, 3720], [1993, 3730], [1994, 3636], [1995, 3598], [1996, 3610], [1997, 3655], [1998, 3695], [1999, 3673], [2000, 3553], [2001, 3774], [2002, 3728], [2003, 3618], [2004, 3638], [2005, 3467], [2006, 3770]]
+ },
+ "sweden": {
+ label: "Sweden",
+ data: [[1988, 6402], [1989, 6474], [1990, 6605], [1991, 6209], [1992, 6035], [1993, 6020], [1994, 6000], [1995, 6018], [1996, 3958], [1997, 5780], [1998, 5954], [1999, 6178], [2000, 6411], [2001, 5993], [2002, 5833], [2003, 5791], [2004, 5450], [2005, 5521], [2006, 5271]]
+ },
+ "norway": {
+ label: "Norway",
+ data: [[1988, 4382], [1989, 4498], [1990, 4535], [1991, 4398], [1992, 4766], [1993, 4441], [1994, 4670], [1995, 4217], [1996, 4275], [1997, 4203], [1998, 4482], [1999, 4506], [2000, 4358], [2001, 4385], [2002, 5269], [2003, 5066], [2004, 5194], [2005, 4887], [2006, 4891]]
+ }
+ };
+
+ // hard-code color indices to prevent them from shifting as
+ // countries are turned on/off
+
+ var i = 0;
+ $.each(datasets, function(key, val) {
+ val.color = i;
+ ++i;
+ });
+
+ // insert checkboxes
+ var choiceContainer = $("#choices");
+ $.each(datasets, function(key, val) {
+ choiceContainer.append("<br/><input type='checkbox' name='" + key +
+ "' checked='checked' id='id" + key + "'></input>" +
+ "<label for='id" + key + "'>"
+ + val.label + "</label>");
+ });
+
+ choiceContainer.find("input").click(plotAccordingToChoices);
+
+ function plotAccordingToChoices() {
+
+ var data = [];
+
+ choiceContainer.find("input:checked").each(function () {
+ var key = $(this).attr("name");
+ if (key && datasets[key]) {
+ data.push(datasets[key]);
+ }
+ });
+
+ if (data.length > 0) {
+ $.plot("#placeholder", data, {
+ yaxis: {
+ min: 0
+ },
+ xaxis: {
+ tickDecimals: 0
+ }
+ });
+ }
+ }
+
+ plotAccordingToChoices();
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Toggling Series</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder" style="float:left; width:675px;"></div>
+ <p id="choices" style="float:right; width:135px;"></p>
+ </div>
+
+ <p>This example shows military budgets for various countries in constant (2005) million US dollars (source: <a href="http://www.sipri.org/">SIPRI</a>).</p>
+
+ <p>Since all data is available client-side, it's pretty easy to make the plot interactive. Try turning countries on and off with the checkboxes next to the plot.</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/series-types/index.html b/misc/flot/examples/series-types/index.html
new file mode 100644
index 0000000..91f7243
--- /dev/null
+++ b/misc/flot/examples/series-types/index.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Series Types</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var d1 = [];
+ for (var i = 0; i < 14; i += 0.5) {
+ d1.push([i, Math.sin(i)]);
+ }
+
+ var d2 = [[0, 3], [4, 8], [8, 5], [9, 13]];
+
+ var d3 = [];
+ for (var i = 0; i < 14; i += 0.5) {
+ d3.push([i, Math.cos(i)]);
+ }
+
+ var d4 = [];
+ for (var i = 0; i < 14; i += 0.1) {
+ d4.push([i, Math.sqrt(i * 10)]);
+ }
+
+ var d5 = [];
+ for (var i = 0; i < 14; i += 0.5) {
+ d5.push([i, Math.sqrt(i)]);
+ }
+
+ var d6 = [];
+ for (var i = 0; i < 14; i += 0.5 + Math.random()) {
+ d6.push([i, Math.sqrt(2*i + Math.sin(i) + 5)]);
+ }
+
+ $.plot("#placeholder", [{
+ data: d1,
+ lines: { show: true, fill: true }
+ }, {
+ data: d2,
+ bars: { show: true }
+ }, {
+ data: d3,
+ points: { show: true }
+ }, {
+ data: d4,
+ lines: { show: true }
+ }, {
+ data: d5,
+ lines: { show: true },
+ points: { show: true }
+ }, {
+ data: d6,
+ lines: { show: true, steps: true }
+ }]);
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Series Types</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>Flot supports lines, points, filled areas, bars and any combinations of these, in the same plot and even on the same data series.</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/shared/jquery-ui/jquery-ui.min.css b/misc/flot/examples/shared/jquery-ui/jquery-ui.min.css
new file mode 100644
index 0000000..08331c1
--- /dev/null
+++ b/misc/flot/examples/shared/jquery-ui/jquery-ui.min.css
@@ -0,0 +1,6 @@
+/*! jQuery UI - v1.10.0 - 2013-01-26
+* http://jqueryui.com
+* Includes: jquery.ui.core.css, jquery.ui.resizable.css
+* Copyright (c) 2013 jQuery Foundation and other contributors Licensed MIT */
+
+.ui-helper-hidden{display:none}.ui-helper-hidden-accessible{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.ui-helper-reset{margin:0;padding:0;border:0;outline:0;line-height:1.3;text-decoration:none;font-size:100%;list-style:none}.ui-helper-clearfix:before,.ui-helper-clearfix:after{content:"";display:table}.ui-helper-clearfix:after{clear:both}.ui-helper-clearfix{min-height:0}.ui-helper-zfix{width:100%;height:100%;top:0;left:0;position:absolute;opacity:0;filter:Alpha(Opacity=0)}.ui-front{z-index:100}.ui-state-disabled{cursor:default!important}.ui-icon{display:block;text-indent:-99999px;overflow:hidden;background-repeat:no-repeat}.ui-widget-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.ui-resizable{position:relative}.ui-resizable-handle{position:absolute;font-size:0.1px;display:block}.ui-resizable-disabled .ui-resizable-handle,.ui-resizable-autohide .ui-resizable-handle{display:none}.ui-resizable-n{cursor:n-resize;height:7px;width:100%;top:-5px;left:0}.ui-resizable-s{cursor:s-resize;height:7px;width:100%;bottom:-5px;left:0}.ui-resizable-e{cursor:e-resize;width:7px;right:-5px;top:0;height:100%}.ui-resizable-w{cursor:w-resize;width:7px;left:-5px;top:0;height:100%}.ui-resizable-se{cursor:se-resize;width:12px;height:12px;right:1px;bottom:1px}.ui-resizable-sw{cursor:sw-resize;width:9px;height:9px;left:-5px;bottom:-5px}.ui-resizable-nw{cursor:nw-resize;width:9px;height:9px;left:-5px;top:-5px}.ui-resizable-ne{cursor:ne-resize;width:9px;height:9px;right:-5px;top:-5px} \ No newline at end of file
diff --git a/misc/flot/examples/shared/jquery-ui/jquery-ui.min.js b/misc/flot/examples/shared/jquery-ui/jquery-ui.min.js
new file mode 100644
index 0000000..8902282
--- /dev/null
+++ b/misc/flot/examples/shared/jquery-ui/jquery-ui.min.js
@@ -0,0 +1,6 @@
+/*! jQuery UI - v1.10.0 - 2013-01-26
+* http://jqueryui.com
+* Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.resizable.js
+* Copyright (c) 2013 jQuery Foundation and other contributors Licensed MIT */
+
+(function(e,t){function i(t,n){var r,i,o,u=t.nodeName.toLowerCase();return"area"===u?(r=t.parentNode,i=r.name,!t.href||!i||r.nodeName.toLowerCase()!=="map"?!1:(o=e("img[usemap=#"+i+"]")[0],!!o&&s(o))):(/input|select|textarea|button|object/.test(u)?!t.disabled:"a"===u?t.href||n:n)&&s(t)}function s(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return e.css(this,"visibility")==="hidden"}).length}var n=0,r=/^ui-id-\d+$/;e.ui=e.ui||{};if(e.ui.version)return;e.extend(e.ui,{version:"1.10.0",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({_focus:e.fn.focus,focus:function(t,n){return typeof t=="number"?this.each(function(){var r=this;setTimeout(function(){e(r).focus(),n&&n.call(r)},t)}):this._focus.apply(this,arguments)},scrollParent:function(){var t;return e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?t=this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):t=this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(n){if(n!==t)return this.css("zIndex",n);if(this.length){var r=e(this[0]),i,s;while(r.length&&r[0]!==document){i=r.css("position");if(i==="absolute"||i==="relative"||i==="fixed"){s=parseInt(r.css("zIndex"),10);if(!isNaN(s)&&s!==0)return s}r=r.parent()}}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++n)})},removeUniqueId:function(){return this.each(function(){r.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(n){return!!e.data(n,t)}}):function(t,n,r){return!!e.data(t,r[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var n=e.attr(t,"tabindex"),r=isNaN(n);return(r||n>=0)&&i(t,!r)}}),e("<a>").outerWidth(1).jquery||e.each(["Width","Height"],function(n,r){function u(t,n,r,s){return e.each(i,function(){n-=parseFloat(e.css(t,"padding"+this))||0,r&&(n-=parseFloat(e.css(t,"border"+this+"Width"))||0),s&&(n-=parseFloat(e.css(t,"margin"+this))||0)}),n}var i=r==="Width"?["Left","Right"]:["Top","Bottom"],s=r.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+r]=function(n){return n===t?o["inner"+r].call(this):this.each(function(){e(this).css(s,u(this,n)+"px")})},e.fn["outer"+r]=function(t,n){return typeof t!="number"?o["outer"+r].call(this,t):this.each(function(){e(this).css(s,u(this,t,!0,n)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}),e("<a>").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(n){return arguments.length?t.call(this,e.camelCase(n)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,n,r){var i,s=e.ui[t].prototype;for(i in r)s.plugins[i]=s.plugins[i]||[],s.plugins[i].push([n,r[i]])},call:function(e,t,n){var r,i=e.plugins[t];if(!i||!e.element[0].parentNode||e.element[0].parentNode.nodeType===11)return;for(r=0;r<i.length;r++)e.options[i[r][0]]&&i[r][1].apply(e.element,n)}},hasScroll:function(t,n){if(e(t).css("overflow")==="hidden")return!1;var r=n&&n==="left"?"scrollLeft":"scrollTop",i=!1;return t[r]>0?!0:(t[r]=1,i=t[r]>0,t[r]=0,i)}})})(jQuery);(function(e,t){var n=0,r=Array.prototype.slice,i=e.cleanData;e.cleanData=function(t){for(var n=0,r;(r=t[n])!=null;n++)try{e(r).triggerHandler("remove")}catch(s){}i(t)},e.widget=function(t,n,r){var i,s,o,u,a={},f=t.split(".")[0];t=t.split(".")[1],i=f+"-"+t,r||(r=n,n=e.Widget),e.expr[":"][i.toLowerCase()]=function(t){return!!e.data(t,i)},e[f]=e[f]||{},s=e[f][t],o=e[f][t]=function(e,t){if(!this._createWidget)return new o(e,t);arguments.length&&this._createWidget(e,t)},e.extend(o,s,{version:r.version,_proto:e.extend({},r),_childConstructors:[]}),u=new n,u.options=e.widget.extend({},u.options),e.each(r,function(t,r){if(!e.isFunction(r)){a[t]=r;return}a[t]=function(){var e=function(){return n.prototype[t].apply(this,arguments)},i=function(e){return n.prototype[t].apply(this,e)};return function(){var t=this._super,n=this._superApply,s;return this._super=e,this._superApply=i,s=r.apply(this,arguments),this._super=t,this._superApply=n,s}}()}),o.prototype=e.widget.extend(u,{widgetEventPrefix:s?u.widgetEventPrefix:t},a,{constructor:o,namespace:f,widgetName:t,widgetFullName:i}),s?(e.each(s._childConstructors,function(t,n){var r=n.prototype;e.widget(r.namespace+"."+r.widgetName,o,n._proto)}),delete s._childConstructors):n._childConstructors.push(o),e.widget.bridge(t,o)},e.widget.extend=function(n){var i=r.call(arguments,1),s=0,o=i.length,u,a;for(;s<o;s++)for(u in i[s])a=i[s][u],i[s].hasOwnProperty(u)&&a!==t&&(e.isPlainObject(a)?n[u]=e.isPlainObject(n[u])?e.widget.extend({},n[u],a):e.widget.extend({},a):n[u]=a);return n},e.widget.bridge=function(n,i){var s=i.prototype.widgetFullName||n;e.fn[n]=function(o){var u=typeof o=="string",a=r.call(arguments,1),f=this;return o=!u&&a.length?e.widget.extend.apply(null,[o].concat(a)):o,u?this.each(function(){var r,i=e.data(this,s);if(!i)return e.error("cannot call methods on "+n+" prior to initialization; "+"attempted to call method '"+o+"'");if(!e.isFunction(i[o])||o.charAt(0)==="_")return e.error("no such method '"+o+"' for "+n+" widget instance");r=i[o].apply(i,a);if(r!==i&&r!==t)return f=r&&r.jquery?f.pushStack(r.get()):r,!1}):this.each(function(){var t=e.data(this,s);t?t.option(o||{})._init():e.data(this,s,new i(o,this))}),f}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{disabled:!1,create:null},_createWidget:function(t,r){r=e(r||this.defaultElement||this)[0],this.element=e(r),this.uuid=n++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),r!==this&&(e.data(r,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===r&&this.destroy()}}),this.document=e(r.style?r.ownerDocument:r.document||r),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(n,r){var i=n,s,o,u;if(arguments.length===0)return e.widget.extend({},this.options);if(typeof n=="string"){i={},s=n.split("."),n=s.shift();if(s.length){o=i[n]=e.widget.extend({},this.options[n]);for(u=0;u<s.length-1;u++)o[s[u]]=o[s[u]]||{},o=o[s[u]];n=s.pop();if(r===t)return o[n]===t?null:o[n];o[n]=r}else{if(r===t)return this.options[n]===t?null:this.options[n];i[n]=r}}return this._setOptions(i),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,e==="disabled"&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!t).attr("aria-disabled",t),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(t,n,r){var i,s=this;typeof t!="boolean"&&(r=n,n=t,t=!1),r?(n=i=e(n),this.bindings=this.bindings.add(n)):(r=n,n=this.element,i=this.widget()),e.each(r,function(r,o){function u(){if(!t&&(s.options.disabled===!0||e(this).hasClass("ui-state-disabled")))return;return(typeof o=="string"?s[o]:o).apply(s,arguments)}typeof o!="string"&&(u.guid=o.guid=o.guid||u.guid||e.guid++);var a=r.match(/^(\w+)\s*(.*)$/),f=a[1]+s.eventNamespace,l=a[2];l?i.delegate(l,f,u):n.bind(f,u)})},_off:function(e,t){t=(t||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.unbind(t).undelegate(t)},_delay:function(e,t){function n(){return(typeof e=="string"?r[e]:e).apply(r,arguments)}var r=this;return setTimeout(n,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,n,r){var i,s,o=this.options[t];r=r||{},n=e.Event(n),n.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),n.target=this.element[0],s=n.originalEvent;if(s)for(i in s)i in n||(n[i]=s[i]);return this.element.trigger(n,r),!(e.isFunction(o)&&o.apply(this.element[0],[n].concat(r))===!1||n.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,n){e.Widget.prototype["_"+t]=function(r,i,s){typeof i=="string"&&(i={effect:i});var o,u=i?i===!0||typeof i=="number"?n:i.effect||n:t;i=i||{},typeof i=="number"&&(i={duration:i}),o=!e.isEmptyObject(i),i.complete=s,i.delay&&r.delay(i.delay),o&&e.effects&&e.effects.effect[u]?r[t](i):u!==t&&r[u]?r[u](i.duration,i.easing,s):r.queue(function(n){e(this)[t](),s&&s.call(r[0]),n()})}})})(jQuery);(function(e,t){var n=!1;e(document).mouseup(function(){n=!1}),e.widget("ui.mouse",{version:"1.10.0",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var t=this;this.element.bind("mousedown."+this.widgetName,function(e){return t._mouseDown(e)}).bind("click."+this.widgetName,function(n){if(!0===e.data(n.target,t.widgetName+".preventClickEvent"))return e.removeData(n.target,t.widgetName+".preventClickEvent"),n.stopImmediatePropagation(),!1}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(t){if(n)return;this._mouseStarted&&this._mouseUp(t),this._mouseDownEvent=t;var r=this,i=t.which===1,s=typeof this.options.cancel=="string"&&t.target.nodeName?e(t.target).closest(this.options.cancel).length:!1;if(!i||s||!this._mouseCapture(t))return!0;this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){r.mouseDelayMet=!0},this.options.delay));if(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)){this._mouseStarted=this._mouseStart(t)!==!1;if(!this._mouseStarted)return t.preventDefault(),!0}return!0===e.data(t.target,this.widgetName+".preventClickEvent")&&e.removeData(t.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(e){return r._mouseMove(e)},this._mouseUpDelegate=function(e){return r._mouseUp(e)},e(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),t.preventDefault(),n=!0,!0},_mouseMove:function(t){return e.ui.ie&&(!document.documentMode||document.documentMode<9)&&!t.button?this._mouseUp(t):this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted)},_mouseUp:function(t){return e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery);(function(e,t){function n(e){return parseInt(e,10)||0}function r(e){return!isNaN(parseInt(e,10))}e.widget("ui.resizable",e.ui.mouse,{version:"1.10.0",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_create:function(){var t,n,r,i,s,o=this,u=this.options;this.element.addClass("ui-resizable"),e.extend(this,{_aspectRatio:!!u.aspectRatio,aspectRatio:u.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:u.helper||u.ghost||u.animate?u.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)&&(this.element.wrap(e("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.data("ui-resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=u.handles||(e(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se");if(this.handles.constructor===String){this.handles==="all"&&(this.handles="n,e,s,w,se,sw,ne,nw"),t=this.handles.split(","),this.handles={};for(n=0;n<t.length;n++)r=e.trim(t[n]),s="ui-resizable-"+r,i=e("<div class='ui-resizable-handle "+s+"'></div>"),i.css({zIndex:u.zIndex}),"se"===r&&i.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[r]=".ui-resizable-"+r,this.element.append(i)}this._renderAxis=function(t){var n,r,i,s;t=t||this.element;for(n in this.handles){this.handles[n].constructor===String&&(this.handles[n]=e(this.handles[n],this.element).show()),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)&&(r=e(this.handles[n],this.element),s=/sw|ne|nw|se|n|s/.test(n)?r.outerHeight():r.outerWidth(),i=["padding",/ne|nw|n/.test(n)?"Top":/se|sw|s/.test(n)?"Bottom":/^e$/.test(n)?"Right":"Left"].join(""),t.css(i,s),this._proportionallyResize());if(!e(this.handles[n]).length)continue}},this._renderAxis(this.element),this._handles=e(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){o.resizing||(this.className&&(i=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),o.axis=i&&i[1]?i[1]:"se")}),u.autoHide&&(this._handles.hide(),e(this.element).addClass("ui-resizable-autohide").mouseenter(function(){if(u.disabled)return;e(this).removeClass("ui-resizable-autohide"),o._handles.show()}).mouseleave(function(){if(u.disabled)return;o.resizing||(e(this).addClass("ui-resizable-autohide"),o._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var t,n=function(t){e(t).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(n(this.element),t=this.element,this.originalElement.css({position:t.css("position"),width:t.outerWidth(),height:t.outerHeight(),top:t.css("top"),left:t.css("left")}).insertAfter(t),t.remove()),this.originalElement.css("resize",this.originalResizeStyle),n(this.originalElement),this},_mouseCapture:function(t){var n,r,i=!1;for(n in this.handles){r=e(this.handles[n])[0];if(r===t.target||e.contains(r,t.target))i=!0}return!this.options.disabled&&i},_mouseStart:function(t){var r,i,s,o=this.options,u=this.element.position(),a=this.element;return this.resizing=!0,/absolute/.test(a.css("position"))?a.css({position:"absolute",top:a.css("top"),left:a.css("left")}):a.is(".ui-draggable")&&a.css({position:"absolute",top:u.top,left:u.left}),this._renderProxy(),r=n(this.helper.css("left")),i=n(this.helper.css("top")),o.containment&&(r+=e(o.containment).scrollLeft()||0,i+=e(o.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:r,top:i},this.size=this._helper?{width:a.outerWidth(),height:a.outerHeight()}:{width:a.width(),height:a.height()},this.originalSize=this._helper?{width:a.outerWidth(),height:a.outerHeight()}:{width:a.width(),height:a.height()},this.originalPosition={left:r,top:i},this.sizeDiff={width:a.outerWidth()-a.width(),height:a.outerHeight()-a.height()},this.originalMousePosition={left:t.pageX,top:t.pageY},this.aspectRatio=typeof o.aspectRatio=="number"?o.aspectRatio:this.originalSize.width/this.originalSize.height||1,s=e(".ui-resizable-"+this.axis).css("cursor"),e("body").css("cursor",s==="auto"?this.axis+"-resize":s),a.addClass("ui-resizable-resizing"),this._propagate("start",t),!0},_mouseDrag:function(t){var n,r=this.helper,i={},s=this.originalMousePosition,o=this.axis,u=this.position.top,a=this.position.left,f=this.size.width,l=this.size.height,c=t.pageX-s.left||0,h=t.pageY-s.top||0,p=this._change[o];if(!p)return!1;n=p.apply(this,[t,c,h]),this._updateVirtualBoundaries(t.shiftKey);if(this._aspectRatio||t.shiftKey)n=this._updateRatio(n,t);return n=this._respectSize(n,t),this._updateCache(n),this._propagate("resize",t),this.position.top!==u&&(i.top=this.position.top+"px"),this.position.left!==a&&(i.left=this.position.left+"px"),this.size.width!==f&&(i.width=this.size.width+"px"),this.size.height!==l&&(i.height=this.size.height+"px"),r.css(i),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),e.isEmptyObject(i)||this._trigger("resize",t,this.ui()),!1},_mouseStop:function(t){this.resizing=!1;var n,r,i,s,o,u,a,f=this.options,l=this;return this._helper&&(n=this._proportionallyResizeElements,r=n.length&&/textarea/i.test(n[0].nodeName),i=r&&e.ui.hasScroll(n[0],"left")?0:l.sizeDiff.height,s=r?0:l.sizeDiff.width,o={width:l.helper.width()-s,height:l.helper.height()-i},u=parseInt(l.element.css("left"),10)+(l.position.left-l.originalPosition.left)||null,a=parseInt(l.element.css("top"),10)+(l.position.top-l.originalPosition.top)||null,f.animate||this.element.css(e.extend(o,{top:a,left:u})),l.helper.height(l.size.height),l.helper.width(l.size.width),this._helper&&!f.animate&&this._proportionallyResize()),e("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(e){var t,n,i,s,o,u=this.options;o={minWidth:r(u.minWidth)?u.minWidth:0,maxWidth:r(u.maxWidth)?u.maxWidth:Infinity,minHeight:r(u.minHeight)?u.minHeight:0,maxHeight:r(u.maxHeight)?u.maxHeight:Infinity};if(this._aspectRatio||e)t=o.minHeight*this.aspectRatio,i=o.minWidth/this.aspectRatio,n=o.maxHeight*this.aspectRatio,s=o.maxWidth/this.aspectRatio,t>o.minWidth&&(o.minWidth=t),i>o.minHeight&&(o.minHeight=i),n<o.maxWidth&&(o.maxWidth=n),s<o.maxHeight&&(o.maxHeight=s);this._vBoundaries=o},_updateCache:function(e){this.offset=this.helper.offset(),r(e.left)&&(this.position.left=e.left),r(e.top)&&(this.position.top=e.top),r(e.height)&&(this.size.height=e.height),r(e.width)&&(this.size.width=e.width)},_updateRatio:function(e){var t=this.position,n=this.size,i=this.axis;return r(e.height)?e.width=e.height*this.aspectRatio:r(e.width)&&(e.height=e.width/this.aspectRatio),i==="sw"&&(e.left=t.left+(n.width-e.width),e.top=null),i==="nw"&&(e.top=t.top+(n.height-e.height),e.left=t.left+(n.width-e.width)),e},_respectSize:function(e){var t=this._vBoundaries,n=this.axis,i=r(e.width)&&t.maxWidth&&t.maxWidth<e.width,s=r(e.height)&&t.maxHeight&&t.maxHeight<e.height,o=r(e.width)&&t.minWidth&&t.minWidth>e.width,u=r(e.height)&&t.minHeight&&t.minHeight>e.height,a=this.originalPosition.left+this.originalSize.width,f=this.position.top+this.size.height,l=/sw|nw|w/.test(n),c=/nw|ne|n/.test(n);return o&&(e.width=t.minWidth),u&&(e.height=t.minHeight),i&&(e.width=t.maxWidth),s&&(e.height=t.maxHeight),o&&l&&(e.left=a-t.minWidth),i&&l&&(e.left=a-t.maxWidth),u&&c&&(e.top=f-t.minHeight),s&&c&&(e.top=f-t.maxHeight),!e.width&&!e.height&&!e.left&&e.top?e.top=null:!e.width&&!e.height&&!e.top&&e.left&&(e.left=null),e},_proportionallyResize:function(){if(!this._proportionallyResizeElements.length)return;var e,t,n,r,i,s=this.helper||this.element;for(e=0;e<this._proportionallyResizeElements.length;e++){i=this._proportionallyResizeElements[e];if(!this.borderDif){this.borderDif=[],n=[i.css("borderTopWidth"),i.css("borderRightWidth"),i.css("borderBottomWidth"),i.css("borderLeftWidth")],r=[i.css("paddingTop"),i.css("paddingRight"),i.css("paddingBottom"),i.css("paddingLeft")];for(t=0;t<n.length;t++)this.borderDif[t]=(parseInt(n[t],10)||0)+(parseInt(r[t],10)||0)}i.css({height:s.height()-this.borderDif[0]-this.borderDif[2]||0,width:s.width()-this.borderDif[1]-this.borderDif[3]||0})}},_renderProxy:function(){var t=this.element,n=this.options;this.elementOffset=t.offset(),this._helper?(this.helper=this.helper||e("<div style='overflow:hidden;'></div>"),this.helper.addClass(this._helper).css({width:this.element.outerWidth()-1,height:this.element.outerHeight()-1,position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++n.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(e,t){return{width:this.originalSize.width+t}},w:function(e,t){var n=this.originalSize,r=this.originalPosition;return{left:r.left+t,width:n.width-t}},n:function(e,t,n){var r=this.originalSize,i=this.originalPosition;return{top:i.top+n,height:r.height-n}},s:function(e,t,n){return{height:this.originalSize.height+n}},se:function(t,n,r){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,n,r]))},sw:function(t,n,r){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,n,r]))},ne:function(t,n,r){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,n,r]))},nw:function(t,n,r){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,n,r]))}},_propagate:function(t,n){e.ui.plugin.call(this,t,[n,this.ui()]),t!=="resize"&&this._trigger(t,n,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),e.ui.plugin.add("resizable","animate",{stop:function(t){var n=e(this).data("ui-resizable"),r=n.options,i=n._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),o=s&&e.ui.hasScroll(i[0],"left")?0:n.sizeDiff.height,u=s?0:n.sizeDiff.width,a={width:n.size.width-u,height:n.size.height-o},f=parseInt(n.element.css("left"),10)+(n.position.left-n.originalPosition.left)||null,l=parseInt(n.element.css("top"),10)+(n.position.top-n.originalPosition.top)||null;n.element.animate(e.extend(a,l&&f?{top:l,left:f}:{}),{duration:r.animateDuration,easing:r.animateEasing,step:function(){var r={width:parseInt(n.element.css("width"),10),height:parseInt(n.element.css("height"),10),top:parseInt(n.element.css("top"),10),left:parseInt(n.element.css("left"),10)};i&&i.length&&e(i[0]).css({width:r.width,height:r.height}),n._updateCache(r),n._propagate("resize",t)}})}}),e.ui.plugin.add("resizable","containment",{start:function(){var t,r,i,s,o,u,a,f=e(this).data("ui-resizable"),l=f.options,c=f.element,h=l.containment,p=h instanceof e?h.get(0):/parent/.test(h)?c.parent().get(0):h;if(!p)return;f.containerElement=e(p),/document/.test(h)||h===document?(f.containerOffset={left:0,top:0},f.containerPosition={left:0,top:0},f.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight}):(t=e(p),r=[],e(["Top","Right","Left","Bottom"]).each(function(e,i){r[e]=n(t.css("padding"+i))}),f.containerOffset=t.offset(),f.containerPosition=t.position(),f.containerSize={height:t.innerHeight()-r[3],width:t.innerWidth()-r[1]},i=f.containerOffset,s=f.containerSize.height,o=f.containerSize.width,u=e.ui.hasScroll(p,"left")?p.scrollWidth:o,a=e.ui.hasScroll(p)?p.scrollHeight:s,f.parentData={element:p,left:i.left,top:i.top,width:u,height:a})},resize:function(t){var n,r,i,s,o=e(this).data("ui-resizable"),u=o.options,a=o.containerOffset,f=o.position,l=o._aspectRatio||t.shiftKey,c={top:0,left:0},h=o.containerElement;h[0]!==document&&/static/.test(h.css("position"))&&(c=a),f.left<(o._helper?a.left:0)&&(o.size.width=o.size.width+(o._helper?o.position.left-a.left:o.position.left-c.left),l&&(o.size.height=o.size.width/o.aspectRatio),o.position.left=u.helper?a.left:0),f.top<(o._helper?a.top:0)&&(o.size.height=o.size.height+(o._helper?o.position.top-a.top:o.position.top),l&&(o.size.width=o.size.height*o.aspectRatio),o.position.top=o._helper?a.top:0),o.offset.left=o.parentData.left+o.position.left,o.offset.top=o.parentData.top+o.position.top,n=Math.abs((o._helper?o.offset.left-c.left:o.offset.left-c.left)+o.sizeDiff.width),r=Math.abs((o._helper?o.offset.top-c.top:o.offset.top-a.top)+o.sizeDiff.height),i=o.containerElement.get(0)===o.element.parent().get(0),s=/relative|absolute/.test(o.containerElement.css("position")),i&&s&&(n-=o.parentData.left),n+o.size.width>=o.parentData.width&&(o.size.width=o.parentData.width-n,l&&(o.size.height=o.size.width/o.aspectRatio)),r+o.size.height>=o.parentData.height&&(o.size.height=o.parentData.height-r,l&&(o.size.width=o.size.height*o.aspectRatio))},stop:function(){var t=e(this).data("ui-resizable"),n=t.options,r=t.containerOffset,i=t.containerPosition,s=t.containerElement,o=e(t.helper),u=o.offset(),a=o.outerWidth()-t.sizeDiff.width,f=o.outerHeight()-t.sizeDiff.height;t._helper&&!n.animate&&/relative/.test(s.css("position"))&&e(this).css({left:u.left-i.left-r.left,width:a,height:f}),t._helper&&!n.animate&&/static/.test(s.css("position"))&&e(this).css({left:u.left-i.left-r.left,width:a,height:f})}}),e.ui.plugin.add("resizable","alsoResize",{start:function(){var t=e(this).data("ui-resizable"),n=t.options,r=function(t){e(t).each(function(){var t=e(this);t.data("ui-resizable-alsoresize",{width:parseInt(t.width(),10),height:parseInt(t.height(),10),left:parseInt(t.css("left"),10),top:parseInt(t.css("top"),10)})})};typeof n.alsoResize=="object"&&!n.alsoResize.parentNode?n.alsoResize.length?(n.alsoResize=n.alsoResize[0],r(n.alsoResize)):e.each(n.alsoResize,function(e){r(e)}):r(n.alsoResize)},resize:function(t,n){var r=e(this).data("ui-resizable"),i=r.options,s=r.originalSize,o=r.originalPosition,u={height:r.size.height-s.height||0,width:r.size.width-s.width||0,top:r.position.top-o.top||0,left:r.position.left-o.left||0},a=function(t,r){e(t).each(function(){var t=e(this),i=e(this).data("ui-resizable-alsoresize"),s={},o=r&&r.length?r:t.parents(n.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(o,function(e,t){var n=(i[t]||0)+(u[t]||0);n&&n>=0&&(s[t]=n||null)}),t.css(s)})};typeof i.alsoResize=="object"&&!i.alsoResize.nodeType?e.each(i.alsoResize,function(e,t){a(e,t)}):a(i.alsoResize)},stop:function(){e(this).removeData("resizable-alsoresize")}}),e.ui.plugin.add("resizable","ghost",{start:function(){var t=e(this).data("ui-resizable"),n=t.options,r=t.size;t.ghost=t.originalElement.clone(),t.ghost.css({opacity:.25,display:"block",position:"relative",height:r.height,width:r.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass(typeof n.ghost=="string"?n.ghost:""),t.ghost.appendTo(t.helper)},resize:function(){var t=e(this).data("ui-resizable");t.ghost&&t.ghost.css({position:"relative",height:t.size.height,width:t.size.width})},stop:function(){var t=e(this).data("ui-resizable");t.ghost&&t.helper&&t.helper.get(0).removeChild(t.ghost.get(0))}}),e.ui.plugin.add("resizable","grid",{resize:function(){var t=e(this).data("ui-resizable"),n=t.options,r=t.size,i=t.originalSize,s=t.originalPosition,o=t.axis,u=typeof n.grid=="number"?[n.grid,n.grid]:n.grid,a=u[0]||1,f=u[1]||1,l=Math.round((r.width-i.width)/a)*a,c=Math.round((r.height-i.height)/f)*f,h=i.width+l,p=i.height+c,d=n.maxWidth&&n.maxWidth<h,v=n.maxHeight&&n.maxHeight<p,m=n.minWidth&&n.minWidth>h,g=n.minHeight&&n.minHeight>p;n.grid=u,m&&(h+=a),g&&(p+=f),d&&(h-=a),v&&(p-=f),/^(se|s|e)$/.test(o)?(t.size.width=h,t.size.height=p):/^(ne)$/.test(o)?(t.size.width=h,t.size.height=p,t.position.top=s.top-c):/^(sw)$/.test(o)?(t.size.width=h,t.size.height=p,t.position.left=s.left-l):(t.size.width=h,t.size.height=p,t.position.top=s.top-c,t.position.left=s.left-l)}})})(jQuery); \ No newline at end of file
diff --git a/misc/flot/examples/stacking/index.html b/misc/flot/examples/stacking/index.html
new file mode 100644
index 0000000..bf5414e
--- /dev/null
+++ b/misc/flot/examples/stacking/index.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Stacking</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.stack.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var d1 = [];
+ for (var i = 0; i <= 10; i += 1) {
+ d1.push([i, parseInt(Math.random() * 30)]);
+ }
+
+ var d2 = [];
+ for (var i = 0; i <= 10; i += 1) {
+ d2.push([i, parseInt(Math.random() * 30)]);
+ }
+
+ var d3 = [];
+ for (var i = 0; i <= 10; i += 1) {
+ d3.push([i, parseInt(Math.random() * 30)]);
+ }
+
+ var stack = 0,
+ bars = true,
+ lines = false,
+ steps = false;
+
+ function plotWithOptions() {
+ $.plot("#placeholder", [ d1, d2, d3 ], {
+ series: {
+ stack: stack,
+ lines: {
+ show: lines,
+ fill: true,
+ steps: steps
+ },
+ bars: {
+ show: bars,
+ barWidth: 0.6
+ }
+ }
+ });
+ }
+
+ plotWithOptions();
+
+ $(".stackControls button").click(function (e) {
+ e.preventDefault();
+ stack = $(this).text() == "With stacking" ? true : null;
+ plotWithOptions();
+ });
+
+ $(".graphControls button").click(function (e) {
+ e.preventDefault();
+ bars = $(this).text().indexOf("Bars") != -1;
+ lines = $(this).text().indexOf("Lines") != -1;
+ steps = $(this).text().indexOf("steps") != -1;
+ plotWithOptions();
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Stacking</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>With the stack plugin, you can have Flot stack the series. This is useful if you wish to display both a total and the constituents it is made of. The only requirement is that you provide the input sorted on x.</p>
+
+ <p class="stackControls">
+ <button>With stacking</button>
+ <button>Without stacking</button>
+ </p>
+
+ <p class="graphControls">
+ <button>Bars</button>
+ <button>Lines</button>
+ <button>Lines with steps</button>
+ </p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/symbols/index.html b/misc/flot/examples/symbols/index.html
new file mode 100644
index 0000000..e2507be
--- /dev/null
+++ b/misc/flot/examples/symbols/index.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Symbols</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.symbol.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ function generate(offset, amplitude) {
+
+ var res = [];
+ var start = 0, end = 10;
+
+ for (var i = 0; i <= 50; ++i) {
+ var x = start + i / 50 * (end - start);
+ res.push([x, amplitude * Math.sin(x + offset)]);
+ }
+
+ return res;
+ }
+
+ var data = [
+ { data: generate(2, 1.8), points: { symbol: "circle" } },
+ { data: generate(3, 1.5), points: { symbol: "square" } },
+ { data: generate(4, 0.9), points: { symbol: "diamond" } },
+ { data: generate(6, 1.4), points: { symbol: "triangle" } },
+ { data: generate(7, 1.1), points: { symbol: "cross" } }
+ ];
+
+ $.plot("#placeholder", data, {
+ series: {
+ points: {
+ show: true,
+ radius: 3
+ }
+ },
+ grid: {
+ hoverable: true
+ }
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Symbols</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>Points can be marked in several ways, with circles being the built-in default. For other point types, you can define a callback function to draw the symbol. Some common symbols are available in the symbol plugin.</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/threshold/index.html b/misc/flot/examples/threshold/index.html
new file mode 100644
index 0000000..c2c596f
--- /dev/null
+++ b/misc/flot/examples/threshold/index.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Thresholds</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.threshold.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var d1 = [];
+ for (var i = 0; i <= 60; i += 1) {
+ d1.push([i, parseInt(Math.random() * 30 - 10)]);
+ }
+
+ function plotWithOptions(t) {
+ $.plot("#placeholder", [{
+ data: d1,
+ color: "rgb(30, 180, 20)",
+ threshold: {
+ below: t,
+ color: "rgb(200, 20, 30)"
+ },
+ lines: {
+ steps: true
+ }
+ }]);
+ }
+
+ plotWithOptions(0);
+
+ $(".controls button").click(function (e) {
+ e.preventDefault();
+ var t = parseFloat($(this).text().replace("Threshold at ", ""));
+ plotWithOptions(t);
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Thresholds</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>With the threshold plugin, you can apply a specific color to the part of a data series below a threshold. This is can be useful for highlighting negative values, e.g. when displaying net results or what's in stock.</p>
+
+ <p class="controls">
+ <button>Threshold at 5</button>
+ <button>Threshold at 0</button>
+ <button>Threshold at -2.5</button>
+ </p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/tracking/index.html b/misc/flot/examples/tracking/index.html
new file mode 100644
index 0000000..69276c2
--- /dev/null
+++ b/misc/flot/examples/tracking/index.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Tracking</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.crosshair.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var sin = [], cos = [];
+ for (var i = 0; i < 14; i += 0.1) {
+ sin.push([i, Math.sin(i)]);
+ cos.push([i, Math.cos(i)]);
+ }
+
+ plot = $.plot("#placeholder", [
+ { data: sin, label: "sin(x) = -0.00"},
+ { data: cos, label: "cos(x) = -0.00" }
+ ], {
+ series: {
+ lines: {
+ show: true
+ }
+ },
+ crosshair: {
+ mode: "x"
+ },
+ grid: {
+ hoverable: true,
+ autoHighlight: false
+ },
+ yaxis: {
+ min: -1.2,
+ max: 1.2
+ }
+ });
+
+ var legends = $("#placeholder .legendLabel");
+
+ legends.each(function () {
+ // fix the widths so they don't jump around
+ $(this).css('width', $(this).width());
+ });
+
+ var updateLegendTimeout = null;
+ var latestPosition = null;
+
+ function updateLegend() {
+
+ updateLegendTimeout = null;
+
+ var pos = latestPosition;
+
+ var axes = plot.getAxes();
+ if (pos.x < axes.xaxis.min || pos.x > axes.xaxis.max ||
+ pos.y < axes.yaxis.min || pos.y > axes.yaxis.max) {
+ return;
+ }
+
+ var i, j, dataset = plot.getData();
+ for (i = 0; i < dataset.length; ++i) {
+
+ var series = dataset[i];
+
+ // Find the nearest points, x-wise
+
+ for (j = 0; j < series.data.length; ++j) {
+ if (series.data[j][0] > pos.x) {
+ break;
+ }
+ }
+
+ // Now Interpolate
+
+ var y,
+ p1 = series.data[j - 1],
+ p2 = series.data[j];
+
+ if (p1 == null) {
+ y = p2[1];
+ } else if (p2 == null) {
+ y = p1[1];
+ } else {
+ y = p1[1] + (p2[1] - p1[1]) * (pos.x - p1[0]) / (p2[0] - p1[0]);
+ }
+
+ legends.eq(i).text(series.label.replace(/=.*/, "= " + y.toFixed(2)));
+ }
+ }
+
+ $("#placeholder").bind("plothover", function (event, pos, item) {
+ latestPosition = pos;
+ if (!updateLegendTimeout) {
+ updateLegendTimeout = setTimeout(updateLegend, 50);
+ }
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Tracking</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <p>You can add crosshairs that'll track the mouse position, either on both axes or as here on only one.</p>
+
+ <p>If you combine it with listening on hover events, you can use it to track the intersection on the curves by interpolating the data points (look at the legend).</p>
+
+ <p id="hoverdata"></p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/visitors/index.html b/misc/flot/examples/visitors/index.html
new file mode 100644
index 0000000..803c0bc
--- /dev/null
+++ b/misc/flot/examples/visitors/index.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Visitors</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.time.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.selection.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ var d = [[1196463600000, 0], [1196550000000, 0], [1196636400000, 0], [1196722800000, 77], [1196809200000, 3636], [1196895600000, 3575], [1196982000000, 2736], [1197068400000, 1086], [1197154800000, 676], [1197241200000, 1205], [1197327600000, 906], [1197414000000, 710], [1197500400000, 639], [1197586800000, 540], [1197673200000, 435], [1197759600000, 301], [1197846000000, 575], [1197932400000, 481], [1198018800000, 591], [1198105200000, 608], [1198191600000, 459], [1198278000000, 234], [1198364400000, 1352], [1198450800000, 686], [1198537200000, 279], [1198623600000, 449], [1198710000000, 468], [1198796400000, 392], [1198882800000, 282], [1198969200000, 208], [1199055600000, 229], [1199142000000, 177], [1199228400000, 374], [1199314800000, 436], [1199401200000, 404], [1199487600000, 253], [1199574000000, 218], [1199660400000, 476], [1199746800000, 462], [1199833200000, 448], [1199919600000, 442], [1200006000000, 403], [1200092400000, 204], [1200178800000, 194], [1200265200000, 327], [1200351600000, 374], [1200438000000, 507], [1200524400000, 546], [1200610800000, 482], [1200697200000, 283], [1200783600000, 221], [1200870000000, 483], [1200956400000, 523], [1201042800000, 528], [1201129200000, 483], [1201215600000, 452], [1201302000000, 270], [1201388400000, 222], [1201474800000, 439], [1201561200000, 559], [1201647600000, 521], [1201734000000, 477], [1201820400000, 442], [1201906800000, 252], [1201993200000, 236], [1202079600000, 525], [1202166000000, 477], [1202252400000, 386], [1202338800000, 409], [1202425200000, 408], [1202511600000, 237], [1202598000000, 193], [1202684400000, 357], [1202770800000, 414], [1202857200000, 393], [1202943600000, 353], [1203030000000, 364], [1203116400000, 215], [1203202800000, 214], [1203289200000, 356], [1203375600000, 399], [1203462000000, 334], [1203548400000, 348], [1203634800000, 243], [1203721200000, 126], [1203807600000, 157], [1203894000000, 288]];
+
+ // first correct the timestamps - they are recorded as the daily
+ // midnights in UTC+0100, but Flot always displays dates in UTC
+ // so we have to add one hour to hit the midnights in the plot
+
+ for (var i = 0; i < d.length; ++i) {
+ d[i][0] += 60 * 60 * 1000;
+ }
+
+ // helper for returning the weekends in a period
+
+ function weekendAreas(axes) {
+
+ var markings = [],
+ d = new Date(axes.xaxis.min);
+
+ // go to the first Saturday
+
+ d.setUTCDate(d.getUTCDate() - ((d.getUTCDay() + 1) % 7))
+ d.setUTCSeconds(0);
+ d.setUTCMinutes(0);
+ d.setUTCHours(0);
+
+ var i = d.getTime();
+
+ // when we don't set yaxis, the rectangle automatically
+ // extends to infinity upwards and downwards
+
+ do {
+ markings.push({ xaxis: { from: i, to: i + 2 * 24 * 60 * 60 * 1000 } });
+ i += 7 * 24 * 60 * 60 * 1000;
+ } while (i < axes.xaxis.max);
+
+ return markings;
+ }
+
+ var options = {
+ xaxis: {
+ mode: "time",
+ tickLength: 5
+ },
+ selection: {
+ mode: "x"
+ },
+ grid: {
+ markings: weekendAreas
+ }
+ };
+
+ var plot = $.plot("#placeholder", [d], options);
+
+ var overview = $.plot("#overview", [d], {
+ series: {
+ lines: {
+ show: true,
+ lineWidth: 1
+ },
+ shadowSize: 0
+ },
+ xaxis: {
+ ticks: [],
+ mode: "time"
+ },
+ yaxis: {
+ ticks: [],
+ min: 0,
+ autoscaleMargin: 0.1
+ },
+ selection: {
+ mode: "x"
+ }
+ });
+
+ // now connect the two
+
+ $("#placeholder").bind("plotselected", function (event, ranges) {
+
+ // do the zooming
+ $.each(plot.getXAxes(), function(_, axis) {
+ var opts = axis.options;
+ opts.min = ranges.xaxis.from;
+ opts.max = ranges.xaxis.to;
+ });
+ plot.setupGrid();
+ plot.draw();
+ plot.clearSelection();
+
+ // don't fire event on the overview to prevent eternal loop
+
+ overview.setSelection(ranges, true);
+ });
+
+ $("#overview").bind("plotselected", function (event, ranges) {
+ plot.setSelection(ranges);
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Visitors</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder"></div>
+ </div>
+
+ <div class="demo-container" style="height:150px;">
+ <div id="overview" class="demo-placeholder"></div>
+ </div>
+
+ <p>This plot shows visitors per day to the Flot homepage, with weekends colored.</p>
+
+ <p>The smaller plot is linked to the main plot, so it acts as an overview. Try dragging a selection on either plot, and watch the behavior of the other.</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/examples/zooming/index.html b/misc/flot/examples/zooming/index.html
new file mode 100644
index 0000000..fb295d5
--- /dev/null
+++ b/misc/flot/examples/zooming/index.html
@@ -0,0 +1,144 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Flot Examples: Selection and zooming</title>
+ <link href="../examples.css" rel="stylesheet" type="text/css">
+ <!--[if lte IE 8]><script language="javascript" type="text/javascript" src="../../excanvas.min.js"></script><![endif]-->
+ <script language="javascript" type="text/javascript" src="../../jquery.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.js"></script>
+ <script language="javascript" type="text/javascript" src="../../jquery.flot.selection.js"></script>
+ <script type="text/javascript">
+
+ $(function() {
+
+ // setup plot
+
+ function getData(x1, x2) {
+
+ var d = [];
+ for (var i = 0; i <= 100; ++i) {
+ var x = x1 + i * (x2 - x1) / 100;
+ d.push([x, Math.sin(x * Math.sin(x))]);
+ }
+
+ return [
+ { label: "sin(x sin(x))", data: d }
+ ];
+ }
+
+ var options = {
+ legend: {
+ show: false
+ },
+ series: {
+ lines: {
+ show: true
+ },
+ points: {
+ show: true
+ }
+ },
+ yaxis: {
+ ticks: 10
+ },
+ selection: {
+ mode: "xy"
+ }
+ };
+
+ var startData = getData(0, 3 * Math.PI);
+
+ var plot = $.plot("#placeholder", startData, options);
+
+ // Create the overview plot
+
+ var overview = $.plot("#overview", startData, {
+ legend: {
+ show: false
+ },
+ series: {
+ lines: {
+ show: true,
+ lineWidth: 1
+ },
+ shadowSize: 0
+ },
+ xaxis: {
+ ticks: 4
+ },
+ yaxis: {
+ ticks: 3,
+ min: -2,
+ max: 2
+ },
+ grid: {
+ color: "#999"
+ },
+ selection: {
+ mode: "xy"
+ }
+ });
+
+ // now connect the two
+
+ $("#placeholder").bind("plotselected", function (event, ranges) {
+
+ // clamp the zooming to prevent eternal zoom
+
+ if (ranges.xaxis.to - ranges.xaxis.from < 0.00001) {
+ ranges.xaxis.to = ranges.xaxis.from + 0.00001;
+ }
+
+ if (ranges.yaxis.to - ranges.yaxis.from < 0.00001) {
+ ranges.yaxis.to = ranges.yaxis.from + 0.00001;
+ }
+
+ // do the zooming
+
+ plot = $.plot("#placeholder", getData(ranges.xaxis.from, ranges.xaxis.to),
+ $.extend(true, {}, options, {
+ xaxis: { min: ranges.xaxis.from, max: ranges.xaxis.to },
+ yaxis: { min: ranges.yaxis.from, max: ranges.yaxis.to }
+ })
+ );
+
+ // don't fire event on the overview to prevent eternal loop
+
+ overview.setSelection(ranges, true);
+ });
+
+ $("#overview").bind("plotselected", function (event, ranges) {
+ plot.setSelection(ranges);
+ });
+
+ // Add the Flot version string to the footer
+
+ $("#footer").prepend("Flot " + $.plot.version + " &ndash; ");
+ });
+
+ </script>
+</head>
+<body>
+
+ <div id="header">
+ <h2>Selection and zooming</h2>
+ </div>
+
+ <div id="content">
+
+ <div class="demo-container">
+ <div id="placeholder" class="demo-placeholder" style="float:left; width:650px;"></div>
+ <div id="overview" class="demo-placeholder" style="float:right;width:160px; height:125px;"></div>
+ </div>
+
+ <p>Selection support makes it easy to construct flexible zooming schemes. With a few lines of code, the small overview plot to the right has been connected to the large plot. Try selecting a rectangle on either of them.</p>
+
+ </div>
+
+ <div id="footer">
+ Copyright &copy; 2007 - 2014 IOLA and Ole Laursen
+ </div>
+
+</body>
+</html>
diff --git a/misc/flot/excanvas.js b/misc/flot/excanvas.js
new file mode 100644
index 0000000..70a8f25
--- /dev/null
+++ b/misc/flot/excanvas.js
@@ -0,0 +1,1428 @@
+// Copyright 2006 Google 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.
+
+
+// Known Issues:
+//
+// * Patterns only support repeat.
+// * Radial gradient are not implemented. The VML version of these look very
+// different from the canvas one.
+// * Clipping paths are not implemented.
+// * Coordsize. The width and height attribute have higher priority than the
+// width and height style values which isn't correct.
+// * Painting mode isn't implemented.
+// * Canvas width/height should is using content-box by default. IE in
+// Quirks mode will draw the canvas using border-box. Either change your
+// doctype to HTML5
+// (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
+// or use Box Sizing Behavior from WebFX
+// (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
+// * Non uniform scaling does not correctly scale strokes.
+// * Filling very large shapes (above 5000 points) is buggy.
+// * Optimize. There is always room for speed improvements.
+
+// Only add this code if we do not already have a canvas implementation
+if (!document.createElement('canvas').getContext) {
+
+(function() {
+
+ // alias some functions to make (compiled) code shorter
+ var m = Math;
+ var mr = m.round;
+ var ms = m.sin;
+ var mc = m.cos;
+ var abs = m.abs;
+ var sqrt = m.sqrt;
+
+ // this is used for sub pixel precision
+ var Z = 10;
+ var Z2 = Z / 2;
+
+ var IE_VERSION = +navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];
+
+ /**
+ * This funtion is assigned to the <canvas> elements as element.getContext().
+ * @this {HTMLElement}
+ * @return {CanvasRenderingContext2D_}
+ */
+ function getContext() {
+ return this.context_ ||
+ (this.context_ = new CanvasRenderingContext2D_(this));
+ }
+
+ var slice = Array.prototype.slice;
+
+ /**
+ * Binds a function to an object. The returned function will always use the
+ * passed in {@code obj} as {@code this}.
+ *
+ * Example:
+ *
+ * g = bind(f, obj, a, b)
+ * g(c, d) // will do f.call(obj, a, b, c, d)
+ *
+ * @param {Function} f The function to bind the object to
+ * @param {Object} obj The object that should act as this when the function
+ * is called
+ * @param {*} var_args Rest arguments that will be used as the initial
+ * arguments when the function is called
+ * @return {Function} A new function that has bound this
+ */
+ function bind(f, obj, var_args) {
+ var a = slice.call(arguments, 2);
+ return function() {
+ return f.apply(obj, a.concat(slice.call(arguments)));
+ };
+ }
+
+ function encodeHtmlAttribute(s) {
+ return String(s).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
+ }
+
+ function addNamespace(doc, prefix, urn) {
+ if (!doc.namespaces[prefix]) {
+ doc.namespaces.add(prefix, urn, '#default#VML');
+ }
+ }
+
+ function addNamespacesAndStylesheet(doc) {
+ addNamespace(doc, 'g_vml_', 'urn:schemas-microsoft-com:vml');
+ addNamespace(doc, 'g_o_', 'urn:schemas-microsoft-com:office:office');
+
+ // Setup default CSS. Only add one style sheet per document
+ if (!doc.styleSheets['ex_canvas_']) {
+ var ss = doc.createStyleSheet();
+ ss.owningElement.id = 'ex_canvas_';
+ ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
+ // default size is 300x150 in Gecko and Opera
+ 'text-align:left;width:300px;height:150px}';
+ }
+ }
+
+ // Add namespaces and stylesheet at startup.
+ addNamespacesAndStylesheet(document);
+
+ var G_vmlCanvasManager_ = {
+ init: function(opt_doc) {
+ var doc = opt_doc || document;
+ // Create a dummy element so that IE will allow canvas elements to be
+ // recognized.
+ doc.createElement('canvas');
+ doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
+ },
+
+ init_: function(doc) {
+ // find all canvas elements
+ var els = doc.getElementsByTagName('canvas');
+ for (var i = 0; i < els.length; i++) {
+ this.initElement(els[i]);
+ }
+ },
+
+ /**
+ * Public initializes a canvas element so that it can be used as canvas
+ * element from now on. This is called automatically before the page is
+ * loaded but if you are creating elements using createElement you need to
+ * make sure this is called on the element.
+ * @param {HTMLElement} el The canvas element to initialize.
+ * @return {HTMLElement} the element that was created.
+ */
+ initElement: function(el) {
+ if (!el.getContext) {
+ el.getContext = getContext;
+
+ // Add namespaces and stylesheet to document of the element.
+ addNamespacesAndStylesheet(el.ownerDocument);
+
+ // Remove fallback content. There is no way to hide text nodes so we
+ // just remove all childNodes. We could hide all elements and remove
+ // text nodes but who really cares about the fallback content.
+ el.innerHTML = '';
+
+ // do not use inline function because that will leak memory
+ el.attachEvent('onpropertychange', onPropertyChange);
+ el.attachEvent('onresize', onResize);
+
+ var attrs = el.attributes;
+ if (attrs.width && attrs.width.specified) {
+ // TODO: use runtimeStyle and coordsize
+ // el.getContext().setWidth_(attrs.width.nodeValue);
+ el.style.width = attrs.width.nodeValue + 'px';
+ } else {
+ el.width = el.clientWidth;
+ }
+ if (attrs.height && attrs.height.specified) {
+ // TODO: use runtimeStyle and coordsize
+ // el.getContext().setHeight_(attrs.height.nodeValue);
+ el.style.height = attrs.height.nodeValue + 'px';
+ } else {
+ el.height = el.clientHeight;
+ }
+ //el.getContext().setCoordsize_()
+ }
+ return el;
+ }
+ };
+
+ function onPropertyChange(e) {
+ var el = e.srcElement;
+
+ switch (e.propertyName) {
+ case 'width':
+ el.getContext().clearRect();
+ el.style.width = el.attributes.width.nodeValue + 'px';
+ // In IE8 this does not trigger onresize.
+ el.firstChild.style.width = el.clientWidth + 'px';
+ break;
+ case 'height':
+ el.getContext().clearRect();
+ el.style.height = el.attributes.height.nodeValue + 'px';
+ el.firstChild.style.height = el.clientHeight + 'px';
+ break;
+ }
+ }
+
+ function onResize(e) {
+ var el = e.srcElement;
+ if (el.firstChild) {
+ el.firstChild.style.width = el.clientWidth + 'px';
+ el.firstChild.style.height = el.clientHeight + 'px';
+ }
+ }
+
+ G_vmlCanvasManager_.init();
+
+ // precompute "00" to "FF"
+ var decToHex = [];
+ for (var i = 0; i < 16; i++) {
+ for (var j = 0; j < 16; j++) {
+ decToHex[i * 16 + j] = i.toString(16) + j.toString(16);
+ }
+ }
+
+ function createMatrixIdentity() {
+ return [
+ [1, 0, 0],
+ [0, 1, 0],
+ [0, 0, 1]
+ ];
+ }
+
+ function matrixMultiply(m1, m2) {
+ var result = createMatrixIdentity();
+
+ for (var x = 0; x < 3; x++) {
+ for (var y = 0; y < 3; y++) {
+ var sum = 0;
+
+ for (var z = 0; z < 3; z++) {
+ sum += m1[x][z] * m2[z][y];
+ }
+
+ result[x][y] = sum;
+ }
+ }
+ return result;
+ }
+
+ function copyState(o1, o2) {
+ o2.fillStyle = o1.fillStyle;
+ o2.lineCap = o1.lineCap;
+ o2.lineJoin = o1.lineJoin;
+ o2.lineWidth = o1.lineWidth;
+ o2.miterLimit = o1.miterLimit;
+ o2.shadowBlur = o1.shadowBlur;
+ o2.shadowColor = o1.shadowColor;
+ o2.shadowOffsetX = o1.shadowOffsetX;
+ o2.shadowOffsetY = o1.shadowOffsetY;
+ o2.strokeStyle = o1.strokeStyle;
+ o2.globalAlpha = o1.globalAlpha;
+ o2.font = o1.font;
+ o2.textAlign = o1.textAlign;
+ o2.textBaseline = o1.textBaseline;
+ o2.arcScaleX_ = o1.arcScaleX_;
+ o2.arcScaleY_ = o1.arcScaleY_;
+ o2.lineScale_ = o1.lineScale_;
+ }
+
+ var colorData = {
+ aliceblue: '#F0F8FF',
+ antiquewhite: '#FAEBD7',
+ aquamarine: '#7FFFD4',
+ azure: '#F0FFFF',
+ beige: '#F5F5DC',
+ bisque: '#FFE4C4',
+ black: '#000000',
+ blanchedalmond: '#FFEBCD',
+ blueviolet: '#8A2BE2',
+ brown: '#A52A2A',
+ burlywood: '#DEB887',
+ cadetblue: '#5F9EA0',
+ chartreuse: '#7FFF00',
+ chocolate: '#D2691E',
+ coral: '#FF7F50',
+ cornflowerblue: '#6495ED',
+ cornsilk: '#FFF8DC',
+ crimson: '#DC143C',
+ cyan: '#00FFFF',
+ darkblue: '#00008B',
+ darkcyan: '#008B8B',
+ darkgoldenrod: '#B8860B',
+ darkgray: '#A9A9A9',
+ darkgreen: '#006400',
+ darkgrey: '#A9A9A9',
+ darkkhaki: '#BDB76B',
+ darkmagenta: '#8B008B',
+ darkolivegreen: '#556B2F',
+ darkorange: '#FF8C00',
+ darkorchid: '#9932CC',
+ darkred: '#8B0000',
+ darksalmon: '#E9967A',
+ darkseagreen: '#8FBC8F',
+ darkslateblue: '#483D8B',
+ darkslategray: '#2F4F4F',
+ darkslategrey: '#2F4F4F',
+ darkturquoise: '#00CED1',
+ darkviolet: '#9400D3',
+ deeppink: '#FF1493',
+ deepskyblue: '#00BFFF',
+ dimgray: '#696969',
+ dimgrey: '#696969',
+ dodgerblue: '#1E90FF',
+ firebrick: '#B22222',
+ floralwhite: '#FFFAF0',
+ forestgreen: '#228B22',
+ gainsboro: '#DCDCDC',
+ ghostwhite: '#F8F8FF',
+ gold: '#FFD700',
+ goldenrod: '#DAA520',
+ grey: '#808080',
+ greenyellow: '#ADFF2F',
+ honeydew: '#F0FFF0',
+ hotpink: '#FF69B4',
+ indianred: '#CD5C5C',
+ indigo: '#4B0082',
+ ivory: '#FFFFF0',
+ khaki: '#F0E68C',
+ lavender: '#E6E6FA',
+ lavenderblush: '#FFF0F5',
+ lawngreen: '#7CFC00',
+ lemonchiffon: '#FFFACD',
+ lightblue: '#ADD8E6',
+ lightcoral: '#F08080',
+ lightcyan: '#E0FFFF',
+ lightgoldenrodyellow: '#FAFAD2',
+ lightgreen: '#90EE90',
+ lightgrey: '#D3D3D3',
+ lightpink: '#FFB6C1',
+ lightsalmon: '#FFA07A',
+ lightseagreen: '#20B2AA',
+ lightskyblue: '#87CEFA',
+ lightslategray: '#778899',
+ lightslategrey: '#778899',
+ lightsteelblue: '#B0C4DE',
+ lightyellow: '#FFFFE0',
+ limegreen: '#32CD32',
+ linen: '#FAF0E6',
+ magenta: '#FF00FF',
+ mediumaquamarine: '#66CDAA',
+ mediumblue: '#0000CD',
+ mediumorchid: '#BA55D3',
+ mediumpurple: '#9370DB',
+ mediumseagreen: '#3CB371',
+ mediumslateblue: '#7B68EE',
+ mediumspringgreen: '#00FA9A',
+ mediumturquoise: '#48D1CC',
+ mediumvioletred: '#C71585',
+ midnightblue: '#191970',
+ mintcream: '#F5FFFA',
+ mistyrose: '#FFE4E1',
+ moccasin: '#FFE4B5',
+ navajowhite: '#FFDEAD',
+ oldlace: '#FDF5E6',
+ olivedrab: '#6B8E23',
+ orange: '#FFA500',
+ orangered: '#FF4500',
+ orchid: '#DA70D6',
+ palegoldenrod: '#EEE8AA',
+ palegreen: '#98FB98',
+ paleturquoise: '#AFEEEE',
+ palevioletred: '#DB7093',
+ papayawhip: '#FFEFD5',
+ peachpuff: '#FFDAB9',
+ peru: '#CD853F',
+ pink: '#FFC0CB',
+ plum: '#DDA0DD',
+ powderblue: '#B0E0E6',
+ rosybrown: '#BC8F8F',
+ royalblue: '#4169E1',
+ saddlebrown: '#8B4513',
+ salmon: '#FA8072',
+ sandybrown: '#F4A460',
+ seagreen: '#2E8B57',
+ seashell: '#FFF5EE',
+ sienna: '#A0522D',
+ skyblue: '#87CEEB',
+ slateblue: '#6A5ACD',
+ slategray: '#708090',
+ slategrey: '#708090',
+ snow: '#FFFAFA',
+ springgreen: '#00FF7F',
+ steelblue: '#4682B4',
+ tan: '#D2B48C',
+ thistle: '#D8BFD8',
+ tomato: '#FF6347',
+ turquoise: '#40E0D0',
+ violet: '#EE82EE',
+ wheat: '#F5DEB3',
+ whitesmoke: '#F5F5F5',
+ yellowgreen: '#9ACD32'
+ };
+
+
+ function getRgbHslContent(styleString) {
+ var start = styleString.indexOf('(', 3);
+ var end = styleString.indexOf(')', start + 1);
+ var parts = styleString.substring(start + 1, end).split(',');
+ // add alpha if needed
+ if (parts.length != 4 || styleString.charAt(3) != 'a') {
+ parts[3] = 1;
+ }
+ return parts;
+ }
+
+ function percent(s) {
+ return parseFloat(s) / 100;
+ }
+
+ function clamp(v, min, max) {
+ return Math.min(max, Math.max(min, v));
+ }
+
+ function hslToRgb(parts){
+ var r, g, b, h, s, l;
+ h = parseFloat(parts[0]) / 360 % 360;
+ if (h < 0)
+ h++;
+ s = clamp(percent(parts[1]), 0, 1);
+ l = clamp(percent(parts[2]), 0, 1);
+ if (s == 0) {
+ r = g = b = l; // achromatic
+ } else {
+ var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ var p = 2 * l - q;
+ r = hueToRgb(p, q, h + 1 / 3);
+ g = hueToRgb(p, q, h);
+ b = hueToRgb(p, q, h - 1 / 3);
+ }
+
+ return '#' + decToHex[Math.floor(r * 255)] +
+ decToHex[Math.floor(g * 255)] +
+ decToHex[Math.floor(b * 255)];
+ }
+
+ function hueToRgb(m1, m2, h) {
+ if (h < 0)
+ h++;
+ if (h > 1)
+ h--;
+
+ if (6 * h < 1)
+ return m1 + (m2 - m1) * 6 * h;
+ else if (2 * h < 1)
+ return m2;
+ else if (3 * h < 2)
+ return m1 + (m2 - m1) * (2 / 3 - h) * 6;
+ else
+ return m1;
+ }
+
+ var processStyleCache = {};
+
+ function processStyle(styleString) {
+ if (styleString in processStyleCache) {
+ return processStyleCache[styleString];
+ }
+
+ var str, alpha = 1;
+
+ styleString = String(styleString);
+ if (styleString.charAt(0) == '#') {
+ str = styleString;
+ } else if (/^rgb/.test(styleString)) {
+ var parts = getRgbHslContent(styleString);
+ var str = '#', n;
+ for (var i = 0; i < 3; i++) {
+ if (parts[i].indexOf('%') != -1) {
+ n = Math.floor(percent(parts[i]) * 255);
+ } else {
+ n = +parts[i];
+ }
+ str += decToHex[clamp(n, 0, 255)];
+ }
+ alpha = +parts[3];
+ } else if (/^hsl/.test(styleString)) {
+ var parts = getRgbHslContent(styleString);
+ str = hslToRgb(parts);
+ alpha = parts[3];
+ } else {
+ str = colorData[styleString] || styleString;
+ }
+ return processStyleCache[styleString] = {color: str, alpha: alpha};
+ }
+
+ var DEFAULT_STYLE = {
+ style: 'normal',
+ variant: 'normal',
+ weight: 'normal',
+ size: 10,
+ family: 'sans-serif'
+ };
+
+ // Internal text style cache
+ var fontStyleCache = {};
+
+ function processFontStyle(styleString) {
+ if (fontStyleCache[styleString]) {
+ return fontStyleCache[styleString];
+ }
+
+ var el = document.createElement('div');
+ var style = el.style;
+ try {
+ style.font = styleString;
+ } catch (ex) {
+ // Ignore failures to set to invalid font.
+ }
+
+ return fontStyleCache[styleString] = {
+ style: style.fontStyle || DEFAULT_STYLE.style,
+ variant: style.fontVariant || DEFAULT_STYLE.variant,
+ weight: style.fontWeight || DEFAULT_STYLE.weight,
+ size: style.fontSize || DEFAULT_STYLE.size,
+ family: style.fontFamily || DEFAULT_STYLE.family
+ };
+ }
+
+ function getComputedStyle(style, element) {
+ var computedStyle = {};
+
+ for (var p in style) {
+ computedStyle[p] = style[p];
+ }
+
+ // Compute the size
+ var canvasFontSize = parseFloat(element.currentStyle.fontSize),
+ fontSize = parseFloat(style.size);
+
+ if (typeof style.size == 'number') {
+ computedStyle.size = style.size;
+ } else if (style.size.indexOf('px') != -1) {
+ computedStyle.size = fontSize;
+ } else if (style.size.indexOf('em') != -1) {
+ computedStyle.size = canvasFontSize * fontSize;
+ } else if(style.size.indexOf('%') != -1) {
+ computedStyle.size = (canvasFontSize / 100) * fontSize;
+ } else if (style.size.indexOf('pt') != -1) {
+ computedStyle.size = fontSize / .75;
+ } else {
+ computedStyle.size = canvasFontSize;
+ }
+
+ // Different scaling between normal text and VML text. This was found using
+ // trial and error to get the same size as non VML text.
+ computedStyle.size *= 0.981;
+
+ return computedStyle;
+ }
+
+ function buildStyle(style) {
+ return style.style + ' ' + style.variant + ' ' + style.weight + ' ' +
+ style.size + 'px ' + style.family;
+ }
+
+ var lineCapMap = {
+ 'butt': 'flat',
+ 'round': 'round'
+ };
+
+ function processLineCap(lineCap) {
+ return lineCapMap[lineCap] || 'square';
+ }
+
+ /**
+ * This class implements CanvasRenderingContext2D interface as described by
+ * the WHATWG.
+ * @param {HTMLElement} canvasElement The element that the 2D context should
+ * be associated with
+ */
+ function CanvasRenderingContext2D_(canvasElement) {
+ this.m_ = createMatrixIdentity();
+
+ this.mStack_ = [];
+ this.aStack_ = [];
+ this.currentPath_ = [];
+
+ // Canvas context properties
+ this.strokeStyle = '#000';
+ this.fillStyle = '#000';
+
+ this.lineWidth = 1;
+ this.lineJoin = 'miter';
+ this.lineCap = 'butt';
+ this.miterLimit = Z * 1;
+ this.globalAlpha = 1;
+ this.font = '10px sans-serif';
+ this.textAlign = 'left';
+ this.textBaseline = 'alphabetic';
+ this.canvas = canvasElement;
+
+ var cssText = 'width:' + canvasElement.clientWidth + 'px;height:' +
+ canvasElement.clientHeight + 'px;overflow:hidden;position:absolute';
+ var el = canvasElement.ownerDocument.createElement('div');
+ el.style.cssText = cssText;
+ canvasElement.appendChild(el);
+
+ var overlayEl = el.cloneNode(false);
+ // Use a non transparent background.
+ overlayEl.style.backgroundColor = 'red';
+ overlayEl.style.filter = 'alpha(opacity=0)';
+ canvasElement.appendChild(overlayEl);
+
+ this.element_ = el;
+ this.arcScaleX_ = 1;
+ this.arcScaleY_ = 1;
+ this.lineScale_ = 1;
+ }
+
+ var contextPrototype = CanvasRenderingContext2D_.prototype;
+ contextPrototype.clearRect = function() {
+ if (this.textMeasureEl_) {
+ this.textMeasureEl_.removeNode(true);
+ this.textMeasureEl_ = null;
+ }
+ this.element_.innerHTML = '';
+ };
+
+ contextPrototype.beginPath = function() {
+ // TODO: Branch current matrix so that save/restore has no effect
+ // as per safari docs.
+ this.currentPath_ = [];
+ };
+
+ contextPrototype.moveTo = function(aX, aY) {
+ var p = getCoords(this, aX, aY);
+ this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
+ this.currentX_ = p.x;
+ this.currentY_ = p.y;
+ };
+
+ contextPrototype.lineTo = function(aX, aY) {
+ var p = getCoords(this, aX, aY);
+ this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
+
+ this.currentX_ = p.x;
+ this.currentY_ = p.y;
+ };
+
+ contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
+ aCP2x, aCP2y,
+ aX, aY) {
+ var p = getCoords(this, aX, aY);
+ var cp1 = getCoords(this, aCP1x, aCP1y);
+ var cp2 = getCoords(this, aCP2x, aCP2y);
+ bezierCurveTo(this, cp1, cp2, p);
+ };
+
+ // Helper function that takes the already fixed cordinates.
+ function bezierCurveTo(self, cp1, cp2, p) {
+ self.currentPath_.push({
+ type: 'bezierCurveTo',
+ cp1x: cp1.x,
+ cp1y: cp1.y,
+ cp2x: cp2.x,
+ cp2y: cp2.y,
+ x: p.x,
+ y: p.y
+ });
+ self.currentX_ = p.x;
+ self.currentY_ = p.y;
+ }
+
+ contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
+ // the following is lifted almost directly from
+ // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
+
+ var cp = getCoords(this, aCPx, aCPy);
+ var p = getCoords(this, aX, aY);
+
+ var cp1 = {
+ x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
+ y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
+ };
+ var cp2 = {
+ x: cp1.x + (p.x - this.currentX_) / 3.0,
+ y: cp1.y + (p.y - this.currentY_) / 3.0
+ };
+
+ bezierCurveTo(this, cp1, cp2, p);
+ };
+
+ contextPrototype.arc = function(aX, aY, aRadius,
+ aStartAngle, aEndAngle, aClockwise) {
+ aRadius *= Z;
+ var arcType = aClockwise ? 'at' : 'wa';
+
+ var xStart = aX + mc(aStartAngle) * aRadius - Z2;
+ var yStart = aY + ms(aStartAngle) * aRadius - Z2;
+
+ var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
+ var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
+
+ // IE won't render arches drawn counter clockwise if xStart == xEnd.
+ if (xStart == xEnd && !aClockwise) {
+ xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
+ // that can be represented in binary
+ }
+
+ var p = getCoords(this, aX, aY);
+ var pStart = getCoords(this, xStart, yStart);
+ var pEnd = getCoords(this, xEnd, yEnd);
+
+ this.currentPath_.push({type: arcType,
+ x: p.x,
+ y: p.y,
+ radius: aRadius,
+ xStart: pStart.x,
+ yStart: pStart.y,
+ xEnd: pEnd.x,
+ yEnd: pEnd.y});
+
+ };
+
+ contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ };
+
+ contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
+ var oldPath = this.currentPath_;
+ this.beginPath();
+
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ this.stroke();
+
+ this.currentPath_ = oldPath;
+ };
+
+ contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
+ var oldPath = this.currentPath_;
+ this.beginPath();
+
+ this.moveTo(aX, aY);
+ this.lineTo(aX + aWidth, aY);
+ this.lineTo(aX + aWidth, aY + aHeight);
+ this.lineTo(aX, aY + aHeight);
+ this.closePath();
+ this.fill();
+
+ this.currentPath_ = oldPath;
+ };
+
+ contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
+ var gradient = new CanvasGradient_('gradient');
+ gradient.x0_ = aX0;
+ gradient.y0_ = aY0;
+ gradient.x1_ = aX1;
+ gradient.y1_ = aY1;
+ return gradient;
+ };
+
+ contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
+ aX1, aY1, aR1) {
+ var gradient = new CanvasGradient_('gradientradial');
+ gradient.x0_ = aX0;
+ gradient.y0_ = aY0;
+ gradient.r0_ = aR0;
+ gradient.x1_ = aX1;
+ gradient.y1_ = aY1;
+ gradient.r1_ = aR1;
+ return gradient;
+ };
+
+ contextPrototype.drawImage = function(image, var_args) {
+ var dx, dy, dw, dh, sx, sy, sw, sh;
+
+ // to find the original width we overide the width and height
+ var oldRuntimeWidth = image.runtimeStyle.width;
+ var oldRuntimeHeight = image.runtimeStyle.height;
+ image.runtimeStyle.width = 'auto';
+ image.runtimeStyle.height = 'auto';
+
+ // get the original size
+ var w = image.width;
+ var h = image.height;
+
+ // and remove overides
+ image.runtimeStyle.width = oldRuntimeWidth;
+ image.runtimeStyle.height = oldRuntimeHeight;
+
+ if (arguments.length == 3) {
+ dx = arguments[1];
+ dy = arguments[2];
+ sx = sy = 0;
+ sw = dw = w;
+ sh = dh = h;
+ } else if (arguments.length == 5) {
+ dx = arguments[1];
+ dy = arguments[2];
+ dw = arguments[3];
+ dh = arguments[4];
+ sx = sy = 0;
+ sw = w;
+ sh = h;
+ } else if (arguments.length == 9) {
+ sx = arguments[1];
+ sy = arguments[2];
+ sw = arguments[3];
+ sh = arguments[4];
+ dx = arguments[5];
+ dy = arguments[6];
+ dw = arguments[7];
+ dh = arguments[8];
+ } else {
+ throw Error('Invalid number of arguments');
+ }
+
+ var d = getCoords(this, dx, dy);
+
+ var w2 = sw / 2;
+ var h2 = sh / 2;
+
+ var vmlStr = [];
+
+ var W = 10;
+ var H = 10;
+
+ // For some reason that I've now forgotten, using divs didn't work
+ vmlStr.push(' <g_vml_:group',
+ ' coordsize="', Z * W, ',', Z * H, '"',
+ ' coordorigin="0,0"' ,
+ ' style="width:', W, 'px;height:', H, 'px;position:absolute;');
+
+ // If filters are necessary (rotation exists), create them
+ // filters are bog-slow, so only create them if abbsolutely necessary
+ // The following check doesn't account for skews (which don't exist
+ // in the canvas spec (yet) anyway.
+
+ if (this.m_[0][0] != 1 || this.m_[0][1] ||
+ this.m_[1][1] != 1 || this.m_[1][0]) {
+ var filter = [];
+
+ // Note the 12/21 reversal
+ filter.push('M11=', this.m_[0][0], ',',
+ 'M12=', this.m_[1][0], ',',
+ 'M21=', this.m_[0][1], ',',
+ 'M22=', this.m_[1][1], ',',
+ 'Dx=', mr(d.x / Z), ',',
+ 'Dy=', mr(d.y / Z), '');
+
+ // Bounding box calculation (need to minimize displayed area so that
+ // filters don't waste time on unused pixels.
+ var max = d;
+ var c2 = getCoords(this, dx + dw, dy);
+ var c3 = getCoords(this, dx, dy + dh);
+ var c4 = getCoords(this, dx + dw, dy + dh);
+
+ max.x = m.max(max.x, c2.x, c3.x, c4.x);
+ max.y = m.max(max.y, c2.y, c3.y, c4.y);
+
+ vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
+ 'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
+ filter.join(''), ", sizingmethod='clip');");
+
+ } else {
+ vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
+ }
+
+ vmlStr.push(' ">' ,
+ '<g_vml_:image src="', image.src, '"',
+ ' style="width:', Z * dw, 'px;',
+ ' height:', Z * dh, 'px"',
+ ' cropleft="', sx / w, '"',
+ ' croptop="', sy / h, '"',
+ ' cropright="', (w - sx - sw) / w, '"',
+ ' cropbottom="', (h - sy - sh) / h, '"',
+ ' />',
+ '</g_vml_:group>');
+
+ this.element_.insertAdjacentHTML('BeforeEnd', vmlStr.join(''));
+ };
+
+ contextPrototype.stroke = function(aFill) {
+ var W = 10;
+ var H = 10;
+ // Divide the shape into chunks if it's too long because IE has a limit
+ // somewhere for how long a VML shape can be. This simple division does
+ // not work with fills, only strokes, unfortunately.
+ var chunkSize = 5000;
+
+ var min = {x: null, y: null};
+ var max = {x: null, y: null};
+
+ for (var j = 0; j < this.currentPath_.length; j += chunkSize) {
+ var lineStr = [];
+ var lineOpen = false;
+
+ lineStr.push('<g_vml_:shape',
+ ' filled="', !!aFill, '"',
+ ' style="position:absolute;width:', W, 'px;height:', H, 'px;"',
+ ' coordorigin="0,0"',
+ ' coordsize="', Z * W, ',', Z * H, '"',
+ ' stroked="', !aFill, '"',
+ ' path="');
+
+ var newSeq = false;
+
+ for (var i = j; i < Math.min(j + chunkSize, this.currentPath_.length); i++) {
+ if (i % chunkSize == 0 && i > 0) { // move into position for next chunk
+ lineStr.push(' m ', mr(this.currentPath_[i-1].x), ',', mr(this.currentPath_[i-1].y));
+ }
+
+ var p = this.currentPath_[i];
+ var c;
+
+ switch (p.type) {
+ case 'moveTo':
+ c = p;
+ lineStr.push(' m ', mr(p.x), ',', mr(p.y));
+ break;
+ case 'lineTo':
+ lineStr.push(' l ', mr(p.x), ',', mr(p.y));
+ break;
+ case 'close':
+ lineStr.push(' x ');
+ p = null;
+ break;
+ case 'bezierCurveTo':
+ lineStr.push(' c ',
+ mr(p.cp1x), ',', mr(p.cp1y), ',',
+ mr(p.cp2x), ',', mr(p.cp2y), ',',
+ mr(p.x), ',', mr(p.y));
+ break;
+ case 'at':
+ case 'wa':
+ lineStr.push(' ', p.type, ' ',
+ mr(p.x - this.arcScaleX_ * p.radius), ',',
+ mr(p.y - this.arcScaleY_ * p.radius), ' ',
+ mr(p.x + this.arcScaleX_ * p.radius), ',',
+ mr(p.y + this.arcScaleY_ * p.radius), ' ',
+ mr(p.xStart), ',', mr(p.yStart), ' ',
+ mr(p.xEnd), ',', mr(p.yEnd));
+ break;
+ }
+
+
+ // TODO: Following is broken for curves due to
+ // move to proper paths.
+
+ // Figure out dimensions so we can do gradient fills
+ // properly
+ if (p) {
+ if (min.x == null || p.x < min.x) {
+ min.x = p.x;
+ }
+ if (max.x == null || p.x > max.x) {
+ max.x = p.x;
+ }
+ if (min.y == null || p.y < min.y) {
+ min.y = p.y;
+ }
+ if (max.y == null || p.y > max.y) {
+ max.y = p.y;
+ }
+ }
+ }
+ lineStr.push(' ">');
+
+ if (!aFill) {
+ appendStroke(this, lineStr);
+ } else {
+ appendFill(this, lineStr, min, max);
+ }
+
+ lineStr.push('</g_vml_:shape>');
+
+ this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+ }
+ };
+
+ function appendStroke(ctx, lineStr) {
+ var a = processStyle(ctx.strokeStyle);
+ var color = a.color;
+ var opacity = a.alpha * ctx.globalAlpha;
+ var lineWidth = ctx.lineScale_ * ctx.lineWidth;
+
+ // VML cannot correctly render a line if the width is less than 1px.
+ // In that case, we dilute the color to make the line look thinner.
+ if (lineWidth < 1) {
+ opacity *= lineWidth;
+ }
+
+ lineStr.push(
+ '<g_vml_:stroke',
+ ' opacity="', opacity, '"',
+ ' joinstyle="', ctx.lineJoin, '"',
+ ' miterlimit="', ctx.miterLimit, '"',
+ ' endcap="', processLineCap(ctx.lineCap), '"',
+ ' weight="', lineWidth, 'px"',
+ ' color="', color, '" />'
+ );
+ }
+
+ function appendFill(ctx, lineStr, min, max) {
+ var fillStyle = ctx.fillStyle;
+ var arcScaleX = ctx.arcScaleX_;
+ var arcScaleY = ctx.arcScaleY_;
+ var width = max.x - min.x;
+ var height = max.y - min.y;
+ if (fillStyle instanceof CanvasGradient_) {
+ // TODO: Gradients transformed with the transformation matrix.
+ var angle = 0;
+ var focus = {x: 0, y: 0};
+
+ // additional offset
+ var shift = 0;
+ // scale factor for offset
+ var expansion = 1;
+
+ if (fillStyle.type_ == 'gradient') {
+ var x0 = fillStyle.x0_ / arcScaleX;
+ var y0 = fillStyle.y0_ / arcScaleY;
+ var x1 = fillStyle.x1_ / arcScaleX;
+ var y1 = fillStyle.y1_ / arcScaleY;
+ var p0 = getCoords(ctx, x0, y0);
+ var p1 = getCoords(ctx, x1, y1);
+ var dx = p1.x - p0.x;
+ var dy = p1.y - p0.y;
+ angle = Math.atan2(dx, dy) * 180 / Math.PI;
+
+ // The angle should be a non-negative number.
+ if (angle < 0) {
+ angle += 360;
+ }
+
+ // Very small angles produce an unexpected result because they are
+ // converted to a scientific notation string.
+ if (angle < 1e-6) {
+ angle = 0;
+ }
+ } else {
+ var p0 = getCoords(ctx, fillStyle.x0_, fillStyle.y0_);
+ focus = {
+ x: (p0.x - min.x) / width,
+ y: (p0.y - min.y) / height
+ };
+
+ width /= arcScaleX * Z;
+ height /= arcScaleY * Z;
+ var dimension = m.max(width, height);
+ shift = 2 * fillStyle.r0_ / dimension;
+ expansion = 2 * fillStyle.r1_ / dimension - shift;
+ }
+
+ // We need to sort the color stops in ascending order by offset,
+ // otherwise IE won't interpret it correctly.
+ var stops = fillStyle.colors_;
+ stops.sort(function(cs1, cs2) {
+ return cs1.offset - cs2.offset;
+ });
+
+ var length = stops.length;
+ var color1 = stops[0].color;
+ var color2 = stops[length - 1].color;
+ var opacity1 = stops[0].alpha * ctx.globalAlpha;
+ var opacity2 = stops[length - 1].alpha * ctx.globalAlpha;
+
+ var colors = [];
+ for (var i = 0; i < length; i++) {
+ var stop = stops[i];
+ colors.push(stop.offset * expansion + shift + ' ' + stop.color);
+ }
+
+ // When colors attribute is used, the meanings of opacity and o:opacity2
+ // are reversed.
+ lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"',
+ ' method="none" focus="100%"',
+ ' color="', color1, '"',
+ ' color2="', color2, '"',
+ ' colors="', colors.join(','), '"',
+ ' opacity="', opacity2, '"',
+ ' g_o_:opacity2="', opacity1, '"',
+ ' angle="', angle, '"',
+ ' focusposition="', focus.x, ',', focus.y, '" />');
+ } else if (fillStyle instanceof CanvasPattern_) {
+ if (width && height) {
+ var deltaLeft = -min.x;
+ var deltaTop = -min.y;
+ lineStr.push('<g_vml_:fill',
+ ' position="',
+ deltaLeft / width * arcScaleX * arcScaleX, ',',
+ deltaTop / height * arcScaleY * arcScaleY, '"',
+ ' type="tile"',
+ // TODO: Figure out the correct size to fit the scale.
+ //' size="', w, 'px ', h, 'px"',
+ ' src="', fillStyle.src_, '" />');
+ }
+ } else {
+ var a = processStyle(ctx.fillStyle);
+ var color = a.color;
+ var opacity = a.alpha * ctx.globalAlpha;
+ lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity,
+ '" />');
+ }
+ }
+
+ contextPrototype.fill = function() {
+ this.stroke(true);
+ };
+
+ contextPrototype.closePath = function() {
+ this.currentPath_.push({type: 'close'});
+ };
+
+ function getCoords(ctx, aX, aY) {
+ var m = ctx.m_;
+ return {
+ x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
+ y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
+ };
+ };
+
+ contextPrototype.save = function() {
+ var o = {};
+ copyState(this, o);
+ this.aStack_.push(o);
+ this.mStack_.push(this.m_);
+ this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
+ };
+
+ contextPrototype.restore = function() {
+ if (this.aStack_.length) {
+ copyState(this.aStack_.pop(), this);
+ this.m_ = this.mStack_.pop();
+ }
+ };
+
+ function matrixIsFinite(m) {
+ return isFinite(m[0][0]) && isFinite(m[0][1]) &&
+ isFinite(m[1][0]) && isFinite(m[1][1]) &&
+ isFinite(m[2][0]) && isFinite(m[2][1]);
+ }
+
+ function setM(ctx, m, updateLineScale) {
+ if (!matrixIsFinite(m)) {
+ return;
+ }
+ ctx.m_ = m;
+
+ if (updateLineScale) {
+ // Get the line scale.
+ // Determinant of this.m_ means how much the area is enlarged by the
+ // transformation. So its square root can be used as a scale factor
+ // for width.
+ var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
+ ctx.lineScale_ = sqrt(abs(det));
+ }
+ }
+
+ contextPrototype.translate = function(aX, aY) {
+ var m1 = [
+ [1, 0, 0],
+ [0, 1, 0],
+ [aX, aY, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), false);
+ };
+
+ contextPrototype.rotate = function(aRot) {
+ var c = mc(aRot);
+ var s = ms(aRot);
+
+ var m1 = [
+ [c, s, 0],
+ [-s, c, 0],
+ [0, 0, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), false);
+ };
+
+ contextPrototype.scale = function(aX, aY) {
+ this.arcScaleX_ *= aX;
+ this.arcScaleY_ *= aY;
+ var m1 = [
+ [aX, 0, 0],
+ [0, aY, 0],
+ [0, 0, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), true);
+ };
+
+ contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
+ var m1 = [
+ [m11, m12, 0],
+ [m21, m22, 0],
+ [dx, dy, 1]
+ ];
+
+ setM(this, matrixMultiply(m1, this.m_), true);
+ };
+
+ contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
+ var m = [
+ [m11, m12, 0],
+ [m21, m22, 0],
+ [dx, dy, 1]
+ ];
+
+ setM(this, m, true);
+ };
+
+ /**
+ * The text drawing function.
+ * The maxWidth argument isn't taken in account, since no browser supports
+ * it yet.
+ */
+ contextPrototype.drawText_ = function(text, x, y, maxWidth, stroke) {
+ var m = this.m_,
+ delta = 1000,
+ left = 0,
+ right = delta,
+ offset = {x: 0, y: 0},
+ lineStr = [];
+
+ var fontStyle = getComputedStyle(processFontStyle(this.font),
+ this.element_);
+
+ var fontStyleString = buildStyle(fontStyle);
+
+ var elementStyle = this.element_.currentStyle;
+ var textAlign = this.textAlign.toLowerCase();
+ switch (textAlign) {
+ case 'left':
+ case 'center':
+ case 'right':
+ break;
+ case 'end':
+ textAlign = elementStyle.direction == 'ltr' ? 'right' : 'left';
+ break;
+ case 'start':
+ textAlign = elementStyle.direction == 'rtl' ? 'right' : 'left';
+ break;
+ default:
+ textAlign = 'left';
+ }
+
+ // 1.75 is an arbitrary number, as there is no info about the text baseline
+ switch (this.textBaseline) {
+ case 'hanging':
+ case 'top':
+ offset.y = fontStyle.size / 1.75;
+ break;
+ case 'middle':
+ break;
+ default:
+ case null:
+ case 'alphabetic':
+ case 'ideographic':
+ case 'bottom':
+ offset.y = -fontStyle.size / 2.25;
+ break;
+ }
+
+ switch(textAlign) {
+ case 'right':
+ left = delta;
+ right = 0.05;
+ break;
+ case 'center':
+ left = right = delta / 2;
+ break;
+ }
+
+ var d = getCoords(this, x + offset.x, y + offset.y);
+
+ lineStr.push('<g_vml_:line from="', -left ,' 0" to="', right ,' 0.05" ',
+ ' coordsize="100 100" coordorigin="0 0"',
+ ' filled="', !stroke, '" stroked="', !!stroke,
+ '" style="position:absolute;width:1px;height:1px;">');
+
+ if (stroke) {
+ appendStroke(this, lineStr);
+ } else {
+ // TODO: Fix the min and max params.
+ appendFill(this, lineStr, {x: -left, y: 0},
+ {x: right, y: fontStyle.size});
+ }
+
+ var skewM = m[0][0].toFixed(3) + ',' + m[1][0].toFixed(3) + ',' +
+ m[0][1].toFixed(3) + ',' + m[1][1].toFixed(3) + ',0,0';
+
+ var skewOffset = mr(d.x / Z) + ',' + mr(d.y / Z);
+
+ lineStr.push('<g_vml_:skew on="t" matrix="', skewM ,'" ',
+ ' offset="', skewOffset, '" origin="', left ,' 0" />',
+ '<g_vml_:path textpathok="true" />',
+ '<g_vml_:textpath on="true" string="',
+ encodeHtmlAttribute(text),
+ '" style="v-text-align:', textAlign,
+ ';font:', encodeHtmlAttribute(fontStyleString),
+ '" /></g_vml_:line>');
+
+ this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+ };
+
+ contextPrototype.fillText = function(text, x, y, maxWidth) {
+ this.drawText_(text, x, y, maxWidth, false);
+ };
+
+ contextPrototype.strokeText = function(text, x, y, maxWidth) {
+ this.drawText_(text, x, y, maxWidth, true);
+ };
+
+ contextPrototype.measureText = function(text) {
+ if (!this.textMeasureEl_) {
+ var s = '<span style="position:absolute;' +
+ 'top:-20000px;left:0;padding:0;margin:0;border:none;' +
+ 'white-space:pre;"></span>';
+ this.element_.insertAdjacentHTML('beforeEnd', s);
+ this.textMeasureEl_ = this.element_.lastChild;
+ }
+ var doc = this.element_.ownerDocument;
+ this.textMeasureEl_.innerHTML = '';
+ this.textMeasureEl_.style.font = this.font;
+ // Don't use innerHTML or innerText because they allow markup/whitespace.
+ this.textMeasureEl_.appendChild(doc.createTextNode(text));
+ return {width: this.textMeasureEl_.offsetWidth};
+ };
+
+ /******** STUBS ********/
+ contextPrototype.clip = function() {
+ // TODO: Implement
+ };
+
+ contextPrototype.arcTo = function() {
+ // TODO: Implement
+ };
+
+ contextPrototype.createPattern = function(image, repetition) {
+ return new CanvasPattern_(image, repetition);
+ };
+
+ // Gradient / Pattern Stubs
+ function CanvasGradient_(aType) {
+ this.type_ = aType;
+ this.x0_ = 0;
+ this.y0_ = 0;
+ this.r0_ = 0;
+ this.x1_ = 0;
+ this.y1_ = 0;
+ this.r1_ = 0;
+ this.colors_ = [];
+ }
+
+ CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
+ aColor = processStyle(aColor);
+ this.colors_.push({offset: aOffset,
+ color: aColor.color,
+ alpha: aColor.alpha});
+ };
+
+ function CanvasPattern_(image, repetition) {
+ assertImageIsValid(image);
+ switch (repetition) {
+ case 'repeat':
+ case null:
+ case '':
+ this.repetition_ = 'repeat';
+ break
+ case 'repeat-x':
+ case 'repeat-y':
+ case 'no-repeat':
+ this.repetition_ = repetition;
+ break;
+ default:
+ throwException('SYNTAX_ERR');
+ }
+
+ this.src_ = image.src;
+ this.width_ = image.width;
+ this.height_ = image.height;
+ }
+
+ function throwException(s) {
+ throw new DOMException_(s);
+ }
+
+ function assertImageIsValid(img) {
+ if (!img || img.nodeType != 1 || img.tagName != 'IMG') {
+ throwException('TYPE_MISMATCH_ERR');
+ }
+ if (img.readyState != 'complete') {
+ throwException('INVALID_STATE_ERR');
+ }
+ }
+
+ function DOMException_(s) {
+ this.code = this[s];
+ this.message = s +': DOM Exception ' + this.code;
+ }
+ var p = DOMException_.prototype = new Error;
+ p.INDEX_SIZE_ERR = 1;
+ p.DOMSTRING_SIZE_ERR = 2;
+ p.HIERARCHY_REQUEST_ERR = 3;
+ p.WRONG_DOCUMENT_ERR = 4;
+ p.INVALID_CHARACTER_ERR = 5;
+ p.NO_DATA_ALLOWED_ERR = 6;
+ p.NO_MODIFICATION_ALLOWED_ERR = 7;
+ p.NOT_FOUND_ERR = 8;
+ p.NOT_SUPPORTED_ERR = 9;
+ p.INUSE_ATTRIBUTE_ERR = 10;
+ p.INVALID_STATE_ERR = 11;
+ p.SYNTAX_ERR = 12;
+ p.INVALID_MODIFICATION_ERR = 13;
+ p.NAMESPACE_ERR = 14;
+ p.INVALID_ACCESS_ERR = 15;
+ p.VALIDATION_ERR = 16;
+ p.TYPE_MISMATCH_ERR = 17;
+
+ // set up externs
+ G_vmlCanvasManager = G_vmlCanvasManager_;
+ CanvasRenderingContext2D = CanvasRenderingContext2D_;
+ CanvasGradient = CanvasGradient_;
+ CanvasPattern = CanvasPattern_;
+ DOMException = DOMException_;
+})();
+
+} // if
diff --git a/misc/flot/excanvas.min.js b/misc/flot/excanvas.min.js
new file mode 100644
index 0000000..fcf876c
--- /dev/null
+++ b/misc/flot/excanvas.min.js
@@ -0,0 +1 @@
+if(!document.createElement("canvas").getContext){(function(){var ab=Math;var n=ab.round;var l=ab.sin;var A=ab.cos;var H=ab.abs;var N=ab.sqrt;var d=10;var f=d/2;var z=+navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];function y(){return this.context_||(this.context_=new D(this))}var t=Array.prototype.slice;function g(j,m,p){var i=t.call(arguments,2);return function(){return j.apply(m,i.concat(t.call(arguments)))}}function af(i){return String(i).replace(/&/g,"&amp;").replace(/"/g,"&quot;")}function Y(m,j,i){if(!m.namespaces[j]){m.namespaces.add(j,i,"#default#VML")}}function R(j){Y(j,"g_vml_","urn:schemas-microsoft-com:vml");Y(j,"g_o_","urn:schemas-microsoft-com:office:office");if(!j.styleSheets.ex_canvas_){var i=j.createStyleSheet();i.owningElement.id="ex_canvas_";i.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}R(document);var e={init:function(i){var j=i||document;j.createElement("canvas");j.attachEvent("onreadystatechange",g(this.init_,this,j))},init_:function(p){var m=p.getElementsByTagName("canvas");for(var j=0;j<m.length;j++){this.initElement(m[j])}},initElement:function(j){if(!j.getContext){j.getContext=y;R(j.ownerDocument);j.innerHTML="";j.attachEvent("onpropertychange",x);j.attachEvent("onresize",W);var i=j.attributes;if(i.width&&i.width.specified){j.style.width=i.width.nodeValue+"px"}else{j.width=j.clientWidth}if(i.height&&i.height.specified){j.style.height=i.height.nodeValue+"px"}else{j.height=j.clientHeight}}return j}};function x(j){var i=j.srcElement;switch(j.propertyName){case"width":i.getContext().clearRect();i.style.width=i.attributes.width.nodeValue+"px";i.firstChild.style.width=i.clientWidth+"px";break;case"height":i.getContext().clearRect();i.style.height=i.attributes.height.nodeValue+"px";i.firstChild.style.height=i.clientHeight+"px";break}}function W(j){var i=j.srcElement;if(i.firstChild){i.firstChild.style.width=i.clientWidth+"px";i.firstChild.style.height=i.clientHeight+"px"}}e.init();var k=[];for(var ae=0;ae<16;ae++){for(var ad=0;ad<16;ad++){k[ae*16+ad]=ae.toString(16)+ad.toString(16)}}function B(){return[[1,0,0],[0,1,0],[0,0,1]]}function J(p,m){var j=B();for(var i=0;i<3;i++){for(var ah=0;ah<3;ah++){var Z=0;for(var ag=0;ag<3;ag++){Z+=p[i][ag]*m[ag][ah]}j[i][ah]=Z}}return j}function v(j,i){i.fillStyle=j.fillStyle;i.lineCap=j.lineCap;i.lineJoin=j.lineJoin;i.lineWidth=j.lineWidth;i.miterLimit=j.miterLimit;i.shadowBlur=j.shadowBlur;i.shadowColor=j.shadowColor;i.shadowOffsetX=j.shadowOffsetX;i.shadowOffsetY=j.shadowOffsetY;i.strokeStyle=j.strokeStyle;i.globalAlpha=j.globalAlpha;i.font=j.font;i.textAlign=j.textAlign;i.textBaseline=j.textBaseline;i.arcScaleX_=j.arcScaleX_;i.arcScaleY_=j.arcScaleY_;i.lineScale_=j.lineScale_}var b={aliceblue:"#F0F8FF",antiquewhite:"#FAEBD7",aquamarine:"#7FFFD4",azure:"#F0FFFF",beige:"#F5F5DC",bisque:"#FFE4C4",black:"#000000",blanchedalmond:"#FFEBCD",blueviolet:"#8A2BE2",brown:"#A52A2A",burlywood:"#DEB887",cadetblue:"#5F9EA0",chartreuse:"#7FFF00",chocolate:"#D2691E",coral:"#FF7F50",cornflowerblue:"#6495ED",cornsilk:"#FFF8DC",crimson:"#DC143C",cyan:"#00FFFF",darkblue:"#00008B",darkcyan:"#008B8B",darkgoldenrod:"#B8860B",darkgray:"#A9A9A9",darkgreen:"#006400",darkgrey:"#A9A9A9",darkkhaki:"#BDB76B",darkmagenta:"#8B008B",darkolivegreen:"#556B2F",darkorange:"#FF8C00",darkorchid:"#9932CC",darkred:"#8B0000",darksalmon:"#E9967A",darkseagreen:"#8FBC8F",darkslateblue:"#483D8B",darkslategray:"#2F4F4F",darkslategrey:"#2F4F4F",darkturquoise:"#00CED1",darkviolet:"#9400D3",deeppink:"#FF1493",deepskyblue:"#00BFFF",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1E90FF",firebrick:"#B22222",floralwhite:"#FFFAF0",forestgreen:"#228B22",gainsboro:"#DCDCDC",ghostwhite:"#F8F8FF",gold:"#FFD700",goldenrod:"#DAA520",grey:"#808080",greenyellow:"#ADFF2F",honeydew:"#F0FFF0",hotpink:"#FF69B4",indianred:"#CD5C5C",indigo:"#4B0082",ivory:"#FFFFF0",khaki:"#F0E68C",lavender:"#E6E6FA",lavenderblush:"#FFF0F5",lawngreen:"#7CFC00",lemonchiffon:"#FFFACD",lightblue:"#ADD8E6",lightcoral:"#F08080",lightcyan:"#E0FFFF",lightgoldenrodyellow:"#FAFAD2",lightgreen:"#90EE90",lightgrey:"#D3D3D3",lightpink:"#FFB6C1",lightsalmon:"#FFA07A",lightseagreen:"#20B2AA",lightskyblue:"#87CEFA",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#B0C4DE",lightyellow:"#FFFFE0",limegreen:"#32CD32",linen:"#FAF0E6",magenta:"#FF00FF",mediumaquamarine:"#66CDAA",mediumblue:"#0000CD",mediumorchid:"#BA55D3",mediumpurple:"#9370DB",mediumseagreen:"#3CB371",mediumslateblue:"#7B68EE",mediumspringgreen:"#00FA9A",mediumturquoise:"#48D1CC",mediumvioletred:"#C71585",midnightblue:"#191970",mintcream:"#F5FFFA",mistyrose:"#FFE4E1",moccasin:"#FFE4B5",navajowhite:"#FFDEAD",oldlace:"#FDF5E6",olivedrab:"#6B8E23",orange:"#FFA500",orangered:"#FF4500",orchid:"#DA70D6",palegoldenrod:"#EEE8AA",palegreen:"#98FB98",paleturquoise:"#AFEEEE",palevioletred:"#DB7093",papayawhip:"#FFEFD5",peachpuff:"#FFDAB9",peru:"#CD853F",pink:"#FFC0CB",plum:"#DDA0DD",powderblue:"#B0E0E6",rosybrown:"#BC8F8F",royalblue:"#4169E1",saddlebrown:"#8B4513",salmon:"#FA8072",sandybrown:"#F4A460",seagreen:"#2E8B57",seashell:"#FFF5EE",sienna:"#A0522D",skyblue:"#87CEEB",slateblue:"#6A5ACD",slategray:"#708090",slategrey:"#708090",snow:"#FFFAFA",springgreen:"#00FF7F",steelblue:"#4682B4",tan:"#D2B48C",thistle:"#D8BFD8",tomato:"#FF6347",turquoise:"#40E0D0",violet:"#EE82EE",wheat:"#F5DEB3",whitesmoke:"#F5F5F5",yellowgreen:"#9ACD32"};function M(j){var p=j.indexOf("(",3);var i=j.indexOf(")",p+1);var m=j.substring(p+1,i).split(",");if(m.length!=4||j.charAt(3)!="a"){m[3]=1}return m}function c(i){return parseFloat(i)/100}function r(j,m,i){return Math.min(i,Math.max(m,j))}function I(ag){var i,ai,aj,ah,ak,Z;ah=parseFloat(ag[0])/360%360;if(ah<0){ah++}ak=r(c(ag[1]),0,1);Z=r(c(ag[2]),0,1);if(ak==0){i=ai=aj=Z}else{var j=Z<0.5?Z*(1+ak):Z+ak-Z*ak;var m=2*Z-j;i=a(m,j,ah+1/3);ai=a(m,j,ah);aj=a(m,j,ah-1/3)}return"#"+k[Math.floor(i*255)]+k[Math.floor(ai*255)]+k[Math.floor(aj*255)]}function a(j,i,m){if(m<0){m++}if(m>1){m--}if(6*m<1){return j+(i-j)*6*m}else{if(2*m<1){return i}else{if(3*m<2){return j+(i-j)*(2/3-m)*6}else{return j}}}}var C={};function F(j){if(j in C){return C[j]}var ag,Z=1;j=String(j);if(j.charAt(0)=="#"){ag=j}else{if(/^rgb/.test(j)){var p=M(j);var ag="#",ah;for(var m=0;m<3;m++){if(p[m].indexOf("%")!=-1){ah=Math.floor(c(p[m])*255)}else{ah=+p[m]}ag+=k[r(ah,0,255)]}Z=+p[3]}else{if(/^hsl/.test(j)){var p=M(j);ag=I(p);Z=p[3]}else{ag=b[j]||j}}}return C[j]={color:ag,alpha:Z}}var o={style:"normal",variant:"normal",weight:"normal",size:10,family:"sans-serif"};var L={};function E(i){if(L[i]){return L[i]}var p=document.createElement("div");var m=p.style;try{m.font=i}catch(j){}return L[i]={style:m.fontStyle||o.style,variant:m.fontVariant||o.variant,weight:m.fontWeight||o.weight,size:m.fontSize||o.size,family:m.fontFamily||o.family}}function u(m,j){var i={};for(var ah in m){i[ah]=m[ah]}var ag=parseFloat(j.currentStyle.fontSize),Z=parseFloat(m.size);if(typeof m.size=="number"){i.size=m.size}else{if(m.size.indexOf("px")!=-1){i.size=Z}else{if(m.size.indexOf("em")!=-1){i.size=ag*Z}else{if(m.size.indexOf("%")!=-1){i.size=(ag/100)*Z}else{if(m.size.indexOf("pt")!=-1){i.size=Z/0.75}else{i.size=ag}}}}}i.size*=0.981;return i}function ac(i){return i.style+" "+i.variant+" "+i.weight+" "+i.size+"px "+i.family}var s={butt:"flat",round:"round"};function S(i){return s[i]||"square"}function D(i){this.m_=B();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=d*1;this.globalAlpha=1;this.font="10px sans-serif";this.textAlign="left";this.textBaseline="alphabetic";this.canvas=i;var m="width:"+i.clientWidth+"px;height:"+i.clientHeight+"px;overflow:hidden;position:absolute";var j=i.ownerDocument.createElement("div");j.style.cssText=m;i.appendChild(j);var p=j.cloneNode(false);p.style.backgroundColor="red";p.style.filter="alpha(opacity=0)";i.appendChild(p);this.element_=j;this.arcScaleX_=1;this.arcScaleY_=1;this.lineScale_=1}var q=D.prototype;q.clearRect=function(){if(this.textMeasureEl_){this.textMeasureEl_.removeNode(true);this.textMeasureEl_=null}this.element_.innerHTML=""};q.beginPath=function(){this.currentPath_=[]};q.moveTo=function(j,i){var m=V(this,j,i);this.currentPath_.push({type:"moveTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};q.lineTo=function(j,i){var m=V(this,j,i);this.currentPath_.push({type:"lineTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};q.bezierCurveTo=function(m,j,ak,aj,ai,ag){var i=V(this,ai,ag);var ah=V(this,m,j);var Z=V(this,ak,aj);K(this,ah,Z,i)};function K(i,Z,m,j){i.currentPath_.push({type:"bezierCurveTo",cp1x:Z.x,cp1y:Z.y,cp2x:m.x,cp2y:m.y,x:j.x,y:j.y});i.currentX_=j.x;i.currentY_=j.y}q.quadraticCurveTo=function(ai,m,j,i){var ah=V(this,ai,m);var ag=V(this,j,i);var aj={x:this.currentX_+2/3*(ah.x-this.currentX_),y:this.currentY_+2/3*(ah.y-this.currentY_)};var Z={x:aj.x+(ag.x-this.currentX_)/3,y:aj.y+(ag.y-this.currentY_)/3};K(this,aj,Z,ag)};q.arc=function(al,aj,ak,ag,j,m){ak*=d;var ap=m?"at":"wa";var am=al+A(ag)*ak-f;var ao=aj+l(ag)*ak-f;var i=al+A(j)*ak-f;var an=aj+l(j)*ak-f;if(am==i&&!m){am+=0.125}var Z=V(this,al,aj);var ai=V(this,am,ao);var ah=V(this,i,an);this.currentPath_.push({type:ap,x:Z.x,y:Z.y,radius:ak,xStart:ai.x,yStart:ai.y,xEnd:ah.x,yEnd:ah.y})};q.rect=function(m,j,i,p){this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath()};q.strokeRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.stroke();this.currentPath_=Z};q.fillRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.fill();this.currentPath_=Z};q.createLinearGradient=function(j,p,i,m){var Z=new U("gradient");Z.x0_=j;Z.y0_=p;Z.x1_=i;Z.y1_=m;return Z};q.createRadialGradient=function(p,ag,m,j,Z,i){var ah=new U("gradientradial");ah.x0_=p;ah.y0_=ag;ah.r0_=m;ah.x1_=j;ah.y1_=Z;ah.r1_=i;return ah};q.drawImage=function(aq,m){var aj,ah,al,ay,ao,am,at,aA;var ak=aq.runtimeStyle.width;var ap=aq.runtimeStyle.height;aq.runtimeStyle.width="auto";aq.runtimeStyle.height="auto";var ai=aq.width;var aw=aq.height;aq.runtimeStyle.width=ak;aq.runtimeStyle.height=ap;if(arguments.length==3){aj=arguments[1];ah=arguments[2];ao=am=0;at=al=ai;aA=ay=aw}else{if(arguments.length==5){aj=arguments[1];ah=arguments[2];al=arguments[3];ay=arguments[4];ao=am=0;at=ai;aA=aw}else{if(arguments.length==9){ao=arguments[1];am=arguments[2];at=arguments[3];aA=arguments[4];aj=arguments[5];ah=arguments[6];al=arguments[7];ay=arguments[8]}else{throw Error("Invalid number of arguments")}}}var az=V(this,aj,ah);var p=at/2;var j=aA/2;var ax=[];var i=10;var ag=10;ax.push(" <g_vml_:group",' coordsize="',d*i,",",d*ag,'"',' coordorigin="0,0"',' style="width:',i,"px;height:",ag,"px;position:absolute;");if(this.m_[0][0]!=1||this.m_[0][1]||this.m_[1][1]!=1||this.m_[1][0]){var Z=[];Z.push("M11=",this.m_[0][0],",","M12=",this.m_[1][0],",","M21=",this.m_[0][1],",","M22=",this.m_[1][1],",","Dx=",n(az.x/d),",","Dy=",n(az.y/d),"");var av=az;var au=V(this,aj+al,ah);var ar=V(this,aj,ah+ay);var an=V(this,aj+al,ah+ay);av.x=ab.max(av.x,au.x,ar.x,an.x);av.y=ab.max(av.y,au.y,ar.y,an.y);ax.push("padding:0 ",n(av.x/d),"px ",n(av.y/d),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",Z.join(""),", sizingmethod='clip');")}else{ax.push("top:",n(az.y/d),"px;left:",n(az.x/d),"px;")}ax.push(' ">','<g_vml_:image src="',aq.src,'"',' style="width:',d*al,"px;"," height:",d*ay,'px"',' cropleft="',ao/ai,'"',' croptop="',am/aw,'"',' cropright="',(ai-ao-at)/ai,'"',' cropbottom="',(aw-am-aA)/aw,'"'," />","</g_vml_:group>");this.element_.insertAdjacentHTML("BeforeEnd",ax.join(""))};q.stroke=function(ao){var Z=10;var ap=10;var ag=5000;var ai={x:null,y:null};var an={x:null,y:null};for(var aj=0;aj<this.currentPath_.length;aj+=ag){var am=[];var ah=false;am.push("<g_vml_:shape",' filled="',!!ao,'"',' style="position:absolute;width:',Z,"px;height:",ap,'px;"',' coordorigin="0,0"',' coordsize="',d*Z,",",d*ap,'"',' stroked="',!ao,'"',' path="');var aq=false;for(var ak=aj;ak<Math.min(aj+ag,this.currentPath_.length);ak++){if(ak%ag==0&&ak>0){am.push(" m ",n(this.currentPath_[ak-1].x),",",n(this.currentPath_[ak-1].y))}var m=this.currentPath_[ak];var al;switch(m.type){case"moveTo":al=m;am.push(" m ",n(m.x),",",n(m.y));break;case"lineTo":am.push(" l ",n(m.x),",",n(m.y));break;case"close":am.push(" x ");m=null;break;case"bezierCurveTo":am.push(" c ",n(m.cp1x),",",n(m.cp1y),",",n(m.cp2x),",",n(m.cp2y),",",n(m.x),",",n(m.y));break;case"at":case"wa":am.push(" ",m.type," ",n(m.x-this.arcScaleX_*m.radius),",",n(m.y-this.arcScaleY_*m.radius)," ",n(m.x+this.arcScaleX_*m.radius),",",n(m.y+this.arcScaleY_*m.radius)," ",n(m.xStart),",",n(m.yStart)," ",n(m.xEnd),",",n(m.yEnd));break}if(m){if(ai.x==null||m.x<ai.x){ai.x=m.x}if(an.x==null||m.x>an.x){an.x=m.x}if(ai.y==null||m.y<ai.y){ai.y=m.y}if(an.y==null||m.y>an.y){an.y=m.y}}}am.push(' ">');if(!ao){w(this,am)}else{G(this,am,ai,an)}am.push("</g_vml_:shape>");this.element_.insertAdjacentHTML("beforeEnd",am.join(""))}};function w(m,ag){var j=F(m.strokeStyle);var p=j.color;var Z=j.alpha*m.globalAlpha;var i=m.lineScale_*m.lineWidth;if(i<1){Z*=i}ag.push("<g_vml_:stroke",' opacity="',Z,'"',' joinstyle="',m.lineJoin,'"',' miterlimit="',m.miterLimit,'"',' endcap="',S(m.lineCap),'"',' weight="',i,'px"',' color="',p,'" />')}function G(aq,ai,aK,ar){var aj=aq.fillStyle;var aB=aq.arcScaleX_;var aA=aq.arcScaleY_;var j=ar.x-aK.x;var p=ar.y-aK.y;if(aj instanceof U){var an=0;var aF={x:0,y:0};var ax=0;var am=1;if(aj.type_=="gradient"){var al=aj.x0_/aB;var m=aj.y0_/aA;var ak=aj.x1_/aB;var aM=aj.y1_/aA;var aJ=V(aq,al,m);var aI=V(aq,ak,aM);var ag=aI.x-aJ.x;var Z=aI.y-aJ.y;an=Math.atan2(ag,Z)*180/Math.PI;if(an<0){an+=360}if(an<0.000001){an=0}}else{var aJ=V(aq,aj.x0_,aj.y0_);aF={x:(aJ.x-aK.x)/j,y:(aJ.y-aK.y)/p};j/=aB*d;p/=aA*d;var aD=ab.max(j,p);ax=2*aj.r0_/aD;am=2*aj.r1_/aD-ax}var av=aj.colors_;av.sort(function(aN,i){return aN.offset-i.offset});var ap=av.length;var au=av[0].color;var at=av[ap-1].color;var az=av[0].alpha*aq.globalAlpha;var ay=av[ap-1].alpha*aq.globalAlpha;var aE=[];for(var aH=0;aH<ap;aH++){var ao=av[aH];aE.push(ao.offset*am+ax+" "+ao.color)}ai.push('<g_vml_:fill type="',aj.type_,'"',' method="none" focus="100%"',' color="',au,'"',' color2="',at,'"',' colors="',aE.join(","),'"',' opacity="',ay,'"',' g_o_:opacity2="',az,'"',' angle="',an,'"',' focusposition="',aF.x,",",aF.y,'" />')}else{if(aj instanceof T){if(j&&p){var ah=-aK.x;var aC=-aK.y;ai.push("<g_vml_:fill",' position="',ah/j*aB*aB,",",aC/p*aA*aA,'"',' type="tile"',' src="',aj.src_,'" />')}}else{var aL=F(aq.fillStyle);var aw=aL.color;var aG=aL.alpha*aq.globalAlpha;ai.push('<g_vml_:fill color="',aw,'" opacity="',aG,'" />')}}}q.fill=function(){this.stroke(true)};q.closePath=function(){this.currentPath_.push({type:"close"})};function V(j,Z,p){var i=j.m_;return{x:d*(Z*i[0][0]+p*i[1][0]+i[2][0])-f,y:d*(Z*i[0][1]+p*i[1][1]+i[2][1])-f}}q.save=function(){var i={};v(this,i);this.aStack_.push(i);this.mStack_.push(this.m_);this.m_=J(B(),this.m_)};q.restore=function(){if(this.aStack_.length){v(this.aStack_.pop(),this);this.m_=this.mStack_.pop()}};function h(i){return isFinite(i[0][0])&&isFinite(i[0][1])&&isFinite(i[1][0])&&isFinite(i[1][1])&&isFinite(i[2][0])&&isFinite(i[2][1])}function aa(j,i,p){if(!h(i)){return}j.m_=i;if(p){var Z=i[0][0]*i[1][1]-i[0][1]*i[1][0];j.lineScale_=N(H(Z))}}q.translate=function(m,j){var i=[[1,0,0],[0,1,0],[m,j,1]];aa(this,J(i,this.m_),false)};q.rotate=function(j){var p=A(j);var m=l(j);var i=[[p,m,0],[-m,p,0],[0,0,1]];aa(this,J(i,this.m_),false)};q.scale=function(m,j){this.arcScaleX_*=m;this.arcScaleY_*=j;var i=[[m,0,0],[0,j,0],[0,0,1]];aa(this,J(i,this.m_),true)};q.transform=function(Z,p,ah,ag,j,i){var m=[[Z,p,0],[ah,ag,0],[j,i,1]];aa(this,J(m,this.m_),true)};q.setTransform=function(ag,Z,ai,ah,p,j){var i=[[ag,Z,0],[ai,ah,0],[p,j,1]];aa(this,i,true)};q.drawText_=function(am,ak,aj,ap,ai){var ao=this.m_,at=1000,j=0,ar=at,ah={x:0,y:0},ag=[];var i=u(E(this.font),this.element_);var p=ac(i);var au=this.element_.currentStyle;var Z=this.textAlign.toLowerCase();switch(Z){case"left":case"center":case"right":break;case"end":Z=au.direction=="ltr"?"right":"left";break;case"start":Z=au.direction=="rtl"?"right":"left";break;default:Z="left"}switch(this.textBaseline){case"hanging":case"top":ah.y=i.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":ah.y=-i.size/2.25;break}switch(Z){case"right":j=at;ar=0.05;break;case"center":j=ar=at/2;break}var aq=V(this,ak+ah.x,aj+ah.y);ag.push('<g_vml_:line from="',-j,' 0" to="',ar,' 0.05" ',' coordsize="100 100" coordorigin="0 0"',' filled="',!ai,'" stroked="',!!ai,'" style="position:absolute;width:1px;height:1px;">');if(ai){w(this,ag)}else{G(this,ag,{x:-j,y:0},{x:ar,y:i.size})}var an=ao[0][0].toFixed(3)+","+ao[1][0].toFixed(3)+","+ao[0][1].toFixed(3)+","+ao[1][1].toFixed(3)+",0,0";var al=n(aq.x/d)+","+n(aq.y/d);ag.push('<g_vml_:skew on="t" matrix="',an,'" ',' offset="',al,'" origin="',j,' 0" />','<g_vml_:path textpathok="true" />','<g_vml_:textpath on="true" string="',af(am),'" style="v-text-align:',Z,";font:",af(p),'" /></g_vml_:line>');this.element_.insertAdjacentHTML("beforeEnd",ag.join(""))};q.fillText=function(m,i,p,j){this.drawText_(m,i,p,j,false)};q.strokeText=function(m,i,p,j){this.drawText_(m,i,p,j,true)};q.measureText=function(m){if(!this.textMeasureEl_){var i='<span style="position:absolute;top:-20000px;left:0;padding:0;margin:0;border:none;white-space:pre;"></span>';this.element_.insertAdjacentHTML("beforeEnd",i);this.textMeasureEl_=this.element_.lastChild}var j=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(j.createTextNode(m));return{width:this.textMeasureEl_.offsetWidth}};q.clip=function(){};q.arcTo=function(){};q.createPattern=function(j,i){return new T(j,i)};function U(i){this.type_=i;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}U.prototype.addColorStop=function(j,i){i=F(i);this.colors_.push({offset:j,color:i.color,alpha:i.alpha})};function T(j,i){Q(j);switch(i){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=i;break;default:O("SYNTAX_ERR")}this.src_=j.src;this.width_=j.width;this.height_=j.height}function O(i){throw new P(i)}function Q(i){if(!i||i.nodeType!=1||i.tagName!="IMG"){O("TYPE_MISMATCH_ERR")}if(i.readyState!="complete"){O("INVALID_STATE_ERR")}}function P(i){this.code=this[i];this.message=i+": DOM Exception "+this.code}var X=P.prototype=new Error;X.INDEX_SIZE_ERR=1;X.DOMSTRING_SIZE_ERR=2;X.HIERARCHY_REQUEST_ERR=3;X.WRONG_DOCUMENT_ERR=4;X.INVALID_CHARACTER_ERR=5;X.NO_DATA_ALLOWED_ERR=6;X.NO_MODIFICATION_ALLOWED_ERR=7;X.NOT_FOUND_ERR=8;X.NOT_SUPPORTED_ERR=9;X.INUSE_ATTRIBUTE_ERR=10;X.INVALID_STATE_ERR=11;X.SYNTAX_ERR=12;X.INVALID_MODIFICATION_ERR=13;X.NAMESPACE_ERR=14;X.INVALID_ACCESS_ERR=15;X.VALIDATION_ERR=16;X.TYPE_MISMATCH_ERR=17;G_vmlCanvasManager=e;CanvasRenderingContext2D=D;CanvasGradient=U;CanvasPattern=T;DOMException=P})()}; \ No newline at end of file
diff --git a/misc/flot/jquery.colorhelpers.js b/misc/flot/jquery.colorhelpers.js
new file mode 100644
index 0000000..b2f6dc4
--- /dev/null
+++ b/misc/flot/jquery.colorhelpers.js
@@ -0,0 +1,180 @@
+/* Plugin for jQuery for working with colors.
+ *
+ * Version 1.1.
+ *
+ * Inspiration from jQuery color animation plugin by John Resig.
+ *
+ * Released under the MIT license by Ole Laursen, October 2009.
+ *
+ * Examples:
+ *
+ * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
+ * var c = $.color.extract($("#mydiv"), 'background-color');
+ * console.log(c.r, c.g, c.b, c.a);
+ * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
+ *
+ * Note that .scale() and .add() return the same modified object
+ * instead of making a new one.
+ *
+ * V. 1.1: Fix error handling so e.g. parsing an empty string does
+ * produce a color rather than just crashing.
+ */
+
+(function($) {
+ $.color = {};
+
+ // construct color object with some convenient chainable helpers
+ $.color.make = function (r, g, b, a) {
+ var o = {};
+ o.r = r || 0;
+ o.g = g || 0;
+ o.b = b || 0;
+ o.a = a != null ? a : 1;
+
+ o.add = function (c, d) {
+ for (var i = 0; i < c.length; ++i)
+ o[c.charAt(i)] += d;
+ return o.normalize();
+ };
+
+ o.scale = function (c, f) {
+ for (var i = 0; i < c.length; ++i)
+ o[c.charAt(i)] *= f;
+ return o.normalize();
+ };
+
+ o.toString = function () {
+ if (o.a >= 1.0) {
+ return "rgb("+[o.r, o.g, o.b].join(",")+")";
+ } else {
+ return "rgba("+[o.r, o.g, o.b, o.a].join(",")+")";
+ }
+ };
+
+ o.normalize = function () {
+ function clamp(min, value, max) {
+ return value < min ? min: (value > max ? max: value);
+ }
+
+ o.r = clamp(0, parseInt(o.r), 255);
+ o.g = clamp(0, parseInt(o.g), 255);
+ o.b = clamp(0, parseInt(o.b), 255);
+ o.a = clamp(0, o.a, 1);
+ return o;
+ };
+
+ o.clone = function () {
+ return $.color.make(o.r, o.b, o.g, o.a);
+ };
+
+ return o.normalize();
+ }
+
+ // extract CSS color property from element, going up in the DOM
+ // if it's "transparent"
+ $.color.extract = function (elem, css) {
+ var c;
+
+ do {
+ c = elem.css(css).toLowerCase();
+ // keep going until we find an element that has color, or
+ // we hit the body or root (have no parent)
+ if (c != '' && c != 'transparent')
+ break;
+ elem = elem.parent();
+ } while (elem.length && !$.nodeName(elem.get(0), "body"));
+
+ // catch Safari's way of signalling transparent
+ if (c == "rgba(0, 0, 0, 0)")
+ c = "transparent";
+
+ return $.color.parse(c);
+ }
+
+ // parse CSS color string (like "rgb(10, 32, 43)" or "#fff"),
+ // returns color object, if parsing failed, you get black (0, 0,
+ // 0) out
+ $.color.parse = function (str) {
+ var res, m = $.color.make;
+
+ // Look for rgb(num,num,num)
+ if (res = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))
+ return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10));
+
+ // Look for rgba(num,num,num,num)
+ if (res = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
+ return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10), parseFloat(res[4]));
+
+ // Look for rgb(num%,num%,num%)
+ if (res = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))
+ return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55);
+
+ // Look for rgba(num%,num%,num%,num)
+ if (res = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))
+ return m(parseFloat(res[1])*2.55, parseFloat(res[2])*2.55, parseFloat(res[3])*2.55, parseFloat(res[4]));
+
+ // Look for #a0b1c2
+ if (res = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))
+ return m(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16));
+
+ // Look for #fff
+ if (res = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))
+ return m(parseInt(res[1]+res[1], 16), parseInt(res[2]+res[2], 16), parseInt(res[3]+res[3], 16));
+
+ // Otherwise, we're most likely dealing with a named color
+ var name = $.trim(str).toLowerCase();
+ if (name == "transparent")
+ return m(255, 255, 255, 0);
+ else {
+ // default to black
+ res = lookupColors[name] || [0, 0, 0];
+ return m(res[0], res[1], res[2]);
+ }
+ }
+
+ var lookupColors = {
+ aqua:[0,255,255],
+ azure:[240,255,255],
+ beige:[245,245,220],
+ black:[0,0,0],
+ blue:[0,0,255],
+ brown:[165,42,42],
+ cyan:[0,255,255],
+ darkblue:[0,0,139],
+ darkcyan:[0,139,139],
+ darkgrey:[169,169,169],
+ darkgreen:[0,100,0],
+ darkkhaki:[189,183,107],
+ darkmagenta:[139,0,139],
+ darkolivegreen:[85,107,47],
+ darkorange:[255,140,0],
+ darkorchid:[153,50,204],
+ darkred:[139,0,0],
+ darksalmon:[233,150,122],
+ darkviolet:[148,0,211],
+ fuchsia:[255,0,255],
+ gold:[255,215,0],
+ green:[0,128,0],
+ indigo:[75,0,130],
+ khaki:[240,230,140],
+ lightblue:[173,216,230],
+ lightcyan:[224,255,255],
+ lightgreen:[144,238,144],
+ lightgrey:[211,211,211],
+ lightpink:[255,182,193],
+ lightyellow:[255,255,224],
+ lime:[0,255,0],
+ magenta:[255,0,255],
+ maroon:[128,0,0],
+ navy:[0,0,128],
+ olive:[128,128,0],
+ orange:[255,165,0],
+ pink:[255,192,203],
+ purple:[128,0,128],
+ violet:[128,0,128],
+ red:[255,0,0],
+ silver:[192,192,192],
+ white:[255,255,255],
+ yellow:[255,255,0]
+ };
+})(jQuery);
diff --git a/misc/flot/jquery.colorhelpers.min.js b/misc/flot/jquery.colorhelpers.min.js
new file mode 100644
index 0000000..7f42659
--- /dev/null
+++ b/misc/flot/jquery.colorhelpers.min.js
@@ -0,0 +1 @@
+(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i<c.length;++i)o[c.charAt(i)]+=d;return o.normalize()};o.scale=function(c,f){for(var i=0;i<c.length;++i)o[c.charAt(i)]*=f;return o.normalize()};o.toString=function(){if(o.a>=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return value<min?min:value>max?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery); \ No newline at end of file
diff --git a/misc/flot/jquery.flot.canvas.js b/misc/flot/jquery.flot.canvas.js
new file mode 100644
index 0000000..29328d5
--- /dev/null
+++ b/misc/flot/jquery.flot.canvas.js
@@ -0,0 +1,345 @@
+/* Flot plugin for drawing all elements of a plot on the canvas.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+Flot normally produces certain elements, like axis labels and the legend, using
+HTML elements. This permits greater interactivity and customization, and often
+looks better, due to cross-browser canvas text inconsistencies and limitations.
+
+It can also be desirable to render the plot entirely in canvas, particularly
+if the goal is to save it as an image, or if Flot is being used in a context
+where the HTML DOM does not exist, as is the case within Node.js. This plugin
+switches out Flot's standard drawing operations for canvas-only replacements.
+
+Currently the plugin supports only axis labels, but it will eventually allow
+every element of the plot to be rendered directly to canvas.
+
+The plugin supports these options:
+
+{
+ canvas: boolean
+}
+
+The "canvas" option controls whether full canvas drawing is enabled, making it
+possible to toggle on and off. This is useful when a plot uses HTML text in the
+browser, but needs to redraw with canvas text when exporting as an image.
+
+*/
+
+(function($) {
+
+ var options = {
+ canvas: true
+ };
+
+ var render, getTextInfo, addText;
+
+ // Cache the prototype hasOwnProperty for faster access
+
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+ function init(plot, classes) {
+
+ var Canvas = classes.Canvas;
+
+ // We only want to replace the functions once; the second time around
+ // we would just get our new function back. This whole replacing of
+ // prototype functions is a disaster, and needs to be changed ASAP.
+
+ if (render == null) {
+ getTextInfo = Canvas.prototype.getTextInfo,
+ addText = Canvas.prototype.addText,
+ render = Canvas.prototype.render;
+ }
+
+ // Finishes rendering the canvas, including overlaid text
+
+ Canvas.prototype.render = function() {
+
+ if (!plot.getOptions().canvas) {
+ return render.call(this);
+ }
+
+ var context = this.context,
+ cache = this._textCache;
+
+ // For each text layer, render elements marked as active
+
+ context.save();
+ context.textBaseline = "middle";
+
+ for (var layerKey in cache) {
+ if (hasOwnProperty.call(cache, layerKey)) {
+ var layerCache = cache[layerKey];
+ for (var styleKey in layerCache) {
+ if (hasOwnProperty.call(layerCache, styleKey)) {
+ var styleCache = layerCache[styleKey],
+ updateStyles = true;
+ for (var key in styleCache) {
+ if (hasOwnProperty.call(styleCache, key)) {
+
+ var info = styleCache[key],
+ positions = info.positions,
+ lines = info.lines;
+
+ // Since every element at this level of the cache have the
+ // same font and fill styles, we can just change them once
+ // using the values from the first element.
+
+ if (updateStyles) {
+ context.fillStyle = info.font.color;
+ context.font = info.font.definition;
+ updateStyles = false;
+ }
+
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.active) {
+ for (var j = 0, line; line = position.lines[j]; j++) {
+ context.fillText(lines[j].text, line[0], line[1]);
+ }
+ } else {
+ positions.splice(i--, 1);
+ }
+ }
+
+ if (positions.length == 0) {
+ delete styleCache[key];
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ context.restore();
+ };
+
+ // Creates (if necessary) and returns a text info object.
+ //
+ // When the canvas option is set, the object looks like this:
+ //
+ // {
+ // width: Width of the text's bounding box.
+ // height: Height of the text's bounding box.
+ // positions: Array of positions at which this text is drawn.
+ // lines: [{
+ // height: Height of this line.
+ // widths: Width of this line.
+ // text: Text on this line.
+ // }],
+ // font: {
+ // definition: Canvas font property string.
+ // color: Color of the text.
+ // },
+ // }
+ //
+ // The positions array contains objects that look like this:
+ //
+ // {
+ // active: Flag indicating whether the text should be visible.
+ // lines: Array of [x, y] coordinates at which to draw the line.
+ // x: X coordinate at which to draw the text.
+ // y: Y coordinate at which to draw the text.
+ // }
+
+ Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {
+
+ if (!plot.getOptions().canvas) {
+ return getTextInfo.call(this, layer, text, font, angle, width);
+ }
+
+ var textStyle, layerCache, styleCache, info;
+
+ // Cast the value to a string, in case we were given a number
+
+ text = "" + text;
+
+ // If the font is a font-spec object, generate a CSS definition
+
+ if (typeof font === "object") {
+ textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family;
+ } else {
+ textStyle = font;
+ }
+
+ // Retrieve (or create) the cache for the text's layer and styles
+
+ layerCache = this._textCache[layer];
+
+ if (layerCache == null) {
+ layerCache = this._textCache[layer] = {};
+ }
+
+ styleCache = layerCache[textStyle];
+
+ if (styleCache == null) {
+ styleCache = layerCache[textStyle] = {};
+ }
+
+ info = styleCache[text];
+
+ if (info == null) {
+
+ var context = this.context;
+
+ // If the font was provided as CSS, create a div with those
+ // classes and examine it to generate a canvas font spec.
+
+ if (typeof font !== "object") {
+
+ var element = $("<div>&nbsp;</div>")
+ .css("position", "absolute")
+ .addClass(typeof font === "string" ? font : null)
+ .appendTo(this.getTextLayer(layer));
+
+ font = {
+ lineHeight: element.height(),
+ style: element.css("font-style"),
+ variant: element.css("font-variant"),
+ weight: element.css("font-weight"),
+ family: element.css("font-family"),
+ color: element.css("color")
+ };
+
+ // Setting line-height to 1, without units, sets it equal
+ // to the font-size, even if the font-size is abstract,
+ // like 'smaller'. This enables us to read the real size
+ // via the element's height, working around browsers that
+ // return the literal 'smaller' value.
+
+ font.size = element.css("line-height", 1).height();
+
+ element.remove();
+ }
+
+ textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px " + font.family;
+
+ // Create a new info object, initializing the dimensions to
+ // zero so we can count them up line-by-line.
+
+ info = styleCache[text] = {
+ width: 0,
+ height: 0,
+ positions: [],
+ lines: [],
+ font: {
+ definition: textStyle,
+ color: font.color
+ }
+ };
+
+ context.save();
+ context.font = textStyle;
+
+ // Canvas can't handle multi-line strings; break on various
+ // newlines, including HTML brs, to build a list of lines.
+ // Note that we could split directly on regexps, but IE < 9 is
+ // broken; revisit when we drop IE 7/8 support.
+
+ var lines = (text + "").replace(/<br ?\/?>|\r\n|\r/g, "\n").split("\n");
+
+ for (var i = 0; i < lines.length; ++i) {
+
+ var lineText = lines[i],
+ measured = context.measureText(lineText);
+
+ info.width = Math.max(measured.width, info.width);
+ info.height += font.lineHeight;
+
+ info.lines.push({
+ text: lineText,
+ width: measured.width,
+ height: font.lineHeight
+ });
+ }
+
+ context.restore();
+ }
+
+ return info;
+ };
+
+ // Adds a text string to the canvas text overlay.
+
+ Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) {
+
+ if (!plot.getOptions().canvas) {
+ return addText.call(this, layer, x, y, text, font, angle, width, halign, valign);
+ }
+
+ var info = this.getTextInfo(layer, text, font, angle, width),
+ positions = info.positions,
+ lines = info.lines;
+
+ // Text is drawn with baseline 'middle', which we need to account
+ // for by adding half a line's height to the y position.
+
+ y += info.height / lines.length / 2;
+
+ // Tweak the initial y-position to match vertical alignment
+
+ if (valign == "middle") {
+ y = Math.round(y - info.height / 2);
+ } else if (valign == "bottom") {
+ y = Math.round(y - info.height);
+ } else {
+ y = Math.round(y);
+ }
+
+ // FIXME: LEGACY BROWSER FIX
+ // AFFECTS: Opera < 12.00
+
+ // Offset the y coordinate, since Opera is off pretty
+ // consistently compared to the other browsers.
+
+ if (!!(window.opera && window.opera.version().split(".")[0] < 12)) {
+ y -= 2;
+ }
+
+ // Determine whether this text already exists at this position.
+ // If so, mark it for inclusion in the next render pass.
+
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.x == x && position.y == y) {
+ position.active = true;
+ return;
+ }
+ }
+
+ // If the text doesn't exist at this position, create a new entry
+
+ position = {
+ active: true,
+ lines: [],
+ x: x,
+ y: y
+ };
+
+ positions.push(position);
+
+ // Fill in the x & y positions of each line, adjusting them
+ // individually for horizontal alignment.
+
+ for (var i = 0, line; line = lines[i]; i++) {
+ if (halign == "center") {
+ position.lines.push([Math.round(x - line.width / 2), y]);
+ } else if (halign == "right") {
+ position.lines.push([Math.round(x - line.width), y]);
+ } else {
+ position.lines.push([Math.round(x), y]);
+ }
+ y += line.height;
+ }
+ };
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: "canvas",
+ version: "1.0"
+ });
+
+})(jQuery);
diff --git a/misc/flot/jquery.flot.canvas.min.js b/misc/flot/jquery.flot.canvas.min.js
new file mode 100644
index 0000000..40c1051
--- /dev/null
+++ b/misc/flot/jquery.flot.canvas.min.js
@@ -0,0 +1,7 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function($){var options={canvas:true};var render,getTextInfo,addText;var hasOwnProperty=Object.prototype.hasOwnProperty;function init(plot,classes){var Canvas=classes.Canvas;if(render==null){getTextInfo=Canvas.prototype.getTextInfo,addText=Canvas.prototype.addText,render=Canvas.prototype.render}Canvas.prototype.render=function(){if(!plot.getOptions().canvas){return render.call(this)}var context=this.context,cache=this._textCache;context.save();context.textBaseline="middle";for(var layerKey in cache){if(hasOwnProperty.call(cache,layerKey)){var layerCache=cache[layerKey];for(var styleKey in layerCache){if(hasOwnProperty.call(layerCache,styleKey)){var styleCache=layerCache[styleKey],updateStyles=true;for(var key in styleCache){if(hasOwnProperty.call(styleCache,key)){var info=styleCache[key],positions=info.positions,lines=info.lines;if(updateStyles){context.fillStyle=info.font.color;context.font=info.font.definition;updateStyles=false}for(var i=0,position;position=positions[i];i++){if(position.active){for(var j=0,line;line=position.lines[j];j++){context.fillText(lines[j].text,line[0],line[1])}}else{positions.splice(i--,1)}}if(positions.length==0){delete styleCache[key]}}}}}}}context.restore()};Canvas.prototype.getTextInfo=function(layer,text,font,angle,width){if(!plot.getOptions().canvas){return getTextInfo.call(this,layer,text,font,angle,width)}var textStyle,layerCache,styleCache,info;text=""+text;if(typeof font==="object"){textStyle=font.style+" "+font.variant+" "+font.weight+" "+font.size+"px "+font.family}else{textStyle=font}layerCache=this._textCache[layer];if(layerCache==null){layerCache=this._textCache[layer]={}}styleCache=layerCache[textStyle];if(styleCache==null){styleCache=layerCache[textStyle]={}}info=styleCache[text];if(info==null){var context=this.context;if(typeof font!=="object"){var element=$("<div>&nbsp;</div>").css("position","absolute").addClass(typeof font==="string"?font:null).appendTo(this.getTextLayer(layer));font={lineHeight:element.height(),style:element.css("font-style"),variant:element.css("font-variant"),weight:element.css("font-weight"),family:element.css("font-family"),color:element.css("color")};font.size=element.css("line-height",1).height();element.remove()}textStyle=font.style+" "+font.variant+" "+font.weight+" "+font.size+"px "+font.family;info=styleCache[text]={width:0,height:0,positions:[],lines:[],font:{definition:textStyle,color:font.color}};context.save();context.font=textStyle;var lines=(text+"").replace(/<br ?\/?>|\r\n|\r/g,"\n").split("\n");for(var i=0;i<lines.length;++i){var lineText=lines[i],measured=context.measureText(lineText);info.width=Math.max(measured.width,info.width);info.height+=font.lineHeight;info.lines.push({text:lineText,width:measured.width,height:font.lineHeight})}context.restore()}return info};Canvas.prototype.addText=function(layer,x,y,text,font,angle,width,halign,valign){if(!plot.getOptions().canvas){return addText.call(this,layer,x,y,text,font,angle,width,halign,valign)}var info=this.getTextInfo(layer,text,font,angle,width),positions=info.positions,lines=info.lines;y+=info.height/lines.length/2;if(valign=="middle"){y=Math.round(y-info.height/2)}else if(valign=="bottom"){y=Math.round(y-info.height)}else{y=Math.round(y)}if(!!(window.opera&&window.opera.version().split(".")[0]<12)){y-=2}for(var i=0,position;position=positions[i];i++){if(position.x==x&&position.y==y){position.active=true;return}}position={active:true,lines:[],x:x,y:y};positions.push(position);for(var i=0,line;line=lines[i];i++){if(halign=="center"){position.lines.push([Math.round(x-line.width/2),y])}else if(halign=="right"){position.lines.push([Math.round(x-line.width),y])}else{position.lines.push([Math.round(x),y])}y+=line.height}}}$.plot.plugins.push({init:init,options:options,name:"canvas",version:"1.0"})})(jQuery); \ No newline at end of file
diff --git a/misc/flot/jquery.flot.categories.js b/misc/flot/jquery.flot.categories.js
new file mode 100644
index 0000000..2f9b257
--- /dev/null
+++ b/misc/flot/jquery.flot.categories.js
@@ -0,0 +1,190 @@
+/* Flot plugin for plotting textual data or categories.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+Consider a dataset like [["February", 34], ["March", 20], ...]. This plugin
+allows you to plot such a dataset directly.
+
+To enable it, you must specify mode: "categories" on the axis with the textual
+labels, e.g.
+
+ $.plot("#placeholder", data, { xaxis: { mode: "categories" } });
+
+By default, the labels are ordered as they are met in the data series. If you
+need a different ordering, you can specify "categories" on the axis options
+and list the categories there:
+
+ xaxis: {
+ mode: "categories",
+ categories: ["February", "March", "April"]
+ }
+
+If you need to customize the distances between the categories, you can specify
+"categories" as an object mapping labels to values
+
+ xaxis: {
+ mode: "categories",
+ categories: { "February": 1, "March": 3, "April": 4 }
+ }
+
+If you don't specify all categories, the remaining categories will be numbered
+from the max value plus 1 (with a spacing of 1 between each).
+
+Internally, the plugin works by transforming the input data through an auto-
+generated mapping where the first category becomes 0, the second 1, etc.
+Hence, a point like ["February", 34] becomes [0, 34] internally in Flot (this
+is visible in hover and click events that return numbers rather than the
+category labels). The plugin also overrides the tick generator to spit out the
+categories as ticks instead of the values.
+
+If you need to map a value back to its label, the mapping is always accessible
+as "categories" on the axis object, e.g. plot.getAxes().xaxis.categories.
+
+*/
+
+(function ($) {
+ var options = {
+ xaxis: {
+ categories: null
+ },
+ yaxis: {
+ categories: null
+ }
+ };
+
+ function processRawData(plot, series, data, datapoints) {
+ // if categories are enabled, we need to disable
+ // auto-transformation to numbers so the strings are intact
+ // for later processing
+
+ var xCategories = series.xaxis.options.mode == "categories",
+ yCategories = series.yaxis.options.mode == "categories";
+
+ if (!(xCategories || yCategories))
+ return;
+
+ var format = datapoints.format;
+
+ if (!format) {
+ // FIXME: auto-detection should really not be defined here
+ var s = series;
+ format = [];
+ format.push({ x: true, number: true, required: true });
+ format.push({ y: true, number: true, required: true });
+
+ if (s.bars.show || (s.lines.show && s.lines.fill)) {
+ var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
+ format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale });
+ if (s.bars.horizontal) {
+ delete format[format.length - 1].y;
+ format[format.length - 1].x = true;
+ }
+ }
+
+ datapoints.format = format;
+ }
+
+ for (var m = 0; m < format.length; ++m) {
+ if (format[m].x && xCategories)
+ format[m].number = false;
+
+ if (format[m].y && yCategories)
+ format[m].number = false;
+ }
+ }
+
+ function getNextIndex(categories) {
+ var index = -1;
+
+ for (var v in categories)
+ if (categories[v] > index)
+ index = categories[v];
+
+ return index + 1;
+ }
+
+ function categoriesTickGenerator(axis) {
+ var res = [];
+ for (var label in axis.categories) {
+ var v = axis.categories[label];
+ if (v >= axis.min && v <= axis.max)
+ res.push([v, label]);
+ }
+
+ res.sort(function (a, b) { return a[0] - b[0]; });
+
+ return res;
+ }
+
+ function setupCategoriesForAxis(series, axis, datapoints) {
+ if (series[axis].options.mode != "categories")
+ return;
+
+ if (!series[axis].categories) {
+ // parse options
+ var c = {}, o = series[axis].options.categories || {};
+ if ($.isArray(o)) {
+ for (var i = 0; i < o.length; ++i)
+ c[o[i]] = i;
+ }
+ else {
+ for (var v in o)
+ c[v] = o[v];
+ }
+
+ series[axis].categories = c;
+ }
+
+ // fix ticks
+ if (!series[axis].options.ticks)
+ series[axis].options.ticks = categoriesTickGenerator;
+
+ transformPointsOnAxis(datapoints, axis, series[axis].categories);
+ }
+
+ function transformPointsOnAxis(datapoints, axis, categories) {
+ // go through the points, transforming them
+ var points = datapoints.points,
+ ps = datapoints.pointsize,
+ format = datapoints.format,
+ formatColumn = axis.charAt(0),
+ index = getNextIndex(categories);
+
+ for (var i = 0; i < points.length; i += ps) {
+ if (points[i] == null)
+ continue;
+
+ for (var m = 0; m < ps; ++m) {
+ var val = points[i + m];
+
+ if (val == null || !format[m][formatColumn])
+ continue;
+
+ if (!(val in categories)) {
+ categories[val] = index;
+ ++index;
+ }
+
+ points[i + m] = categories[val];
+ }
+ }
+ }
+
+ function processDatapoints(plot, series, datapoints) {
+ setupCategoriesForAxis(series, "xaxis", datapoints);
+ setupCategoriesForAxis(series, "yaxis", datapoints);
+ }
+
+ function init(plot) {
+ plot.hooks.processRawData.push(processRawData);
+ plot.hooks.processDatapoints.push(processDatapoints);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'categories',
+ version: '1.0'
+ });
+})(jQuery);
diff --git a/misc/flot/jquery.flot.categories.min.js b/misc/flot/jquery.flot.categories.min.js
new file mode 100644
index 0000000..5bce588
--- /dev/null
+++ b/misc/flot/jquery.flot.categories.min.js
@@ -0,0 +1,7 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function($){var options={xaxis:{categories:null},yaxis:{categories:null}};function processRawData(plot,series,data,datapoints){var xCategories=series.xaxis.options.mode=="categories",yCategories=series.yaxis.options.mode=="categories";if(!(xCategories||yCategories))return;var format=datapoints.format;if(!format){var s=series;format=[];format.push({x:true,number:true,required:true});format.push({y:true,number:true,required:true});if(s.bars.show||s.lines.show&&s.lines.fill){var autoscale=!!(s.bars.show&&s.bars.zero||s.lines.show&&s.lines.zero);format.push({y:true,number:true,required:false,defaultValue:0,autoscale:autoscale});if(s.bars.horizontal){delete format[format.length-1].y;format[format.length-1].x=true}}datapoints.format=format}for(var m=0;m<format.length;++m){if(format[m].x&&xCategories)format[m].number=false;if(format[m].y&&yCategories)format[m].number=false}}function getNextIndex(categories){var index=-1;for(var v in categories)if(categories[v]>index)index=categories[v];return index+1}function categoriesTickGenerator(axis){var res=[];for(var label in axis.categories){var v=axis.categories[label];if(v>=axis.min&&v<=axis.max)res.push([v,label])}res.sort(function(a,b){return a[0]-b[0]});return res}function setupCategoriesForAxis(series,axis,datapoints){if(series[axis].options.mode!="categories")return;if(!series[axis].categories){var c={},o=series[axis].options.categories||{};if($.isArray(o)){for(var i=0;i<o.length;++i)c[o[i]]=i}else{for(var v in o)c[v]=o[v]}series[axis].categories=c}if(!series[axis].options.ticks)series[axis].options.ticks=categoriesTickGenerator;transformPointsOnAxis(datapoints,axis,series[axis].categories)}function transformPointsOnAxis(datapoints,axis,categories){var points=datapoints.points,ps=datapoints.pointsize,format=datapoints.format,formatColumn=axis.charAt(0),index=getNextIndex(categories);for(var i=0;i<points.length;i+=ps){if(points[i]==null)continue;for(var m=0;m<ps;++m){var val=points[i+m];if(val==null||!format[m][formatColumn])continue;if(!(val in categories)){categories[val]=index;++index}points[i+m]=categories[val]}}}function processDatapoints(plot,series,datapoints){setupCategoriesForAxis(series,"xaxis",datapoints);setupCategoriesForAxis(series,"yaxis",datapoints)}function init(plot){plot.hooks.processRawData.push(processRawData);plot.hooks.processDatapoints.push(processDatapoints)}$.plot.plugins.push({init:init,options:options,name:"categories",version:"1.0"})})(jQuery); \ No newline at end of file
diff --git a/misc/flot/jquery.flot.crosshair.js b/misc/flot/jquery.flot.crosshair.js
new file mode 100644
index 0000000..5111695
--- /dev/null
+++ b/misc/flot/jquery.flot.crosshair.js
@@ -0,0 +1,176 @@
+/* Flot plugin for showing crosshairs when the mouse hovers over the plot.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The plugin supports these options:
+
+ crosshair: {
+ mode: null or "x" or "y" or "xy"
+ color: color
+ lineWidth: number
+ }
+
+Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical
+crosshair that lets you trace the values on the x axis, "y" enables a
+horizontal crosshair and "xy" enables them both. "color" is the color of the
+crosshair (default is "rgba(170, 0, 0, 0.80)"), "lineWidth" is the width of
+the drawn lines (default is 1).
+
+The plugin also adds four public methods:
+
+ - setCrosshair( pos )
+
+ Set the position of the crosshair. Note that this is cleared if the user
+ moves the mouse. "pos" is in coordinates of the plot and should be on the
+ form { x: xpos, y: ypos } (you can use x2/x3/... if you're using multiple
+ axes), which is coincidentally the same format as what you get from a
+ "plothover" event. If "pos" is null, the crosshair is cleared.
+
+ - clearCrosshair()
+
+ Clear the crosshair.
+
+ - lockCrosshair(pos)
+
+ Cause the crosshair to lock to the current location, no longer updating if
+ the user moves the mouse. Optionally supply a position (passed on to
+ setCrosshair()) to move it to.
+
+ Example usage:
+
+ var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } };
+ $("#graph").bind( "plothover", function ( evt, position, item ) {
+ if ( item ) {
+ // Lock the crosshair to the data point being hovered
+ myFlot.lockCrosshair({
+ x: item.datapoint[ 0 ],
+ y: item.datapoint[ 1 ]
+ });
+ } else {
+ // Return normal crosshair operation
+ myFlot.unlockCrosshair();
+ }
+ });
+
+ - unlockCrosshair()
+
+ Free the crosshair to move again after locking it.
+*/
+
+(function ($) {
+ var options = {
+ crosshair: {
+ mode: null, // one of null, "x", "y" or "xy",
+ color: "rgba(170, 0, 0, 0.80)",
+ lineWidth: 1
+ }
+ };
+
+ function init(plot) {
+ // position of crosshair in pixels
+ var crosshair = { x: -1, y: -1, locked: false };
+
+ plot.setCrosshair = function setCrosshair(pos) {
+ if (!pos)
+ crosshair.x = -1;
+ else {
+ var o = plot.p2c(pos);
+ crosshair.x = Math.max(0, Math.min(o.left, plot.width()));
+ crosshair.y = Math.max(0, Math.min(o.top, plot.height()));
+ }
+
+ plot.triggerRedrawOverlay();
+ };
+
+ plot.clearCrosshair = plot.setCrosshair; // passes null for pos
+
+ plot.lockCrosshair = function lockCrosshair(pos) {
+ if (pos)
+ plot.setCrosshair(pos);
+ crosshair.locked = true;
+ };
+
+ plot.unlockCrosshair = function unlockCrosshair() {
+ crosshair.locked = false;
+ };
+
+ function onMouseOut(e) {
+ if (crosshair.locked)
+ return;
+
+ if (crosshair.x != -1) {
+ crosshair.x = -1;
+ plot.triggerRedrawOverlay();
+ }
+ }
+
+ function onMouseMove(e) {
+ if (crosshair.locked)
+ return;
+
+ if (plot.getSelection && plot.getSelection()) {
+ crosshair.x = -1; // hide the crosshair while selecting
+ return;
+ }
+
+ var offset = plot.offset();
+ crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
+ crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
+ plot.triggerRedrawOverlay();
+ }
+
+ plot.hooks.bindEvents.push(function (plot, eventHolder) {
+ if (!plot.getOptions().crosshair.mode)
+ return;
+
+ eventHolder.mouseout(onMouseOut);
+ eventHolder.mousemove(onMouseMove);
+ });
+
+ plot.hooks.drawOverlay.push(function (plot, ctx) {
+ var c = plot.getOptions().crosshair;
+ if (!c.mode)
+ return;
+
+ var plotOffset = plot.getPlotOffset();
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ if (crosshair.x != -1) {
+ var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0;
+
+ ctx.strokeStyle = c.color;
+ ctx.lineWidth = c.lineWidth;
+ ctx.lineJoin = "round";
+
+ ctx.beginPath();
+ if (c.mode.indexOf("x") != -1) {
+ var drawX = Math.floor(crosshair.x) + adj;
+ ctx.moveTo(drawX, 0);
+ ctx.lineTo(drawX, plot.height());
+ }
+ if (c.mode.indexOf("y") != -1) {
+ var drawY = Math.floor(crosshair.y) + adj;
+ ctx.moveTo(0, drawY);
+ ctx.lineTo(plot.width(), drawY);
+ }
+ ctx.stroke();
+ }
+ ctx.restore();
+ });
+
+ plot.hooks.shutdown.push(function (plot, eventHolder) {
+ eventHolder.unbind("mouseout", onMouseOut);
+ eventHolder.unbind("mousemove", onMouseMove);
+ });
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'crosshair',
+ version: '1.0'
+ });
+})(jQuery);
diff --git a/misc/flot/jquery.flot.crosshair.min.js b/misc/flot/jquery.flot.crosshair.min.js
new file mode 100644
index 0000000..e98ee06
--- /dev/null
+++ b/misc/flot/jquery.flot.crosshair.min.js
@@ -0,0 +1,7 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function($){var options={crosshair:{mode:null,color:"rgba(170, 0, 0, 0.80)",lineWidth:1}};function init(plot){var crosshair={x:-1,y:-1,locked:false};plot.setCrosshair=function setCrosshair(pos){if(!pos)crosshair.x=-1;else{var o=plot.p2c(pos);crosshair.x=Math.max(0,Math.min(o.left,plot.width()));crosshair.y=Math.max(0,Math.min(o.top,plot.height()))}plot.triggerRedrawOverlay()};plot.clearCrosshair=plot.setCrosshair;plot.lockCrosshair=function lockCrosshair(pos){if(pos)plot.setCrosshair(pos);crosshair.locked=true};plot.unlockCrosshair=function unlockCrosshair(){crosshair.locked=false};function onMouseOut(e){if(crosshair.locked)return;if(crosshair.x!=-1){crosshair.x=-1;plot.triggerRedrawOverlay()}}function onMouseMove(e){if(crosshair.locked)return;if(plot.getSelection&&plot.getSelection()){crosshair.x=-1;return}var offset=plot.offset();crosshair.x=Math.max(0,Math.min(e.pageX-offset.left,plot.width()));crosshair.y=Math.max(0,Math.min(e.pageY-offset.top,plot.height()));plot.triggerRedrawOverlay()}plot.hooks.bindEvents.push(function(plot,eventHolder){if(!plot.getOptions().crosshair.mode)return;eventHolder.mouseout(onMouseOut);eventHolder.mousemove(onMouseMove)});plot.hooks.drawOverlay.push(function(plot,ctx){var c=plot.getOptions().crosshair;if(!c.mode)return;var plotOffset=plot.getPlotOffset();ctx.save();ctx.translate(plotOffset.left,plotOffset.top);if(crosshair.x!=-1){var adj=plot.getOptions().crosshair.lineWidth%2?.5:0;ctx.strokeStyle=c.color;ctx.lineWidth=c.lineWidth;ctx.lineJoin="round";ctx.beginPath();if(c.mode.indexOf("x")!=-1){var drawX=Math.floor(crosshair.x)+adj;ctx.moveTo(drawX,0);ctx.lineTo(drawX,plot.height())}if(c.mode.indexOf("y")!=-1){var drawY=Math.floor(crosshair.y)+adj;ctx.moveTo(0,drawY);ctx.lineTo(plot.width(),drawY)}ctx.stroke()}ctx.restore()});plot.hooks.shutdown.push(function(plot,eventHolder){eventHolder.unbind("mouseout",onMouseOut);eventHolder.unbind("mousemove",onMouseMove)})}$.plot.plugins.push({init:init,options:options,name:"crosshair",version:"1.0"})})(jQuery); \ No newline at end of file
diff --git a/misc/flot/jquery.flot.errorbars.js b/misc/flot/jquery.flot.errorbars.js
new file mode 100644
index 0000000..2583d5c
--- /dev/null
+++ b/misc/flot/jquery.flot.errorbars.js
@@ -0,0 +1,353 @@
+/* Flot plugin for plotting error bars.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+Error bars are used to show standard deviation and other statistical
+properties in a plot.
+
+* Created by Rui Pereira - rui (dot) pereira (at) gmail (dot) com
+
+This plugin allows you to plot error-bars over points. Set "errorbars" inside
+the points series to the axis name over which there will be error values in
+your data array (*even* if you do not intend to plot them later, by setting
+"show: null" on xerr/yerr).
+
+The plugin supports these options:
+
+ series: {
+ points: {
+ errorbars: "x" or "y" or "xy",
+ xerr: {
+ show: null/false or true,
+ asymmetric: null/false or true,
+ upperCap: null or "-" or function,
+ lowerCap: null or "-" or function,
+ color: null or color,
+ radius: null or number
+ },
+ yerr: { same options as xerr }
+ }
+ }
+
+Each data point array is expected to be of the type:
+
+ "x" [ x, y, xerr ]
+ "y" [ x, y, yerr ]
+ "xy" [ x, y, xerr, yerr ]
+
+Where xerr becomes xerr_lower,xerr_upper for the asymmetric error case, and
+equivalently for yerr. Eg., a datapoint for the "xy" case with symmetric
+error-bars on X and asymmetric on Y would be:
+
+ [ x, y, xerr, yerr_lower, yerr_upper ]
+
+By default no end caps are drawn. Setting upperCap and/or lowerCap to "-" will
+draw a small cap perpendicular to the error bar. They can also be set to a
+user-defined drawing function, with (ctx, x, y, radius) as parameters, as eg.
+
+ function drawSemiCircle( ctx, x, y, radius ) {
+ ctx.beginPath();
+ ctx.arc( x, y, radius, 0, Math.PI, false );
+ ctx.moveTo( x - radius, y );
+ ctx.lineTo( x + radius, y );
+ ctx.stroke();
+ }
+
+Color and radius both default to the same ones of the points series if not
+set. The independent radius parameter on xerr/yerr is useful for the case when
+we may want to add error-bars to a line, without showing the interconnecting
+points (with radius: 0), and still showing end caps on the error-bars.
+shadowSize and lineWidth are derived as well from the points series.
+
+*/
+
+(function ($) {
+ var options = {
+ series: {
+ points: {
+ errorbars: null, //should be 'x', 'y' or 'xy'
+ xerr: { err: 'x', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null},
+ yerr: { err: 'y', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null}
+ }
+ }
+ };
+
+ function processRawData(plot, series, data, datapoints){
+ if (!series.points.errorbars)
+ return;
+
+ // x,y values
+ var format = [
+ { x: true, number: true, required: true },
+ { y: true, number: true, required: true }
+ ];
+
+ var errors = series.points.errorbars;
+ // error bars - first X then Y
+ if (errors == 'x' || errors == 'xy') {
+ // lower / upper error
+ if (series.points.xerr.asymmetric) {
+ format.push({ x: true, number: true, required: true });
+ format.push({ x: true, number: true, required: true });
+ } else
+ format.push({ x: true, number: true, required: true });
+ }
+ if (errors == 'y' || errors == 'xy') {
+ // lower / upper error
+ if (series.points.yerr.asymmetric) {
+ format.push({ y: true, number: true, required: true });
+ format.push({ y: true, number: true, required: true });
+ } else
+ format.push({ y: true, number: true, required: true });
+ }
+ datapoints.format = format;
+ }
+
+ function parseErrors(series, i){
+
+ var points = series.datapoints.points;
+
+ // read errors from points array
+ var exl = null,
+ exu = null,
+ eyl = null,
+ eyu = null;
+ var xerr = series.points.xerr,
+ yerr = series.points.yerr;
+
+ var eb = series.points.errorbars;
+ // error bars - first X
+ if (eb == 'x' || eb == 'xy') {
+ if (xerr.asymmetric) {
+ exl = points[i + 2];
+ exu = points[i + 3];
+ if (eb == 'xy')
+ if (yerr.asymmetric){
+ eyl = points[i + 4];
+ eyu = points[i + 5];
+ } else eyl = points[i + 4];
+ } else {
+ exl = points[i + 2];
+ if (eb == 'xy')
+ if (yerr.asymmetric) {
+ eyl = points[i + 3];
+ eyu = points[i + 4];
+ } else eyl = points[i + 3];
+ }
+ // only Y
+ } else if (eb == 'y')
+ if (yerr.asymmetric) {
+ eyl = points[i + 2];
+ eyu = points[i + 3];
+ } else eyl = points[i + 2];
+
+ // symmetric errors?
+ if (exu == null) exu = exl;
+ if (eyu == null) eyu = eyl;
+
+ var errRanges = [exl, exu, eyl, eyu];
+ // nullify if not showing
+ if (!xerr.show){
+ errRanges[0] = null;
+ errRanges[1] = null;
+ }
+ if (!yerr.show){
+ errRanges[2] = null;
+ errRanges[3] = null;
+ }
+ return errRanges;
+ }
+
+ function drawSeriesErrors(plot, ctx, s){
+
+ var points = s.datapoints.points,
+ ps = s.datapoints.pointsize,
+ ax = [s.xaxis, s.yaxis],
+ radius = s.points.radius,
+ err = [s.points.xerr, s.points.yerr];
+
+ //sanity check, in case some inverted axis hack is applied to flot
+ var invertX = false;
+ if (ax[0].p2c(ax[0].max) < ax[0].p2c(ax[0].min)) {
+ invertX = true;
+ var tmp = err[0].lowerCap;
+ err[0].lowerCap = err[0].upperCap;
+ err[0].upperCap = tmp;
+ }
+
+ var invertY = false;
+ if (ax[1].p2c(ax[1].min) < ax[1].p2c(ax[1].max)) {
+ invertY = true;
+ var tmp = err[1].lowerCap;
+ err[1].lowerCap = err[1].upperCap;
+ err[1].upperCap = tmp;
+ }
+
+ for (var i = 0; i < s.datapoints.points.length; i += ps) {
+
+ //parse
+ var errRanges = parseErrors(s, i);
+
+ //cycle xerr & yerr
+ for (var e = 0; e < err.length; e++){
+
+ var minmax = [ax[e].min, ax[e].max];
+
+ //draw this error?
+ if (errRanges[e * err.length]){
+
+ //data coordinates
+ var x = points[i],
+ y = points[i + 1];
+
+ //errorbar ranges
+ var upper = [x, y][e] + errRanges[e * err.length + 1],
+ lower = [x, y][e] - errRanges[e * err.length];
+
+ //points outside of the canvas
+ if (err[e].err == 'x')
+ if (y > ax[1].max || y < ax[1].min || upper < ax[0].min || lower > ax[0].max)
+ continue;
+ if (err[e].err == 'y')
+ if (x > ax[0].max || x < ax[0].min || upper < ax[1].min || lower > ax[1].max)
+ continue;
+
+ // prevent errorbars getting out of the canvas
+ var drawUpper = true,
+ drawLower = true;
+
+ if (upper > minmax[1]) {
+ drawUpper = false;
+ upper = minmax[1];
+ }
+ if (lower < minmax[0]) {
+ drawLower = false;
+ lower = minmax[0];
+ }
+
+ //sanity check, in case some inverted axis hack is applied to flot
+ if ((err[e].err == 'x' && invertX) || (err[e].err == 'y' && invertY)) {
+ //swap coordinates
+ var tmp = lower;
+ lower = upper;
+ upper = tmp;
+ tmp = drawLower;
+ drawLower = drawUpper;
+ drawUpper = tmp;
+ tmp = minmax[0];
+ minmax[0] = minmax[1];
+ minmax[1] = tmp;
+ }
+
+ // convert to pixels
+ x = ax[0].p2c(x),
+ y = ax[1].p2c(y),
+ upper = ax[e].p2c(upper);
+ lower = ax[e].p2c(lower);
+ minmax[0] = ax[e].p2c(minmax[0]);
+ minmax[1] = ax[e].p2c(minmax[1]);
+
+ //same style as points by default
+ var lw = err[e].lineWidth ? err[e].lineWidth : s.points.lineWidth,
+ sw = s.points.shadowSize != null ? s.points.shadowSize : s.shadowSize;
+
+ //shadow as for points
+ if (lw > 0 && sw > 0) {
+ var w = sw / 2;
+ ctx.lineWidth = w;
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
+ drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w + w/2, minmax);
+
+ ctx.strokeStyle = "rgba(0,0,0,0.2)";
+ drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w/2, minmax);
+ }
+
+ ctx.strokeStyle = err[e].color? err[e].color: s.color;
+ ctx.lineWidth = lw;
+ //draw it
+ drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, 0, minmax);
+ }
+ }
+ }
+ }
+
+ function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){
+
+ //shadow offset
+ y += offset;
+ upper += offset;
+ lower += offset;
+
+ // error bar - avoid plotting over circles
+ if (err.err == 'x'){
+ if (upper > x + radius) drawPath(ctx, [[upper,y],[Math.max(x + radius,minmax[0]),y]]);
+ else drawUpper = false;
+ if (lower < x - radius) drawPath(ctx, [[Math.min(x - radius,minmax[1]),y],[lower,y]] );
+ else drawLower = false;
+ }
+ else {
+ if (upper < y - radius) drawPath(ctx, [[x,upper],[x,Math.min(y - radius,minmax[0])]] );
+ else drawUpper = false;
+ if (lower > y + radius) drawPath(ctx, [[x,Math.max(y + radius,minmax[1])],[x,lower]] );
+ else drawLower = false;
+ }
+
+ //internal radius value in errorbar, allows to plot radius 0 points and still keep proper sized caps
+ //this is a way to get errorbars on lines without visible connecting dots
+ radius = err.radius != null? err.radius: radius;
+
+ // upper cap
+ if (drawUpper) {
+ if (err.upperCap == '-'){
+ if (err.err=='x') drawPath(ctx, [[upper,y - radius],[upper,y + radius]] );
+ else drawPath(ctx, [[x - radius,upper],[x + radius,upper]] );
+ } else if ($.isFunction(err.upperCap)){
+ if (err.err=='x') err.upperCap(ctx, upper, y, radius);
+ else err.upperCap(ctx, x, upper, radius);
+ }
+ }
+ // lower cap
+ if (drawLower) {
+ if (err.lowerCap == '-'){
+ if (err.err=='x') drawPath(ctx, [[lower,y - radius],[lower,y + radius]] );
+ else drawPath(ctx, [[x - radius,lower],[x + radius,lower]] );
+ } else if ($.isFunction(err.lowerCap)){
+ if (err.err=='x') err.lowerCap(ctx, lower, y, radius);
+ else err.lowerCap(ctx, x, lower, radius);
+ }
+ }
+ }
+
+ function drawPath(ctx, pts){
+ ctx.beginPath();
+ ctx.moveTo(pts[0][0], pts[0][1]);
+ for (var p=1; p < pts.length; p++)
+ ctx.lineTo(pts[p][0], pts[p][1]);
+ ctx.stroke();
+ }
+
+ function draw(plot, ctx){
+ var plotOffset = plot.getPlotOffset();
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+ $.each(plot.getData(), function (i, s) {
+ if (s.points.errorbars && (s.points.xerr.show || s.points.yerr.show))
+ drawSeriesErrors(plot, ctx, s);
+ });
+ ctx.restore();
+ }
+
+ function init(plot) {
+ plot.hooks.processRawData.push(processRawData);
+ plot.hooks.draw.push(draw);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'errorbars',
+ version: '1.0'
+ });
+})(jQuery);
diff --git a/misc/flot/jquery.flot.errorbars.min.js b/misc/flot/jquery.flot.errorbars.min.js
new file mode 100644
index 0000000..aa79f54
--- /dev/null
+++ b/misc/flot/jquery.flot.errorbars.min.js
@@ -0,0 +1,7 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function($){var options={series:{points:{errorbars:null,xerr:{err:"x",show:null,asymmetric:null,upperCap:null,lowerCap:null,color:null,radius:null},yerr:{err:"y",show:null,asymmetric:null,upperCap:null,lowerCap:null,color:null,radius:null}}}};function processRawData(plot,series,data,datapoints){if(!series.points.errorbars)return;var format=[{x:true,number:true,required:true},{y:true,number:true,required:true}];var errors=series.points.errorbars;if(errors=="x"||errors=="xy"){if(series.points.xerr.asymmetric){format.push({x:true,number:true,required:true});format.push({x:true,number:true,required:true})}else format.push({x:true,number:true,required:true})}if(errors=="y"||errors=="xy"){if(series.points.yerr.asymmetric){format.push({y:true,number:true,required:true});format.push({y:true,number:true,required:true})}else format.push({y:true,number:true,required:true})}datapoints.format=format}function parseErrors(series,i){var points=series.datapoints.points;var exl=null,exu=null,eyl=null,eyu=null;var xerr=series.points.xerr,yerr=series.points.yerr;var eb=series.points.errorbars;if(eb=="x"||eb=="xy"){if(xerr.asymmetric){exl=points[i+2];exu=points[i+3];if(eb=="xy")if(yerr.asymmetric){eyl=points[i+4];eyu=points[i+5]}else eyl=points[i+4]}else{exl=points[i+2];if(eb=="xy")if(yerr.asymmetric){eyl=points[i+3];eyu=points[i+4]}else eyl=points[i+3]}}else if(eb=="y")if(yerr.asymmetric){eyl=points[i+2];eyu=points[i+3]}else eyl=points[i+2];if(exu==null)exu=exl;if(eyu==null)eyu=eyl;var errRanges=[exl,exu,eyl,eyu];if(!xerr.show){errRanges[0]=null;errRanges[1]=null}if(!yerr.show){errRanges[2]=null;errRanges[3]=null}return errRanges}function drawSeriesErrors(plot,ctx,s){var points=s.datapoints.points,ps=s.datapoints.pointsize,ax=[s.xaxis,s.yaxis],radius=s.points.radius,err=[s.points.xerr,s.points.yerr];var invertX=false;if(ax[0].p2c(ax[0].max)<ax[0].p2c(ax[0].min)){invertX=true;var tmp=err[0].lowerCap;err[0].lowerCap=err[0].upperCap;err[0].upperCap=tmp}var invertY=false;if(ax[1].p2c(ax[1].min)<ax[1].p2c(ax[1].max)){invertY=true;var tmp=err[1].lowerCap;err[1].lowerCap=err[1].upperCap;err[1].upperCap=tmp}for(var i=0;i<s.datapoints.points.length;i+=ps){var errRanges=parseErrors(s,i);for(var e=0;e<err.length;e++){var minmax=[ax[e].min,ax[e].max];if(errRanges[e*err.length]){var x=points[i],y=points[i+1];var upper=[x,y][e]+errRanges[e*err.length+1],lower=[x,y][e]-errRanges[e*err.length];if(err[e].err=="x")if(y>ax[1].max||y<ax[1].min||upper<ax[0].min||lower>ax[0].max)continue;if(err[e].err=="y")if(x>ax[0].max||x<ax[0].min||upper<ax[1].min||lower>ax[1].max)continue;var drawUpper=true,drawLower=true;if(upper>minmax[1]){drawUpper=false;upper=minmax[1]}if(lower<minmax[0]){drawLower=false;lower=minmax[0]}if(err[e].err=="x"&&invertX||err[e].err=="y"&&invertY){var tmp=lower;lower=upper;upper=tmp;tmp=drawLower;drawLower=drawUpper;drawUpper=tmp;tmp=minmax[0];minmax[0]=minmax[1];minmax[1]=tmp}x=ax[0].p2c(x),y=ax[1].p2c(y),upper=ax[e].p2c(upper);lower=ax[e].p2c(lower);minmax[0]=ax[e].p2c(minmax[0]);minmax[1]=ax[e].p2c(minmax[1]);var lw=err[e].lineWidth?err[e].lineWidth:s.points.lineWidth,sw=s.points.shadowSize!=null?s.points.shadowSize:s.shadowSize;if(lw>0&&sw>0){var w=sw/2;ctx.lineWidth=w;ctx.strokeStyle="rgba(0,0,0,0.1)";drawError(ctx,err[e],x,y,upper,lower,drawUpper,drawLower,radius,w+w/2,minmax);ctx.strokeStyle="rgba(0,0,0,0.2)";drawError(ctx,err[e],x,y,upper,lower,drawUpper,drawLower,radius,w/2,minmax)}ctx.strokeStyle=err[e].color?err[e].color:s.color;ctx.lineWidth=lw;drawError(ctx,err[e],x,y,upper,lower,drawUpper,drawLower,radius,0,minmax)}}}}function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){y+=offset;upper+=offset;lower+=offset;if(err.err=="x"){if(upper>x+radius)drawPath(ctx,[[upper,y],[Math.max(x+radius,minmax[0]),y]]);else drawUpper=false;if(lower<x-radius)drawPath(ctx,[[Math.min(x-radius,minmax[1]),y],[lower,y]]);else drawLower=false}else{if(upper<y-radius)drawPath(ctx,[[x,upper],[x,Math.min(y-radius,minmax[0])]]);else drawUpper=false;if(lower>y+radius)drawPath(ctx,[[x,Math.max(y+radius,minmax[1])],[x,lower]]);else drawLower=false}radius=err.radius!=null?err.radius:radius;if(drawUpper){if(err.upperCap=="-"){if(err.err=="x")drawPath(ctx,[[upper,y-radius],[upper,y+radius]]);else drawPath(ctx,[[x-radius,upper],[x+radius,upper]])}else if($.isFunction(err.upperCap)){if(err.err=="x")err.upperCap(ctx,upper,y,radius);else err.upperCap(ctx,x,upper,radius)}}if(drawLower){if(err.lowerCap=="-"){if(err.err=="x")drawPath(ctx,[[lower,y-radius],[lower,y+radius]]);else drawPath(ctx,[[x-radius,lower],[x+radius,lower]])}else if($.isFunction(err.lowerCap)){if(err.err=="x")err.lowerCap(ctx,lower,y,radius);else err.lowerCap(ctx,x,lower,radius)}}}function drawPath(ctx,pts){ctx.beginPath();ctx.moveTo(pts[0][0],pts[0][1]);for(var p=1;p<pts.length;p++)ctx.lineTo(pts[p][0],pts[p][1]);ctx.stroke()}function draw(plot,ctx){var plotOffset=plot.getPlotOffset();ctx.save();ctx.translate(plotOffset.left,plotOffset.top);$.each(plot.getData(),function(i,s){if(s.points.errorbars&&(s.points.xerr.show||s.points.yerr.show))drawSeriesErrors(plot,ctx,s)});ctx.restore()}function init(plot){plot.hooks.processRawData.push(processRawData);plot.hooks.draw.push(draw)}$.plot.plugins.push({init:init,options:options,name:"errorbars",version:"1.0"})})(jQuery); \ No newline at end of file
diff --git a/misc/flot/jquery.flot.fillbetween.js b/misc/flot/jquery.flot.fillbetween.js
new file mode 100644
index 0000000..18b15d2
--- /dev/null
+++ b/misc/flot/jquery.flot.fillbetween.js
@@ -0,0 +1,226 @@
+/* Flot plugin for computing bottoms for filled line and bar charts.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The case: you've got two series that you want to fill the area between. In Flot
+terms, you need to use one as the fill bottom of the other. You can specify the
+bottom of each data point as the third coordinate manually, or you can use this
+plugin to compute it for you.
+
+In order to name the other series, you need to give it an id, like this:
+
+ var dataset = [
+ { data: [ ... ], id: "foo" } , // use default bottom
+ { data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom
+ ];
+
+ $.plot($("#placeholder"), dataset, { lines: { show: true, fill: true }});
+
+As a convenience, if the id given is a number that doesn't appear as an id in
+the series, it is interpreted as the index in the array instead (so fillBetween:
+0 can also mean the first series).
+
+Internally, the plugin modifies the datapoints in each series. For line series,
+extra data points might be inserted through interpolation. Note that at points
+where the bottom line is not defined (due to a null point or start/end of line),
+the current line will show a gap too. The algorithm comes from the
+jquery.flot.stack.js plugin, possibly some code could be shared.
+
+*/
+
+(function ( $ ) {
+
+ var options = {
+ series: {
+ fillBetween: null // or number
+ }
+ };
+
+ function init( plot ) {
+
+ function findBottomSeries( s, allseries ) {
+
+ var i;
+
+ for ( i = 0; i < allseries.length; ++i ) {
+ if ( allseries[ i ].id === s.fillBetween ) {
+ return allseries[ i ];
+ }
+ }
+
+ if ( typeof s.fillBetween === "number" ) {
+ if ( s.fillBetween < 0 || s.fillBetween >= allseries.length ) {
+ return null;
+ }
+ return allseries[ s.fillBetween ];
+ }
+
+ return null;
+ }
+
+ function computeFillBottoms( plot, s, datapoints ) {
+
+ if ( s.fillBetween == null ) {
+ return;
+ }
+
+ var other = findBottomSeries( s, plot.getData() );
+
+ if ( !other ) {
+ return;
+ }
+
+ var ps = datapoints.pointsize,
+ points = datapoints.points,
+ otherps = other.datapoints.pointsize,
+ otherpoints = other.datapoints.points,
+ newpoints = [],
+ px, py, intery, qx, qy, bottom,
+ withlines = s.lines.show,
+ withbottom = ps > 2 && datapoints.format[2].y,
+ withsteps = withlines && s.lines.steps,
+ fromgap = true,
+ i = 0,
+ j = 0,
+ l, m;
+
+ while ( true ) {
+
+ if ( i >= points.length ) {
+ break;
+ }
+
+ l = newpoints.length;
+
+ if ( points[ i ] == null ) {
+
+ // copy gaps
+
+ for ( m = 0; m < ps; ++m ) {
+ newpoints.push( points[ i + m ] );
+ }
+
+ i += ps;
+
+ } else if ( j >= otherpoints.length ) {
+
+ // for lines, we can't use the rest of the points
+
+ if ( !withlines ) {
+ for ( m = 0; m < ps; ++m ) {
+ newpoints.push( points[ i + m ] );
+ }
+ }
+
+ i += ps;
+
+ } else if ( otherpoints[ j ] == null ) {
+
+ // oops, got a gap
+
+ for ( m = 0; m < ps; ++m ) {
+ newpoints.push( null );
+ }
+
+ fromgap = true;
+ j += otherps;
+
+ } else {
+
+ // cases where we actually got two points
+
+ px = points[ i ];
+ py = points[ i + 1 ];
+ qx = otherpoints[ j ];
+ qy = otherpoints[ j + 1 ];
+ bottom = 0;
+
+ if ( px === qx ) {
+
+ for ( m = 0; m < ps; ++m ) {
+ newpoints.push( points[ i + m ] );
+ }
+
+ //newpoints[ l + 1 ] += qy;
+ bottom = qy;
+
+ i += ps;
+ j += otherps;
+
+ } else if ( px > qx ) {
+
+ // we got past point below, might need to
+ // insert interpolated extra point
+
+ if ( withlines && i > 0 && points[ i - ps ] != null ) {
+ intery = py + ( points[ i - ps + 1 ] - py ) * ( qx - px ) / ( points[ i - ps ] - px );
+ newpoints.push( qx );
+ newpoints.push( intery );
+ for ( m = 2; m < ps; ++m ) {
+ newpoints.push( points[ i + m ] );
+ }
+ bottom = qy;
+ }
+
+ j += otherps;
+
+ } else { // px < qx
+
+ // if we come from a gap, we just skip this point
+
+ if ( fromgap && withlines ) {
+ i += ps;
+ continue;
+ }
+
+ for ( m = 0; m < ps; ++m ) {
+ newpoints.push( points[ i + m ] );
+ }
+
+ // we might be able to interpolate a point below,
+ // this can give us a better y
+
+ if ( withlines && j > 0 && otherpoints[ j - otherps ] != null ) {
+ bottom = qy + ( otherpoints[ j - otherps + 1 ] - qy ) * ( px - qx ) / ( otherpoints[ j - otherps ] - qx );
+ }
+
+ //newpoints[l + 1] += bottom;
+
+ i += ps;
+ }
+
+ fromgap = false;
+
+ if ( l !== newpoints.length && withbottom ) {
+ newpoints[ l + 2 ] = bottom;
+ }
+ }
+
+ // maintain the line steps invariant
+
+ if ( withsteps && l !== newpoints.length && l > 0 &&
+ newpoints[ l ] !== null &&
+ newpoints[ l ] !== newpoints[ l - ps ] &&
+ newpoints[ l + 1 ] !== newpoints[ l - ps + 1 ] ) {
+ for (m = 0; m < ps; ++m) {
+ newpoints[ l + ps + m ] = newpoints[ l + m ];
+ }
+ newpoints[ l + 1 ] = newpoints[ l - ps + 1 ];
+ }
+ }
+
+ datapoints.points = newpoints;
+ }
+
+ plot.hooks.processDatapoints.push( computeFillBottoms );
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: "fillbetween",
+ version: "1.0"
+ });
+
+})(jQuery);
diff --git a/misc/flot/jquery.flot.fillbetween.min.js b/misc/flot/jquery.flot.fillbetween.min.js
new file mode 100644
index 0000000..464bf72
--- /dev/null
+++ b/misc/flot/jquery.flot.fillbetween.min.js
@@ -0,0 +1,7 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function($){var options={series:{fillBetween:null}};function init(plot){function findBottomSeries(s,allseries){var i;for(i=0;i<allseries.length;++i){if(allseries[i].id===s.fillBetween){return allseries[i]}}if(typeof s.fillBetween==="number"){if(s.fillBetween<0||s.fillBetween>=allseries.length){return null}return allseries[s.fillBetween]}return null}function computeFillBottoms(plot,s,datapoints){if(s.fillBetween==null){return}var other=findBottomSeries(s,plot.getData());if(!other){return}var ps=datapoints.pointsize,points=datapoints.points,otherps=other.datapoints.pointsize,otherpoints=other.datapoints.points,newpoints=[],px,py,intery,qx,qy,bottom,withlines=s.lines.show,withbottom=ps>2&&datapoints.format[2].y,withsteps=withlines&&s.lines.steps,fromgap=true,i=0,j=0,l,m;while(true){if(i>=points.length){break}l=newpoints.length;if(points[i]==null){for(m=0;m<ps;++m){newpoints.push(points[i+m])}i+=ps}else if(j>=otherpoints.length){if(!withlines){for(m=0;m<ps;++m){newpoints.push(points[i+m])}}i+=ps}else if(otherpoints[j]==null){for(m=0;m<ps;++m){newpoints.push(null)}fromgap=true;j+=otherps}else{px=points[i];py=points[i+1];qx=otherpoints[j];qy=otherpoints[j+1];bottom=0;if(px===qx){for(m=0;m<ps;++m){newpoints.push(points[i+m])}bottom=qy;i+=ps;j+=otherps}else if(px>qx){if(withlines&&i>0&&points[i-ps]!=null){intery=py+(points[i-ps+1]-py)*(qx-px)/(points[i-ps]-px);newpoints.push(qx);newpoints.push(intery);for(m=2;m<ps;++m){newpoints.push(points[i+m])}bottom=qy}j+=otherps}else{if(fromgap&&withlines){i+=ps;continue}for(m=0;m<ps;++m){newpoints.push(points[i+m])}if(withlines&&j>0&&otherpoints[j-otherps]!=null){bottom=qy+(otherpoints[j-otherps+1]-qy)*(px-qx)/(otherpoints[j-otherps]-qx)}i+=ps}fromgap=false;if(l!==newpoints.length&&withbottom){newpoints[l+2]=bottom}}if(withsteps&&l!==newpoints.length&&l>0&&newpoints[l]!==null&&newpoints[l]!==newpoints[l-ps]&&newpoints[l+1]!==newpoints[l-ps+1]){for(m=0;m<ps;++m){newpoints[l+ps+m]=newpoints[l+m]}newpoints[l+1]=newpoints[l-ps+1]}}datapoints.points=newpoints}plot.hooks.processDatapoints.push(computeFillBottoms)}$.plot.plugins.push({init:init,options:options,name:"fillbetween",version:"1.0"})})(jQuery); \ No newline at end of file
diff --git a/misc/flot/jquery.flot.image.js b/misc/flot/jquery.flot.image.js
new file mode 100644
index 0000000..625a035
--- /dev/null
+++ b/misc/flot/jquery.flot.image.js
@@ -0,0 +1,241 @@
+/* Flot plugin for plotting images.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The data syntax is [ [ image, x1, y1, x2, y2 ], ... ] where (x1, y1) and
+(x2, y2) are where you intend the two opposite corners of the image to end up
+in the plot. Image must be a fully loaded Javascript image (you can make one
+with new Image()). If the image is not complete, it's skipped when plotting.
+
+There are two helpers included for retrieving images. The easiest work the way
+that you put in URLs instead of images in the data, like this:
+
+ [ "myimage.png", 0, 0, 10, 10 ]
+
+Then call $.plot.image.loadData( data, options, callback ) where data and
+options are the same as you pass in to $.plot. This loads the images, replaces
+the URLs in the data with the corresponding images and calls "callback" when
+all images are loaded (or failed loading). In the callback, you can then call
+$.plot with the data set. See the included example.
+
+A more low-level helper, $.plot.image.load(urls, callback) is also included.
+Given a list of URLs, it calls callback with an object mapping from URL to
+Image object when all images are loaded or have failed loading.
+
+The plugin supports these options:
+
+ series: {
+ images: {
+ show: boolean
+ anchor: "corner" or "center"
+ alpha: [ 0, 1 ]
+ }
+ }
+
+They can be specified for a specific series:
+
+ $.plot( $("#placeholder"), [{
+ data: [ ... ],
+ images: { ... }
+ ])
+
+Note that because the data format is different from usual data points, you
+can't use images with anything else in a specific data series.
+
+Setting "anchor" to "center" causes the pixels in the image to be anchored at
+the corner pixel centers inside of at the pixel corners, effectively letting
+half a pixel stick out to each side in the plot.
+
+A possible future direction could be support for tiling for large images (like
+Google Maps).
+
+*/
+
+(function ($) {
+ var options = {
+ series: {
+ images: {
+ show: false,
+ alpha: 1,
+ anchor: "corner" // or "center"
+ }
+ }
+ };
+
+ $.plot.image = {};
+
+ $.plot.image.loadDataImages = function (series, options, callback) {
+ var urls = [], points = [];
+
+ var defaultShow = options.series.images.show;
+
+ $.each(series, function (i, s) {
+ if (!(defaultShow || s.images.show))
+ return;
+
+ if (s.data)
+ s = s.data;
+
+ $.each(s, function (i, p) {
+ if (typeof p[0] == "string") {
+ urls.push(p[0]);
+ points.push(p);
+ }
+ });
+ });
+
+ $.plot.image.load(urls, function (loadedImages) {
+ $.each(points, function (i, p) {
+ var url = p[0];
+ if (loadedImages[url])
+ p[0] = loadedImages[url];
+ });
+
+ callback();
+ });
+ }
+
+ $.plot.image.load = function (urls, callback) {
+ var missing = urls.length, loaded = {};
+ if (missing == 0)
+ callback({});
+
+ $.each(urls, function (i, url) {
+ var handler = function () {
+ --missing;
+
+ loaded[url] = this;
+
+ if (missing == 0)
+ callback(loaded);
+ };
+
+ $('<img />').load(handler).error(handler).attr('src', url);
+ });
+ };
+
+ function drawSeries(plot, ctx, series) {
+ var plotOffset = plot.getPlotOffset();
+
+ if (!series.images || !series.images.show)
+ return;
+
+ var points = series.datapoints.points,
+ ps = series.datapoints.pointsize;
+
+ for (var i = 0; i < points.length; i += ps) {
+ var img = points[i],
+ x1 = points[i + 1], y1 = points[i + 2],
+ x2 = points[i + 3], y2 = points[i + 4],
+ xaxis = series.xaxis, yaxis = series.yaxis,
+ tmp;
+
+ // actually we should check img.complete, but it
+ // appears to be a somewhat unreliable indicator in
+ // IE6 (false even after load event)
+ if (!img || img.width <= 0 || img.height <= 0)
+ continue;
+
+ if (x1 > x2) {
+ tmp = x2;
+ x2 = x1;
+ x1 = tmp;
+ }
+ if (y1 > y2) {
+ tmp = y2;
+ y2 = y1;
+ y1 = tmp;
+ }
+
+ // if the anchor is at the center of the pixel, expand the
+ // image by 1/2 pixel in each direction
+ if (series.images.anchor == "center") {
+ tmp = 0.5 * (x2-x1) / (img.width - 1);
+ x1 -= tmp;
+ x2 += tmp;
+ tmp = 0.5 * (y2-y1) / (img.height - 1);
+ y1 -= tmp;
+ y2 += tmp;
+ }
+
+ // clip
+ if (x1 == x2 || y1 == y2 ||
+ x1 >= xaxis.max || x2 <= xaxis.min ||
+ y1 >= yaxis.max || y2 <= yaxis.min)
+ continue;
+
+ var sx1 = 0, sy1 = 0, sx2 = img.width, sy2 = img.height;
+ if (x1 < xaxis.min) {
+ sx1 += (sx2 - sx1) * (xaxis.min - x1) / (x2 - x1);
+ x1 = xaxis.min;
+ }
+
+ if (x2 > xaxis.max) {
+ sx2 += (sx2 - sx1) * (xaxis.max - x2) / (x2 - x1);
+ x2 = xaxis.max;
+ }
+
+ if (y1 < yaxis.min) {
+ sy2 += (sy1 - sy2) * (yaxis.min - y1) / (y2 - y1);
+ y1 = yaxis.min;
+ }
+
+ if (y2 > yaxis.max) {
+ sy1 += (sy1 - sy2) * (yaxis.max - y2) / (y2 - y1);
+ y2 = yaxis.max;
+ }
+
+ x1 = xaxis.p2c(x1);
+ x2 = xaxis.p2c(x2);
+ y1 = yaxis.p2c(y1);
+ y2 = yaxis.p2c(y2);
+
+ // the transformation may have swapped us
+ if (x1 > x2) {
+ tmp = x2;
+ x2 = x1;
+ x1 = tmp;
+ }
+ if (y1 > y2) {
+ tmp = y2;
+ y2 = y1;
+ y1 = tmp;
+ }
+
+ tmp = ctx.globalAlpha;
+ ctx.globalAlpha *= series.images.alpha;
+ ctx.drawImage(img,
+ sx1, sy1, sx2 - sx1, sy2 - sy1,
+ x1 + plotOffset.left, y1 + plotOffset.top,
+ x2 - x1, y2 - y1);
+ ctx.globalAlpha = tmp;
+ }
+ }
+
+ function processRawData(plot, series, data, datapoints) {
+ if (!series.images.show)
+ return;
+
+ // format is Image, x1, y1, x2, y2 (opposite corners)
+ datapoints.format = [
+ { required: true },
+ { x: true, number: true, required: true },
+ { y: true, number: true, required: true },
+ { x: true, number: true, required: true },
+ { y: true, number: true, required: true }
+ ];
+ }
+
+ function init(plot) {
+ plot.hooks.processRawData.push(processRawData);
+ plot.hooks.drawSeries.push(drawSeries);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'image',
+ version: '1.1'
+ });
+})(jQuery);
diff --git a/misc/flot/jquery.flot.image.min.js b/misc/flot/jquery.flot.image.min.js
new file mode 100644
index 0000000..09df132
--- /dev/null
+++ b/misc/flot/jquery.flot.image.min.js
@@ -0,0 +1,7 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function($){var options={series:{images:{show:false,alpha:1,anchor:"corner"}}};$.plot.image={};$.plot.image.loadDataImages=function(series,options,callback){var urls=[],points=[];var defaultShow=options.series.images.show;$.each(series,function(i,s){if(!(defaultShow||s.images.show))return;if(s.data)s=s.data;$.each(s,function(i,p){if(typeof p[0]=="string"){urls.push(p[0]);points.push(p)}})});$.plot.image.load(urls,function(loadedImages){$.each(points,function(i,p){var url=p[0];if(loadedImages[url])p[0]=loadedImages[url]});callback()})};$.plot.image.load=function(urls,callback){var missing=urls.length,loaded={};if(missing==0)callback({});$.each(urls,function(i,url){var handler=function(){--missing;loaded[url]=this;if(missing==0)callback(loaded)};$("<img />").load(handler).error(handler).attr("src",url)})};function drawSeries(plot,ctx,series){var plotOffset=plot.getPlotOffset();if(!series.images||!series.images.show)return;var points=series.datapoints.points,ps=series.datapoints.pointsize;for(var i=0;i<points.length;i+=ps){var img=points[i],x1=points[i+1],y1=points[i+2],x2=points[i+3],y2=points[i+4],xaxis=series.xaxis,yaxis=series.yaxis,tmp;if(!img||img.width<=0||img.height<=0)continue;if(x1>x2){tmp=x2;x2=x1;x1=tmp}if(y1>y2){tmp=y2;y2=y1;y1=tmp}if(series.images.anchor=="center"){tmp=.5*(x2-x1)/(img.width-1);x1-=tmp;x2+=tmp;tmp=.5*(y2-y1)/(img.height-1);y1-=tmp;y2+=tmp}if(x1==x2||y1==y2||x1>=xaxis.max||x2<=xaxis.min||y1>=yaxis.max||y2<=yaxis.min)continue;var sx1=0,sy1=0,sx2=img.width,sy2=img.height;if(x1<xaxis.min){sx1+=(sx2-sx1)*(xaxis.min-x1)/(x2-x1);x1=xaxis.min}if(x2>xaxis.max){sx2+=(sx2-sx1)*(xaxis.max-x2)/(x2-x1);x2=xaxis.max}if(y1<yaxis.min){sy2+=(sy1-sy2)*(yaxis.min-y1)/(y2-y1);y1=yaxis.min}if(y2>yaxis.max){sy1+=(sy1-sy2)*(yaxis.max-y2)/(y2-y1);y2=yaxis.max}x1=xaxis.p2c(x1);x2=xaxis.p2c(x2);y1=yaxis.p2c(y1);y2=yaxis.p2c(y2);if(x1>x2){tmp=x2;x2=x1;x1=tmp}if(y1>y2){tmp=y2;y2=y1;y1=tmp}tmp=ctx.globalAlpha;ctx.globalAlpha*=series.images.alpha;ctx.drawImage(img,sx1,sy1,sx2-sx1,sy2-sy1,x1+plotOffset.left,y1+plotOffset.top,x2-x1,y2-y1);ctx.globalAlpha=tmp}}function processRawData(plot,series,data,datapoints){if(!series.images.show)return;datapoints.format=[{required:true},{x:true,number:true,required:true},{y:true,number:true,required:true},{x:true,number:true,required:true},{y:true,number:true,required:true}]}function init(plot){plot.hooks.processRawData.push(processRawData);plot.hooks.drawSeries.push(drawSeries)}$.plot.plugins.push({init:init,options:options,name:"image",version:"1.1"})})(jQuery); \ No newline at end of file
diff --git a/misc/flot/jquery.flot.js b/misc/flot/jquery.flot.js
new file mode 100644
index 0000000..39f3e4c
--- /dev/null
+++ b/misc/flot/jquery.flot.js
@@ -0,0 +1,3168 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+
+// first an inline dependency, jquery.colorhelpers.js, we inline it here
+// for convenience
+
+/* Plugin for jQuery for working with colors.
+ *
+ * Version 1.1.
+ *
+ * Inspiration from jQuery color animation plugin by John Resig.
+ *
+ * Released under the MIT license by Ole Laursen, October 2009.
+ *
+ * Examples:
+ *
+ * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
+ * var c = $.color.extract($("#mydiv"), 'background-color');
+ * console.log(c.r, c.g, c.b, c.a);
+ * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
+ *
+ * Note that .scale() and .add() return the same modified object
+ * instead of making a new one.
+ *
+ * V. 1.1: Fix error handling so e.g. parsing an empty string does
+ * produce a color rather than just crashing.
+ */
+(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i<c.length;++i)o[c.charAt(i)]+=d;return o.normalize()};o.scale=function(c,f){for(var i=0;i<c.length;++i)o[c.charAt(i)]*=f;return o.normalize()};o.toString=function(){if(o.a>=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return value<min?min:value>max?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);
+
+// the actual Flot code
+(function($) {
+
+ // Cache the prototype hasOwnProperty for faster access
+
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
+
+ // A shim to provide 'detach' to jQuery versions prior to 1.4. Using a DOM
+ // operation produces the same effect as detach, i.e. removing the element
+ // without touching its jQuery data.
+
+ // Do not merge this into Flot 0.9, since it requires jQuery 1.4.4+.
+
+ if (!$.fn.detach) {
+ $.fn.detach = function() {
+ return this.each(function() {
+ if (this.parentNode) {
+ this.parentNode.removeChild( this );
+ }
+ });
+ };
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+ // The Canvas object is a wrapper around an HTML5 <canvas> tag.
+ //
+ // @constructor
+ // @param {string} cls List of classes to apply to the canvas.
+ // @param {element} container Element onto which to append the canvas.
+ //
+ // Requiring a container is a little iffy, but unfortunately canvas
+ // operations don't work unless the canvas is attached to the DOM.
+
+ function Canvas(cls, container) {
+
+ var element = container.children("." + cls)[0];
+
+ if (element == null) {
+
+ element = document.createElement("canvas");
+ element.className = cls;
+
+ $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 })
+ .appendTo(container);
+
+ // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas
+
+ if (!element.getContext) {
+ if (window.G_vmlCanvasManager) {
+ element = window.G_vmlCanvasManager.initElement(element);
+ } else {
+ throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.");
+ }
+ }
+ }
+
+ this.element = element;
+
+ var context = this.context = element.getContext("2d");
+
+ // Determine the screen's ratio of physical to device-independent
+ // pixels. This is the ratio between the canvas width that the browser
+ // advertises and the number of pixels actually present in that space.
+
+ // The iPhone 4, for example, has a device-independent width of 320px,
+ // but its screen is actually 640px wide. It therefore has a pixel
+ // ratio of 2, while most normal devices have a ratio of 1.
+
+ var devicePixelRatio = window.devicePixelRatio || 1,
+ backingStoreRatio =
+ context.webkitBackingStorePixelRatio ||
+ context.mozBackingStorePixelRatio ||
+ context.msBackingStorePixelRatio ||
+ context.oBackingStorePixelRatio ||
+ context.backingStorePixelRatio || 1;
+
+ this.pixelRatio = devicePixelRatio / backingStoreRatio;
+
+ // Size the canvas to match the internal dimensions of its container
+
+ this.resize(container.width(), container.height());
+
+ // Collection of HTML div layers for text overlaid onto the canvas
+
+ this.textContainer = null;
+ this.text = {};
+
+ // Cache of text fragments and metrics, so we can avoid expensively
+ // re-calculating them when the plot is re-rendered in a loop.
+
+ this._textCache = {};
+ }
+
+ // Resizes the canvas to the given dimensions.
+ //
+ // @param {number} width New width of the canvas, in pixels.
+ // @param {number} width New height of the canvas, in pixels.
+
+ Canvas.prototype.resize = function(width, height) {
+
+ if (width <= 0 || height <= 0) {
+ throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height);
+ }
+
+ var element = this.element,
+ context = this.context,
+ pixelRatio = this.pixelRatio;
+
+ // Resize the canvas, increasing its density based on the display's
+ // pixel ratio; basically giving it more pixels without increasing the
+ // size of its element, to take advantage of the fact that retina
+ // displays have that many more pixels in the same advertised space.
+
+ // Resizing should reset the state (excanvas seems to be buggy though)
+
+ if (this.width != width) {
+ element.width = width * pixelRatio;
+ element.style.width = width + "px";
+ this.width = width;
+ }
+
+ if (this.height != height) {
+ element.height = height * pixelRatio;
+ element.style.height = height + "px";
+ this.height = height;
+ }
+
+ // Save the context, so we can reset in case we get replotted. The
+ // restore ensure that we're really back at the initial state, and
+ // should be safe even if we haven't saved the initial state yet.
+
+ context.restore();
+ context.save();
+
+ // Scale the coordinate space to match the display density; so even though we
+ // may have twice as many pixels, we still want lines and other drawing to
+ // appear at the same size; the extra pixels will just make them crisper.
+
+ context.scale(pixelRatio, pixelRatio);
+ };
+
+ // Clears the entire canvas area, not including any overlaid HTML text
+
+ Canvas.prototype.clear = function() {
+ this.context.clearRect(0, 0, this.width, this.height);
+ };
+
+ // Finishes rendering the canvas, including managing the text overlay.
+
+ Canvas.prototype.render = function() {
+
+ var cache = this._textCache;
+
+ // For each text layer, add elements marked as active that haven't
+ // already been rendered, and remove those that are no longer active.
+
+ for (var layerKey in cache) {
+ if (hasOwnProperty.call(cache, layerKey)) {
+
+ var layer = this.getTextLayer(layerKey),
+ layerCache = cache[layerKey];
+
+ layer.hide();
+
+ for (var styleKey in layerCache) {
+ if (hasOwnProperty.call(layerCache, styleKey)) {
+ var styleCache = layerCache[styleKey];
+ for (var key in styleCache) {
+ if (hasOwnProperty.call(styleCache, key)) {
+
+ var positions = styleCache[key].positions;
+
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.active) {
+ if (!position.rendered) {
+ layer.append(position.element);
+ position.rendered = true;
+ }
+ } else {
+ positions.splice(i--, 1);
+ if (position.rendered) {
+ position.element.detach();
+ }
+ }
+ }
+
+ if (positions.length == 0) {
+ delete styleCache[key];
+ }
+ }
+ }
+ }
+ }
+
+ layer.show();
+ }
+ }
+ };
+
+ // Creates (if necessary) and returns the text overlay container.
+ //
+ // @param {string} classes String of space-separated CSS classes used to
+ // uniquely identify the text layer.
+ // @return {object} The jQuery-wrapped text-layer div.
+
+ Canvas.prototype.getTextLayer = function(classes) {
+
+ var layer = this.text[classes];
+
+ // Create the text layer if it doesn't exist
+
+ if (layer == null) {
+
+ // Create the text layer container, if it doesn't exist
+
+ if (this.textContainer == null) {
+ this.textContainer = $("<div class='flot-text'></div>")
+ .css({
+ position: "absolute",
+ top: 0,
+ left: 0,
+ bottom: 0,
+ right: 0,
+ 'font-size': "smaller",
+ color: "#545454"
+ })
+ .insertAfter(this.element);
+ }
+
+ layer = this.text[classes] = $("<div></div>")
+ .addClass(classes)
+ .css({
+ position: "absolute",
+ top: 0,
+ left: 0,
+ bottom: 0,
+ right: 0
+ })
+ .appendTo(this.textContainer);
+ }
+
+ return layer;
+ };
+
+ // Creates (if necessary) and returns a text info object.
+ //
+ // The object looks like this:
+ //
+ // {
+ // width: Width of the text's wrapper div.
+ // height: Height of the text's wrapper div.
+ // element: The jQuery-wrapped HTML div containing the text.
+ // positions: Array of positions at which this text is drawn.
+ // }
+ //
+ // The positions array contains objects that look like this:
+ //
+ // {
+ // active: Flag indicating whether the text should be visible.
+ // rendered: Flag indicating whether the text is currently visible.
+ // element: The jQuery-wrapped HTML div containing the text.
+ // x: X coordinate at which to draw the text.
+ // y: Y coordinate at which to draw the text.
+ // }
+ //
+ // Each position after the first receives a clone of the original element.
+ //
+ // The idea is that that the width, height, and general 'identity' of the
+ // text is constant no matter where it is placed; the placements are a
+ // secondary property.
+ //
+ // Canvas maintains a cache of recently-used text info objects; getTextInfo
+ // either returns the cached element or creates a new entry.
+ //
+ // @param {string} layer A string of space-separated CSS classes uniquely
+ // identifying the layer containing this text.
+ // @param {string} text Text string to retrieve info for.
+ // @param {(string|object)=} font Either a string of space-separated CSS
+ // classes or a font-spec object, defining the text's font and style.
+ // @param {number=} angle Angle at which to rotate the text, in degrees.
+ // Angle is currently unused, it will be implemented in the future.
+ // @param {number=} width Maximum width of the text before it wraps.
+ // @return {object} a text info object.
+
+ Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {
+
+ var textStyle, layerCache, styleCache, info;
+
+ // Cast the value to a string, in case we were given a number or such
+
+ text = "" + text;
+
+ // If the font is a font-spec object, generate a CSS font definition
+
+ if (typeof font === "object") {
+ textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family;
+ } else {
+ textStyle = font;
+ }
+
+ // Retrieve (or create) the cache for the text's layer and styles
+
+ layerCache = this._textCache[layer];
+
+ if (layerCache == null) {
+ layerCache = this._textCache[layer] = {};
+ }
+
+ styleCache = layerCache[textStyle];
+
+ if (styleCache == null) {
+ styleCache = layerCache[textStyle] = {};
+ }
+
+ info = styleCache[text];
+
+ // If we can't find a matching element in our cache, create a new one
+
+ if (info == null) {
+
+ var element = $("<div></div>").html(text)
+ .css({
+ position: "absolute",
+ 'max-width': width,
+ top: -9999
+ })
+ .appendTo(this.getTextLayer(layer));
+
+ if (typeof font === "object") {
+ element.css({
+ font: textStyle,
+ color: font.color
+ });
+ } else if (typeof font === "string") {
+ element.addClass(font);
+ }
+
+ info = styleCache[text] = {
+ width: element.outerWidth(true),
+ height: element.outerHeight(true),
+ element: element,
+ positions: []
+ };
+
+ element.detach();
+ }
+
+ return info;
+ };
+
+ // Adds a text string to the canvas text overlay.
+ //
+ // The text isn't drawn immediately; it is marked as rendering, which will
+ // result in its addition to the canvas on the next render pass.
+ //
+ // @param {string} layer A string of space-separated CSS classes uniquely
+ // identifying the layer containing this text.
+ // @param {number} x X coordinate at which to draw the text.
+ // @param {number} y Y coordinate at which to draw the text.
+ // @param {string} text Text string to draw.
+ // @param {(string|object)=} font Either a string of space-separated CSS
+ // classes or a font-spec object, defining the text's font and style.
+ // @param {number=} angle Angle at which to rotate the text, in degrees.
+ // Angle is currently unused, it will be implemented in the future.
+ // @param {number=} width Maximum width of the text before it wraps.
+ // @param {string=} halign Horizontal alignment of the text; either "left",
+ // "center" or "right".
+ // @param {string=} valign Vertical alignment of the text; either "top",
+ // "middle" or "bottom".
+
+ Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) {
+
+ var info = this.getTextInfo(layer, text, font, angle, width),
+ positions = info.positions;
+
+ // Tweak the div's position to match the text's alignment
+
+ if (halign == "center") {
+ x -= info.width / 2;
+ } else if (halign == "right") {
+ x -= info.width;
+ }
+
+ if (valign == "middle") {
+ y -= info.height / 2;
+ } else if (valign == "bottom") {
+ y -= info.height;
+ }
+
+ // Determine whether this text already exists at this position.
+ // If so, mark it for inclusion in the next render pass.
+
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.x == x && position.y == y) {
+ position.active = true;
+ return;
+ }
+ }
+
+ // If the text doesn't exist at this position, create a new entry
+
+ // For the very first position we'll re-use the original element,
+ // while for subsequent ones we'll clone it.
+
+ position = {
+ active: true,
+ rendered: false,
+ element: positions.length ? info.element.clone() : info.element,
+ x: x,
+ y: y
+ };
+
+ positions.push(position);
+
+ // Move the element to its final position within the container
+
+ position.element.css({
+ top: Math.round(y),
+ left: Math.round(x),
+ 'text-align': halign // In case the text wraps
+ });
+ };
+
+ // Removes one or more text strings from the canvas text overlay.
+ //
+ // If no parameters are given, all text within the layer is removed.
+ //
+ // Note that the text is not immediately removed; it is simply marked as
+ // inactive, which will result in its removal on the next render pass.
+ // This avoids the performance penalty for 'clear and redraw' behavior,
+ // where we potentially get rid of all text on a layer, but will likely
+ // add back most or all of it later, as when redrawing axes, for example.
+ //
+ // @param {string} layer A string of space-separated CSS classes uniquely
+ // identifying the layer containing this text.
+ // @param {number=} x X coordinate of the text.
+ // @param {number=} y Y coordinate of the text.
+ // @param {string=} text Text string to remove.
+ // @param {(string|object)=} font Either a string of space-separated CSS
+ // classes or a font-spec object, defining the text's font and style.
+ // @param {number=} angle Angle at which the text is rotated, in degrees.
+ // Angle is currently unused, it will be implemented in the future.
+
+ Canvas.prototype.removeText = function(layer, x, y, text, font, angle) {
+ if (text == null) {
+ var layerCache = this._textCache[layer];
+ if (layerCache != null) {
+ for (var styleKey in layerCache) {
+ if (hasOwnProperty.call(layerCache, styleKey)) {
+ var styleCache = layerCache[styleKey];
+ for (var key in styleCache) {
+ if (hasOwnProperty.call(styleCache, key)) {
+ var positions = styleCache[key].positions;
+ for (var i = 0, position; position = positions[i]; i++) {
+ position.active = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ } else {
+ var positions = this.getTextInfo(layer, text, font, angle).positions;
+ for (var i = 0, position; position = positions[i]; i++) {
+ if (position.x == x && position.y == y) {
+ position.active = false;
+ }
+ }
+ }
+ };
+
+ ///////////////////////////////////////////////////////////////////////////
+ // The top-level container for the entire plot.
+
+ function Plot(placeholder, data_, options_, plugins) {
+ // data is on the form:
+ // [ series1, series2 ... ]
+ // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
+ // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
+
+ var series = [],
+ options = {
+ // the color theme used for graphs
+ colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
+ legend: {
+ show: true,
+ noColumns: 1, // number of colums in legend table
+ labelFormatter: null, // fn: string -> string
+ labelBoxBorderColor: "#ccc", // border color for the little label boxes
+ container: null, // container (as jQuery object) to put legend in, null means default on top of graph
+ position: "ne", // position of default legend container within plot
+ margin: 5, // distance from grid edge to default legend container within plot
+ backgroundColor: null, // null means auto-detect
+ backgroundOpacity: 0.85, // set to 0 to avoid background
+ sorted: null // default to no legend sorting
+ },
+ xaxis: {
+ show: null, // null = auto-detect, true = always, false = never
+ position: "bottom", // or "top"
+ mode: null, // null or "time"
+ font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" }
+ color: null, // base color, labels, ticks
+ tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
+ transform: null, // null or f: number -> number to transform axis
+ inverseTransform: null, // if transform is set, this should be the inverse function
+ min: null, // min. value to show, null means set automatically
+ max: null, // max. value to show, null means set automatically
+ autoscaleMargin: null, // margin in % to add if auto-setting min/max
+ ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
+ tickFormatter: null, // fn: number -> string
+ labelWidth: null, // size of tick labels in pixels
+ labelHeight: null,
+ reserveSpace: null, // whether to reserve space even if axis isn't shown
+ tickLength: null, // size in pixels of ticks, or "full" for whole line
+ alignTicksWithAxis: null, // axis number or null for no sync
+ tickDecimals: null, // no. of decimals, null means auto
+ tickSize: null, // number or [number, "unit"]
+ minTickSize: null // number or [number, "unit"]
+ },
+ yaxis: {
+ autoscaleMargin: 0.02,
+ position: "left" // or "right"
+ },
+ xaxes: [],
+ yaxes: [],
+ series: {
+ points: {
+ show: false,
+ radius: 3,
+ lineWidth: 2, // in pixels
+ fill: true,
+ fillColor: "#ffffff",
+ symbol: "circle" // or callback
+ },
+ lines: {
+ // we don't put in show: false so we can see
+ // whether lines were actively disabled
+ lineWidth: 2, // in pixels
+ fill: false,
+ fillColor: null,
+ steps: false
+ // Omit 'zero', so we can later default its value to
+ // match that of the 'fill' option.
+ },
+ bars: {
+ show: false,
+ lineWidth: 2, // in pixels
+ barWidth: 1, // in units of the x axis
+ fill: true,
+ fillColor: null,
+ align: "left", // "left", "right", or "center"
+ horizontal: false,
+ zero: true
+ },
+ shadowSize: 3,
+ highlightColor: null
+ },
+ grid: {
+ show: true,
+ aboveData: false,
+ color: "#545454", // primary color used for outline and labels
+ backgroundColor: null, // null for transparent, else color
+ borderColor: null, // set if different from the grid color
+ tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
+ margin: 0, // distance from the canvas edge to the grid
+ labelMargin: 5, // in pixels
+ axisMargin: 8, // in pixels
+ borderWidth: 2, // in pixels
+ minBorderMargin: null, // in pixels, null means taken from points radius
+ markings: null, // array of ranges or fn: axes -> array of ranges
+ markingsColor: "#f4f4f4",
+ markingsLineWidth: 2,
+ // interactive stuff
+ clickable: false,
+ hoverable: false,
+ autoHighlight: true, // highlight in case mouse is near
+ mouseActiveRadius: 10 // how far the mouse can be away to activate an item
+ },
+ interaction: {
+ redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow
+ },
+ hooks: {}
+ },
+ surface = null, // the canvas for the plot itself
+ overlay = null, // canvas for interactive stuff on top of plot
+ eventHolder = null, // jQuery object that events should be bound to
+ ctx = null, octx = null,
+ xaxes = [], yaxes = [],
+ plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
+ plotWidth = 0, plotHeight = 0,
+ hooks = {
+ processOptions: [],
+ processRawData: [],
+ processDatapoints: [],
+ processOffset: [],
+ drawBackground: [],
+ drawSeries: [],
+ draw: [],
+ bindEvents: [],
+ drawOverlay: [],
+ shutdown: []
+ },
+ plot = this;
+
+ // public functions
+ plot.setData = setData;
+ plot.setupGrid = setupGrid;
+ plot.draw = draw;
+ plot.getPlaceholder = function() { return placeholder; };
+ plot.getCanvas = function() { return surface.element; };
+ plot.getPlotOffset = function() { return plotOffset; };
+ plot.width = function () { return plotWidth; };
+ plot.height = function () { return plotHeight; };
+ plot.offset = function () {
+ var o = eventHolder.offset();
+ o.left += plotOffset.left;
+ o.top += plotOffset.top;
+ return o;
+ };
+ plot.getData = function () { return series; };
+ plot.getAxes = function () {
+ var res = {}, i;
+ $.each(xaxes.concat(yaxes), function (_, axis) {
+ if (axis)
+ res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis;
+ });
+ return res;
+ };
+ plot.getXAxes = function () { return xaxes; };
+ plot.getYAxes = function () { return yaxes; };
+ plot.c2p = canvasToAxisCoords;
+ plot.p2c = axisToCanvasCoords;
+ plot.getOptions = function () { return options; };
+ plot.highlight = highlight;
+ plot.unhighlight = unhighlight;
+ plot.triggerRedrawOverlay = triggerRedrawOverlay;
+ plot.pointOffset = function(point) {
+ return {
+ left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10),
+ top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10)
+ };
+ };
+ plot.shutdown = shutdown;
+ plot.destroy = function () {
+ shutdown();
+ placeholder.removeData("plot").empty();
+
+ series = [];
+ options = null;
+ surface = null;
+ overlay = null;
+ eventHolder = null;
+ ctx = null;
+ octx = null;
+ xaxes = [];
+ yaxes = [];
+ hooks = null;
+ highlights = [];
+ plot = null;
+ };
+ plot.resize = function () {
+ var width = placeholder.width(),
+ height = placeholder.height();
+ surface.resize(width, height);
+ overlay.resize(width, height);
+ };
+
+ // public attributes
+ plot.hooks = hooks;
+
+ // initialize
+ initPlugins(plot);
+ parseOptions(options_);
+ setupCanvases();
+ setData(data_);
+ setupGrid();
+ draw();
+ bindEvents();
+
+
+ function executeHooks(hook, args) {
+ args = [plot].concat(args);
+ for (var i = 0; i < hook.length; ++i)
+ hook[i].apply(this, args);
+ }
+
+ function initPlugins() {
+
+ // References to key classes, allowing plugins to modify them
+
+ var classes = {
+ Canvas: Canvas
+ };
+
+ for (var i = 0; i < plugins.length; ++i) {
+ var p = plugins[i];
+ p.init(plot, classes);
+ if (p.options)
+ $.extend(true, options, p.options);
+ }
+ }
+
+ function parseOptions(opts) {
+
+ $.extend(true, options, opts);
+
+ // $.extend merges arrays, rather than replacing them. When less
+ // colors are provided than the size of the default palette, we
+ // end up with those colors plus the remaining defaults, which is
+ // not expected behavior; avoid it by replacing them here.
+
+ if (opts && opts.colors) {
+ options.colors = opts.colors;
+ }
+
+ if (options.xaxis.color == null)
+ options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
+ if (options.yaxis.color == null)
+ options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
+
+ if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility
+ options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color;
+ if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility
+ options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color;
+
+ if (options.grid.borderColor == null)
+ options.grid.borderColor = options.grid.color;
+ if (options.grid.tickColor == null)
+ options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
+
+ // Fill in defaults for axis options, including any unspecified
+ // font-spec fields, if a font-spec was provided.
+
+ // If no x/y axis options were provided, create one of each anyway,
+ // since the rest of the code assumes that they exist.
+
+ var i, axisOptions, axisCount,
+ fontSize = placeholder.css("font-size"),
+ fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13,
+ fontDefaults = {
+ style: placeholder.css("font-style"),
+ size: Math.round(0.8 * fontSizeDefault),
+ variant: placeholder.css("font-variant"),
+ weight: placeholder.css("font-weight"),
+ family: placeholder.css("font-family")
+ };
+
+ axisCount = options.xaxes.length || 1;
+ for (i = 0; i < axisCount; ++i) {
+
+ axisOptions = options.xaxes[i];
+ if (axisOptions && !axisOptions.tickColor) {
+ axisOptions.tickColor = axisOptions.color;
+ }
+
+ axisOptions = $.extend(true, {}, options.xaxis, axisOptions);
+ options.xaxes[i] = axisOptions;
+
+ if (axisOptions.font) {
+ axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
+ if (!axisOptions.font.color) {
+ axisOptions.font.color = axisOptions.color;
+ }
+ if (!axisOptions.font.lineHeight) {
+ axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
+ }
+ }
+ }
+
+ axisCount = options.yaxes.length || 1;
+ for (i = 0; i < axisCount; ++i) {
+
+ axisOptions = options.yaxes[i];
+ if (axisOptions && !axisOptions.tickColor) {
+ axisOptions.tickColor = axisOptions.color;
+ }
+
+ axisOptions = $.extend(true, {}, options.yaxis, axisOptions);
+ options.yaxes[i] = axisOptions;
+
+ if (axisOptions.font) {
+ axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
+ if (!axisOptions.font.color) {
+ axisOptions.font.color = axisOptions.color;
+ }
+ if (!axisOptions.font.lineHeight) {
+ axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
+ }
+ }
+ }
+
+ // backwards compatibility, to be removed in future
+ if (options.xaxis.noTicks && options.xaxis.ticks == null)
+ options.xaxis.ticks = options.xaxis.noTicks;
+ if (options.yaxis.noTicks && options.yaxis.ticks == null)
+ options.yaxis.ticks = options.yaxis.noTicks;
+ if (options.x2axis) {
+ options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
+ options.xaxes[1].position = "top";
+ // Override the inherit to allow the axis to auto-scale
+ if (options.x2axis.min == null) {
+ options.xaxes[1].min = null;
+ }
+ if (options.x2axis.max == null) {
+ options.xaxes[1].max = null;
+ }
+ }
+ if (options.y2axis) {
+ options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
+ options.yaxes[1].position = "right";
+ // Override the inherit to allow the axis to auto-scale
+ if (options.y2axis.min == null) {
+ options.yaxes[1].min = null;
+ }
+ if (options.y2axis.max == null) {
+ options.yaxes[1].max = null;
+ }
+ }
+ if (options.grid.coloredAreas)
+ options.grid.markings = options.grid.coloredAreas;
+ if (options.grid.coloredAreasColor)
+ options.grid.markingsColor = options.grid.coloredAreasColor;
+ if (options.lines)
+ $.extend(true, options.series.lines, options.lines);
+ if (options.points)
+ $.extend(true, options.series.points, options.points);
+ if (options.bars)
+ $.extend(true, options.series.bars, options.bars);
+ if (options.shadowSize != null)
+ options.series.shadowSize = options.shadowSize;
+ if (options.highlightColor != null)
+ options.series.highlightColor = options.highlightColor;
+
+ // save options on axes for future reference
+ for (i = 0; i < options.xaxes.length; ++i)
+ getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
+ for (i = 0; i < options.yaxes.length; ++i)
+ getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
+
+ // add hooks from options
+ for (var n in hooks)
+ if (options.hooks[n] && options.hooks[n].length)
+ hooks[n] = hooks[n].concat(options.hooks[n]);
+
+ executeHooks(hooks.processOptions, [options]);
+ }
+
+ function setData(d) {
+ series = parseData(d);
+ fillInSeriesOptions();
+ processData();
+ }
+
+ function parseData(d) {
+ var res = [];
+ for (var i = 0; i < d.length; ++i) {
+ var s = $.extend(true, {}, options.series);
+
+ if (d[i].data != null) {
+ s.data = d[i].data; // move the data instead of deep-copy
+ delete d[i].data;
+
+ $.extend(true, s, d[i]);
+
+ d[i].data = s.data;
+ }
+ else
+ s.data = d[i];
+ res.push(s);
+ }
+
+ return res;
+ }
+
+ function axisNumber(obj, coord) {
+ var a = obj[coord + "axis"];
+ if (typeof a == "object") // if we got a real axis, extract number
+ a = a.n;
+ if (typeof a != "number")
+ a = 1; // default to first axis
+ return a;
+ }
+
+ function allAxes() {
+ // return flat array without annoying null entries
+ return $.grep(xaxes.concat(yaxes), function (a) { return a; });
+ }
+
+ function canvasToAxisCoords(pos) {
+ // return an object with x/y corresponding to all used axes
+ var res = {}, i, axis;
+ for (i = 0; i < xaxes.length; ++i) {
+ axis = xaxes[i];
+ if (axis && axis.used)
+ res["x" + axis.n] = axis.c2p(pos.left);
+ }
+
+ for (i = 0; i < yaxes.length; ++i) {
+ axis = yaxes[i];
+ if (axis && axis.used)
+ res["y" + axis.n] = axis.c2p(pos.top);
+ }
+
+ if (res.x1 !== undefined)
+ res.x = res.x1;
+ if (res.y1 !== undefined)
+ res.y = res.y1;
+
+ return res;
+ }
+
+ function axisToCanvasCoords(pos) {
+ // get canvas coords from the first pair of x/y found in pos
+ var res = {}, i, axis, key;
+
+ for (i = 0; i < xaxes.length; ++i) {
+ axis = xaxes[i];
+ if (axis && axis.used) {
+ key = "x" + axis.n;
+ if (pos[key] == null && axis.n == 1)
+ key = "x";
+
+ if (pos[key] != null) {
+ res.left = axis.p2c(pos[key]);
+ break;
+ }
+ }
+ }
+
+ for (i = 0; i < yaxes.length; ++i) {
+ axis = yaxes[i];
+ if (axis && axis.used) {
+ key = "y" + axis.n;
+ if (pos[key] == null && axis.n == 1)
+ key = "y";
+
+ if (pos[key] != null) {
+ res.top = axis.p2c(pos[key]);
+ break;
+ }
+ }
+ }
+
+ return res;
+ }
+
+ function getOrCreateAxis(axes, number) {
+ if (!axes[number - 1])
+ axes[number - 1] = {
+ n: number, // save the number for future reference
+ direction: axes == xaxes ? "x" : "y",
+ options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
+ };
+
+ return axes[number - 1];
+ }
+
+ function fillInSeriesOptions() {
+
+ var neededColors = series.length, maxIndex = -1, i;
+
+ // Subtract the number of series that already have fixed colors or
+ // color indexes from the number that we still need to generate.
+
+ for (i = 0; i < series.length; ++i) {
+ var sc = series[i].color;
+ if (sc != null) {
+ neededColors--;
+ if (typeof sc == "number" && sc > maxIndex) {
+ maxIndex = sc;
+ }
+ }
+ }
+
+ // If any of the series have fixed color indexes, then we need to
+ // generate at least as many colors as the highest index.
+
+ if (neededColors <= maxIndex) {
+ neededColors = maxIndex + 1;
+ }
+
+ // Generate all the colors, using first the option colors and then
+ // variations on those colors once they're exhausted.
+
+ var c, colors = [], colorPool = options.colors,
+ colorPoolSize = colorPool.length, variation = 0;
+
+ for (i = 0; i < neededColors; i++) {
+
+ c = $.color.parse(colorPool[i % colorPoolSize] || "#666");
+
+ // Each time we exhaust the colors in the pool we adjust
+ // a scaling factor used to produce more variations on
+ // those colors. The factor alternates negative/positive
+ // to produce lighter/darker colors.
+
+ // Reset the variation after every few cycles, or else
+ // it will end up producing only white or black colors.
+
+ if (i % colorPoolSize == 0 && i) {
+ if (variation >= 0) {
+ if (variation < 0.5) {
+ variation = -variation - 0.2;
+ } else variation = 0;
+ } else variation = -variation;
+ }
+
+ colors[i] = c.scale('rgb', 1 + variation);
+ }
+
+ // Finalize the series options, filling in their colors
+
+ var colori = 0, s;
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+
+ // assign colors
+ if (s.color == null) {
+ s.color = colors[colori].toString();
+ ++colori;
+ }
+ else if (typeof s.color == "number")
+ s.color = colors[s.color].toString();
+
+ // turn on lines automatically in case nothing is set
+ if (s.lines.show == null) {
+ var v, show = true;
+ for (v in s)
+ if (s[v] && s[v].show) {
+ show = false;
+ break;
+ }
+ if (show)
+ s.lines.show = true;
+ }
+
+ // If nothing was provided for lines.zero, default it to match
+ // lines.fill, since areas by default should extend to zero.
+
+ if (s.lines.zero == null) {
+ s.lines.zero = !!s.lines.fill;
+ }
+
+ // setup axes
+ s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
+ s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"));
+ }
+ }
+
+ function processData() {
+ var topSentry = Number.POSITIVE_INFINITY,
+ bottomSentry = Number.NEGATIVE_INFINITY,
+ fakeInfinity = Number.MAX_VALUE,
+ i, j, k, m, length,
+ s, points, ps, x, y, axis, val, f, p,
+ data, format;
+
+ function updateAxis(axis, min, max) {
+ if (min < axis.datamin && min != -fakeInfinity)
+ axis.datamin = min;
+ if (max > axis.datamax && max != fakeInfinity)
+ axis.datamax = max;
+ }
+
+ $.each(allAxes(), function (_, axis) {
+ // init axis
+ axis.datamin = topSentry;
+ axis.datamax = bottomSentry;
+ axis.used = false;
+ });
+
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+ s.datapoints = { points: [] };
+
+ executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
+ }
+
+ // first pass: clean and copy data
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+
+ data = s.data;
+ format = s.datapoints.format;
+
+ if (!format) {
+ format = [];
+ // find out how to copy
+ format.push({ x: true, number: true, required: true });
+ format.push({ y: true, number: true, required: true });
+
+ if (s.bars.show || (s.lines.show && s.lines.fill)) {
+ var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
+ format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale });
+ if (s.bars.horizontal) {
+ delete format[format.length - 1].y;
+ format[format.length - 1].x = true;
+ }
+ }
+
+ s.datapoints.format = format;
+ }
+
+ if (s.datapoints.pointsize != null)
+ continue; // already filled in
+
+ s.datapoints.pointsize = format.length;
+
+ ps = s.datapoints.pointsize;
+ points = s.datapoints.points;
+
+ var insertSteps = s.lines.show && s.lines.steps;
+ s.xaxis.used = s.yaxis.used = true;
+
+ for (j = k = 0; j < data.length; ++j, k += ps) {
+ p = data[j];
+
+ var nullify = p == null;
+ if (!nullify) {
+ for (m = 0; m < ps; ++m) {
+ val = p[m];
+ f = format[m];
+
+ if (f) {
+ if (f.number && val != null) {
+ val = +val; // convert to number
+ if (isNaN(val))
+ val = null;
+ else if (val == Infinity)
+ val = fakeInfinity;
+ else if (val == -Infinity)
+ val = -fakeInfinity;
+ }
+
+ if (val == null) {
+ if (f.required)
+ nullify = true;
+
+ if (f.defaultValue != null)
+ val = f.defaultValue;
+ }
+ }
+
+ points[k + m] = val;
+ }
+ }
+
+ if (nullify) {
+ for (m = 0; m < ps; ++m) {
+ val = points[k + m];
+ if (val != null) {
+ f = format[m];
+ // extract min/max info
+ if (f.autoscale !== false) {
+ if (f.x) {
+ updateAxis(s.xaxis, val, val);
+ }
+ if (f.y) {
+ updateAxis(s.yaxis, val, val);
+ }
+ }
+ }
+ points[k + m] = null;
+ }
+ }
+ else {
+ // a little bit of line specific stuff that
+ // perhaps shouldn't be here, but lacking
+ // better means...
+ if (insertSteps && k > 0
+ && points[k - ps] != null
+ && points[k - ps] != points[k]
+ && points[k - ps + 1] != points[k + 1]) {
+ // copy the point to make room for a middle point
+ for (m = 0; m < ps; ++m)
+ points[k + ps + m] = points[k + m];
+
+ // middle point has same y
+ points[k + 1] = points[k - ps + 1];
+
+ // we've added a point, better reflect that
+ k += ps;
+ }
+ }
+ }
+ }
+
+ // give the hooks a chance to run
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+
+ executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
+ }
+
+ // second pass: find datamax/datamin for auto-scaling
+ for (i = 0; i < series.length; ++i) {
+ s = series[i];
+ points = s.datapoints.points;
+ ps = s.datapoints.pointsize;
+ format = s.datapoints.format;
+
+ var xmin = topSentry, ymin = topSentry,
+ xmax = bottomSentry, ymax = bottomSentry;
+
+ for (j = 0; j < points.length; j += ps) {
+ if (points[j] == null)
+ continue;
+
+ for (m = 0; m < ps; ++m) {
+ val = points[j + m];
+ f = format[m];
+ if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity)
+ continue;
+
+ if (f.x) {
+ if (val < xmin)
+ xmin = val;
+ if (val > xmax)
+ xmax = val;
+ }
+ if (f.y) {
+ if (val < ymin)
+ ymin = val;
+ if (val > ymax)
+ ymax = val;
+ }
+ }
+ }
+
+ if (s.bars.show) {
+ // make sure we got room for the bar on the dancing floor
+ var delta;
+
+ switch (s.bars.align) {
+ case "left":
+ delta = 0;
+ break;
+ case "right":
+ delta = -s.bars.barWidth;
+ break;
+ default:
+ delta = -s.bars.barWidth / 2;
+ }
+
+ if (s.bars.horizontal) {
+ ymin += delta;
+ ymax += delta + s.bars.barWidth;
+ }
+ else {
+ xmin += delta;
+ xmax += delta + s.bars.barWidth;
+ }
+ }
+
+ updateAxis(s.xaxis, xmin, xmax);
+ updateAxis(s.yaxis, ymin, ymax);
+ }
+
+ $.each(allAxes(), function (_, axis) {
+ if (axis.datamin == topSentry)
+ axis.datamin = null;
+ if (axis.datamax == bottomSentry)
+ axis.datamax = null;
+ });
+ }
+
+ function setupCanvases() {
+
+ // Make sure the placeholder is clear of everything except canvases
+ // from a previous plot in this container that we'll try to re-use.
+
+ placeholder.css("padding", 0) // padding messes up the positioning
+ .children().filter(function(){
+ return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base');
+ }).remove();
+
+ if (placeholder.css("position") == 'static')
+ placeholder.css("position", "relative"); // for positioning labels and overlay
+
+ surface = new Canvas("flot-base", placeholder);
+ overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features
+
+ ctx = surface.context;
+ octx = overlay.context;
+
+ // define which element we're listening for events on
+ eventHolder = $(overlay.element).unbind();
+
+ // If we're re-using a plot object, shut down the old one
+
+ var existing = placeholder.data("plot");
+
+ if (existing) {
+ existing.shutdown();
+ overlay.clear();
+ }
+
+ // save in case we get replotted
+ placeholder.data("plot", plot);
+ }
+
+ function bindEvents() {
+ // bind events
+ if (options.grid.hoverable) {
+ eventHolder.mousemove(onMouseMove);
+
+ // Use bind, rather than .mouseleave, because we officially
+ // still support jQuery 1.2.6, which doesn't define a shortcut
+ // for mouseenter or mouseleave. This was a bug/oversight that
+ // was fixed somewhere around 1.3.x. We can return to using
+ // .mouseleave when we drop support for 1.2.6.
+
+ eventHolder.bind("mouseleave", onMouseLeave);
+ }
+
+ if (options.grid.clickable)
+ eventHolder.click(onClick);
+
+ executeHooks(hooks.bindEvents, [eventHolder]);
+ }
+
+ function shutdown() {
+ if (redrawTimeout)
+ clearTimeout(redrawTimeout);
+
+ eventHolder.unbind("mousemove", onMouseMove);
+ eventHolder.unbind("mouseleave", onMouseLeave);
+ eventHolder.unbind("click", onClick);
+
+ executeHooks(hooks.shutdown, [eventHolder]);
+ }
+
+ function setTransformationHelpers(axis) {
+ // set helper functions on the axis, assumes plot area
+ // has been computed already
+
+ function identity(x) { return x; }
+
+ var s, m, t = axis.options.transform || identity,
+ it = axis.options.inverseTransform;
+
+ // precompute how much the axis is scaling a point
+ // in canvas space
+ if (axis.direction == "x") {
+ s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
+ m = Math.min(t(axis.max), t(axis.min));
+ }
+ else {
+ s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
+ s = -s;
+ m = Math.max(t(axis.max), t(axis.min));
+ }
+
+ // data point to canvas coordinate
+ if (t == identity) // slight optimization
+ axis.p2c = function (p) { return (p - m) * s; };
+ else
+ axis.p2c = function (p) { return (t(p) - m) * s; };
+ // canvas coordinate to data point
+ if (!it)
+ axis.c2p = function (c) { return m + c / s; };
+ else
+ axis.c2p = function (c) { return it(m + c / s); };
+ }
+
+ function measureTickLabels(axis) {
+
+ var opts = axis.options,
+ ticks = axis.ticks || [],
+ labelWidth = opts.labelWidth || 0,
+ labelHeight = opts.labelHeight || 0,
+ maxWidth = labelWidth || (axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null),
+ legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
+ layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
+ font = opts.font || "flot-tick-label tickLabel";
+
+ for (var i = 0; i < ticks.length; ++i) {
+
+ var t = ticks[i];
+
+ if (!t.label)
+ continue;
+
+ var info = surface.getTextInfo(layer, t.label, font, null, maxWidth);
+
+ labelWidth = Math.max(labelWidth, info.width);
+ labelHeight = Math.max(labelHeight, info.height);
+ }
+
+ axis.labelWidth = opts.labelWidth || labelWidth;
+ axis.labelHeight = opts.labelHeight || labelHeight;
+ }
+
+ function allocateAxisBoxFirstPhase(axis) {
+ // find the bounding box of the axis by looking at label
+ // widths/heights and ticks, make room by diminishing the
+ // plotOffset; this first phase only looks at one
+ // dimension per axis, the other dimension depends on the
+ // other axes so will have to wait
+
+ var lw = axis.labelWidth,
+ lh = axis.labelHeight,
+ pos = axis.options.position,
+ isXAxis = axis.direction === "x",
+ tickLength = axis.options.tickLength,
+ axisMargin = options.grid.axisMargin,
+ padding = options.grid.labelMargin,
+ innermost = true,
+ outermost = true,
+ first = true,
+ found = false;
+
+ // Determine the axis's position in its direction and on its side
+
+ $.each(isXAxis ? xaxes : yaxes, function(i, a) {
+ if (a && (a.show || a.reserveSpace)) {
+ if (a === axis) {
+ found = true;
+ } else if (a.options.position === pos) {
+ if (found) {
+ outermost = false;
+ } else {
+ innermost = false;
+ }
+ }
+ if (!found) {
+ first = false;
+ }
+ }
+ });
+
+ // The outermost axis on each side has no margin
+
+ if (outermost) {
+ axisMargin = 0;
+ }
+
+ // The ticks for the first axis in each direction stretch across
+
+ if (tickLength == null) {
+ tickLength = first ? "full" : 5;
+ }
+
+ if (!isNaN(+tickLength))
+ padding += +tickLength;
+
+ if (isXAxis) {
+ lh += padding;
+
+ if (pos == "bottom") {
+ plotOffset.bottom += lh + axisMargin;
+ axis.box = { top: surface.height - plotOffset.bottom, height: lh };
+ }
+ else {
+ axis.box = { top: plotOffset.top + axisMargin, height: lh };
+ plotOffset.top += lh + axisMargin;
+ }
+ }
+ else {
+ lw += padding;
+
+ if (pos == "left") {
+ axis.box = { left: plotOffset.left + axisMargin, width: lw };
+ plotOffset.left += lw + axisMargin;
+ }
+ else {
+ plotOffset.right += lw + axisMargin;
+ axis.box = { left: surface.width - plotOffset.right, width: lw };
+ }
+ }
+
+ // save for future reference
+ axis.position = pos;
+ axis.tickLength = tickLength;
+ axis.box.padding = padding;
+ axis.innermost = innermost;
+ }
+
+ function allocateAxisBoxSecondPhase(axis) {
+ // now that all axis boxes have been placed in one
+ // dimension, we can set the remaining dimension coordinates
+ if (axis.direction == "x") {
+ axis.box.left = plotOffset.left - axis.labelWidth / 2;
+ axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth;
+ }
+ else {
+ axis.box.top = plotOffset.top - axis.labelHeight / 2;
+ axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight;
+ }
+ }
+
+ function adjustLayoutForThingsStickingOut() {
+ // possibly adjust plot offset to ensure everything stays
+ // inside the canvas and isn't clipped off
+
+ var minMargin = options.grid.minBorderMargin,
+ axis, i;
+
+ // check stuff from the plot (FIXME: this should just read
+ // a value from the series, otherwise it's impossible to
+ // customize)
+ if (minMargin == null) {
+ minMargin = 0;
+ for (i = 0; i < series.length; ++i)
+ minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2));
+ }
+
+ var margins = {
+ left: minMargin,
+ right: minMargin,
+ top: minMargin,
+ bottom: minMargin
+ };
+
+ // check axis labels, note we don't check the actual
+ // labels but instead use the overall width/height to not
+ // jump as much around with replots
+ $.each(allAxes(), function (_, axis) {
+ if (axis.reserveSpace && axis.ticks && axis.ticks.length) {
+ if (axis.direction === "x") {
+ margins.left = Math.max(margins.left, axis.labelWidth / 2);
+ margins.right = Math.max(margins.right, axis.labelWidth / 2);
+ } else {
+ margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2);
+ margins.top = Math.max(margins.top, axis.labelHeight / 2);
+ }
+ }
+ });
+
+ plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left));
+ plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right));
+ plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top));
+ plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom));
+ }
+
+ function setupGrid() {
+ var i, axes = allAxes(), showGrid = options.grid.show;
+
+ // Initialize the plot's offset from the edge of the canvas
+
+ for (var a in plotOffset) {
+ var margin = options.grid.margin || 0;
+ plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0;
+ }
+
+ executeHooks(hooks.processOffset, [plotOffset]);
+
+ // If the grid is visible, add its border width to the offset
+
+ for (var a in plotOffset) {
+ if(typeof(options.grid.borderWidth) == "object") {
+ plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0;
+ }
+ else {
+ plotOffset[a] += showGrid ? options.grid.borderWidth : 0;
+ }
+ }
+
+ $.each(axes, function (_, axis) {
+ var axisOpts = axis.options;
+ axis.show = axisOpts.show == null ? axis.used : axisOpts.show;
+ axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace;
+ setRange(axis);
+ });
+
+ if (showGrid) {
+
+ var allocatedAxes = $.grep(axes, function (axis) {
+ return axis.show || axis.reserveSpace;
+ });
+
+ $.each(allocatedAxes, function (_, axis) {
+ // make the ticks
+ setupTickGeneration(axis);
+ setTicks(axis);
+ snapRangeToTicks(axis, axis.ticks);
+ // find labelWidth/Height for axis
+ measureTickLabels(axis);
+ });
+
+ // with all dimensions calculated, we can compute the
+ // axis bounding boxes, start from the outside
+ // (reverse order)
+ for (i = allocatedAxes.length - 1; i >= 0; --i)
+ allocateAxisBoxFirstPhase(allocatedAxes[i]);
+
+ // make sure we've got enough space for things that
+ // might stick out
+ adjustLayoutForThingsStickingOut();
+
+ $.each(allocatedAxes, function (_, axis) {
+ allocateAxisBoxSecondPhase(axis);
+ });
+ }
+
+ plotWidth = surface.width - plotOffset.left - plotOffset.right;
+ plotHeight = surface.height - plotOffset.bottom - plotOffset.top;
+
+ // now we got the proper plot dimensions, we can compute the scaling
+ $.each(axes, function (_, axis) {
+ setTransformationHelpers(axis);
+ });
+
+ if (showGrid) {
+ drawAxisLabels();
+ }
+
+ insertLegend();
+ }
+
+ function setRange(axis) {
+ var opts = axis.options,
+ min = +(opts.min != null ? opts.min : axis.datamin),
+ max = +(opts.max != null ? opts.max : axis.datamax),
+ delta = max - min;
+
+ if (delta == 0.0) {
+ // degenerate case
+ var widen = max == 0 ? 1 : 0.01;
+
+ if (opts.min == null)
+ min -= widen;
+ // always widen max if we couldn't widen min to ensure we
+ // don't fall into min == max which doesn't work
+ if (opts.max == null || opts.min != null)
+ max += widen;
+ }
+ else {
+ // consider autoscaling
+ var margin = opts.autoscaleMargin;
+ if (margin != null) {
+ if (opts.min == null) {
+ min -= delta * margin;
+ // make sure we don't go below zero if all values
+ // are positive
+ if (min < 0 && axis.datamin != null && axis.datamin >= 0)
+ min = 0;
+ }
+ if (opts.max == null) {
+ max += delta * margin;
+ if (max > 0 && axis.datamax != null && axis.datamax <= 0)
+ max = 0;
+ }
+ }
+ }
+ axis.min = min;
+ axis.max = max;
+ }
+
+ function setupTickGeneration(axis) {
+ var opts = axis.options;
+
+ // estimate number of ticks
+ var noTicks;
+ if (typeof opts.ticks == "number" && opts.ticks > 0)
+ noTicks = opts.ticks;
+ else
+ // heuristic based on the model a*sqrt(x) fitted to
+ // some data points that seemed reasonable
+ noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height);
+
+ var delta = (axis.max - axis.min) / noTicks,
+ dec = -Math.floor(Math.log(delta) / Math.LN10),
+ maxDec = opts.tickDecimals;
+
+ if (maxDec != null && dec > maxDec) {
+ dec = maxDec;
+ }
+
+ var magn = Math.pow(10, -dec),
+ norm = delta / magn, // norm is between 1.0 and 10.0
+ size;
+
+ if (norm < 1.5) {
+ size = 1;
+ } else if (norm < 3) {
+ size = 2;
+ // special case for 2.5, requires an extra decimal
+ if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
+ size = 2.5;
+ ++dec;
+ }
+ } else if (norm < 7.5) {
+ size = 5;
+ } else {
+ size = 10;
+ }
+
+ size *= magn;
+
+ if (opts.minTickSize != null && size < opts.minTickSize) {
+ size = opts.minTickSize;
+ }
+
+ axis.delta = delta;
+ axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
+ axis.tickSize = opts.tickSize || size;
+
+ // Time mode was moved to a plug-in in 0.8, and since so many people use it
+ // we'll add an especially friendly reminder to make sure they included it.
+
+ if (opts.mode == "time" && !axis.tickGenerator) {
+ throw new Error("Time mode requires the flot.time plugin.");
+ }
+
+ // Flot supports base-10 axes; any other mode else is handled by a plug-in,
+ // like flot.time.js.
+
+ if (!axis.tickGenerator) {
+
+ axis.tickGenerator = function (axis) {
+
+ var ticks = [],
+ start = floorInBase(axis.min, axis.tickSize),
+ i = 0,
+ v = Number.NaN,
+ prev;
+
+ do {
+ prev = v;
+ v = start + i * axis.tickSize;
+ ticks.push(v);
+ ++i;
+ } while (v < axis.max && v != prev);
+ return ticks;
+ };
+
+ axis.tickFormatter = function (value, axis) {
+
+ var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1;
+ var formatted = "" + Math.round(value * factor) / factor;
+
+ // If tickDecimals was specified, ensure that we have exactly that
+ // much precision; otherwise default to the value's own precision.
+
+ if (axis.tickDecimals != null) {
+ var decimal = formatted.indexOf(".");
+ var precision = decimal == -1 ? 0 : formatted.length - decimal - 1;
+ if (precision < axis.tickDecimals) {
+ return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision);
+ }
+ }
+
+ return formatted;
+ };
+ }
+
+ if ($.isFunction(opts.tickFormatter))
+ axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); };
+
+ if (opts.alignTicksWithAxis != null) {
+ var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
+ if (otherAxis && otherAxis.used && otherAxis != axis) {
+ // consider snapping min/max to outermost nice ticks
+ var niceTicks = axis.tickGenerator(axis);
+ if (niceTicks.length > 0) {
+ if (opts.min == null)
+ axis.min = Math.min(axis.min, niceTicks[0]);
+ if (opts.max == null && niceTicks.length > 1)
+ axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
+ }
+
+ axis.tickGenerator = function (axis) {
+ // copy ticks, scaled to this axis
+ var ticks = [], v, i;
+ for (i = 0; i < otherAxis.ticks.length; ++i) {
+ v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
+ v = axis.min + v * (axis.max - axis.min);
+ ticks.push(v);
+ }
+ return ticks;
+ };
+
+ // we might need an extra decimal since forced
+ // ticks don't necessarily fit naturally
+ if (!axis.mode && opts.tickDecimals == null) {
+ var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1),
+ ts = axis.tickGenerator(axis);
+
+ // only proceed if the tick interval rounded
+ // with an extra decimal doesn't give us a
+ // zero at end
+ if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))
+ axis.tickDecimals = extraDec;
+ }
+ }
+ }
+ }
+
+ function setTicks(axis) {
+ var oticks = axis.options.ticks, ticks = [];
+ if (oticks == null || (typeof oticks == "number" && oticks > 0))
+ ticks = axis.tickGenerator(axis);
+ else if (oticks) {
+ if ($.isFunction(oticks))
+ // generate the ticks
+ ticks = oticks(axis);
+ else
+ ticks = oticks;
+ }
+
+ // clean up/labelify the supplied ticks, copy them over
+ var i, v;
+ axis.ticks = [];
+ for (i = 0; i < ticks.length; ++i) {
+ var label = null;
+ var t = ticks[i];
+ if (typeof t == "object") {
+ v = +t[0];
+ if (t.length > 1)
+ label = t[1];
+ }
+ else
+ v = +t;
+ if (label == null)
+ label = axis.tickFormatter(v, axis);
+ if (!isNaN(v))
+ axis.ticks.push({ v: v, label: label });
+ }
+ }
+
+ function snapRangeToTicks(axis, ticks) {
+ if (axis.options.autoscaleMargin && ticks.length > 0) {
+ // snap to ticks
+ if (axis.options.min == null)
+ axis.min = Math.min(axis.min, ticks[0].v);
+ if (axis.options.max == null && ticks.length > 1)
+ axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
+ }
+ }
+
+ function draw() {
+
+ surface.clear();
+
+ executeHooks(hooks.drawBackground, [ctx]);
+
+ var grid = options.grid;
+
+ // draw background, if any
+ if (grid.show && grid.backgroundColor)
+ drawBackground();
+
+ if (grid.show && !grid.aboveData) {
+ drawGrid();
+ }
+
+ for (var i = 0; i < series.length; ++i) {
+ executeHooks(hooks.drawSeries, [ctx, series[i]]);
+ drawSeries(series[i]);
+ }
+
+ executeHooks(hooks.draw, [ctx]);
+
+ if (grid.show && grid.aboveData) {
+ drawGrid();
+ }
+
+ surface.render();
+
+ // A draw implies that either the axes or data have changed, so we
+ // should probably update the overlay highlights as well.
+
+ triggerRedrawOverlay();
+ }
+
+ function extractRange(ranges, coord) {
+ var axis, from, to, key, axes = allAxes();
+
+ for (var i = 0; i < axes.length; ++i) {
+ axis = axes[i];
+ if (axis.direction == coord) {
+ key = coord + axis.n + "axis";
+ if (!ranges[key] && axis.n == 1)
+ key = coord + "axis"; // support x1axis as xaxis
+ if (ranges[key]) {
+ from = ranges[key].from;
+ to = ranges[key].to;
+ break;
+ }
+ }
+ }
+
+ // backwards-compat stuff - to be removed in future
+ if (!ranges[key]) {
+ axis = coord == "x" ? xaxes[0] : yaxes[0];
+ from = ranges[coord + "1"];
+ to = ranges[coord + "2"];
+ }
+
+ // auto-reverse as an added bonus
+ if (from != null && to != null && from > to) {
+ var tmp = from;
+ from = to;
+ to = tmp;
+ }
+
+ return { from: from, to: to, axis: axis };
+ }
+
+ function drawBackground() {
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
+ ctx.fillRect(0, 0, plotWidth, plotHeight);
+ ctx.restore();
+ }
+
+ function drawGrid() {
+ var i, axes, bw, bc;
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ // draw markings
+ var markings = options.grid.markings;
+ if (markings) {
+ if ($.isFunction(markings)) {
+ axes = plot.getAxes();
+ // xmin etc. is backwards compatibility, to be
+ // removed in the future
+ axes.xmin = axes.xaxis.min;
+ axes.xmax = axes.xaxis.max;
+ axes.ymin = axes.yaxis.min;
+ axes.ymax = axes.yaxis.max;
+
+ markings = markings(axes);
+ }
+
+ for (i = 0; i < markings.length; ++i) {
+ var m = markings[i],
+ xrange = extractRange(m, "x"),
+ yrange = extractRange(m, "y");
+
+ // fill in missing
+ if (xrange.from == null)
+ xrange.from = xrange.axis.min;
+ if (xrange.to == null)
+ xrange.to = xrange.axis.max;
+ if (yrange.from == null)
+ yrange.from = yrange.axis.min;
+ if (yrange.to == null)
+ yrange.to = yrange.axis.max;
+
+ // clip
+ if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
+ yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
+ continue;
+
+ xrange.from = Math.max(xrange.from, xrange.axis.min);
+ xrange.to = Math.min(xrange.to, xrange.axis.max);
+ yrange.from = Math.max(yrange.from, yrange.axis.min);
+ yrange.to = Math.min(yrange.to, yrange.axis.max);
+
+ var xequal = xrange.from === xrange.to,
+ yequal = yrange.from === yrange.to;
+
+ if (xequal && yequal) {
+ continue;
+ }
+
+ // then draw
+ xrange.from = Math.floor(xrange.axis.p2c(xrange.from));
+ xrange.to = Math.floor(xrange.axis.p2c(xrange.to));
+ yrange.from = Math.floor(yrange.axis.p2c(yrange.from));
+ yrange.to = Math.floor(yrange.axis.p2c(yrange.to));
+
+ if (xequal || yequal) {
+ var lineWidth = m.lineWidth || options.grid.markingsLineWidth,
+ subPixel = lineWidth % 2 ? 0.5 : 0;
+ ctx.beginPath();
+ ctx.strokeStyle = m.color || options.grid.markingsColor;
+ ctx.lineWidth = lineWidth;
+ if (xequal) {
+ ctx.moveTo(xrange.to + subPixel, yrange.from);
+ ctx.lineTo(xrange.to + subPixel, yrange.to);
+ } else {
+ ctx.moveTo(xrange.from, yrange.to + subPixel);
+ ctx.lineTo(xrange.to, yrange.to + subPixel);
+ }
+ ctx.stroke();
+ } else {
+ ctx.fillStyle = m.color || options.grid.markingsColor;
+ ctx.fillRect(xrange.from, yrange.to,
+ xrange.to - xrange.from,
+ yrange.from - yrange.to);
+ }
+ }
+ }
+
+ // draw the ticks
+ axes = allAxes();
+ bw = options.grid.borderWidth;
+
+ for (var j = 0; j < axes.length; ++j) {
+ var axis = axes[j], box = axis.box,
+ t = axis.tickLength, x, y, xoff, yoff;
+ if (!axis.show || axis.ticks.length == 0)
+ continue;
+
+ ctx.lineWidth = 1;
+
+ // find the edges
+ if (axis.direction == "x") {
+ x = 0;
+ if (t == "full")
+ y = (axis.position == "top" ? 0 : plotHeight);
+ else
+ y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0);
+ }
+ else {
+ y = 0;
+ if (t == "full")
+ x = (axis.position == "left" ? 0 : plotWidth);
+ else
+ x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0);
+ }
+
+ // draw tick bar
+ if (!axis.innermost) {
+ ctx.strokeStyle = axis.options.color;
+ ctx.beginPath();
+ xoff = yoff = 0;
+ if (axis.direction == "x")
+ xoff = plotWidth + 1;
+ else
+ yoff = plotHeight + 1;
+
+ if (ctx.lineWidth == 1) {
+ if (axis.direction == "x") {
+ y = Math.floor(y) + 0.5;
+ } else {
+ x = Math.floor(x) + 0.5;
+ }
+ }
+
+ ctx.moveTo(x, y);
+ ctx.lineTo(x + xoff, y + yoff);
+ ctx.stroke();
+ }
+
+ // draw ticks
+
+ ctx.strokeStyle = axis.options.tickColor;
+
+ ctx.beginPath();
+ for (i = 0; i < axis.ticks.length; ++i) {
+ var v = axis.ticks[i].v;
+
+ xoff = yoff = 0;
+
+ if (isNaN(v) || v < axis.min || v > axis.max
+ // skip those lying on the axes if we got a border
+ || (t == "full"
+ && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0)
+ && (v == axis.min || v == axis.max)))
+ continue;
+
+ if (axis.direction == "x") {
+ x = axis.p2c(v);
+ yoff = t == "full" ? -plotHeight : t;
+
+ if (axis.position == "top")
+ yoff = -yoff;
+ }
+ else {
+ y = axis.p2c(v);
+ xoff = t == "full" ? -plotWidth : t;
+
+ if (axis.position == "left")
+ xoff = -xoff;
+ }
+
+ if (ctx.lineWidth == 1) {
+ if (axis.direction == "x")
+ x = Math.floor(x) + 0.5;
+ else
+ y = Math.floor(y) + 0.5;
+ }
+
+ ctx.moveTo(x, y);
+ ctx.lineTo(x + xoff, y + yoff);
+ }
+
+ ctx.stroke();
+ }
+
+
+ // draw border
+ if (bw) {
+ // If either borderWidth or borderColor is an object, then draw the border
+ // line by line instead of as one rectangle
+ bc = options.grid.borderColor;
+ if(typeof bw == "object" || typeof bc == "object") {
+ if (typeof bw !== "object") {
+ bw = {top: bw, right: bw, bottom: bw, left: bw};
+ }
+ if (typeof bc !== "object") {
+ bc = {top: bc, right: bc, bottom: bc, left: bc};
+ }
+
+ if (bw.top > 0) {
+ ctx.strokeStyle = bc.top;
+ ctx.lineWidth = bw.top;
+ ctx.beginPath();
+ ctx.moveTo(0 - bw.left, 0 - bw.top/2);
+ ctx.lineTo(plotWidth, 0 - bw.top/2);
+ ctx.stroke();
+ }
+
+ if (bw.right > 0) {
+ ctx.strokeStyle = bc.right;
+ ctx.lineWidth = bw.right;
+ ctx.beginPath();
+ ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top);
+ ctx.lineTo(plotWidth + bw.right / 2, plotHeight);
+ ctx.stroke();
+ }
+
+ if (bw.bottom > 0) {
+ ctx.strokeStyle = bc.bottom;
+ ctx.lineWidth = bw.bottom;
+ ctx.beginPath();
+ ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2);
+ ctx.lineTo(0, plotHeight + bw.bottom / 2);
+ ctx.stroke();
+ }
+
+ if (bw.left > 0) {
+ ctx.strokeStyle = bc.left;
+ ctx.lineWidth = bw.left;
+ ctx.beginPath();
+ ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom);
+ ctx.lineTo(0- bw.left/2, 0);
+ ctx.stroke();
+ }
+ }
+ else {
+ ctx.lineWidth = bw;
+ ctx.strokeStyle = options.grid.borderColor;
+ ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
+ }
+ }
+
+ ctx.restore();
+ }
+
+ function drawAxisLabels() {
+
+ $.each(allAxes(), function (_, axis) {
+ var box = axis.box,
+ legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
+ layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
+ font = axis.options.font || "flot-tick-label tickLabel",
+ tick, x, y, halign, valign;
+
+ // Remove text before checking for axis.show and ticks.length;
+ // otherwise plugins, like flot-tickrotor, that draw their own
+ // tick labels will end up with both theirs and the defaults.
+
+ surface.removeText(layer);
+
+ if (!axis.show || axis.ticks.length == 0)
+ return;
+
+ for (var i = 0; i < axis.ticks.length; ++i) {
+
+ tick = axis.ticks[i];
+ if (!tick.label || tick.v < axis.min || tick.v > axis.max)
+ continue;
+
+ if (axis.direction == "x") {
+ halign = "center";
+ x = plotOffset.left + axis.p2c(tick.v);
+ if (axis.position == "bottom") {
+ y = box.top + box.padding;
+ } else {
+ y = box.top + box.height - box.padding;
+ valign = "bottom";
+ }
+ } else {
+ valign = "middle";
+ y = plotOffset.top + axis.p2c(tick.v);
+ if (axis.position == "left") {
+ x = box.left + box.width - box.padding;
+ halign = "right";
+ } else {
+ x = box.left + box.padding;
+ }
+ }
+
+ surface.addText(layer, x, y, tick.label, font, null, null, halign, valign);
+ }
+ });
+ }
+
+ function drawSeries(series) {
+ if (series.lines.show)
+ drawSeriesLines(series);
+ if (series.bars.show)
+ drawSeriesBars(series);
+ if (series.points.show)
+ drawSeriesPoints(series);
+ }
+
+ function drawSeriesLines(series) {
+ function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
+ var points = datapoints.points,
+ ps = datapoints.pointsize,
+ prevx = null, prevy = null;
+
+ ctx.beginPath();
+ for (var i = ps; i < points.length; i += ps) {
+ var x1 = points[i - ps], y1 = points[i - ps + 1],
+ x2 = points[i], y2 = points[i + 1];
+
+ if (x1 == null || x2 == null)
+ continue;
+
+ // clip with ymin
+ if (y1 <= y2 && y1 < axisy.min) {
+ if (y2 < axisy.min)
+ continue; // line segment is outside
+ // compute new intersection point
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.min;
+ }
+ else if (y2 <= y1 && y2 < axisy.min) {
+ if (y1 < axisy.min)
+ continue;
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.min;
+ }
+
+ // clip with ymax
+ if (y1 >= y2 && y1 > axisy.max) {
+ if (y2 > axisy.max)
+ continue;
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.max;
+ }
+ else if (y2 >= y1 && y2 > axisy.max) {
+ if (y1 > axisy.max)
+ continue;
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.max;
+ }
+
+ // clip with xmin
+ if (x1 <= x2 && x1 < axisx.min) {
+ if (x2 < axisx.min)
+ continue;
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.min;
+ }
+ else if (x2 <= x1 && x2 < axisx.min) {
+ if (x1 < axisx.min)
+ continue;
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.min;
+ }
+
+ // clip with xmax
+ if (x1 >= x2 && x1 > axisx.max) {
+ if (x2 > axisx.max)
+ continue;
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.max;
+ }
+ else if (x2 >= x1 && x2 > axisx.max) {
+ if (x1 > axisx.max)
+ continue;
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.max;
+ }
+
+ if (x1 != prevx || y1 != prevy)
+ ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
+
+ prevx = x2;
+ prevy = y2;
+ ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
+ }
+ ctx.stroke();
+ }
+
+ function plotLineArea(datapoints, axisx, axisy) {
+ var points = datapoints.points,
+ ps = datapoints.pointsize,
+ bottom = Math.min(Math.max(0, axisy.min), axisy.max),
+ i = 0, top, areaOpen = false,
+ ypos = 1, segmentStart = 0, segmentEnd = 0;
+
+ // we process each segment in two turns, first forward
+ // direction to sketch out top, then once we hit the
+ // end we go backwards to sketch the bottom
+ while (true) {
+ if (ps > 0 && i > points.length + ps)
+ break;
+
+ i += ps; // ps is negative if going backwards
+
+ var x1 = points[i - ps],
+ y1 = points[i - ps + ypos],
+ x2 = points[i], y2 = points[i + ypos];
+
+ if (areaOpen) {
+ if (ps > 0 && x1 != null && x2 == null) {
+ // at turning point
+ segmentEnd = i;
+ ps = -ps;
+ ypos = 2;
+ continue;
+ }
+
+ if (ps < 0 && i == segmentStart + ps) {
+ // done with the reverse sweep
+ ctx.fill();
+ areaOpen = false;
+ ps = -ps;
+ ypos = 1;
+ i = segmentStart = segmentEnd + ps;
+ continue;
+ }
+ }
+
+ if (x1 == null || x2 == null)
+ continue;
+
+ // clip x values
+
+ // clip with xmin
+ if (x1 <= x2 && x1 < axisx.min) {
+ if (x2 < axisx.min)
+ continue;
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.min;
+ }
+ else if (x2 <= x1 && x2 < axisx.min) {
+ if (x1 < axisx.min)
+ continue;
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.min;
+ }
+
+ // clip with xmax
+ if (x1 >= x2 && x1 > axisx.max) {
+ if (x2 > axisx.max)
+ continue;
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x1 = axisx.max;
+ }
+ else if (x2 >= x1 && x2 > axisx.max) {
+ if (x1 > axisx.max)
+ continue;
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
+ x2 = axisx.max;
+ }
+
+ if (!areaOpen) {
+ // open area
+ ctx.beginPath();
+ ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
+ areaOpen = true;
+ }
+
+ // now first check the case where both is outside
+ if (y1 >= axisy.max && y2 >= axisy.max) {
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
+ continue;
+ }
+ else if (y1 <= axisy.min && y2 <= axisy.min) {
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
+ continue;
+ }
+
+ // else it's a bit more complicated, there might
+ // be a flat maxed out rectangle first, then a
+ // triangular cutout or reverse; to find these
+ // keep track of the current x values
+ var x1old = x1, x2old = x2;
+
+ // clip the y values, without shortcutting, we
+ // go through all cases in turn
+
+ // clip with ymin
+ if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.min;
+ }
+ else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.min;
+ }
+
+ // clip with ymax
+ if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y1 = axisy.max;
+ }
+ else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
+ y2 = axisy.max;
+ }
+
+ // if the x value was changed we got a rectangle
+ // to fill
+ if (x1 != x1old) {
+ ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
+ // it goes to (x1, y1), but we fill that below
+ }
+
+ // fill triangular section, this sometimes result
+ // in redundant points if (x1, y1) hasn't changed
+ // from previous line to, but we just ignore that
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
+
+ // fill the other rectangle if it's there
+ if (x2 != x2old) {
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
+ ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
+ }
+ }
+ }
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+ ctx.lineJoin = "round";
+
+ var lw = series.lines.lineWidth,
+ sw = series.shadowSize;
+ // FIXME: consider another form of shadow when filling is turned on
+ if (lw > 0 && sw > 0) {
+ // draw shadow as a thick and thin line with transparency
+ ctx.lineWidth = sw;
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
+ // position shadow at angle from the mid of line
+ var angle = Math.PI/18;
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
+ ctx.lineWidth = sw/2;
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
+ }
+
+ ctx.lineWidth = lw;
+ ctx.strokeStyle = series.color;
+ var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
+ if (fillStyle) {
+ ctx.fillStyle = fillStyle;
+ plotLineArea(series.datapoints, series.xaxis, series.yaxis);
+ }
+
+ if (lw > 0)
+ plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
+ ctx.restore();
+ }
+
+ function drawSeriesPoints(series) {
+ function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) {
+ var points = datapoints.points, ps = datapoints.pointsize;
+
+ for (var i = 0; i < points.length; i += ps) {
+ var x = points[i], y = points[i + 1];
+ if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
+ continue;
+
+ ctx.beginPath();
+ x = axisx.p2c(x);
+ y = axisy.p2c(y) + offset;
+ if (symbol == "circle")
+ ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
+ else
+ symbol(ctx, x, y, radius, shadow);
+ ctx.closePath();
+
+ if (fillStyle) {
+ ctx.fillStyle = fillStyle;
+ ctx.fill();
+ }
+ ctx.stroke();
+ }
+ }
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ var lw = series.points.lineWidth,
+ sw = series.shadowSize,
+ radius = series.points.radius,
+ symbol = series.points.symbol;
+
+ // If the user sets the line width to 0, we change it to a very
+ // small value. A line width of 0 seems to force the default of 1.
+ // Doing the conditional here allows the shadow setting to still be
+ // optional even with a lineWidth of 0.
+
+ if( lw == 0 )
+ lw = 0.0001;
+
+ if (lw > 0 && sw > 0) {
+ // draw shadow in two steps
+ var w = sw / 2;
+ ctx.lineWidth = w;
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
+ plotPoints(series.datapoints, radius, null, w + w/2, true,
+ series.xaxis, series.yaxis, symbol);
+
+ ctx.strokeStyle = "rgba(0,0,0,0.2)";
+ plotPoints(series.datapoints, radius, null, w/2, true,
+ series.xaxis, series.yaxis, symbol);
+ }
+
+ ctx.lineWidth = lw;
+ ctx.strokeStyle = series.color;
+ plotPoints(series.datapoints, radius,
+ getFillStyle(series.points, series.color), 0, false,
+ series.xaxis, series.yaxis, symbol);
+ ctx.restore();
+ }
+
+ function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
+ var left, right, bottom, top,
+ drawLeft, drawRight, drawTop, drawBottom,
+ tmp;
+
+ // in horizontal mode, we start the bar from the left
+ // instead of from the bottom so it appears to be
+ // horizontal rather than vertical
+ if (horizontal) {
+ drawBottom = drawRight = drawTop = true;
+ drawLeft = false;
+ left = b;
+ right = x;
+ top = y + barLeft;
+ bottom = y + barRight;
+
+ // account for negative bars
+ if (right < left) {
+ tmp = right;
+ right = left;
+ left = tmp;
+ drawLeft = true;
+ drawRight = false;
+ }
+ }
+ else {
+ drawLeft = drawRight = drawTop = true;
+ drawBottom = false;
+ left = x + barLeft;
+ right = x + barRight;
+ bottom = b;
+ top = y;
+
+ // account for negative bars
+ if (top < bottom) {
+ tmp = top;
+ top = bottom;
+ bottom = tmp;
+ drawBottom = true;
+ drawTop = false;
+ }
+ }
+
+ // clip
+ if (right < axisx.min || left > axisx.max ||
+ top < axisy.min || bottom > axisy.max)
+ return;
+
+ if (left < axisx.min) {
+ left = axisx.min;
+ drawLeft = false;
+ }
+
+ if (right > axisx.max) {
+ right = axisx.max;
+ drawRight = false;
+ }
+
+ if (bottom < axisy.min) {
+ bottom = axisy.min;
+ drawBottom = false;
+ }
+
+ if (top > axisy.max) {
+ top = axisy.max;
+ drawTop = false;
+ }
+
+ left = axisx.p2c(left);
+ bottom = axisy.p2c(bottom);
+ right = axisx.p2c(right);
+ top = axisy.p2c(top);
+
+ // fill the bar
+ if (fillStyleCallback) {
+ c.fillStyle = fillStyleCallback(bottom, top);
+ c.fillRect(left, top, right - left, bottom - top)
+ }
+
+ // draw outline
+ if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
+ c.beginPath();
+
+ // FIXME: inline moveTo is buggy with excanvas
+ c.moveTo(left, bottom);
+ if (drawLeft)
+ c.lineTo(left, top);
+ else
+ c.moveTo(left, top);
+ if (drawTop)
+ c.lineTo(right, top);
+ else
+ c.moveTo(right, top);
+ if (drawRight)
+ c.lineTo(right, bottom);
+ else
+ c.moveTo(right, bottom);
+ if (drawBottom)
+ c.lineTo(left, bottom);
+ else
+ c.moveTo(left, bottom);
+ c.stroke();
+ }
+ }
+
+ function drawSeriesBars(series) {
+ function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) {
+ var points = datapoints.points, ps = datapoints.pointsize;
+
+ for (var i = 0; i < points.length; i += ps) {
+ if (points[i] == null)
+ continue;
+ drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
+ }
+ }
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ // FIXME: figure out a way to add shadows (for instance along the right edge)
+ ctx.lineWidth = series.bars.lineWidth;
+ ctx.strokeStyle = series.color;
+
+ var barLeft;
+
+ switch (series.bars.align) {
+ case "left":
+ barLeft = 0;
+ break;
+ case "right":
+ barLeft = -series.bars.barWidth;
+ break;
+ default:
+ barLeft = -series.bars.barWidth / 2;
+ }
+
+ var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
+ plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis);
+ ctx.restore();
+ }
+
+ function getFillStyle(filloptions, seriesColor, bottom, top) {
+ var fill = filloptions.fill;
+ if (!fill)
+ return null;
+
+ if (filloptions.fillColor)
+ return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
+
+ var c = $.color.parse(seriesColor);
+ c.a = typeof fill == "number" ? fill : 0.4;
+ c.normalize();
+ return c.toString();
+ }
+
+ function insertLegend() {
+
+ if (options.legend.container != null) {
+ $(options.legend.container).html("");
+ } else {
+ placeholder.find(".legend").remove();
+ }
+
+ if (!options.legend.show) {
+ return;
+ }
+
+ var fragments = [], entries = [], rowStarted = false,
+ lf = options.legend.labelFormatter, s, label;
+
+ // Build a list of legend entries, with each having a label and a color
+
+ for (var i = 0; i < series.length; ++i) {
+ s = series[i];
+ if (s.label) {
+ label = lf ? lf(s.label, s) : s.label;
+ if (label) {
+ entries.push({
+ label: label,
+ color: s.color
+ });
+ }
+ }
+ }
+
+ // Sort the legend using either the default or a custom comparator
+
+ if (options.legend.sorted) {
+ if ($.isFunction(options.legend.sorted)) {
+ entries.sort(options.legend.sorted);
+ } else if (options.legend.sorted == "reverse") {
+ entries.reverse();
+ } else {
+ var ascending = options.legend.sorted != "descending";
+ entries.sort(function(a, b) {
+ return a.label == b.label ? 0 : (
+ (a.label < b.label) != ascending ? 1 : -1 // Logical XOR
+ );
+ });
+ }
+ }
+
+ // Generate markup for the list of entries, in their final order
+
+ for (var i = 0; i < entries.length; ++i) {
+
+ var entry = entries[i];
+
+ if (i % options.legend.noColumns == 0) {
+ if (rowStarted)
+ fragments.push('</tr>');
+ fragments.push('<tr>');
+ rowStarted = true;
+ }
+
+ fragments.push(
+ '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + entry.color + ';overflow:hidden"></div></div></td>' +
+ '<td class="legendLabel">' + entry.label + '</td>'
+ );
+ }
+
+ if (rowStarted)
+ fragments.push('</tr>');
+
+ if (fragments.length == 0)
+ return;
+
+ var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
+ if (options.legend.container != null)
+ $(options.legend.container).html(table);
+ else {
+ var pos = "",
+ p = options.legend.position,
+ m = options.legend.margin;
+ if (m[0] == null)
+ m = [m, m];
+ if (p.charAt(0) == "n")
+ pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
+ else if (p.charAt(0) == "s")
+ pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
+ if (p.charAt(1) == "e")
+ pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
+ else if (p.charAt(1) == "w")
+ pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
+ var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(placeholder);
+ if (options.legend.backgroundOpacity != 0.0) {
+ // put in the transparent background
+ // separately to avoid blended labels and
+ // label boxes
+ var c = options.legend.backgroundColor;
+ if (c == null) {
+ c = options.grid.backgroundColor;
+ if (c && typeof c == "string")
+ c = $.color.parse(c);
+ else
+ c = $.color.extract(legend, 'background-color');
+ c.a = 1;
+ c = c.toString();
+ }
+ var div = legend.children();
+ $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
+ }
+ }
+ }
+
+
+ // interactive features
+
+ var highlights = [],
+ redrawTimeout = null;
+
+ // returns the data item the mouse is over, or null if none is found
+ function findNearbyItem(mouseX, mouseY, seriesFilter) {
+ var maxDistance = options.grid.mouseActiveRadius,
+ smallestDistance = maxDistance * maxDistance + 1,
+ item = null, foundPoint = false, i, j, ps;
+
+ for (i = series.length - 1; i >= 0; --i) {
+ if (!seriesFilter(series[i]))
+ continue;
+
+ var s = series[i],
+ axisx = s.xaxis,
+ axisy = s.yaxis,
+ points = s.datapoints.points,
+ mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
+ my = axisy.c2p(mouseY),
+ maxx = maxDistance / axisx.scale,
+ maxy = maxDistance / axisy.scale;
+
+ ps = s.datapoints.pointsize;
+ // with inverse transforms, we can't use the maxx/maxy
+ // optimization, sadly
+ if (axisx.options.inverseTransform)
+ maxx = Number.MAX_VALUE;
+ if (axisy.options.inverseTransform)
+ maxy = Number.MAX_VALUE;
+
+ if (s.lines.show || s.points.show) {
+ for (j = 0; j < points.length; j += ps) {
+ var x = points[j], y = points[j + 1];
+ if (x == null)
+ continue;
+
+ // For points and lines, the cursor must be within a
+ // certain distance to the data point
+ if (x - mx > maxx || x - mx < -maxx ||
+ y - my > maxy || y - my < -maxy)
+ continue;
+
+ // We have to calculate distances in pixels, not in
+ // data units, because the scales of the axes may be different
+ var dx = Math.abs(axisx.p2c(x) - mouseX),
+ dy = Math.abs(axisy.p2c(y) - mouseY),
+ dist = dx * dx + dy * dy; // we save the sqrt
+
+ // use <= to ensure last point takes precedence
+ // (last generally means on top of)
+ if (dist < smallestDistance) {
+ smallestDistance = dist;
+ item = [i, j / ps];
+ }
+ }
+ }
+
+ if (s.bars.show && !item) { // no other point can be nearby
+
+ var barLeft, barRight;
+
+ switch (s.bars.align) {
+ case "left":
+ barLeft = 0;
+ break;
+ case "right":
+ barLeft = -s.bars.barWidth;
+ break;
+ default:
+ barLeft = -s.bars.barWidth / 2;
+ }
+
+ barRight = barLeft + s.bars.barWidth;
+
+ for (j = 0; j < points.length; j += ps) {
+ var x = points[j], y = points[j + 1], b = points[j + 2];
+ if (x == null)
+ continue;
+
+ // for a bar graph, the cursor must be inside the bar
+ if (series[i].bars.horizontal ?
+ (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
+ my >= y + barLeft && my <= y + barRight) :
+ (mx >= x + barLeft && mx <= x + barRight &&
+ my >= Math.min(b, y) && my <= Math.max(b, y)))
+ item = [i, j / ps];
+ }
+ }
+ }
+
+ if (item) {
+ i = item[0];
+ j = item[1];
+ ps = series[i].datapoints.pointsize;
+
+ return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
+ dataIndex: j,
+ series: series[i],
+ seriesIndex: i };
+ }
+
+ return null;
+ }
+
+ function onMouseMove(e) {
+ if (options.grid.hoverable)
+ triggerClickHoverEvent("plothover", e,
+ function (s) { return s["hoverable"] != false; });
+ }
+
+ function onMouseLeave(e) {
+ if (options.grid.hoverable)
+ triggerClickHoverEvent("plothover", e,
+ function (s) { return false; });
+ }
+
+ function onClick(e) {
+ triggerClickHoverEvent("plotclick", e,
+ function (s) { return s["clickable"] != false; });
+ }
+
+ // trigger click or hover event (they send the same parameters
+ // so we share their code)
+ function triggerClickHoverEvent(eventname, event, seriesFilter) {
+ var offset = eventHolder.offset(),
+ canvasX = event.pageX - offset.left - plotOffset.left,
+ canvasY = event.pageY - offset.top - plotOffset.top,
+ pos = canvasToAxisCoords({ left: canvasX, top: canvasY });
+
+ pos.pageX = event.pageX;
+ pos.pageY = event.pageY;
+
+ var item = findNearbyItem(canvasX, canvasY, seriesFilter);
+
+ if (item) {
+ // fill in mouse pos for any listeners out there
+ item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10);
+ item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10);
+ }
+
+ if (options.grid.autoHighlight) {
+ // clear auto-highlights
+ for (var i = 0; i < highlights.length; ++i) {
+ var h = highlights[i];
+ if (h.auto == eventname &&
+ !(item && h.series == item.series &&
+ h.point[0] == item.datapoint[0] &&
+ h.point[1] == item.datapoint[1]))
+ unhighlight(h.series, h.point);
+ }
+
+ if (item)
+ highlight(item.series, item.datapoint, eventname);
+ }
+
+ placeholder.trigger(eventname, [ pos, item ]);
+ }
+
+ function triggerRedrawOverlay() {
+ var t = options.interaction.redrawOverlayInterval;
+ if (t == -1) { // skip event queue
+ drawOverlay();
+ return;
+ }
+
+ if (!redrawTimeout)
+ redrawTimeout = setTimeout(drawOverlay, t);
+ }
+
+ function drawOverlay() {
+ redrawTimeout = null;
+
+ // draw highlights
+ octx.save();
+ overlay.clear();
+ octx.translate(plotOffset.left, plotOffset.top);
+
+ var i, hi;
+ for (i = 0; i < highlights.length; ++i) {
+ hi = highlights[i];
+
+ if (hi.series.bars.show)
+ drawBarHighlight(hi.series, hi.point);
+ else
+ drawPointHighlight(hi.series, hi.point);
+ }
+ octx.restore();
+
+ executeHooks(hooks.drawOverlay, [octx]);
+ }
+
+ function highlight(s, point, auto) {
+ if (typeof s == "number")
+ s = series[s];
+
+ if (typeof point == "number") {
+ var ps = s.datapoints.pointsize;
+ point = s.datapoints.points.slice(ps * point, ps * (point + 1));
+ }
+
+ var i = indexOfHighlight(s, point);
+ if (i == -1) {
+ highlights.push({ series: s, point: point, auto: auto });
+
+ triggerRedrawOverlay();
+ }
+ else if (!auto)
+ highlights[i].auto = false;
+ }
+
+ function unhighlight(s, point) {
+ if (s == null && point == null) {
+ highlights = [];
+ triggerRedrawOverlay();
+ return;
+ }
+
+ if (typeof s == "number")
+ s = series[s];
+
+ if (typeof point == "number") {
+ var ps = s.datapoints.pointsize;
+ point = s.datapoints.points.slice(ps * point, ps * (point + 1));
+ }
+
+ var i = indexOfHighlight(s, point);
+ if (i != -1) {
+ highlights.splice(i, 1);
+
+ triggerRedrawOverlay();
+ }
+ }
+
+ function indexOfHighlight(s, p) {
+ for (var i = 0; i < highlights.length; ++i) {
+ var h = highlights[i];
+ if (h.series == s && h.point[0] == p[0]
+ && h.point[1] == p[1])
+ return i;
+ }
+ return -1;
+ }
+
+ function drawPointHighlight(series, point) {
+ var x = point[0], y = point[1],
+ axisx = series.xaxis, axisy = series.yaxis,
+ highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString();
+
+ if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
+ return;
+
+ var pointRadius = series.points.radius + series.points.lineWidth / 2;
+ octx.lineWidth = pointRadius;
+ octx.strokeStyle = highlightColor;
+ var radius = 1.5 * pointRadius;
+ x = axisx.p2c(x);
+ y = axisy.p2c(y);
+
+ octx.beginPath();
+ if (series.points.symbol == "circle")
+ octx.arc(x, y, radius, 0, 2 * Math.PI, false);
+ else
+ series.points.symbol(octx, x, y, radius, false);
+ octx.closePath();
+ octx.stroke();
+ }
+
+ function drawBarHighlight(series, point) {
+ var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(),
+ fillStyle = highlightColor,
+ barLeft;
+
+ switch (series.bars.align) {
+ case "left":
+ barLeft = 0;
+ break;
+ case "right":
+ barLeft = -series.bars.barWidth;
+ break;
+ default:
+ barLeft = -series.bars.barWidth / 2;
+ }
+
+ octx.lineWidth = series.bars.lineWidth;
+ octx.strokeStyle = highlightColor;
+
+ drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
+ function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
+ }
+
+ function getColorOrGradient(spec, bottom, top, defaultColor) {
+ if (typeof spec == "string")
+ return spec;
+ else {
+ // assume this is a gradient spec; IE currently only
+ // supports a simple vertical gradient properly, so that's
+ // what we support too
+ var gradient = ctx.createLinearGradient(0, top, 0, bottom);
+
+ for (var i = 0, l = spec.colors.length; i < l; ++i) {
+ var c = spec.colors[i];
+ if (typeof c != "string") {
+ var co = $.color.parse(defaultColor);
+ if (c.brightness != null)
+ co = co.scale('rgb', c.brightness);
+ if (c.opacity != null)
+ co.a *= c.opacity;
+ c = co.toString();
+ }
+ gradient.addColorStop(i / (l - 1), c);
+ }
+
+ return gradient;
+ }
+ }
+ }
+
+ // Add the plot function to the top level of the jQuery object
+
+ $.plot = function(placeholder, data, options) {
+ //var t0 = new Date();
+ var plot = new Plot($(placeholder), data, options, $.plot.plugins);
+ //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
+ return plot;
+ };
+
+ $.plot.version = "0.8.3";
+
+ $.plot.plugins = [];
+
+ // Also add the plot function as a chainable property
+
+ $.fn.plot = function(data, options) {
+ return this.each(function() {
+ $.plot(this, data, options);
+ });
+ };
+
+ // round to nearby lower multiple of base
+ function floorInBase(n, base) {
+ return base * Math.floor(n / base);
+ }
+
+})(jQuery);
diff --git a/misc/flot/jquery.flot.min.js b/misc/flot/jquery.flot.min.js
new file mode 100644
index 0000000..968d3eb
--- /dev/null
+++ b/misc/flot/jquery.flot.min.js
@@ -0,0 +1,8 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i<c.length;++i)o[c.charAt(i)]+=d;return o.normalize()};o.scale=function(c,f){for(var i=0;i<c.length;++i)o[c.charAt(i)]*=f;return o.normalize()};o.toString=function(){if(o.a>=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return value<min?min:value>max?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);(function($){var hasOwnProperty=Object.prototype.hasOwnProperty;if(!$.fn.detach){$.fn.detach=function(){return this.each(function(){if(this.parentNode){this.parentNode.removeChild(this)}})}}function Canvas(cls,container){var element=container.children("."+cls)[0];if(element==null){element=document.createElement("canvas");element.className=cls;$(element).css({direction:"ltr",position:"absolute",left:0,top:0}).appendTo(container);if(!element.getContext){if(window.G_vmlCanvasManager){element=window.G_vmlCanvasManager.initElement(element)}else{throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.")}}}this.element=element;var context=this.context=element.getContext("2d");var devicePixelRatio=window.devicePixelRatio||1,backingStoreRatio=context.webkitBackingStorePixelRatio||context.mozBackingStorePixelRatio||context.msBackingStorePixelRatio||context.oBackingStorePixelRatio||context.backingStorePixelRatio||1;this.pixelRatio=devicePixelRatio/backingStoreRatio;this.resize(container.width(),container.height());this.textContainer=null;this.text={};this._textCache={}}Canvas.prototype.resize=function(width,height){if(width<=0||height<=0){throw new Error("Invalid dimensions for plot, width = "+width+", height = "+height)}var element=this.element,context=this.context,pixelRatio=this.pixelRatio;if(this.width!=width){element.width=width*pixelRatio;element.style.width=width+"px";this.width=width}if(this.height!=height){element.height=height*pixelRatio;element.style.height=height+"px";this.height=height}context.restore();context.save();context.scale(pixelRatio,pixelRatio)};Canvas.prototype.clear=function(){this.context.clearRect(0,0,this.width,this.height)};Canvas.prototype.render=function(){var cache=this._textCache;for(var layerKey in cache){if(hasOwnProperty.call(cache,layerKey)){var layer=this.getTextLayer(layerKey),layerCache=cache[layerKey];layer.hide();for(var styleKey in layerCache){if(hasOwnProperty.call(layerCache,styleKey)){var styleCache=layerCache[styleKey];for(var key in styleCache){if(hasOwnProperty.call(styleCache,key)){var positions=styleCache[key].positions;for(var i=0,position;position=positions[i];i++){if(position.active){if(!position.rendered){layer.append(position.element);position.rendered=true}}else{positions.splice(i--,1);if(position.rendered){position.element.detach()}}}if(positions.length==0){delete styleCache[key]}}}}}layer.show()}}};Canvas.prototype.getTextLayer=function(classes){var layer=this.text[classes];if(layer==null){if(this.textContainer==null){this.textContainer=$("<div class='flot-text'></div>").css({position:"absolute",top:0,left:0,bottom:0,right:0,"font-size":"smaller",color:"#545454"}).insertAfter(this.element)}layer=this.text[classes]=$("<div></div>").addClass(classes).css({position:"absolute",top:0,left:0,bottom:0,right:0}).appendTo(this.textContainer)}return layer};Canvas.prototype.getTextInfo=function(layer,text,font,angle,width){var textStyle,layerCache,styleCache,info;text=""+text;if(typeof font==="object"){textStyle=font.style+" "+font.variant+" "+font.weight+" "+font.size+"px/"+font.lineHeight+"px "+font.family}else{textStyle=font}layerCache=this._textCache[layer];if(layerCache==null){layerCache=this._textCache[layer]={}}styleCache=layerCache[textStyle];if(styleCache==null){styleCache=layerCache[textStyle]={}}info=styleCache[text];if(info==null){var element=$("<div></div>").html(text).css({position:"absolute","max-width":width,top:-9999}).appendTo(this.getTextLayer(layer));if(typeof font==="object"){element.css({font:textStyle,color:font.color})}else if(typeof font==="string"){element.addClass(font)}info=styleCache[text]={width:element.outerWidth(true),height:element.outerHeight(true),element:element,positions:[]};element.detach()}return info};Canvas.prototype.addText=function(layer,x,y,text,font,angle,width,halign,valign){var info=this.getTextInfo(layer,text,font,angle,width),positions=info.positions;if(halign=="center"){x-=info.width/2}else if(halign=="right"){x-=info.width}if(valign=="middle"){y-=info.height/2}else if(valign=="bottom"){y-=info.height}for(var i=0,position;position=positions[i];i++){if(position.x==x&&position.y==y){position.active=true;return}}position={active:true,rendered:false,element:positions.length?info.element.clone():info.element,x:x,y:y};positions.push(position);position.element.css({top:Math.round(y),left:Math.round(x),"text-align":halign})};Canvas.prototype.removeText=function(layer,x,y,text,font,angle){if(text==null){var layerCache=this._textCache[layer];if(layerCache!=null){for(var styleKey in layerCache){if(hasOwnProperty.call(layerCache,styleKey)){var styleCache=layerCache[styleKey];for(var key in styleCache){if(hasOwnProperty.call(styleCache,key)){var positions=styleCache[key].positions;for(var i=0,position;position=positions[i];i++){position.active=false}}}}}}}else{var positions=this.getTextInfo(layer,text,font,angle).positions;for(var i=0,position;position=positions[i];i++){if(position.x==x&&position.y==y){position.active=false}}}};function Plot(placeholder,data_,options_,plugins){var series=[],options={colors:["#edc240","#afd8f8","#cb4b4b","#4da74d","#9440ed"],legend:{show:true,noColumns:1,labelFormatter:null,labelBoxBorderColor:"#ccc",container:null,position:"ne",margin:5,backgroundColor:null,backgroundOpacity:.85,sorted:null},xaxis:{show:null,position:"bottom",mode:null,font:null,color:null,tickColor:null,transform:null,inverseTransform:null,min:null,max:null,autoscaleMargin:null,ticks:null,tickFormatter:null,labelWidth:null,labelHeight:null,reserveSpace:null,tickLength:null,alignTicksWithAxis:null,tickDecimals:null,tickSize:null,minTickSize:null},yaxis:{autoscaleMargin:.02,position:"left"},xaxes:[],yaxes:[],series:{points:{show:false,radius:3,lineWidth:2,fill:true,fillColor:"#ffffff",symbol:"circle"},lines:{lineWidth:2,fill:false,fillColor:null,steps:false},bars:{show:false,lineWidth:2,barWidth:1,fill:true,fillColor:null,align:"left",horizontal:false,zero:true},shadowSize:3,highlightColor:null},grid:{show:true,aboveData:false,color:"#545454",backgroundColor:null,borderColor:null,tickColor:null,margin:0,labelMargin:5,axisMargin:8,borderWidth:2,minBorderMargin:null,markings:null,markingsColor:"#f4f4f4",markingsLineWidth:2,clickable:false,hoverable:false,autoHighlight:true,mouseActiveRadius:10},interaction:{redrawOverlayInterval:1e3/60},hooks:{}},surface=null,overlay=null,eventHolder=null,ctx=null,octx=null,xaxes=[],yaxes=[],plotOffset={left:0,right:0,top:0,bottom:0},plotWidth=0,plotHeight=0,hooks={processOptions:[],processRawData:[],processDatapoints:[],processOffset:[],drawBackground:[],drawSeries:[],draw:[],bindEvents:[],drawOverlay:[],shutdown:[]},plot=this;plot.setData=setData;plot.setupGrid=setupGrid;plot.draw=draw;plot.getPlaceholder=function(){return placeholder};plot.getCanvas=function(){return surface.element};plot.getPlotOffset=function(){return plotOffset};plot.width=function(){return plotWidth};plot.height=function(){return plotHeight};plot.offset=function(){var o=eventHolder.offset();o.left+=plotOffset.left;o.top+=plotOffset.top;return o};plot.getData=function(){return series};plot.getAxes=function(){var res={},i;$.each(xaxes.concat(yaxes),function(_,axis){if(axis)res[axis.direction+(axis.n!=1?axis.n:"")+"axis"]=axis});return res};plot.getXAxes=function(){return xaxes};plot.getYAxes=function(){return yaxes};plot.c2p=canvasToAxisCoords;plot.p2c=axisToCanvasCoords;plot.getOptions=function(){return options};plot.highlight=highlight;plot.unhighlight=unhighlight;plot.triggerRedrawOverlay=triggerRedrawOverlay;plot.pointOffset=function(point){return{left:parseInt(xaxes[axisNumber(point,"x")-1].p2c(+point.x)+plotOffset.left,10),top:parseInt(yaxes[axisNumber(point,"y")-1].p2c(+point.y)+plotOffset.top,10)}};plot.shutdown=shutdown;plot.destroy=function(){shutdown();placeholder.removeData("plot").empty();series=[];options=null;surface=null;overlay=null;eventHolder=null;ctx=null;octx=null;xaxes=[];yaxes=[];hooks=null;highlights=[];plot=null};plot.resize=function(){var width=placeholder.width(),height=placeholder.height();surface.resize(width,height);overlay.resize(width,height)};plot.hooks=hooks;initPlugins(plot);parseOptions(options_);setupCanvases();setData(data_);setupGrid();draw();bindEvents();function executeHooks(hook,args){args=[plot].concat(args);for(var i=0;i<hook.length;++i)hook[i].apply(this,args)}function initPlugins(){var classes={Canvas:Canvas};for(var i=0;i<plugins.length;++i){var p=plugins[i];p.init(plot,classes);if(p.options)$.extend(true,options,p.options)}}function parseOptions(opts){$.extend(true,options,opts);if(opts&&opts.colors){options.colors=opts.colors}if(options.xaxis.color==null)options.xaxis.color=$.color.parse(options.grid.color).scale("a",.22).toString();if(options.yaxis.color==null)options.yaxis.color=$.color.parse(options.grid.color).scale("a",.22).toString();if(options.xaxis.tickColor==null)options.xaxis.tickColor=options.grid.tickColor||options.xaxis.color;if(options.yaxis.tickColor==null)options.yaxis.tickColor=options.grid.tickColor||options.yaxis.color;if(options.grid.borderColor==null)options.grid.borderColor=options.grid.color;if(options.grid.tickColor==null)options.grid.tickColor=$.color.parse(options.grid.color).scale("a",.22).toString();var i,axisOptions,axisCount,fontSize=placeholder.css("font-size"),fontSizeDefault=fontSize?+fontSize.replace("px",""):13,fontDefaults={style:placeholder.css("font-style"),size:Math.round(.8*fontSizeDefault),variant:placeholder.css("font-variant"),weight:placeholder.css("font-weight"),family:placeholder.css("font-family")};axisCount=options.xaxes.length||1;for(i=0;i<axisCount;++i){axisOptions=options.xaxes[i];if(axisOptions&&!axisOptions.tickColor){axisOptions.tickColor=axisOptions.color}axisOptions=$.extend(true,{},options.xaxis,axisOptions);options.xaxes[i]=axisOptions;if(axisOptions.font){axisOptions.font=$.extend({},fontDefaults,axisOptions.font);if(!axisOptions.font.color){axisOptions.font.color=axisOptions.color}if(!axisOptions.font.lineHeight){axisOptions.font.lineHeight=Math.round(axisOptions.font.size*1.15)}}}axisCount=options.yaxes.length||1;for(i=0;i<axisCount;++i){axisOptions=options.yaxes[i];if(axisOptions&&!axisOptions.tickColor){axisOptions.tickColor=axisOptions.color}axisOptions=$.extend(true,{},options.yaxis,axisOptions);options.yaxes[i]=axisOptions;if(axisOptions.font){axisOptions.font=$.extend({},fontDefaults,axisOptions.font);if(!axisOptions.font.color){axisOptions.font.color=axisOptions.color}if(!axisOptions.font.lineHeight){axisOptions.font.lineHeight=Math.round(axisOptions.font.size*1.15)}}}if(options.xaxis.noTicks&&options.xaxis.ticks==null)options.xaxis.ticks=options.xaxis.noTicks;if(options.yaxis.noTicks&&options.yaxis.ticks==null)options.yaxis.ticks=options.yaxis.noTicks;if(options.x2axis){options.xaxes[1]=$.extend(true,{},options.xaxis,options.x2axis);options.xaxes[1].position="top";if(options.x2axis.min==null){options.xaxes[1].min=null}if(options.x2axis.max==null){options.xaxes[1].max=null}}if(options.y2axis){options.yaxes[1]=$.extend(true,{},options.yaxis,options.y2axis);options.yaxes[1].position="right";if(options.y2axis.min==null){options.yaxes[1].min=null}if(options.y2axis.max==null){options.yaxes[1].max=null}}if(options.grid.coloredAreas)options.grid.markings=options.grid.coloredAreas;if(options.grid.coloredAreasColor)options.grid.markingsColor=options.grid.coloredAreasColor;if(options.lines)$.extend(true,options.series.lines,options.lines);if(options.points)$.extend(true,options.series.points,options.points);if(options.bars)$.extend(true,options.series.bars,options.bars);if(options.shadowSize!=null)options.series.shadowSize=options.shadowSize;if(options.highlightColor!=null)options.series.highlightColor=options.highlightColor;for(i=0;i<options.xaxes.length;++i)getOrCreateAxis(xaxes,i+1).options=options.xaxes[i];for(i=0;i<options.yaxes.length;++i)getOrCreateAxis(yaxes,i+1).options=options.yaxes[i];for(var n in hooks)if(options.hooks[n]&&options.hooks[n].length)hooks[n]=hooks[n].concat(options.hooks[n]);executeHooks(hooks.processOptions,[options])}function setData(d){series=parseData(d);fillInSeriesOptions();processData()}function parseData(d){var res=[];for(var i=0;i<d.length;++i){var s=$.extend(true,{},options.series);if(d[i].data!=null){s.data=d[i].data;delete d[i].data;$.extend(true,s,d[i]);d[i].data=s.data}else s.data=d[i];res.push(s)}return res}function axisNumber(obj,coord){var a=obj[coord+"axis"];if(typeof a=="object")a=a.n;if(typeof a!="number")a=1;return a}function allAxes(){return $.grep(xaxes.concat(yaxes),function(a){return a})}function canvasToAxisCoords(pos){var res={},i,axis;for(i=0;i<xaxes.length;++i){axis=xaxes[i];if(axis&&axis.used)res["x"+axis.n]=axis.c2p(pos.left)}for(i=0;i<yaxes.length;++i){axis=yaxes[i];if(axis&&axis.used)res["y"+axis.n]=axis.c2p(pos.top)}if(res.x1!==undefined)res.x=res.x1;if(res.y1!==undefined)res.y=res.y1;return res}function axisToCanvasCoords(pos){var res={},i,axis,key;for(i=0;i<xaxes.length;++i){axis=xaxes[i];if(axis&&axis.used){key="x"+axis.n;if(pos[key]==null&&axis.n==1)key="x";if(pos[key]!=null){res.left=axis.p2c(pos[key]);break}}}for(i=0;i<yaxes.length;++i){axis=yaxes[i];if(axis&&axis.used){key="y"+axis.n;if(pos[key]==null&&axis.n==1)key="y";if(pos[key]!=null){res.top=axis.p2c(pos[key]);break}}}return res}function getOrCreateAxis(axes,number){if(!axes[number-1])axes[number-1]={n:number,direction:axes==xaxes?"x":"y",options:$.extend(true,{},axes==xaxes?options.xaxis:options.yaxis)};return axes[number-1]}function fillInSeriesOptions(){var neededColors=series.length,maxIndex=-1,i;for(i=0;i<series.length;++i){var sc=series[i].color;if(sc!=null){neededColors--;if(typeof sc=="number"&&sc>maxIndex){maxIndex=sc}}}if(neededColors<=maxIndex){neededColors=maxIndex+1}var c,colors=[],colorPool=options.colors,colorPoolSize=colorPool.length,variation=0;for(i=0;i<neededColors;i++){c=$.color.parse(colorPool[i%colorPoolSize]||"#666");if(i%colorPoolSize==0&&i){if(variation>=0){if(variation<.5){variation=-variation-.2}else variation=0}else variation=-variation}colors[i]=c.scale("rgb",1+variation)}var colori=0,s;for(i=0;i<series.length;++i){s=series[i];if(s.color==null){s.color=colors[colori].toString();++colori}else if(typeof s.color=="number")s.color=colors[s.color].toString();if(s.lines.show==null){var v,show=true;for(v in s)if(s[v]&&s[v].show){show=false;break}if(show)s.lines.show=true}if(s.lines.zero==null){s.lines.zero=!!s.lines.fill}s.xaxis=getOrCreateAxis(xaxes,axisNumber(s,"x"));s.yaxis=getOrCreateAxis(yaxes,axisNumber(s,"y"))}}function processData(){var topSentry=Number.POSITIVE_INFINITY,bottomSentry=Number.NEGATIVE_INFINITY,fakeInfinity=Number.MAX_VALUE,i,j,k,m,length,s,points,ps,x,y,axis,val,f,p,data,format;function updateAxis(axis,min,max){if(min<axis.datamin&&min!=-fakeInfinity)axis.datamin=min;if(max>axis.datamax&&max!=fakeInfinity)axis.datamax=max}$.each(allAxes(),function(_,axis){axis.datamin=topSentry;axis.datamax=bottomSentry;axis.used=false});for(i=0;i<series.length;++i){s=series[i];s.datapoints={points:[]};executeHooks(hooks.processRawData,[s,s.data,s.datapoints])}for(i=0;i<series.length;++i){s=series[i];data=s.data;format=s.datapoints.format;if(!format){format=[];format.push({x:true,number:true,required:true});format.push({y:true,number:true,required:true});if(s.bars.show||s.lines.show&&s.lines.fill){var autoscale=!!(s.bars.show&&s.bars.zero||s.lines.show&&s.lines.zero);format.push({y:true,number:true,required:false,defaultValue:0,autoscale:autoscale});if(s.bars.horizontal){delete format[format.length-1].y;format[format.length-1].x=true}}s.datapoints.format=format}if(s.datapoints.pointsize!=null)continue;s.datapoints.pointsize=format.length;ps=s.datapoints.pointsize;points=s.datapoints.points;var insertSteps=s.lines.show&&s.lines.steps;s.xaxis.used=s.yaxis.used=true;for(j=k=0;j<data.length;++j,k+=ps){p=data[j];var nullify=p==null;if(!nullify){for(m=0;m<ps;++m){val=p[m];f=format[m];if(f){if(f.number&&val!=null){val=+val;if(isNaN(val))val=null;else if(val==Infinity)val=fakeInfinity;else if(val==-Infinity)val=-fakeInfinity}if(val==null){if(f.required)nullify=true;if(f.defaultValue!=null)val=f.defaultValue}}points[k+m]=val}}if(nullify){for(m=0;m<ps;++m){val=points[k+m];if(val!=null){f=format[m];if(f.autoscale!==false){if(f.x){updateAxis(s.xaxis,val,val)}if(f.y){updateAxis(s.yaxis,val,val)}}}points[k+m]=null}}else{if(insertSteps&&k>0&&points[k-ps]!=null&&points[k-ps]!=points[k]&&points[k-ps+1]!=points[k+1]){for(m=0;m<ps;++m)points[k+ps+m]=points[k+m];points[k+1]=points[k-ps+1];k+=ps}}}}for(i=0;i<series.length;++i){s=series[i];executeHooks(hooks.processDatapoints,[s,s.datapoints])}for(i=0;i<series.length;++i){s=series[i];points=s.datapoints.points;ps=s.datapoints.pointsize;format=s.datapoints.format;var xmin=topSentry,ymin=topSentry,xmax=bottomSentry,ymax=bottomSentry;for(j=0;j<points.length;j+=ps){if(points[j]==null)continue;for(m=0;m<ps;++m){val=points[j+m];f=format[m];if(!f||f.autoscale===false||val==fakeInfinity||val==-fakeInfinity)continue;if(f.x){if(val<xmin)xmin=val;if(val>xmax)xmax=val}if(f.y){if(val<ymin)ymin=val;if(val>ymax)ymax=val}}}if(s.bars.show){var delta;switch(s.bars.align){case"left":delta=0;break;case"right":delta=-s.bars.barWidth;break;default:delta=-s.bars.barWidth/2}if(s.bars.horizontal){ymin+=delta;ymax+=delta+s.bars.barWidth}else{xmin+=delta;xmax+=delta+s.bars.barWidth}}updateAxis(s.xaxis,xmin,xmax);updateAxis(s.yaxis,ymin,ymax)}$.each(allAxes(),function(_,axis){if(axis.datamin==topSentry)axis.datamin=null;if(axis.datamax==bottomSentry)axis.datamax=null})}function setupCanvases(){placeholder.css("padding",0).children().filter(function(){return!$(this).hasClass("flot-overlay")&&!$(this).hasClass("flot-base")}).remove();if(placeholder.css("position")=="static")placeholder.css("position","relative");surface=new Canvas("flot-base",placeholder);overlay=new Canvas("flot-overlay",placeholder);ctx=surface.context;octx=overlay.context;eventHolder=$(overlay.element).unbind();var existing=placeholder.data("plot");if(existing){existing.shutdown();overlay.clear()}placeholder.data("plot",plot)}function bindEvents(){if(options.grid.hoverable){eventHolder.mousemove(onMouseMove);eventHolder.bind("mouseleave",onMouseLeave)}if(options.grid.clickable)eventHolder.click(onClick);executeHooks(hooks.bindEvents,[eventHolder])}function shutdown(){if(redrawTimeout)clearTimeout(redrawTimeout);eventHolder.unbind("mousemove",onMouseMove);eventHolder.unbind("mouseleave",onMouseLeave);eventHolder.unbind("click",onClick);executeHooks(hooks.shutdown,[eventHolder])}function setTransformationHelpers(axis){function identity(x){return x}var s,m,t=axis.options.transform||identity,it=axis.options.inverseTransform;if(axis.direction=="x"){s=axis.scale=plotWidth/Math.abs(t(axis.max)-t(axis.min));m=Math.min(t(axis.max),t(axis.min))}else{s=axis.scale=plotHeight/Math.abs(t(axis.max)-t(axis.min));s=-s;m=Math.max(t(axis.max),t(axis.min))}if(t==identity)axis.p2c=function(p){return(p-m)*s};else axis.p2c=function(p){return(t(p)-m)*s};if(!it)axis.c2p=function(c){return m+c/s};else axis.c2p=function(c){return it(m+c/s)}}function measureTickLabels(axis){var opts=axis.options,ticks=axis.ticks||[],labelWidth=opts.labelWidth||0,labelHeight=opts.labelHeight||0,maxWidth=labelWidth||(axis.direction=="x"?Math.floor(surface.width/(ticks.length||1)):null),legacyStyles=axis.direction+"Axis "+axis.direction+axis.n+"Axis",layer="flot-"+axis.direction+"-axis flot-"+axis.direction+axis.n+"-axis "+legacyStyles,font=opts.font||"flot-tick-label tickLabel";for(var i=0;i<ticks.length;++i){var t=ticks[i];if(!t.label)continue;var info=surface.getTextInfo(layer,t.label,font,null,maxWidth);labelWidth=Math.max(labelWidth,info.width);labelHeight=Math.max(labelHeight,info.height)}axis.labelWidth=opts.labelWidth||labelWidth;axis.labelHeight=opts.labelHeight||labelHeight}function allocateAxisBoxFirstPhase(axis){var lw=axis.labelWidth,lh=axis.labelHeight,pos=axis.options.position,isXAxis=axis.direction==="x",tickLength=axis.options.tickLength,axisMargin=options.grid.axisMargin,padding=options.grid.labelMargin,innermost=true,outermost=true,first=true,found=false;$.each(isXAxis?xaxes:yaxes,function(i,a){if(a&&(a.show||a.reserveSpace)){if(a===axis){found=true}else if(a.options.position===pos){if(found){outermost=false}else{innermost=false}}if(!found){first=false}}});if(outermost){axisMargin=0}if(tickLength==null){tickLength=first?"full":5}if(!isNaN(+tickLength))padding+=+tickLength;if(isXAxis){lh+=padding;if(pos=="bottom"){plotOffset.bottom+=lh+axisMargin;axis.box={top:surface.height-plotOffset.bottom,height:lh}}else{axis.box={top:plotOffset.top+axisMargin,height:lh};plotOffset.top+=lh+axisMargin}}else{lw+=padding;if(pos=="left"){axis.box={left:plotOffset.left+axisMargin,width:lw};plotOffset.left+=lw+axisMargin}else{plotOffset.right+=lw+axisMargin;axis.box={left:surface.width-plotOffset.right,width:lw}}}axis.position=pos;axis.tickLength=tickLength;axis.box.padding=padding;axis.innermost=innermost}function allocateAxisBoxSecondPhase(axis){if(axis.direction=="x"){axis.box.left=plotOffset.left-axis.labelWidth/2;axis.box.width=surface.width-plotOffset.left-plotOffset.right+axis.labelWidth}else{axis.box.top=plotOffset.top-axis.labelHeight/2;axis.box.height=surface.height-plotOffset.bottom-plotOffset.top+axis.labelHeight}}function adjustLayoutForThingsStickingOut(){var minMargin=options.grid.minBorderMargin,axis,i;if(minMargin==null){minMargin=0;for(i=0;i<series.length;++i)minMargin=Math.max(minMargin,2*(series[i].points.radius+series[i].points.lineWidth/2))}var margins={left:minMargin,right:minMargin,top:minMargin,bottom:minMargin};$.each(allAxes(),function(_,axis){if(axis.reserveSpace&&axis.ticks&&axis.ticks.length){if(axis.direction==="x"){margins.left=Math.max(margins.left,axis.labelWidth/2);margins.right=Math.max(margins.right,axis.labelWidth/2)}else{margins.bottom=Math.max(margins.bottom,axis.labelHeight/2);margins.top=Math.max(margins.top,axis.labelHeight/2)}}});plotOffset.left=Math.ceil(Math.max(margins.left,plotOffset.left));plotOffset.right=Math.ceil(Math.max(margins.right,plotOffset.right));plotOffset.top=Math.ceil(Math.max(margins.top,plotOffset.top));plotOffset.bottom=Math.ceil(Math.max(margins.bottom,plotOffset.bottom))}function setupGrid(){var i,axes=allAxes(),showGrid=options.grid.show;for(var a in plotOffset){var margin=options.grid.margin||0;plotOffset[a]=typeof margin=="number"?margin:margin[a]||0}executeHooks(hooks.processOffset,[plotOffset]);for(var a in plotOffset){if(typeof options.grid.borderWidth=="object"){plotOffset[a]+=showGrid?options.grid.borderWidth[a]:0}else{plotOffset[a]+=showGrid?options.grid.borderWidth:0}}$.each(axes,function(_,axis){var axisOpts=axis.options;axis.show=axisOpts.show==null?axis.used:axisOpts.show;axis.reserveSpace=axisOpts.reserveSpace==null?axis.show:axisOpts.reserveSpace;setRange(axis)});if(showGrid){var allocatedAxes=$.grep(axes,function(axis){return axis.show||axis.reserveSpace});$.each(allocatedAxes,function(_,axis){setupTickGeneration(axis);setTicks(axis);snapRangeToTicks(axis,axis.ticks);measureTickLabels(axis)});for(i=allocatedAxes.length-1;i>=0;--i)allocateAxisBoxFirstPhase(allocatedAxes[i]);adjustLayoutForThingsStickingOut();$.each(allocatedAxes,function(_,axis){allocateAxisBoxSecondPhase(axis)})}plotWidth=surface.width-plotOffset.left-plotOffset.right;plotHeight=surface.height-plotOffset.bottom-plotOffset.top;$.each(axes,function(_,axis){setTransformationHelpers(axis)});if(showGrid){drawAxisLabels()}insertLegend()}function setRange(axis){var opts=axis.options,min=+(opts.min!=null?opts.min:axis.datamin),max=+(opts.max!=null?opts.max:axis.datamax),delta=max-min;if(delta==0){var widen=max==0?1:.01;if(opts.min==null)min-=widen;if(opts.max==null||opts.min!=null)max+=widen}else{var margin=opts.autoscaleMargin;if(margin!=null){if(opts.min==null){min-=delta*margin;if(min<0&&axis.datamin!=null&&axis.datamin>=0)min=0}if(opts.max==null){max+=delta*margin;if(max>0&&axis.datamax!=null&&axis.datamax<=0)max=0}}}axis.min=min;axis.max=max}function setupTickGeneration(axis){var opts=axis.options;var noTicks;if(typeof opts.ticks=="number"&&opts.ticks>0)noTicks=opts.ticks;else noTicks=.3*Math.sqrt(axis.direction=="x"?surface.width:surface.height);var delta=(axis.max-axis.min)/noTicks,dec=-Math.floor(Math.log(delta)/Math.LN10),maxDec=opts.tickDecimals;if(maxDec!=null&&dec>maxDec){dec=maxDec}var magn=Math.pow(10,-dec),norm=delta/magn,size;if(norm<1.5){size=1}else if(norm<3){size=2;if(norm>2.25&&(maxDec==null||dec+1<=maxDec)){size=2.5;++dec}}else if(norm<7.5){size=5}else{size=10}size*=magn;if(opts.minTickSize!=null&&size<opts.minTickSize){size=opts.minTickSize}axis.delta=delta;axis.tickDecimals=Math.max(0,maxDec!=null?maxDec:dec);axis.tickSize=opts.tickSize||size;if(opts.mode=="time"&&!axis.tickGenerator){throw new Error("Time mode requires the flot.time plugin.")}if(!axis.tickGenerator){axis.tickGenerator=function(axis){var ticks=[],start=floorInBase(axis.min,axis.tickSize),i=0,v=Number.NaN,prev;do{prev=v;v=start+i*axis.tickSize;ticks.push(v);++i}while(v<axis.max&&v!=prev);return ticks};axis.tickFormatter=function(value,axis){var factor=axis.tickDecimals?Math.pow(10,axis.tickDecimals):1;var formatted=""+Math.round(value*factor)/factor;if(axis.tickDecimals!=null){var decimal=formatted.indexOf(".");var precision=decimal==-1?0:formatted.length-decimal-1;if(precision<axis.tickDecimals){return(precision?formatted:formatted+".")+(""+factor).substr(1,axis.tickDecimals-precision)}}return formatted}}if($.isFunction(opts.tickFormatter))axis.tickFormatter=function(v,axis){return""+opts.tickFormatter(v,axis)};if(opts.alignTicksWithAxis!=null){var otherAxis=(axis.direction=="x"?xaxes:yaxes)[opts.alignTicksWithAxis-1];if(otherAxis&&otherAxis.used&&otherAxis!=axis){var niceTicks=axis.tickGenerator(axis);if(niceTicks.length>0){if(opts.min==null)axis.min=Math.min(axis.min,niceTicks[0]);if(opts.max==null&&niceTicks.length>1)axis.max=Math.max(axis.max,niceTicks[niceTicks.length-1])}axis.tickGenerator=function(axis){var ticks=[],v,i;for(i=0;i<otherAxis.ticks.length;++i){v=(otherAxis.ticks[i].v-otherAxis.min)/(otherAxis.max-otherAxis.min);v=axis.min+v*(axis.max-axis.min);ticks.push(v)}return ticks};if(!axis.mode&&opts.tickDecimals==null){var extraDec=Math.max(0,-Math.floor(Math.log(axis.delta)/Math.LN10)+1),ts=axis.tickGenerator(axis);if(!(ts.length>1&&/\..*0$/.test((ts[1]-ts[0]).toFixed(extraDec))))axis.tickDecimals=extraDec}}}}function setTicks(axis){var oticks=axis.options.ticks,ticks=[];if(oticks==null||typeof oticks=="number"&&oticks>0)ticks=axis.tickGenerator(axis);else if(oticks){if($.isFunction(oticks))ticks=oticks(axis);else ticks=oticks}var i,v;axis.ticks=[];for(i=0;i<ticks.length;++i){var label=null;var t=ticks[i];if(typeof t=="object"){v=+t[0];if(t.length>1)label=t[1]}else v=+t;if(label==null)label=axis.tickFormatter(v,axis);if(!isNaN(v))axis.ticks.push({v:v,label:label})}}function snapRangeToTicks(axis,ticks){if(axis.options.autoscaleMargin&&ticks.length>0){if(axis.options.min==null)axis.min=Math.min(axis.min,ticks[0].v);if(axis.options.max==null&&ticks.length>1)axis.max=Math.max(axis.max,ticks[ticks.length-1].v)}}function draw(){surface.clear();executeHooks(hooks.drawBackground,[ctx]);var grid=options.grid;if(grid.show&&grid.backgroundColor)drawBackground();if(grid.show&&!grid.aboveData){drawGrid()}for(var i=0;i<series.length;++i){executeHooks(hooks.drawSeries,[ctx,series[i]]);drawSeries(series[i])}executeHooks(hooks.draw,[ctx]);if(grid.show&&grid.aboveData){drawGrid()}surface.render();triggerRedrawOverlay()}function extractRange(ranges,coord){var axis,from,to,key,axes=allAxes();for(var i=0;i<axes.length;++i){axis=axes[i];if(axis.direction==coord){key=coord+axis.n+"axis";if(!ranges[key]&&axis.n==1)key=coord+"axis";if(ranges[key]){from=ranges[key].from;to=ranges[key].to;break}}}if(!ranges[key]){axis=coord=="x"?xaxes[0]:yaxes[0];from=ranges[coord+"1"];to=ranges[coord+"2"]}if(from!=null&&to!=null&&from>to){var tmp=from;from=to;to=tmp}return{from:from,to:to,axis:axis}}function drawBackground(){ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.fillStyle=getColorOrGradient(options.grid.backgroundColor,plotHeight,0,"rgba(255, 255, 255, 0)");ctx.fillRect(0,0,plotWidth,plotHeight);ctx.restore()}function drawGrid(){var i,axes,bw,bc;ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var markings=options.grid.markings;if(markings){if($.isFunction(markings)){axes=plot.getAxes();axes.xmin=axes.xaxis.min;axes.xmax=axes.xaxis.max;axes.ymin=axes.yaxis.min;axes.ymax=axes.yaxis.max;markings=markings(axes)}for(i=0;i<markings.length;++i){var m=markings[i],xrange=extractRange(m,"x"),yrange=extractRange(m,"y");if(xrange.from==null)xrange.from=xrange.axis.min;if(xrange.to==null)xrange.to=xrange.axis.max;
+if(yrange.from==null)yrange.from=yrange.axis.min;if(yrange.to==null)yrange.to=yrange.axis.max;if(xrange.to<xrange.axis.min||xrange.from>xrange.axis.max||yrange.to<yrange.axis.min||yrange.from>yrange.axis.max)continue;xrange.from=Math.max(xrange.from,xrange.axis.min);xrange.to=Math.min(xrange.to,xrange.axis.max);yrange.from=Math.max(yrange.from,yrange.axis.min);yrange.to=Math.min(yrange.to,yrange.axis.max);var xequal=xrange.from===xrange.to,yequal=yrange.from===yrange.to;if(xequal&&yequal){continue}xrange.from=Math.floor(xrange.axis.p2c(xrange.from));xrange.to=Math.floor(xrange.axis.p2c(xrange.to));yrange.from=Math.floor(yrange.axis.p2c(yrange.from));yrange.to=Math.floor(yrange.axis.p2c(yrange.to));if(xequal||yequal){var lineWidth=m.lineWidth||options.grid.markingsLineWidth,subPixel=lineWidth%2?.5:0;ctx.beginPath();ctx.strokeStyle=m.color||options.grid.markingsColor;ctx.lineWidth=lineWidth;if(xequal){ctx.moveTo(xrange.to+subPixel,yrange.from);ctx.lineTo(xrange.to+subPixel,yrange.to)}else{ctx.moveTo(xrange.from,yrange.to+subPixel);ctx.lineTo(xrange.to,yrange.to+subPixel)}ctx.stroke()}else{ctx.fillStyle=m.color||options.grid.markingsColor;ctx.fillRect(xrange.from,yrange.to,xrange.to-xrange.from,yrange.from-yrange.to)}}}axes=allAxes();bw=options.grid.borderWidth;for(var j=0;j<axes.length;++j){var axis=axes[j],box=axis.box,t=axis.tickLength,x,y,xoff,yoff;if(!axis.show||axis.ticks.length==0)continue;ctx.lineWidth=1;if(axis.direction=="x"){x=0;if(t=="full")y=axis.position=="top"?0:plotHeight;else y=box.top-plotOffset.top+(axis.position=="top"?box.height:0)}else{y=0;if(t=="full")x=axis.position=="left"?0:plotWidth;else x=box.left-plotOffset.left+(axis.position=="left"?box.width:0)}if(!axis.innermost){ctx.strokeStyle=axis.options.color;ctx.beginPath();xoff=yoff=0;if(axis.direction=="x")xoff=plotWidth+1;else yoff=plotHeight+1;if(ctx.lineWidth==1){if(axis.direction=="x"){y=Math.floor(y)+.5}else{x=Math.floor(x)+.5}}ctx.moveTo(x,y);ctx.lineTo(x+xoff,y+yoff);ctx.stroke()}ctx.strokeStyle=axis.options.tickColor;ctx.beginPath();for(i=0;i<axis.ticks.length;++i){var v=axis.ticks[i].v;xoff=yoff=0;if(isNaN(v)||v<axis.min||v>axis.max||t=="full"&&(typeof bw=="object"&&bw[axis.position]>0||bw>0)&&(v==axis.min||v==axis.max))continue;if(axis.direction=="x"){x=axis.p2c(v);yoff=t=="full"?-plotHeight:t;if(axis.position=="top")yoff=-yoff}else{y=axis.p2c(v);xoff=t=="full"?-plotWidth:t;if(axis.position=="left")xoff=-xoff}if(ctx.lineWidth==1){if(axis.direction=="x")x=Math.floor(x)+.5;else y=Math.floor(y)+.5}ctx.moveTo(x,y);ctx.lineTo(x+xoff,y+yoff)}ctx.stroke()}if(bw){bc=options.grid.borderColor;if(typeof bw=="object"||typeof bc=="object"){if(typeof bw!=="object"){bw={top:bw,right:bw,bottom:bw,left:bw}}if(typeof bc!=="object"){bc={top:bc,right:bc,bottom:bc,left:bc}}if(bw.top>0){ctx.strokeStyle=bc.top;ctx.lineWidth=bw.top;ctx.beginPath();ctx.moveTo(0-bw.left,0-bw.top/2);ctx.lineTo(plotWidth,0-bw.top/2);ctx.stroke()}if(bw.right>0){ctx.strokeStyle=bc.right;ctx.lineWidth=bw.right;ctx.beginPath();ctx.moveTo(plotWidth+bw.right/2,0-bw.top);ctx.lineTo(plotWidth+bw.right/2,plotHeight);ctx.stroke()}if(bw.bottom>0){ctx.strokeStyle=bc.bottom;ctx.lineWidth=bw.bottom;ctx.beginPath();ctx.moveTo(plotWidth+bw.right,plotHeight+bw.bottom/2);ctx.lineTo(0,plotHeight+bw.bottom/2);ctx.stroke()}if(bw.left>0){ctx.strokeStyle=bc.left;ctx.lineWidth=bw.left;ctx.beginPath();ctx.moveTo(0-bw.left/2,plotHeight+bw.bottom);ctx.lineTo(0-bw.left/2,0);ctx.stroke()}}else{ctx.lineWidth=bw;ctx.strokeStyle=options.grid.borderColor;ctx.strokeRect(-bw/2,-bw/2,plotWidth+bw,plotHeight+bw)}}ctx.restore()}function drawAxisLabels(){$.each(allAxes(),function(_,axis){var box=axis.box,legacyStyles=axis.direction+"Axis "+axis.direction+axis.n+"Axis",layer="flot-"+axis.direction+"-axis flot-"+axis.direction+axis.n+"-axis "+legacyStyles,font=axis.options.font||"flot-tick-label tickLabel",tick,x,y,halign,valign;surface.removeText(layer);if(!axis.show||axis.ticks.length==0)return;for(var i=0;i<axis.ticks.length;++i){tick=axis.ticks[i];if(!tick.label||tick.v<axis.min||tick.v>axis.max)continue;if(axis.direction=="x"){halign="center";x=plotOffset.left+axis.p2c(tick.v);if(axis.position=="bottom"){y=box.top+box.padding}else{y=box.top+box.height-box.padding;valign="bottom"}}else{valign="middle";y=plotOffset.top+axis.p2c(tick.v);if(axis.position=="left"){x=box.left+box.width-box.padding;halign="right"}else{x=box.left+box.padding}}surface.addText(layer,x,y,tick.label,font,null,null,halign,valign)}})}function drawSeries(series){if(series.lines.show)drawSeriesLines(series);if(series.bars.show)drawSeriesBars(series);if(series.points.show)drawSeriesPoints(series)}function drawSeriesLines(series){function plotLine(datapoints,xoffset,yoffset,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize,prevx=null,prevy=null;ctx.beginPath();for(var i=ps;i<points.length;i+=ps){var x1=points[i-ps],y1=points[i-ps+1],x2=points[i],y2=points[i+1];if(x1==null||x2==null)continue;if(y1<=y2&&y1<axisy.min){if(y2<axisy.min)continue;x1=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.min}else if(y2<=y1&&y2<axisy.min){if(y1<axisy.min)continue;x2=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.min}if(y1>=y2&&y1>axisy.max){if(y2>axisy.max)continue;x1=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.max}else if(y2>=y1&&y2>axisy.max){if(y1>axisy.max)continue;x2=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.max}if(x1<=x2&&x1<axisx.min){if(x2<axisx.min)continue;y1=(axisx.min-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.min}else if(x2<=x1&&x2<axisx.min){if(x1<axisx.min)continue;y2=(axisx.min-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.min}if(x1>=x2&&x1>axisx.max){if(x2>axisx.max)continue;y1=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.max}else if(x2>=x1&&x2>axisx.max){if(x1>axisx.max)continue;y2=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.max}if(x1!=prevx||y1!=prevy)ctx.moveTo(axisx.p2c(x1)+xoffset,axisy.p2c(y1)+yoffset);prevx=x2;prevy=y2;ctx.lineTo(axisx.p2c(x2)+xoffset,axisy.p2c(y2)+yoffset)}ctx.stroke()}function plotLineArea(datapoints,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize,bottom=Math.min(Math.max(0,axisy.min),axisy.max),i=0,top,areaOpen=false,ypos=1,segmentStart=0,segmentEnd=0;while(true){if(ps>0&&i>points.length+ps)break;i+=ps;var x1=points[i-ps],y1=points[i-ps+ypos],x2=points[i],y2=points[i+ypos];if(areaOpen){if(ps>0&&x1!=null&&x2==null){segmentEnd=i;ps=-ps;ypos=2;continue}if(ps<0&&i==segmentStart+ps){ctx.fill();areaOpen=false;ps=-ps;ypos=1;i=segmentStart=segmentEnd+ps;continue}}if(x1==null||x2==null)continue;if(x1<=x2&&x1<axisx.min){if(x2<axisx.min)continue;y1=(axisx.min-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.min}else if(x2<=x1&&x2<axisx.min){if(x1<axisx.min)continue;y2=(axisx.min-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.min}if(x1>=x2&&x1>axisx.max){if(x2>axisx.max)continue;y1=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x1=axisx.max}else if(x2>=x1&&x2>axisx.max){if(x1>axisx.max)continue;y2=(axisx.max-x1)/(x2-x1)*(y2-y1)+y1;x2=axisx.max}if(!areaOpen){ctx.beginPath();ctx.moveTo(axisx.p2c(x1),axisy.p2c(bottom));areaOpen=true}if(y1>=axisy.max&&y2>=axisy.max){ctx.lineTo(axisx.p2c(x1),axisy.p2c(axisy.max));ctx.lineTo(axisx.p2c(x2),axisy.p2c(axisy.max));continue}else if(y1<=axisy.min&&y2<=axisy.min){ctx.lineTo(axisx.p2c(x1),axisy.p2c(axisy.min));ctx.lineTo(axisx.p2c(x2),axisy.p2c(axisy.min));continue}var x1old=x1,x2old=x2;if(y1<=y2&&y1<axisy.min&&y2>=axisy.min){x1=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.min}else if(y2<=y1&&y2<axisy.min&&y1>=axisy.min){x2=(axisy.min-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.min}if(y1>=y2&&y1>axisy.max&&y2<=axisy.max){x1=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y1=axisy.max}else if(y2>=y1&&y2>axisy.max&&y1<=axisy.max){x2=(axisy.max-y1)/(y2-y1)*(x2-x1)+x1;y2=axisy.max}if(x1!=x1old){ctx.lineTo(axisx.p2c(x1old),axisy.p2c(y1))}ctx.lineTo(axisx.p2c(x1),axisy.p2c(y1));ctx.lineTo(axisx.p2c(x2),axisy.p2c(y2));if(x2!=x2old){ctx.lineTo(axisx.p2c(x2),axisy.p2c(y2));ctx.lineTo(axisx.p2c(x2old),axisy.p2c(y2))}}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.lineJoin="round";var lw=series.lines.lineWidth,sw=series.shadowSize;if(lw>0&&sw>0){ctx.lineWidth=sw;ctx.strokeStyle="rgba(0,0,0,0.1)";var angle=Math.PI/18;plotLine(series.datapoints,Math.sin(angle)*(lw/2+sw/2),Math.cos(angle)*(lw/2+sw/2),series.xaxis,series.yaxis);ctx.lineWidth=sw/2;plotLine(series.datapoints,Math.sin(angle)*(lw/2+sw/4),Math.cos(angle)*(lw/2+sw/4),series.xaxis,series.yaxis)}ctx.lineWidth=lw;ctx.strokeStyle=series.color;var fillStyle=getFillStyle(series.lines,series.color,0,plotHeight);if(fillStyle){ctx.fillStyle=fillStyle;plotLineArea(series.datapoints,series.xaxis,series.yaxis)}if(lw>0)plotLine(series.datapoints,0,0,series.xaxis,series.yaxis);ctx.restore()}function drawSeriesPoints(series){function plotPoints(datapoints,radius,fillStyle,offset,shadow,axisx,axisy,symbol){var points=datapoints.points,ps=datapoints.pointsize;for(var i=0;i<points.length;i+=ps){var x=points[i],y=points[i+1];if(x==null||x<axisx.min||x>axisx.max||y<axisy.min||y>axisy.max)continue;ctx.beginPath();x=axisx.p2c(x);y=axisy.p2c(y)+offset;if(symbol=="circle")ctx.arc(x,y,radius,0,shadow?Math.PI:Math.PI*2,false);else symbol(ctx,x,y,radius,shadow);ctx.closePath();if(fillStyle){ctx.fillStyle=fillStyle;ctx.fill()}ctx.stroke()}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var lw=series.points.lineWidth,sw=series.shadowSize,radius=series.points.radius,symbol=series.points.symbol;if(lw==0)lw=1e-4;if(lw>0&&sw>0){var w=sw/2;ctx.lineWidth=w;ctx.strokeStyle="rgba(0,0,0,0.1)";plotPoints(series.datapoints,radius,null,w+w/2,true,series.xaxis,series.yaxis,symbol);ctx.strokeStyle="rgba(0,0,0,0.2)";plotPoints(series.datapoints,radius,null,w/2,true,series.xaxis,series.yaxis,symbol)}ctx.lineWidth=lw;ctx.strokeStyle=series.color;plotPoints(series.datapoints,radius,getFillStyle(series.points,series.color),0,false,series.xaxis,series.yaxis,symbol);ctx.restore()}function drawBar(x,y,b,barLeft,barRight,fillStyleCallback,axisx,axisy,c,horizontal,lineWidth){var left,right,bottom,top,drawLeft,drawRight,drawTop,drawBottom,tmp;if(horizontal){drawBottom=drawRight=drawTop=true;drawLeft=false;left=b;right=x;top=y+barLeft;bottom=y+barRight;if(right<left){tmp=right;right=left;left=tmp;drawLeft=true;drawRight=false}}else{drawLeft=drawRight=drawTop=true;drawBottom=false;left=x+barLeft;right=x+barRight;bottom=b;top=y;if(top<bottom){tmp=top;top=bottom;bottom=tmp;drawBottom=true;drawTop=false}}if(right<axisx.min||left>axisx.max||top<axisy.min||bottom>axisy.max)return;if(left<axisx.min){left=axisx.min;drawLeft=false}if(right>axisx.max){right=axisx.max;drawRight=false}if(bottom<axisy.min){bottom=axisy.min;drawBottom=false}if(top>axisy.max){top=axisy.max;drawTop=false}left=axisx.p2c(left);bottom=axisy.p2c(bottom);right=axisx.p2c(right);top=axisy.p2c(top);if(fillStyleCallback){c.fillStyle=fillStyleCallback(bottom,top);c.fillRect(left,top,right-left,bottom-top)}if(lineWidth>0&&(drawLeft||drawRight||drawTop||drawBottom)){c.beginPath();c.moveTo(left,bottom);if(drawLeft)c.lineTo(left,top);else c.moveTo(left,top);if(drawTop)c.lineTo(right,top);else c.moveTo(right,top);if(drawRight)c.lineTo(right,bottom);else c.moveTo(right,bottom);if(drawBottom)c.lineTo(left,bottom);else c.moveTo(left,bottom);c.stroke()}}function drawSeriesBars(series){function plotBars(datapoints,barLeft,barRight,fillStyleCallback,axisx,axisy){var points=datapoints.points,ps=datapoints.pointsize;for(var i=0;i<points.length;i+=ps){if(points[i]==null)continue;drawBar(points[i],points[i+1],points[i+2],barLeft,barRight,fillStyleCallback,axisx,axisy,ctx,series.bars.horizontal,series.bars.lineWidth)}}ctx.save();ctx.translate(plotOffset.left,plotOffset.top);ctx.lineWidth=series.bars.lineWidth;ctx.strokeStyle=series.color;var barLeft;switch(series.bars.align){case"left":barLeft=0;break;case"right":barLeft=-series.bars.barWidth;break;default:barLeft=-series.bars.barWidth/2}var fillStyleCallback=series.bars.fill?function(bottom,top){return getFillStyle(series.bars,series.color,bottom,top)}:null;plotBars(series.datapoints,barLeft,barLeft+series.bars.barWidth,fillStyleCallback,series.xaxis,series.yaxis);ctx.restore()}function getFillStyle(filloptions,seriesColor,bottom,top){var fill=filloptions.fill;if(!fill)return null;if(filloptions.fillColor)return getColorOrGradient(filloptions.fillColor,bottom,top,seriesColor);var c=$.color.parse(seriesColor);c.a=typeof fill=="number"?fill:.4;c.normalize();return c.toString()}function insertLegend(){if(options.legend.container!=null){$(options.legend.container).html("")}else{placeholder.find(".legend").remove()}if(!options.legend.show){return}var fragments=[],entries=[],rowStarted=false,lf=options.legend.labelFormatter,s,label;for(var i=0;i<series.length;++i){s=series[i];if(s.label){label=lf?lf(s.label,s):s.label;if(label){entries.push({label:label,color:s.color})}}}if(options.legend.sorted){if($.isFunction(options.legend.sorted)){entries.sort(options.legend.sorted)}else if(options.legend.sorted=="reverse"){entries.reverse()}else{var ascending=options.legend.sorted!="descending";entries.sort(function(a,b){return a.label==b.label?0:a.label<b.label!=ascending?1:-1})}}for(var i=0;i<entries.length;++i){var entry=entries[i];if(i%options.legend.noColumns==0){if(rowStarted)fragments.push("</tr>");fragments.push("<tr>");rowStarted=true}fragments.push('<td class="legendColorBox"><div style="border:1px solid '+options.legend.labelBoxBorderColor+';padding:1px"><div style="width:4px;height:0;border:5px solid '+entry.color+';overflow:hidden"></div></div></td>'+'<td class="legendLabel">'+entry.label+"</td>")}if(rowStarted)fragments.push("</tr>");if(fragments.length==0)return;var table='<table style="font-size:smaller;color:'+options.grid.color+'">'+fragments.join("")+"</table>";if(options.legend.container!=null)$(options.legend.container).html(table);else{var pos="",p=options.legend.position,m=options.legend.margin;if(m[0]==null)m=[m,m];if(p.charAt(0)=="n")pos+="top:"+(m[1]+plotOffset.top)+"px;";else if(p.charAt(0)=="s")pos+="bottom:"+(m[1]+plotOffset.bottom)+"px;";if(p.charAt(1)=="e")pos+="right:"+(m[0]+plotOffset.right)+"px;";else if(p.charAt(1)=="w")pos+="left:"+(m[0]+plotOffset.left)+"px;";var legend=$('<div class="legend">'+table.replace('style="','style="position:absolute;'+pos+";")+"</div>").appendTo(placeholder);if(options.legend.backgroundOpacity!=0){var c=options.legend.backgroundColor;if(c==null){c=options.grid.backgroundColor;if(c&&typeof c=="string")c=$.color.parse(c);else c=$.color.extract(legend,"background-color");c.a=1;c=c.toString()}var div=legend.children();$('<div style="position:absolute;width:'+div.width()+"px;height:"+div.height()+"px;"+pos+"background-color:"+c+';"> </div>').prependTo(legend).css("opacity",options.legend.backgroundOpacity)}}}var highlights=[],redrawTimeout=null;function findNearbyItem(mouseX,mouseY,seriesFilter){var maxDistance=options.grid.mouseActiveRadius,smallestDistance=maxDistance*maxDistance+1,item=null,foundPoint=false,i,j,ps;for(i=series.length-1;i>=0;--i){if(!seriesFilter(series[i]))continue;var s=series[i],axisx=s.xaxis,axisy=s.yaxis,points=s.datapoints.points,mx=axisx.c2p(mouseX),my=axisy.c2p(mouseY),maxx=maxDistance/axisx.scale,maxy=maxDistance/axisy.scale;ps=s.datapoints.pointsize;if(axisx.options.inverseTransform)maxx=Number.MAX_VALUE;if(axisy.options.inverseTransform)maxy=Number.MAX_VALUE;if(s.lines.show||s.points.show){for(j=0;j<points.length;j+=ps){var x=points[j],y=points[j+1];if(x==null)continue;if(x-mx>maxx||x-mx<-maxx||y-my>maxy||y-my<-maxy)continue;var dx=Math.abs(axisx.p2c(x)-mouseX),dy=Math.abs(axisy.p2c(y)-mouseY),dist=dx*dx+dy*dy;if(dist<smallestDistance){smallestDistance=dist;item=[i,j/ps]}}}if(s.bars.show&&!item){var barLeft,barRight;switch(s.bars.align){case"left":barLeft=0;break;case"right":barLeft=-s.bars.barWidth;break;default:barLeft=-s.bars.barWidth/2}barRight=barLeft+s.bars.barWidth;for(j=0;j<points.length;j+=ps){var x=points[j],y=points[j+1],b=points[j+2];if(x==null)continue;if(series[i].bars.horizontal?mx<=Math.max(b,x)&&mx>=Math.min(b,x)&&my>=y+barLeft&&my<=y+barRight:mx>=x+barLeft&&mx<=x+barRight&&my>=Math.min(b,y)&&my<=Math.max(b,y))item=[i,j/ps]}}}if(item){i=item[0];j=item[1];ps=series[i].datapoints.pointsize;return{datapoint:series[i].datapoints.points.slice(j*ps,(j+1)*ps),dataIndex:j,series:series[i],seriesIndex:i}}return null}function onMouseMove(e){if(options.grid.hoverable)triggerClickHoverEvent("plothover",e,function(s){return s["hoverable"]!=false})}function onMouseLeave(e){if(options.grid.hoverable)triggerClickHoverEvent("plothover",e,function(s){return false})}function onClick(e){triggerClickHoverEvent("plotclick",e,function(s){return s["clickable"]!=false})}function triggerClickHoverEvent(eventname,event,seriesFilter){var offset=eventHolder.offset(),canvasX=event.pageX-offset.left-plotOffset.left,canvasY=event.pageY-offset.top-plotOffset.top,pos=canvasToAxisCoords({left:canvasX,top:canvasY});pos.pageX=event.pageX;pos.pageY=event.pageY;var item=findNearbyItem(canvasX,canvasY,seriesFilter);if(item){item.pageX=parseInt(item.series.xaxis.p2c(item.datapoint[0])+offset.left+plotOffset.left,10);item.pageY=parseInt(item.series.yaxis.p2c(item.datapoint[1])+offset.top+plotOffset.top,10)}if(options.grid.autoHighlight){for(var i=0;i<highlights.length;++i){var h=highlights[i];if(h.auto==eventname&&!(item&&h.series==item.series&&h.point[0]==item.datapoint[0]&&h.point[1]==item.datapoint[1]))unhighlight(h.series,h.point)}if(item)highlight(item.series,item.datapoint,eventname)}placeholder.trigger(eventname,[pos,item])}function triggerRedrawOverlay(){var t=options.interaction.redrawOverlayInterval;if(t==-1){drawOverlay();return}if(!redrawTimeout)redrawTimeout=setTimeout(drawOverlay,t)}function drawOverlay(){redrawTimeout=null;octx.save();overlay.clear();octx.translate(plotOffset.left,plotOffset.top);var i,hi;for(i=0;i<highlights.length;++i){hi=highlights[i];if(hi.series.bars.show)drawBarHighlight(hi.series,hi.point);else drawPointHighlight(hi.series,hi.point)}octx.restore();executeHooks(hooks.drawOverlay,[octx])}function highlight(s,point,auto){if(typeof s=="number")s=series[s];if(typeof point=="number"){var ps=s.datapoints.pointsize;point=s.datapoints.points.slice(ps*point,ps*(point+1))}var i=indexOfHighlight(s,point);if(i==-1){highlights.push({series:s,point:point,auto:auto});triggerRedrawOverlay()}else if(!auto)highlights[i].auto=false}function unhighlight(s,point){if(s==null&&point==null){highlights=[];triggerRedrawOverlay();return}if(typeof s=="number")s=series[s];if(typeof point=="number"){var ps=s.datapoints.pointsize;point=s.datapoints.points.slice(ps*point,ps*(point+1))}var i=indexOfHighlight(s,point);if(i!=-1){highlights.splice(i,1);triggerRedrawOverlay()}}function indexOfHighlight(s,p){for(var i=0;i<highlights.length;++i){var h=highlights[i];if(h.series==s&&h.point[0]==p[0]&&h.point[1]==p[1])return i}return-1}function drawPointHighlight(series,point){var x=point[0],y=point[1],axisx=series.xaxis,axisy=series.yaxis,highlightColor=typeof series.highlightColor==="string"?series.highlightColor:$.color.parse(series.color).scale("a",.5).toString();if(x<axisx.min||x>axisx.max||y<axisy.min||y>axisy.max)return;var pointRadius=series.points.radius+series.points.lineWidth/2;octx.lineWidth=pointRadius;octx.strokeStyle=highlightColor;var radius=1.5*pointRadius;x=axisx.p2c(x);y=axisy.p2c(y);octx.beginPath();if(series.points.symbol=="circle")octx.arc(x,y,radius,0,2*Math.PI,false);else series.points.symbol(octx,x,y,radius,false);octx.closePath();octx.stroke()}function drawBarHighlight(series,point){var highlightColor=typeof series.highlightColor==="string"?series.highlightColor:$.color.parse(series.color).scale("a",.5).toString(),fillStyle=highlightColor,barLeft;switch(series.bars.align){case"left":barLeft=0;break;case"right":barLeft=-series.bars.barWidth;break;default:barLeft=-series.bars.barWidth/2}octx.lineWidth=series.bars.lineWidth;octx.strokeStyle=highlightColor;drawBar(point[0],point[1],point[2]||0,barLeft,barLeft+series.bars.barWidth,function(){return fillStyle},series.xaxis,series.yaxis,octx,series.bars.horizontal,series.bars.lineWidth)}function getColorOrGradient(spec,bottom,top,defaultColor){if(typeof spec=="string")return spec;else{var gradient=ctx.createLinearGradient(0,top,0,bottom);for(var i=0,l=spec.colors.length;i<l;++i){var c=spec.colors[i];if(typeof c!="string"){var co=$.color.parse(defaultColor);if(c.brightness!=null)co=co.scale("rgb",c.brightness);if(c.opacity!=null)co.a*=c.opacity;c=co.toString()}gradient.addColorStop(i/(l-1),c)}return gradient}}}$.plot=function(placeholder,data,options){var plot=new Plot($(placeholder),data,options,$.plot.plugins);return plot};$.plot.version="0.8.3";$.plot.plugins=[];$.fn.plot=function(data,options){return this.each(function(){$.plot(this,data,options)})};function floorInBase(n,base){return base*Math.floor(n/base)}})(jQuery); \ No newline at end of file
diff --git a/misc/flot/jquery.flot.navigate.js b/misc/flot/jquery.flot.navigate.js
new file mode 100644
index 0000000..13fb7f1
--- /dev/null
+++ b/misc/flot/jquery.flot.navigate.js
@@ -0,0 +1,346 @@
+/* Flot plugin for adding the ability to pan and zoom the plot.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The default behaviour is double click and scrollwheel up/down to zoom in, drag
+to pan. The plugin defines plot.zoom({ center }), plot.zoomOut() and
+plot.pan( offset ) so you easily can add custom controls. It also fires
+"plotpan" and "plotzoom" events, useful for synchronizing plots.
+
+The plugin supports these options:
+
+ zoom: {
+ interactive: false
+ trigger: "dblclick" // or "click" for single click
+ amount: 1.5 // 2 = 200% (zoom in), 0.5 = 50% (zoom out)
+ }
+
+ pan: {
+ interactive: false
+ cursor: "move" // CSS mouse cursor value used when dragging, e.g. "pointer"
+ frameRate: 20
+ }
+
+ xaxis, yaxis, x2axis, y2axis: {
+ zoomRange: null // or [ number, number ] (min range, max range) or false
+ panRange: null // or [ number, number ] (min, max) or false
+ }
+
+"interactive" enables the built-in drag/click behaviour. If you enable
+interactive for pan, then you'll have a basic plot that supports moving
+around; the same for zoom.
+
+"amount" specifies the default amount to zoom in (so 1.5 = 150%) relative to
+the current viewport.
+
+"cursor" is a standard CSS mouse cursor string used for visual feedback to the
+user when dragging.
+
+"frameRate" specifies the maximum number of times per second the plot will
+update itself while the user is panning around on it (set to null to disable
+intermediate pans, the plot will then not update until the mouse button is
+released).
+
+"zoomRange" is the interval in which zooming can happen, e.g. with zoomRange:
+[1, 100] the zoom will never scale the axis so that the difference between min
+and max is smaller than 1 or larger than 100. You can set either end to null
+to ignore, e.g. [1, null]. If you set zoomRange to false, zooming on that axis
+will be disabled.
+
+"panRange" confines the panning to stay within a range, e.g. with panRange:
+[-10, 20] panning stops at -10 in one end and at 20 in the other. Either can
+be null, e.g. [-10, null]. If you set panRange to false, panning on that axis
+will be disabled.
+
+Example API usage:
+
+ plot = $.plot(...);
+
+ // zoom default amount in on the pixel ( 10, 20 )
+ plot.zoom({ center: { left: 10, top: 20 } });
+
+ // zoom out again
+ plot.zoomOut({ center: { left: 10, top: 20 } });
+
+ // zoom 200% in on the pixel (10, 20)
+ plot.zoom({ amount: 2, center: { left: 10, top: 20 } });
+
+ // pan 100 pixels to the left and 20 down
+ plot.pan({ left: -100, top: 20 })
+
+Here, "center" specifies where the center of the zooming should happen. Note
+that this is defined in pixel space, not the space of the data points (you can
+use the p2c helpers on the axes in Flot to help you convert between these).
+
+"amount" is the amount to zoom the viewport relative to the current range, so
+1 is 100% (i.e. no change), 1.5 is 150% (zoom in), 0.7 is 70% (zoom out). You
+can set the default in the options.
+
+*/
+
+// First two dependencies, jquery.event.drag.js and
+// jquery.mousewheel.js, we put them inline here to save people the
+// effort of downloading them.
+
+/*
+jquery.event.drag.js ~ v1.5 ~ Copyright (c) 2008, Three Dub Media (http://threedubmedia.com)
+Licensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-LICENSE.txt
+*/
+(function(a){function e(h){var k,j=this,l=h.data||{};if(l.elem)j=h.dragTarget=l.elem,h.dragProxy=d.proxy||j,h.cursorOffsetX=l.pageX-l.left,h.cursorOffsetY=l.pageY-l.top,h.offsetX=h.pageX-h.cursorOffsetX,h.offsetY=h.pageY-h.cursorOffsetY;else if(d.dragging||l.which>0&&h.which!=l.which||a(h.target).is(l.not))return;switch(h.type){case"mousedown":return a.extend(l,a(j).offset(),{elem:j,target:h.target,pageX:h.pageX,pageY:h.pageY}),b.add(document,"mousemove mouseup",e,l),i(j,!1),d.dragging=null,!1;case!d.dragging&&"mousemove":if(g(h.pageX-l.pageX)+g(h.pageY-l.pageY)<l.distance)break;h.target=l.target,k=f(h,"dragstart",j),k!==!1&&(d.dragging=j,d.proxy=h.dragProxy=a(k||j)[0]);case"mousemove":if(d.dragging){if(k=f(h,"drag",j),c.drop&&(c.drop.allowed=k!==!1,c.drop.handler(h)),k!==!1)break;h.type="mouseup"}case"mouseup":b.remove(document,"mousemove mouseup",e),d.dragging&&(c.drop&&c.drop.handler(h),f(h,"dragend",j)),i(j,!0),d.dragging=d.proxy=l.elem=!1}return!0}function f(b,c,d){b.type=c;var e=a.event.dispatch.call(d,b);return e===!1?!1:e||b.result}function g(a){return Math.pow(a,2)}function h(){return d.dragging===!1}function i(a,b){a&&(a.unselectable=b?"off":"on",a.onselectstart=function(){return b},a.style&&(a.style.MozUserSelect=b?"":"none"))}a.fn.drag=function(a,b,c){return b&&this.bind("dragstart",a),c&&this.bind("dragend",c),a?this.bind("drag",b?b:a):this.trigger("drag")};var b=a.event,c=b.special,d=c.drag={not:":input",distance:0,which:1,dragging:!1,setup:function(c){c=a.extend({distance:d.distance,which:d.which,not:d.not},c||{}),c.distance=g(c.distance),b.add(this,"mousedown",e,c),this.attachEvent&&this.attachEvent("ondragstart",h)},teardown:function(){b.remove(this,"mousedown",e),this===d.dragging&&(d.dragging=d.proxy=!1),i(this,!0),this.detachEvent&&this.detachEvent("ondragstart",h)}};c.dragstart=c.dragend={setup:function(){},teardown:function(){}}})(jQuery);
+
+/* jquery.mousewheel.min.js
+ * Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
+ * Licensed under the MIT License (LICENSE.txt).
+ * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
+ * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
+ * Thanks to: Seamus Leahy for adding deltaX and deltaY
+ *
+ * Version: 3.0.6
+ *
+ * Requires: 1.2.2+
+ */
+(function(d){function e(a){var b=a||window.event,c=[].slice.call(arguments,1),f=0,e=0,g=0,a=d.event.fix(b);a.type="mousewheel";b.wheelDelta&&(f=b.wheelDelta/120);b.detail&&(f=-b.detail/3);g=f;void 0!==b.axis&&b.axis===b.HORIZONTAL_AXIS&&(g=0,e=-1*f);void 0!==b.wheelDeltaY&&(g=b.wheelDeltaY/120);void 0!==b.wheelDeltaX&&(e=-1*b.wheelDeltaX/120);c.unshift(a,f,e,g);return(d.event.dispatch||d.event.handle).apply(this,c)}var c=["DOMMouseScroll","mousewheel"];if(d.event.fixHooks)for(var h=c.length;h;)d.event.fixHooks[c[--h]]=d.event.mouseHooks;d.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a=c.length;a;)this.addEventListener(c[--a],e,!1);else this.onmousewheel=e},teardown:function(){if(this.removeEventListener)for(var a=c.length;a;)this.removeEventListener(c[--a],e,!1);else this.onmousewheel=null}};d.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery);
+
+
+
+
+(function ($) {
+ var options = {
+ xaxis: {
+ zoomRange: null, // or [number, number] (min range, max range)
+ panRange: null // or [number, number] (min, max)
+ },
+ zoom: {
+ interactive: false,
+ trigger: "dblclick", // or "click" for single click
+ amount: 1.5 // how much to zoom relative to current position, 2 = 200% (zoom in), 0.5 = 50% (zoom out)
+ },
+ pan: {
+ interactive: false,
+ cursor: "move",
+ frameRate: 20
+ }
+ };
+
+ function init(plot) {
+ function onZoomClick(e, zoomOut) {
+ var c = plot.offset();
+ c.left = e.pageX - c.left;
+ c.top = e.pageY - c.top;
+ if (zoomOut)
+ plot.zoomOut({ center: c });
+ else
+ plot.zoom({ center: c });
+ }
+
+ function onMouseWheel(e, delta) {
+ e.preventDefault();
+ onZoomClick(e, delta < 0);
+ return false;
+ }
+
+ var prevCursor = 'default', prevPageX = 0, prevPageY = 0,
+ panTimeout = null;
+
+ function onDragStart(e) {
+ if (e.which != 1) // only accept left-click
+ return false;
+ var c = plot.getPlaceholder().css('cursor');
+ if (c)
+ prevCursor = c;
+ plot.getPlaceholder().css('cursor', plot.getOptions().pan.cursor);
+ prevPageX = e.pageX;
+ prevPageY = e.pageY;
+ }
+
+ function onDrag(e) {
+ var frameRate = plot.getOptions().pan.frameRate;
+ if (panTimeout || !frameRate)
+ return;
+
+ panTimeout = setTimeout(function () {
+ plot.pan({ left: prevPageX - e.pageX,
+ top: prevPageY - e.pageY });
+ prevPageX = e.pageX;
+ prevPageY = e.pageY;
+
+ panTimeout = null;
+ }, 1 / frameRate * 1000);
+ }
+
+ function onDragEnd(e) {
+ if (panTimeout) {
+ clearTimeout(panTimeout);
+ panTimeout = null;
+ }
+
+ plot.getPlaceholder().css('cursor', prevCursor);
+ plot.pan({ left: prevPageX - e.pageX,
+ top: prevPageY - e.pageY });
+ }
+
+ function bindEvents(plot, eventHolder) {
+ var o = plot.getOptions();
+ if (o.zoom.interactive) {
+ eventHolder[o.zoom.trigger](onZoomClick);
+ eventHolder.mousewheel(onMouseWheel);
+ }
+
+ if (o.pan.interactive) {
+ eventHolder.bind("dragstart", { distance: 10 }, onDragStart);
+ eventHolder.bind("drag", onDrag);
+ eventHolder.bind("dragend", onDragEnd);
+ }
+ }
+
+ plot.zoomOut = function (args) {
+ if (!args)
+ args = {};
+
+ if (!args.amount)
+ args.amount = plot.getOptions().zoom.amount;
+
+ args.amount = 1 / args.amount;
+ plot.zoom(args);
+ };
+
+ plot.zoom = function (args) {
+ if (!args)
+ args = {};
+
+ var c = args.center,
+ amount = args.amount || plot.getOptions().zoom.amount,
+ w = plot.width(), h = plot.height();
+
+ if (!c)
+ c = { left: w / 2, top: h / 2 };
+
+ var xf = c.left / w,
+ yf = c.top / h,
+ minmax = {
+ x: {
+ min: c.left - xf * w / amount,
+ max: c.left + (1 - xf) * w / amount
+ },
+ y: {
+ min: c.top - yf * h / amount,
+ max: c.top + (1 - yf) * h / amount
+ }
+ };
+
+ $.each(plot.getAxes(), function(_, axis) {
+ var opts = axis.options,
+ min = minmax[axis.direction].min,
+ max = minmax[axis.direction].max,
+ zr = opts.zoomRange,
+ pr = opts.panRange;
+
+ if (zr === false) // no zooming on this axis
+ return;
+
+ min = axis.c2p(min);
+ max = axis.c2p(max);
+ if (min > max) {
+ // make sure min < max
+ var tmp = min;
+ min = max;
+ max = tmp;
+ }
+
+ //Check that we are in panRange
+ if (pr) {
+ if (pr[0] != null && min < pr[0]) {
+ min = pr[0];
+ }
+ if (pr[1] != null && max > pr[1]) {
+ max = pr[1];
+ }
+ }
+
+ var range = max - min;
+ if (zr &&
+ ((zr[0] != null && range < zr[0] && amount >1) ||
+ (zr[1] != null && range > zr[1] && amount <1)))
+ return;
+
+ opts.min = min;
+ opts.max = max;
+ });
+
+ plot.setupGrid();
+ plot.draw();
+
+ if (!args.preventEvent)
+ plot.getPlaceholder().trigger("plotzoom", [ plot, args ]);
+ };
+
+ plot.pan = function (args) {
+ var delta = {
+ x: +args.left,
+ y: +args.top
+ };
+
+ if (isNaN(delta.x))
+ delta.x = 0;
+ if (isNaN(delta.y))
+ delta.y = 0;
+
+ $.each(plot.getAxes(), function (_, axis) {
+ var opts = axis.options,
+ min, max, d = delta[axis.direction];
+
+ min = axis.c2p(axis.p2c(axis.min) + d),
+ max = axis.c2p(axis.p2c(axis.max) + d);
+
+ var pr = opts.panRange;
+ if (pr === false) // no panning on this axis
+ return;
+
+ if (pr) {
+ // check whether we hit the wall
+ if (pr[0] != null && pr[0] > min) {
+ d = pr[0] - min;
+ min += d;
+ max += d;
+ }
+
+ if (pr[1] != null && pr[1] < max) {
+ d = pr[1] - max;
+ min += d;
+ max += d;
+ }
+ }
+
+ opts.min = min;
+ opts.max = max;
+ });
+
+ plot.setupGrid();
+ plot.draw();
+
+ if (!args.preventEvent)
+ plot.getPlaceholder().trigger("plotpan", [ plot, args ]);
+ };
+
+ function shutdown(plot, eventHolder) {
+ eventHolder.unbind(plot.getOptions().zoom.trigger, onZoomClick);
+ eventHolder.unbind("mousewheel", onMouseWheel);
+ eventHolder.unbind("dragstart", onDragStart);
+ eventHolder.unbind("drag", onDrag);
+ eventHolder.unbind("dragend", onDragEnd);
+ if (panTimeout)
+ clearTimeout(panTimeout);
+ }
+
+ plot.hooks.bindEvents.push(bindEvents);
+ plot.hooks.shutdown.push(shutdown);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'navigate',
+ version: '1.3'
+ });
+})(jQuery);
diff --git a/misc/flot/jquery.flot.navigate.min.js b/misc/flot/jquery.flot.navigate.min.js
new file mode 100644
index 0000000..7288a23
--- /dev/null
+++ b/misc/flot/jquery.flot.navigate.min.js
@@ -0,0 +1,7 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function(a){function e(h){var k,j=this,l=h.data||{};if(l.elem)j=h.dragTarget=l.elem,h.dragProxy=d.proxy||j,h.cursorOffsetX=l.pageX-l.left,h.cursorOffsetY=l.pageY-l.top,h.offsetX=h.pageX-h.cursorOffsetX,h.offsetY=h.pageY-h.cursorOffsetY;else if(d.dragging||l.which>0&&h.which!=l.which||a(h.target).is(l.not))return;switch(h.type){case"mousedown":return a.extend(l,a(j).offset(),{elem:j,target:h.target,pageX:h.pageX,pageY:h.pageY}),b.add(document,"mousemove mouseup",e,l),i(j,!1),d.dragging=null,!1;case!d.dragging&&"mousemove":if(g(h.pageX-l.pageX)+g(h.pageY-l.pageY)<l.distance)break;h.target=l.target,k=f(h,"dragstart",j),k!==!1&&(d.dragging=j,d.proxy=h.dragProxy=a(k||j)[0]);case"mousemove":if(d.dragging){if(k=f(h,"drag",j),c.drop&&(c.drop.allowed=k!==!1,c.drop.handler(h)),k!==!1)break;h.type="mouseup"}case"mouseup":b.remove(document,"mousemove mouseup",e),d.dragging&&(c.drop&&c.drop.handler(h),f(h,"dragend",j)),i(j,!0),d.dragging=d.proxy=l.elem=!1}return!0}function f(b,c,d){b.type=c;var e=a.event.dispatch.call(d,b);return e===!1?!1:e||b.result}function g(a){return Math.pow(a,2)}function h(){return d.dragging===!1}function i(a,b){a&&(a.unselectable=b?"off":"on",a.onselectstart=function(){return b},a.style&&(a.style.MozUserSelect=b?"":"none"))}a.fn.drag=function(a,b,c){return b&&this.bind("dragstart",a),c&&this.bind("dragend",c),a?this.bind("drag",b?b:a):this.trigger("drag")};var b=a.event,c=b.special,d=c.drag={not:":input",distance:0,which:1,dragging:!1,setup:function(c){c=a.extend({distance:d.distance,which:d.which,not:d.not},c||{}),c.distance=g(c.distance),b.add(this,"mousedown",e,c),this.attachEvent&&this.attachEvent("ondragstart",h)},teardown:function(){b.remove(this,"mousedown",e),this===d.dragging&&(d.dragging=d.proxy=!1),i(this,!0),this.detachEvent&&this.detachEvent("ondragstart",h)}};c.dragstart=c.dragend={setup:function(){},teardown:function(){}}})(jQuery);(function(d){function e(a){var b=a||window.event,c=[].slice.call(arguments,1),f=0,e=0,g=0,a=d.event.fix(b);a.type="mousewheel";b.wheelDelta&&(f=b.wheelDelta/120);b.detail&&(f=-b.detail/3);g=f;void 0!==b.axis&&b.axis===b.HORIZONTAL_AXIS&&(g=0,e=-1*f);void 0!==b.wheelDeltaY&&(g=b.wheelDeltaY/120);void 0!==b.wheelDeltaX&&(e=-1*b.wheelDeltaX/120);c.unshift(a,f,e,g);return(d.event.dispatch||d.event.handle).apply(this,c)}var c=["DOMMouseScroll","mousewheel"];if(d.event.fixHooks)for(var h=c.length;h;)d.event.fixHooks[c[--h]]=d.event.mouseHooks;d.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a=c.length;a;)this.addEventListener(c[--a],e,!1);else this.onmousewheel=e},teardown:function(){if(this.removeEventListener)for(var a=c.length;a;)this.removeEventListener(c[--a],e,!1);else this.onmousewheel=null}};d.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery);(function($){var options={xaxis:{zoomRange:null,panRange:null},zoom:{interactive:false,trigger:"dblclick",amount:1.5},pan:{interactive:false,cursor:"move",frameRate:20}};function init(plot){function onZoomClick(e,zoomOut){var c=plot.offset();c.left=e.pageX-c.left;c.top=e.pageY-c.top;if(zoomOut)plot.zoomOut({center:c});else plot.zoom({center:c})}function onMouseWheel(e,delta){e.preventDefault();onZoomClick(e,delta<0);return false}var prevCursor="default",prevPageX=0,prevPageY=0,panTimeout=null;function onDragStart(e){if(e.which!=1)return false;var c=plot.getPlaceholder().css("cursor");if(c)prevCursor=c;plot.getPlaceholder().css("cursor",plot.getOptions().pan.cursor);prevPageX=e.pageX;prevPageY=e.pageY}function onDrag(e){var frameRate=plot.getOptions().pan.frameRate;if(panTimeout||!frameRate)return;panTimeout=setTimeout(function(){plot.pan({left:prevPageX-e.pageX,top:prevPageY-e.pageY});prevPageX=e.pageX;prevPageY=e.pageY;panTimeout=null},1/frameRate*1e3)}function onDragEnd(e){if(panTimeout){clearTimeout(panTimeout);panTimeout=null}plot.getPlaceholder().css("cursor",prevCursor);plot.pan({left:prevPageX-e.pageX,top:prevPageY-e.pageY})}function bindEvents(plot,eventHolder){var o=plot.getOptions();if(o.zoom.interactive){eventHolder[o.zoom.trigger](onZoomClick);eventHolder.mousewheel(onMouseWheel)}if(o.pan.interactive){eventHolder.bind("dragstart",{distance:10},onDragStart);eventHolder.bind("drag",onDrag);eventHolder.bind("dragend",onDragEnd)}}plot.zoomOut=function(args){if(!args)args={};if(!args.amount)args.amount=plot.getOptions().zoom.amount;args.amount=1/args.amount;plot.zoom(args)};plot.zoom=function(args){if(!args)args={};var c=args.center,amount=args.amount||plot.getOptions().zoom.amount,w=plot.width(),h=plot.height();if(!c)c={left:w/2,top:h/2};var xf=c.left/w,yf=c.top/h,minmax={x:{min:c.left-xf*w/amount,max:c.left+(1-xf)*w/amount},y:{min:c.top-yf*h/amount,max:c.top+(1-yf)*h/amount}};$.each(plot.getAxes(),function(_,axis){var opts=axis.options,min=minmax[axis.direction].min,max=minmax[axis.direction].max,zr=opts.zoomRange,pr=opts.panRange;if(zr===false)return;min=axis.c2p(min);max=axis.c2p(max);if(min>max){var tmp=min;min=max;max=tmp}if(pr){if(pr[0]!=null&&min<pr[0]){min=pr[0]}if(pr[1]!=null&&max>pr[1]){max=pr[1]}}var range=max-min;if(zr&&(zr[0]!=null&&range<zr[0]&&amount>1||zr[1]!=null&&range>zr[1]&&amount<1))return;opts.min=min;opts.max=max});plot.setupGrid();plot.draw();if(!args.preventEvent)plot.getPlaceholder().trigger("plotzoom",[plot,args])};plot.pan=function(args){var delta={x:+args.left,y:+args.top};if(isNaN(delta.x))delta.x=0;if(isNaN(delta.y))delta.y=0;$.each(plot.getAxes(),function(_,axis){var opts=axis.options,min,max,d=delta[axis.direction];min=axis.c2p(axis.p2c(axis.min)+d),max=axis.c2p(axis.p2c(axis.max)+d);var pr=opts.panRange;if(pr===false)return;if(pr){if(pr[0]!=null&&pr[0]>min){d=pr[0]-min;min+=d;max+=d}if(pr[1]!=null&&pr[1]<max){d=pr[1]-max;min+=d;max+=d}}opts.min=min;opts.max=max});plot.setupGrid();plot.draw();if(!args.preventEvent)plot.getPlaceholder().trigger("plotpan",[plot,args])};function shutdown(plot,eventHolder){eventHolder.unbind(plot.getOptions().zoom.trigger,onZoomClick);eventHolder.unbind("mousewheel",onMouseWheel);eventHolder.unbind("dragstart",onDragStart);eventHolder.unbind("drag",onDrag);eventHolder.unbind("dragend",onDragEnd);if(panTimeout)clearTimeout(panTimeout)}plot.hooks.bindEvents.push(bindEvents);plot.hooks.shutdown.push(shutdown)}$.plot.plugins.push({init:init,options:options,name:"navigate",version:"1.3"})})(jQuery); \ No newline at end of file
diff --git a/misc/flot/jquery.flot.pie.js b/misc/flot/jquery.flot.pie.js
new file mode 100644
index 0000000..9c19db9
--- /dev/null
+++ b/misc/flot/jquery.flot.pie.js
@@ -0,0 +1,820 @@
+/* Flot plugin for rendering pie charts.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The plugin assumes that each series has a single data value, and that each
+value is a positive integer or zero. Negative numbers don't make sense for a
+pie chart, and have unpredictable results. The values do NOT need to be
+passed in as percentages; the plugin will calculate the total and per-slice
+percentages internally.
+
+* Created by Brian Medendorp
+
+* Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars
+
+The plugin supports these options:
+
+ series: {
+ pie: {
+ show: true/false
+ radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
+ innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
+ startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
+ tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
+ offset: {
+ top: integer value to move the pie up or down
+ left: integer value to move the pie left or right, or 'auto'
+ },
+ stroke: {
+ color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
+ width: integer pixel width of the stroke
+ },
+ label: {
+ show: true/false, or 'auto'
+ formatter: a user-defined function that modifies the text/style of the label text
+ radius: 0-1 for percentage of fullsize, or a specified pixel length
+ background: {
+ color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
+ opacity: 0-1
+ },
+ threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
+ },
+ combine: {
+ threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
+ color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
+ label: any text value of what the combined slice should be labeled
+ }
+ highlight: {
+ opacity: 0-1
+ }
+ }
+ }
+
+More detail and specific examples can be found in the included HTML file.
+
+*/
+
+(function($) {
+
+ // Maximum redraw attempts when fitting labels within the plot
+
+ var REDRAW_ATTEMPTS = 10;
+
+ // Factor by which to shrink the pie when fitting labels within the plot
+
+ var REDRAW_SHRINK = 0.95;
+
+ function init(plot) {
+
+ var canvas = null,
+ target = null,
+ options = null,
+ maxRadius = null,
+ centerLeft = null,
+ centerTop = null,
+ processed = false,
+ ctx = null;
+
+ // interactive variables
+
+ var highlights = [];
+
+ // add hook to determine if pie plugin in enabled, and then perform necessary operations
+
+ plot.hooks.processOptions.push(function(plot, options) {
+ if (options.series.pie.show) {
+
+ options.grid.show = false;
+
+ // set labels.show
+
+ if (options.series.pie.label.show == "auto") {
+ if (options.legend.show) {
+ options.series.pie.label.show = false;
+ } else {
+ options.series.pie.label.show = true;
+ }
+ }
+
+ // set radius
+
+ if (options.series.pie.radius == "auto") {
+ if (options.series.pie.label.show) {
+ options.series.pie.radius = 3/4;
+ } else {
+ options.series.pie.radius = 1;
+ }
+ }
+
+ // ensure sane tilt
+
+ if (options.series.pie.tilt > 1) {
+ options.series.pie.tilt = 1;
+ } else if (options.series.pie.tilt < 0) {
+ options.series.pie.tilt = 0;
+ }
+ }
+ });
+
+ plot.hooks.bindEvents.push(function(plot, eventHolder) {
+ var options = plot.getOptions();
+ if (options.series.pie.show) {
+ if (options.grid.hoverable) {
+ eventHolder.unbind("mousemove").mousemove(onMouseMove);
+ }
+ if (options.grid.clickable) {
+ eventHolder.unbind("click").click(onClick);
+ }
+ }
+ });
+
+ plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) {
+ var options = plot.getOptions();
+ if (options.series.pie.show) {
+ processDatapoints(plot, series, data, datapoints);
+ }
+ });
+
+ plot.hooks.drawOverlay.push(function(plot, octx) {
+ var options = plot.getOptions();
+ if (options.series.pie.show) {
+ drawOverlay(plot, octx);
+ }
+ });
+
+ plot.hooks.draw.push(function(plot, newCtx) {
+ var options = plot.getOptions();
+ if (options.series.pie.show) {
+ draw(plot, newCtx);
+ }
+ });
+
+ function processDatapoints(plot, series, datapoints) {
+ if (!processed) {
+ processed = true;
+ canvas = plot.getCanvas();
+ target = $(canvas).parent();
+ options = plot.getOptions();
+ plot.setData(combine(plot.getData()));
+ }
+ }
+
+ function combine(data) {
+
+ var total = 0,
+ combined = 0,
+ numCombined = 0,
+ color = options.series.pie.combine.color,
+ newdata = [];
+
+ // Fix up the raw data from Flot, ensuring the data is numeric
+
+ for (var i = 0; i < data.length; ++i) {
+
+ var value = data[i].data;
+
+ // If the data is an array, we'll assume that it's a standard
+ // Flot x-y pair, and are concerned only with the second value.
+
+ // Note how we use the original array, rather than creating a
+ // new one; this is more efficient and preserves any extra data
+ // that the user may have stored in higher indexes.
+
+ if ($.isArray(value) && value.length == 1) {
+ value = value[0];
+ }
+
+ if ($.isArray(value)) {
+ // Equivalent to $.isNumeric() but compatible with jQuery < 1.7
+ if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) {
+ value[1] = +value[1];
+ } else {
+ value[1] = 0;
+ }
+ } else if (!isNaN(parseFloat(value)) && isFinite(value)) {
+ value = [1, +value];
+ } else {
+ value = [1, 0];
+ }
+
+ data[i].data = [value];
+ }
+
+ // Sum up all the slices, so we can calculate percentages for each
+
+ for (var i = 0; i < data.length; ++i) {
+ total += data[i].data[0][1];
+ }
+
+ // Count the number of slices with percentages below the combine
+ // threshold; if it turns out to be just one, we won't combine.
+
+ for (var i = 0; i < data.length; ++i) {
+ var value = data[i].data[0][1];
+ if (value / total <= options.series.pie.combine.threshold) {
+ combined += value;
+ numCombined++;
+ if (!color) {
+ color = data[i].color;
+ }
+ }
+ }
+
+ for (var i = 0; i < data.length; ++i) {
+ var value = data[i].data[0][1];
+ if (numCombined < 2 || value / total > options.series.pie.combine.threshold) {
+ newdata.push(
+ $.extend(data[i], { /* extend to allow keeping all other original data values
+ and using them e.g. in labelFormatter. */
+ data: [[1, value]],
+ color: data[i].color,
+ label: data[i].label,
+ angle: value * Math.PI * 2 / total,
+ percent: value / (total / 100)
+ })
+ );
+ }
+ }
+
+ if (numCombined > 1) {
+ newdata.push({
+ data: [[1, combined]],
+ color: color,
+ label: options.series.pie.combine.label,
+ angle: combined * Math.PI * 2 / total,
+ percent: combined / (total / 100)
+ });
+ }
+
+ return newdata;
+ }
+
+ function draw(plot, newCtx) {
+
+ if (!target) {
+ return; // if no series were passed
+ }
+
+ var canvasWidth = plot.getPlaceholder().width(),
+ canvasHeight = plot.getPlaceholder().height(),
+ legendWidth = target.children().filter(".legend").children().width() || 0;
+
+ ctx = newCtx;
+
+ // WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!
+
+ // When combining smaller slices into an 'other' slice, we need to
+ // add a new series. Since Flot gives plugins no way to modify the
+ // list of series, the pie plugin uses a hack where the first call
+ // to processDatapoints results in a call to setData with the new
+ // list of series, then subsequent processDatapoints do nothing.
+
+ // The plugin-global 'processed' flag is used to control this hack;
+ // it starts out false, and is set to true after the first call to
+ // processDatapoints.
+
+ // Unfortunately this turns future setData calls into no-ops; they
+ // call processDatapoints, the flag is true, and nothing happens.
+
+ // To fix this we'll set the flag back to false here in draw, when
+ // all series have been processed, so the next sequence of calls to
+ // processDatapoints once again starts out with a slice-combine.
+ // This is really a hack; in 0.9 we need to give plugins a proper
+ // way to modify series before any processing begins.
+
+ processed = false;
+
+ // calculate maximum radius and center point
+
+ maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
+ centerTop = canvasHeight / 2 + options.series.pie.offset.top;
+ centerLeft = canvasWidth / 2;
+
+ if (options.series.pie.offset.left == "auto") {
+ if (options.legend.position.match("w")) {
+ centerLeft += legendWidth / 2;
+ } else {
+ centerLeft -= legendWidth / 2;
+ }
+ if (centerLeft < maxRadius) {
+ centerLeft = maxRadius;
+ } else if (centerLeft > canvasWidth - maxRadius) {
+ centerLeft = canvasWidth - maxRadius;
+ }
+ } else {
+ centerLeft += options.series.pie.offset.left;
+ }
+
+ var slices = plot.getData(),
+ attempts = 0;
+
+ // Keep shrinking the pie's radius until drawPie returns true,
+ // indicating that all the labels fit, or we try too many times.
+
+ do {
+ if (attempts > 0) {
+ maxRadius *= REDRAW_SHRINK;
+ }
+ attempts += 1;
+ clear();
+ if (options.series.pie.tilt <= 0.8) {
+ drawShadow();
+ }
+ } while (!drawPie() && attempts < REDRAW_ATTEMPTS)
+
+ if (attempts >= REDRAW_ATTEMPTS) {
+ clear();
+ target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>");
+ }
+
+ if (plot.setSeries && plot.insertLegend) {
+ plot.setSeries(slices);
+ plot.insertLegend();
+ }
+
+ // we're actually done at this point, just defining internal functions at this point
+
+ function clear() {
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
+ target.children().filter(".pieLabel, .pieLabelBackground").remove();
+ }
+
+ function drawShadow() {
+
+ var shadowLeft = options.series.pie.shadow.left;
+ var shadowTop = options.series.pie.shadow.top;
+ var edge = 10;
+ var alpha = options.series.pie.shadow.alpha;
+ var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+
+ if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) {
+ return; // shadow would be outside canvas, so don't draw it
+ }
+
+ ctx.save();
+ ctx.translate(shadowLeft,shadowTop);
+ ctx.globalAlpha = alpha;
+ ctx.fillStyle = "#000";
+
+ // center and rotate to starting position
+
+ ctx.translate(centerLeft,centerTop);
+ ctx.scale(1, options.series.pie.tilt);
+
+ //radius -= edge;
+
+ for (var i = 1; i <= edge; i++) {
+ ctx.beginPath();
+ ctx.arc(0, 0, radius, 0, Math.PI * 2, false);
+ ctx.fill();
+ radius -= i;
+ }
+
+ ctx.restore();
+ }
+
+ function drawPie() {
+
+ var startAngle = Math.PI * options.series.pie.startAngle;
+ var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+
+ // center and rotate to starting position
+
+ ctx.save();
+ ctx.translate(centerLeft,centerTop);
+ ctx.scale(1, options.series.pie.tilt);
+ //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
+
+ // draw slices
+
+ ctx.save();
+ var currentAngle = startAngle;
+ for (var i = 0; i < slices.length; ++i) {
+ slices[i].startAngle = currentAngle;
+ drawSlice(slices[i].angle, slices[i].color, true);
+ }
+ ctx.restore();
+
+ // draw slice outlines
+
+ if (options.series.pie.stroke.width > 0) {
+ ctx.save();
+ ctx.lineWidth = options.series.pie.stroke.width;
+ currentAngle = startAngle;
+ for (var i = 0; i < slices.length; ++i) {
+ drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
+ }
+ ctx.restore();
+ }
+
+ // draw donut hole
+
+ drawDonutHole(ctx);
+
+ ctx.restore();
+
+ // Draw the labels, returning true if they fit within the plot
+
+ if (options.series.pie.label.show) {
+ return drawLabels();
+ } else return true;
+
+ function drawSlice(angle, color, fill) {
+
+ if (angle <= 0 || isNaN(angle)) {
+ return;
+ }
+
+ if (fill) {
+ ctx.fillStyle = color;
+ } else {
+ ctx.strokeStyle = color;
+ ctx.lineJoin = "round";
+ }
+
+ ctx.beginPath();
+ if (Math.abs(angle - Math.PI * 2) > 0.000000001) {
+ ctx.moveTo(0, 0); // Center of the pie
+ }
+
+ //ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera
+ ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false);
+ ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false);
+ ctx.closePath();
+ //ctx.rotate(angle); // This doesn't work properly in Opera
+ currentAngle += angle;
+
+ if (fill) {
+ ctx.fill();
+ } else {
+ ctx.stroke();
+ }
+ }
+
+ function drawLabels() {
+
+ var currentAngle = startAngle;
+ var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius;
+
+ for (var i = 0; i < slices.length; ++i) {
+ if (slices[i].percent >= options.series.pie.label.threshold * 100) {
+ if (!drawLabel(slices[i], currentAngle, i)) {
+ return false;
+ }
+ }
+ currentAngle += slices[i].angle;
+ }
+
+ return true;
+
+ function drawLabel(slice, startAngle, index) {
+
+ if (slice.data[0][1] == 0) {
+ return true;
+ }
+
+ // format label text
+
+ var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
+
+ if (lf) {
+ text = lf(slice.label, slice);
+ } else {
+ text = slice.label;
+ }
+
+ if (plf) {
+ text = plf(text, slice);
+ }
+
+ var halfAngle = ((startAngle + slice.angle) + startAngle) / 2;
+ var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
+ var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;
+
+ var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>";
+ target.append(html);
+
+ var label = target.children("#pieLabel" + index);
+ var labelTop = (y - label.height() / 2);
+ var labelLeft = (x - label.width() / 2);
+
+ label.css("top", labelTop);
+ label.css("left", labelLeft);
+
+ // check to make sure that the label is not outside the canvas
+
+ if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {
+ return false;
+ }
+
+ if (options.series.pie.label.background.opacity != 0) {
+
+ // put in the transparent background separately to avoid blended labels and label boxes
+
+ var c = options.series.pie.label.background.color;
+
+ if (c == null) {
+ c = slice.color;
+ }
+
+ var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;";
+ $("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>")
+ .css("opacity", options.series.pie.label.background.opacity)
+ .insertBefore(label);
+ }
+
+ return true;
+ } // end individual label function
+ } // end drawLabels function
+ } // end drawPie function
+ } // end draw function
+
+ // Placed here because it needs to be accessed from multiple locations
+
+ function drawDonutHole(layer) {
+ if (options.series.pie.innerRadius > 0) {
+
+ // subtract the center
+
+ layer.save();
+ var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
+ layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color
+ layer.beginPath();
+ layer.fillStyle = options.series.pie.stroke.color;
+ layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
+ layer.fill();
+ layer.closePath();
+ layer.restore();
+
+ // add inner stroke
+
+ layer.save();
+ layer.beginPath();
+ layer.strokeStyle = options.series.pie.stroke.color;
+ layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
+ layer.stroke();
+ layer.closePath();
+ layer.restore();
+
+ // TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
+ }
+ }
+
+ //-- Additional Interactive related functions --
+
+ function isPointInPoly(poly, pt) {
+ for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
+ ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1]))
+ && (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])
+ && (c = !c);
+ return c;
+ }
+
+ function findNearbySlice(mouseX, mouseY) {
+
+ var slices = plot.getData(),
+ options = plot.getOptions(),
+ radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius,
+ x, y;
+
+ for (var i = 0; i < slices.length; ++i) {
+
+ var s = slices[i];
+
+ if (s.pie.show) {
+
+ ctx.save();
+ ctx.beginPath();
+ ctx.moveTo(0, 0); // Center of the pie
+ //ctx.scale(1, options.series.pie.tilt); // this actually seems to break everything when here.
+ ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false);
+ ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false);
+ ctx.closePath();
+ x = mouseX - centerLeft;
+ y = mouseY - centerTop;
+
+ if (ctx.isPointInPath) {
+ if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) {
+ ctx.restore();
+ return {
+ datapoint: [s.percent, s.data],
+ dataIndex: 0,
+ series: s,
+ seriesIndex: i
+ };
+ }
+ } else {
+
+ // excanvas for IE doesn;t support isPointInPath, this is a workaround.
+
+ var p1X = radius * Math.cos(s.startAngle),
+ p1Y = radius * Math.sin(s.startAngle),
+ p2X = radius * Math.cos(s.startAngle + s.angle / 4),
+ p2Y = radius * Math.sin(s.startAngle + s.angle / 4),
+ p3X = radius * Math.cos(s.startAngle + s.angle / 2),
+ p3Y = radius * Math.sin(s.startAngle + s.angle / 2),
+ p4X = radius * Math.cos(s.startAngle + s.angle / 1.5),
+ p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5),
+ p5X = radius * Math.cos(s.startAngle + s.angle),
+ p5Y = radius * Math.sin(s.startAngle + s.angle),
+ arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]],
+ arrPoint = [x, y];
+
+ // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
+
+ if (isPointInPoly(arrPoly, arrPoint)) {
+ ctx.restore();
+ return {
+ datapoint: [s.percent, s.data],
+ dataIndex: 0,
+ series: s,
+ seriesIndex: i
+ };
+ }
+ }
+
+ ctx.restore();
+ }
+ }
+
+ return null;
+ }
+
+ function onMouseMove(e) {
+ triggerClickHoverEvent("plothover", e);
+ }
+
+ function onClick(e) {
+ triggerClickHoverEvent("plotclick", e);
+ }
+
+ // trigger click or hover event (they send the same parameters so we share their code)
+
+ function triggerClickHoverEvent(eventname, e) {
+
+ var offset = plot.offset();
+ var canvasX = parseInt(e.pageX - offset.left);
+ var canvasY = parseInt(e.pageY - offset.top);
+ var item = findNearbySlice(canvasX, canvasY);
+
+ if (options.grid.autoHighlight) {
+
+ // clear auto-highlights
+
+ for (var i = 0; i < highlights.length; ++i) {
+ var h = highlights[i];
+ if (h.auto == eventname && !(item && h.series == item.series)) {
+ unhighlight(h.series);
+ }
+ }
+ }
+
+ // highlight the slice
+
+ if (item) {
+ highlight(item.series, eventname);
+ }
+
+ // trigger any hover bind events
+
+ var pos = { pageX: e.pageX, pageY: e.pageY };
+ target.trigger(eventname, [pos, item]);
+ }
+
+ function highlight(s, auto) {
+ //if (typeof s == "number") {
+ // s = series[s];
+ //}
+
+ var i = indexOfHighlight(s);
+
+ if (i == -1) {
+ highlights.push({ series: s, auto: auto });
+ plot.triggerRedrawOverlay();
+ } else if (!auto) {
+ highlights[i].auto = false;
+ }
+ }
+
+ function unhighlight(s) {
+ if (s == null) {
+ highlights = [];
+ plot.triggerRedrawOverlay();
+ }
+
+ //if (typeof s == "number") {
+ // s = series[s];
+ //}
+
+ var i = indexOfHighlight(s);
+
+ if (i != -1) {
+ highlights.splice(i, 1);
+ plot.triggerRedrawOverlay();
+ }
+ }
+
+ function indexOfHighlight(s) {
+ for (var i = 0; i < highlights.length; ++i) {
+ var h = highlights[i];
+ if (h.series == s)
+ return i;
+ }
+ return -1;
+ }
+
+ function drawOverlay(plot, octx) {
+
+ var options = plot.getOptions();
+
+ var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+
+ octx.save();
+ octx.translate(centerLeft, centerTop);
+ octx.scale(1, options.series.pie.tilt);
+
+ for (var i = 0; i < highlights.length; ++i) {
+ drawHighlight(highlights[i].series);
+ }
+
+ drawDonutHole(octx);
+
+ octx.restore();
+
+ function drawHighlight(series) {
+
+ if (series.angle <= 0 || isNaN(series.angle)) {
+ return;
+ }
+
+ //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
+ octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor
+ octx.beginPath();
+ if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) {
+ octx.moveTo(0, 0); // Center of the pie
+ }
+ octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false);
+ octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false);
+ octx.closePath();
+ octx.fill();
+ }
+ }
+ } // end init (plugin body)
+
+ // define pie specific options and their default values
+
+ var options = {
+ series: {
+ pie: {
+ show: false,
+ radius: "auto", // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
+ innerRadius: 0, /* for donut */
+ startAngle: 3/2,
+ tilt: 1,
+ shadow: {
+ left: 5, // shadow left offset
+ top: 15, // shadow top offset
+ alpha: 0.02 // shadow alpha
+ },
+ offset: {
+ top: 0,
+ left: "auto"
+ },
+ stroke: {
+ color: "#fff",
+ width: 1
+ },
+ label: {
+ show: "auto",
+ formatter: function(label, slice) {
+ return "<div style='font-size:x-small;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + "<br/>" + Math.round(slice.percent) + "%</div>";
+ }, // formatter function
+ radius: 1, // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
+ background: {
+ color: null,
+ opacity: 0
+ },
+ threshold: 0 // percentage at which to hide the label (i.e. the slice is too narrow)
+ },
+ combine: {
+ threshold: -1, // percentage at which to combine little slices into one larger slice
+ color: null, // color to give the new slice (auto-generated if null)
+ label: "Other" // label to give the new slice
+ },
+ highlight: {
+ //color: "#fff", // will add this functionality once parseColor is available
+ opacity: 0.5
+ }
+ }
+ }
+ };
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: "pie",
+ version: "1.1"
+ });
+
+})(jQuery);
diff --git a/misc/flot/jquery.flot.pie.min.js b/misc/flot/jquery.flot.pie.min.js
new file mode 100644
index 0000000..9bc488b
--- /dev/null
+++ b/misc/flot/jquery.flot.pie.min.js
@@ -0,0 +1,7 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function($){var REDRAW_ATTEMPTS=10;var REDRAW_SHRINK=.95;function init(plot){var canvas=null,target=null,options=null,maxRadius=null,centerLeft=null,centerTop=null,processed=false,ctx=null;var highlights=[];plot.hooks.processOptions.push(function(plot,options){if(options.series.pie.show){options.grid.show=false;if(options.series.pie.label.show=="auto"){if(options.legend.show){options.series.pie.label.show=false}else{options.series.pie.label.show=true}}if(options.series.pie.radius=="auto"){if(options.series.pie.label.show){options.series.pie.radius=3/4}else{options.series.pie.radius=1}}if(options.series.pie.tilt>1){options.series.pie.tilt=1}else if(options.series.pie.tilt<0){options.series.pie.tilt=0}}});plot.hooks.bindEvents.push(function(plot,eventHolder){var options=plot.getOptions();if(options.series.pie.show){if(options.grid.hoverable){eventHolder.unbind("mousemove").mousemove(onMouseMove)}if(options.grid.clickable){eventHolder.unbind("click").click(onClick)}}});plot.hooks.processDatapoints.push(function(plot,series,data,datapoints){var options=plot.getOptions();if(options.series.pie.show){processDatapoints(plot,series,data,datapoints)}});plot.hooks.drawOverlay.push(function(plot,octx){var options=plot.getOptions();if(options.series.pie.show){drawOverlay(plot,octx)}});plot.hooks.draw.push(function(plot,newCtx){var options=plot.getOptions();if(options.series.pie.show){draw(plot,newCtx)}});function processDatapoints(plot,series,datapoints){if(!processed){processed=true;canvas=plot.getCanvas();target=$(canvas).parent();options=plot.getOptions();plot.setData(combine(plot.getData()))}}function combine(data){var total=0,combined=0,numCombined=0,color=options.series.pie.combine.color,newdata=[];for(var i=0;i<data.length;++i){var value=data[i].data;if($.isArray(value)&&value.length==1){value=value[0]}if($.isArray(value)){if(!isNaN(parseFloat(value[1]))&&isFinite(value[1])){value[1]=+value[1]}else{value[1]=0}}else if(!isNaN(parseFloat(value))&&isFinite(value)){value=[1,+value]}else{value=[1,0]}data[i].data=[value]}for(var i=0;i<data.length;++i){total+=data[i].data[0][1]}for(var i=0;i<data.length;++i){var value=data[i].data[0][1];if(value/total<=options.series.pie.combine.threshold){combined+=value;numCombined++;if(!color){color=data[i].color}}}for(var i=0;i<data.length;++i){var value=data[i].data[0][1];if(numCombined<2||value/total>options.series.pie.combine.threshold){newdata.push($.extend(data[i],{data:[[1,value]],color:data[i].color,label:data[i].label,angle:value*Math.PI*2/total,percent:value/(total/100)}))}}if(numCombined>1){newdata.push({data:[[1,combined]],color:color,label:options.series.pie.combine.label,angle:combined*Math.PI*2/total,percent:combined/(total/100)})}return newdata}function draw(plot,newCtx){if(!target){return}var canvasWidth=plot.getPlaceholder().width(),canvasHeight=plot.getPlaceholder().height(),legendWidth=target.children().filter(".legend").children().width()||0;ctx=newCtx;processed=false;maxRadius=Math.min(canvasWidth,canvasHeight/options.series.pie.tilt)/2;centerTop=canvasHeight/2+options.series.pie.offset.top;centerLeft=canvasWidth/2;if(options.series.pie.offset.left=="auto"){if(options.legend.position.match("w")){centerLeft+=legendWidth/2}else{centerLeft-=legendWidth/2}if(centerLeft<maxRadius){centerLeft=maxRadius}else if(centerLeft>canvasWidth-maxRadius){centerLeft=canvasWidth-maxRadius}}else{centerLeft+=options.series.pie.offset.left}var slices=plot.getData(),attempts=0;do{if(attempts>0){maxRadius*=REDRAW_SHRINK}attempts+=1;clear();if(options.series.pie.tilt<=.8){drawShadow()}}while(!drawPie()&&attempts<REDRAW_ATTEMPTS);if(attempts>=REDRAW_ATTEMPTS){clear();target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>")}if(plot.setSeries&&plot.insertLegend){plot.setSeries(slices);plot.insertLegend()}function clear(){ctx.clearRect(0,0,canvasWidth,canvasHeight);target.children().filter(".pieLabel, .pieLabelBackground").remove()}function drawShadow(){var shadowLeft=options.series.pie.shadow.left;var shadowTop=options.series.pie.shadow.top;var edge=10;var alpha=options.series.pie.shadow.alpha;var radius=options.series.pie.radius>1?options.series.pie.radius:maxRadius*options.series.pie.radius;if(radius>=canvasWidth/2-shadowLeft||radius*options.series.pie.tilt>=canvasHeight/2-shadowTop||radius<=edge){return}ctx.save();ctx.translate(shadowLeft,shadowTop);ctx.globalAlpha=alpha;ctx.fillStyle="#000";ctx.translate(centerLeft,centerTop);ctx.scale(1,options.series.pie.tilt);for(var i=1;i<=edge;i++){ctx.beginPath();ctx.arc(0,0,radius,0,Math.PI*2,false);ctx.fill();radius-=i}ctx.restore()}function drawPie(){var startAngle=Math.PI*options.series.pie.startAngle;var radius=options.series.pie.radius>1?options.series.pie.radius:maxRadius*options.series.pie.radius;ctx.save();ctx.translate(centerLeft,centerTop);ctx.scale(1,options.series.pie.tilt);ctx.save();var currentAngle=startAngle;for(var i=0;i<slices.length;++i){slices[i].startAngle=currentAngle;drawSlice(slices[i].angle,slices[i].color,true)}ctx.restore();if(options.series.pie.stroke.width>0){ctx.save();ctx.lineWidth=options.series.pie.stroke.width;currentAngle=startAngle;for(var i=0;i<slices.length;++i){drawSlice(slices[i].angle,options.series.pie.stroke.color,false)}ctx.restore()}drawDonutHole(ctx);ctx.restore();if(options.series.pie.label.show){return drawLabels()}else return true;function drawSlice(angle,color,fill){if(angle<=0||isNaN(angle)){return}if(fill){ctx.fillStyle=color}else{ctx.strokeStyle=color;ctx.lineJoin="round"}ctx.beginPath();if(Math.abs(angle-Math.PI*2)>1e-9){ctx.moveTo(0,0)}ctx.arc(0,0,radius,currentAngle,currentAngle+angle/2,false);ctx.arc(0,0,radius,currentAngle+angle/2,currentAngle+angle,false);ctx.closePath();currentAngle+=angle;if(fill){ctx.fill()}else{ctx.stroke()}}function drawLabels(){var currentAngle=startAngle;var radius=options.series.pie.label.radius>1?options.series.pie.label.radius:maxRadius*options.series.pie.label.radius;for(var i=0;i<slices.length;++i){if(slices[i].percent>=options.series.pie.label.threshold*100){if(!drawLabel(slices[i],currentAngle,i)){return false}}currentAngle+=slices[i].angle}return true;function drawLabel(slice,startAngle,index){if(slice.data[0][1]==0){return true}var lf=options.legend.labelFormatter,text,plf=options.series.pie.label.formatter;if(lf){text=lf(slice.label,slice)}else{text=slice.label}if(plf){text=plf(text,slice)}var halfAngle=(startAngle+slice.angle+startAngle)/2;var x=centerLeft+Math.round(Math.cos(halfAngle)*radius);var y=centerTop+Math.round(Math.sin(halfAngle)*radius)*options.series.pie.tilt;var html="<span class='pieLabel' id='pieLabel"+index+"' style='position:absolute;top:"+y+"px;left:"+x+"px;'>"+text+"</span>";target.append(html);var label=target.children("#pieLabel"+index);var labelTop=y-label.height()/2;var labelLeft=x-label.width()/2;label.css("top",labelTop);label.css("left",labelLeft);if(0-labelTop>0||0-labelLeft>0||canvasHeight-(labelTop+label.height())<0||canvasWidth-(labelLeft+label.width())<0){return false}if(options.series.pie.label.background.opacity!=0){var c=options.series.pie.label.background.color;if(c==null){c=slice.color}var pos="top:"+labelTop+"px;left:"+labelLeft+"px;";$("<div class='pieLabelBackground' style='position:absolute;width:"+label.width()+"px;height:"+label.height()+"px;"+pos+"background-color:"+c+";'></div>").css("opacity",options.series.pie.label.background.opacity).insertBefore(label)}return true}}}}function drawDonutHole(layer){if(options.series.pie.innerRadius>0){layer.save();var innerRadius=options.series.pie.innerRadius>1?options.series.pie.innerRadius:maxRadius*options.series.pie.innerRadius;layer.globalCompositeOperation="destination-out";layer.beginPath();layer.fillStyle=options.series.pie.stroke.color;layer.arc(0,0,innerRadius,0,Math.PI*2,false);layer.fill();layer.closePath();layer.restore();layer.save();layer.beginPath();layer.strokeStyle=options.series.pie.stroke.color;layer.arc(0,0,innerRadius,0,Math.PI*2,false);layer.stroke();layer.closePath();layer.restore()}}function isPointInPoly(poly,pt){for(var c=false,i=-1,l=poly.length,j=l-1;++i<l;j=i)(poly[i][1]<=pt[1]&&pt[1]<poly[j][1]||poly[j][1]<=pt[1]&&pt[1]<poly[i][1])&&pt[0]<(poly[j][0]-poly[i][0])*(pt[1]-poly[i][1])/(poly[j][1]-poly[i][1])+poly[i][0]&&(c=!c);return c}function findNearbySlice(mouseX,mouseY){var slices=plot.getData(),options=plot.getOptions(),radius=options.series.pie.radius>1?options.series.pie.radius:maxRadius*options.series.pie.radius,x,y;for(var i=0;i<slices.length;++i){var s=slices[i];if(s.pie.show){ctx.save();ctx.beginPath();ctx.moveTo(0,0);ctx.arc(0,0,radius,s.startAngle,s.startAngle+s.angle/2,false);ctx.arc(0,0,radius,s.startAngle+s.angle/2,s.startAngle+s.angle,false);ctx.closePath();x=mouseX-centerLeft;y=mouseY-centerTop;if(ctx.isPointInPath){if(ctx.isPointInPath(mouseX-centerLeft,mouseY-centerTop)){ctx.restore();return{datapoint:[s.percent,s.data],dataIndex:0,series:s,seriesIndex:i}}}else{var p1X=radius*Math.cos(s.startAngle),p1Y=radius*Math.sin(s.startAngle),p2X=radius*Math.cos(s.startAngle+s.angle/4),p2Y=radius*Math.sin(s.startAngle+s.angle/4),p3X=radius*Math.cos(s.startAngle+s.angle/2),p3Y=radius*Math.sin(s.startAngle+s.angle/2),p4X=radius*Math.cos(s.startAngle+s.angle/1.5),p4Y=radius*Math.sin(s.startAngle+s.angle/1.5),p5X=radius*Math.cos(s.startAngle+s.angle),p5Y=radius*Math.sin(s.startAngle+s.angle),arrPoly=[[0,0],[p1X,p1Y],[p2X,p2Y],[p3X,p3Y],[p4X,p4Y],[p5X,p5Y]],arrPoint=[x,y];if(isPointInPoly(arrPoly,arrPoint)){ctx.restore();return{datapoint:[s.percent,s.data],dataIndex:0,series:s,seriesIndex:i}}}ctx.restore()}}return null}function onMouseMove(e){triggerClickHoverEvent("plothover",e)}function onClick(e){triggerClickHoverEvent("plotclick",e)}function triggerClickHoverEvent(eventname,e){var offset=plot.offset();var canvasX=parseInt(e.pageX-offset.left);var canvasY=parseInt(e.pageY-offset.top);var item=findNearbySlice(canvasX,canvasY);if(options.grid.autoHighlight){for(var i=0;i<highlights.length;++i){var h=highlights[i];if(h.auto==eventname&&!(item&&h.series==item.series)){unhighlight(h.series)}}}if(item){highlight(item.series,eventname)}var pos={pageX:e.pageX,pageY:e.pageY};target.trigger(eventname,[pos,item])}function highlight(s,auto){var i=indexOfHighlight(s);if(i==-1){highlights.push({series:s,auto:auto});plot.triggerRedrawOverlay()}else if(!auto){highlights[i].auto=false}}function unhighlight(s){if(s==null){highlights=[];plot.triggerRedrawOverlay()}var i=indexOfHighlight(s);if(i!=-1){highlights.splice(i,1);plot.triggerRedrawOverlay()}}function indexOfHighlight(s){for(var i=0;i<highlights.length;++i){var h=highlights[i];if(h.series==s)return i}return-1}function drawOverlay(plot,octx){var options=plot.getOptions();var radius=options.series.pie.radius>1?options.series.pie.radius:maxRadius*options.series.pie.radius;octx.save();octx.translate(centerLeft,centerTop);octx.scale(1,options.series.pie.tilt);for(var i=0;i<highlights.length;++i){drawHighlight(highlights[i].series)}drawDonutHole(octx);octx.restore();function drawHighlight(series){if(series.angle<=0||isNaN(series.angle)){return}octx.fillStyle="rgba(255, 255, 255, "+options.series.pie.highlight.opacity+")";octx.beginPath();if(Math.abs(series.angle-Math.PI*2)>1e-9){octx.moveTo(0,0)}octx.arc(0,0,radius,series.startAngle,series.startAngle+series.angle/2,false);octx.arc(0,0,radius,series.startAngle+series.angle/2,series.startAngle+series.angle,false);octx.closePath();octx.fill()}}}var options={series:{pie:{show:false,radius:"auto",innerRadius:0,startAngle:3/2,tilt:1,shadow:{left:5,top:15,alpha:.02},offset:{top:0,left:"auto"},stroke:{color:"#fff",width:1},label:{show:"auto",formatter:function(label,slice){return"<div style='font-size:x-small;text-align:center;padding:2px;color:"+slice.color+";'>"+label+"<br/>"+Math.round(slice.percent)+"%</div>"},radius:1,background:{color:null,opacity:0},threshold:0},combine:{threshold:-1,color:null,label:"Other"},highlight:{opacity:.5}}}};$.plot.plugins.push({init:init,options:options,name:"pie",version:"1.1"})})(jQuery); \ No newline at end of file
diff --git a/misc/flot/jquery.flot.resize.js b/misc/flot/jquery.flot.resize.js
new file mode 100644
index 0000000..8a626dd
--- /dev/null
+++ b/misc/flot/jquery.flot.resize.js
@@ -0,0 +1,59 @@
+/* Flot plugin for automatically redrawing plots as the placeholder resizes.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+It works by listening for changes on the placeholder div (through the jQuery
+resize event plugin) - if the size changes, it will redraw the plot.
+
+There are no options. If you need to disable the plugin for some plots, you
+can just fix the size of their placeholders.
+
+*/
+
+/* Inline dependency:
+ * jQuery resize event - v1.1 - 3/14/2010
+ * http://benalman.com/projects/jquery-resize-plugin/
+ *
+ * Copyright (c) 2010 "Cowboy" Ben Alman
+ * Dual licensed under the MIT and GPL licenses.
+ * http://benalman.com/about/license/
+ */
+(function($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this);
+
+(function ($) {
+ var options = { }; // no options
+
+ function init(plot) {
+ function onResize() {
+ var placeholder = plot.getPlaceholder();
+
+ // somebody might have hidden us and we can't plot
+ // when we don't have the dimensions
+ if (placeholder.width() == 0 || placeholder.height() == 0)
+ return;
+
+ plot.resize();
+ plot.setupGrid();
+ plot.draw();
+ }
+
+ function bindEvents(plot, eventHolder) {
+ plot.getPlaceholder().resize(onResize);
+ }
+
+ function shutdown(plot, eventHolder) {
+ plot.getPlaceholder().unbind("resize", onResize);
+ }
+
+ plot.hooks.bindEvents.push(bindEvents);
+ plot.hooks.shutdown.push(shutdown);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'resize',
+ version: '1.0'
+ });
+})(jQuery);
diff --git a/misc/flot/jquery.flot.resize.min.js b/misc/flot/jquery.flot.resize.min.js
new file mode 100644
index 0000000..7e92aa6
--- /dev/null
+++ b/misc/flot/jquery.flot.resize.min.js
@@ -0,0 +1,7 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this);(function($){var options={};function init(plot){function onResize(){var placeholder=plot.getPlaceholder();if(placeholder.width()==0||placeholder.height()==0)return;plot.resize();plot.setupGrid();plot.draw()}function bindEvents(plot,eventHolder){plot.getPlaceholder().resize(onResize)}function shutdown(plot,eventHolder){plot.getPlaceholder().unbind("resize",onResize)}plot.hooks.bindEvents.push(bindEvents);plot.hooks.shutdown.push(shutdown)}$.plot.plugins.push({init:init,options:options,name:"resize",version:"1.0"})})(jQuery); \ No newline at end of file
diff --git a/misc/flot/jquery.flot.selection.js b/misc/flot/jquery.flot.selection.js
new file mode 100644
index 0000000..d3c20fa
--- /dev/null
+++ b/misc/flot/jquery.flot.selection.js
@@ -0,0 +1,360 @@
+/* Flot plugin for selecting regions of a plot.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The plugin supports these options:
+
+selection: {
+ mode: null or "x" or "y" or "xy",
+ color: color,
+ shape: "round" or "miter" or "bevel",
+ minSize: number of pixels
+}
+
+Selection support is enabled by setting the mode to one of "x", "y" or "xy".
+In "x" mode, the user will only be able to specify the x range, similarly for
+"y" mode. For "xy", the selection becomes a rectangle where both ranges can be
+specified. "color" is color of the selection (if you need to change the color
+later on, you can get to it with plot.getOptions().selection.color). "shape"
+is the shape of the corners of the selection.
+
+"minSize" is the minimum size a selection can be in pixels. This value can
+be customized to determine the smallest size a selection can be and still
+have the selection rectangle be displayed. When customizing this value, the
+fact that it refers to pixels, not axis units must be taken into account.
+Thus, for example, if there is a bar graph in time mode with BarWidth set to 1
+minute, setting "minSize" to 1 will not make the minimum selection size 1
+minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent
+"plotunselected" events from being fired when the user clicks the mouse without
+dragging.
+
+When selection support is enabled, a "plotselected" event will be emitted on
+the DOM element you passed into the plot function. The event handler gets a
+parameter with the ranges selected on the axes, like this:
+
+ placeholder.bind( "plotselected", function( event, ranges ) {
+ alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to)
+ // similar for yaxis - with multiple axes, the extra ones are in
+ // x2axis, x3axis, ...
+ });
+
+The "plotselected" event is only fired when the user has finished making the
+selection. A "plotselecting" event is fired during the process with the same
+parameters as the "plotselected" event, in case you want to know what's
+happening while it's happening,
+
+A "plotunselected" event with no arguments is emitted when the user clicks the
+mouse to remove the selection. As stated above, setting "minSize" to 0 will
+destroy this behavior.
+
+The plugin allso adds the following methods to the plot object:
+
+- setSelection( ranges, preventEvent )
+
+ Set the selection rectangle. The passed in ranges is on the same form as
+ returned in the "plotselected" event. If the selection mode is "x", you
+ should put in either an xaxis range, if the mode is "y" you need to put in
+ an yaxis range and both xaxis and yaxis if the selection mode is "xy", like
+ this:
+
+ setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });
+
+ setSelection will trigger the "plotselected" event when called. If you don't
+ want that to happen, e.g. if you're inside a "plotselected" handler, pass
+ true as the second parameter. If you are using multiple axes, you can
+ specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of
+ xaxis, the plugin picks the first one it sees.
+
+- clearSelection( preventEvent )
+
+ Clear the selection rectangle. Pass in true to avoid getting a
+ "plotunselected" event.
+
+- getSelection()
+
+ Returns the current selection in the same format as the "plotselected"
+ event. If there's currently no selection, the function returns null.
+
+*/
+
+(function ($) {
+ function init(plot) {
+ var selection = {
+ first: { x: -1, y: -1}, second: { x: -1, y: -1},
+ show: false,
+ active: false
+ };
+
+ // FIXME: The drag handling implemented here should be
+ // abstracted out, there's some similar code from a library in
+ // the navigation plugin, this should be massaged a bit to fit
+ // the Flot cases here better and reused. Doing this would
+ // make this plugin much slimmer.
+ var savedhandlers = {};
+
+ var mouseUpHandler = null;
+
+ function onMouseMove(e) {
+ if (selection.active) {
+ updateSelection(e);
+
+ plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]);
+ }
+ }
+
+ function onMouseDown(e) {
+ if (e.which != 1) // only accept left-click
+ return;
+
+ // cancel out any text selections
+ document.body.focus();
+
+ // prevent text selection and drag in old-school browsers
+ if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) {
+ savedhandlers.onselectstart = document.onselectstart;
+ document.onselectstart = function () { return false; };
+ }
+ if (document.ondrag !== undefined && savedhandlers.ondrag == null) {
+ savedhandlers.ondrag = document.ondrag;
+ document.ondrag = function () { return false; };
+ }
+
+ setSelectionPos(selection.first, e);
+
+ selection.active = true;
+
+ // this is a bit silly, but we have to use a closure to be
+ // able to whack the same handler again
+ mouseUpHandler = function (e) { onMouseUp(e); };
+
+ $(document).one("mouseup", mouseUpHandler);
+ }
+
+ function onMouseUp(e) {
+ mouseUpHandler = null;
+
+ // revert drag stuff for old-school browsers
+ if (document.onselectstart !== undefined)
+ document.onselectstart = savedhandlers.onselectstart;
+ if (document.ondrag !== undefined)
+ document.ondrag = savedhandlers.ondrag;
+
+ // no more dragging
+ selection.active = false;
+ updateSelection(e);
+
+ if (selectionIsSane())
+ triggerSelectedEvent();
+ else {
+ // this counts as a clear
+ plot.getPlaceholder().trigger("plotunselected", [ ]);
+ plot.getPlaceholder().trigger("plotselecting", [ null ]);
+ }
+
+ return false;
+ }
+
+ function getSelection() {
+ if (!selectionIsSane())
+ return null;
+
+ if (!selection.show) return null;
+
+ var r = {}, c1 = selection.first, c2 = selection.second;
+ $.each(plot.getAxes(), function (name, axis) {
+ if (axis.used) {
+ var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]);
+ r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) };
+ }
+ });
+ return r;
+ }
+
+ function triggerSelectedEvent() {
+ var r = getSelection();
+
+ plot.getPlaceholder().trigger("plotselected", [ r ]);
+
+ // backwards-compat stuff, to be removed in future
+ if (r.xaxis && r.yaxis)
+ plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]);
+ }
+
+ function clamp(min, value, max) {
+ return value < min ? min: (value > max ? max: value);
+ }
+
+ function setSelectionPos(pos, e) {
+ var o = plot.getOptions();
+ var offset = plot.getPlaceholder().offset();
+ var plotOffset = plot.getPlotOffset();
+ pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());
+ pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height());
+
+ if (o.selection.mode == "y")
+ pos.x = pos == selection.first ? 0 : plot.width();
+
+ if (o.selection.mode == "x")
+ pos.y = pos == selection.first ? 0 : plot.height();
+ }
+
+ function updateSelection(pos) {
+ if (pos.pageX == null)
+ return;
+
+ setSelectionPos(selection.second, pos);
+ if (selectionIsSane()) {
+ selection.show = true;
+ plot.triggerRedrawOverlay();
+ }
+ else
+ clearSelection(true);
+ }
+
+ function clearSelection(preventEvent) {
+ if (selection.show) {
+ selection.show = false;
+ plot.triggerRedrawOverlay();
+ if (!preventEvent)
+ plot.getPlaceholder().trigger("plotunselected", [ ]);
+ }
+ }
+
+ // function taken from markings support in Flot
+ function extractRange(ranges, coord) {
+ var axis, from, to, key, axes = plot.getAxes();
+
+ for (var k in axes) {
+ axis = axes[k];
+ if (axis.direction == coord) {
+ key = coord + axis.n + "axis";
+ if (!ranges[key] && axis.n == 1)
+ key = coord + "axis"; // support x1axis as xaxis
+ if (ranges[key]) {
+ from = ranges[key].from;
+ to = ranges[key].to;
+ break;
+ }
+ }
+ }
+
+ // backwards-compat stuff - to be removed in future
+ if (!ranges[key]) {
+ axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0];
+ from = ranges[coord + "1"];
+ to = ranges[coord + "2"];
+ }
+
+ // auto-reverse as an added bonus
+ if (from != null && to != null && from > to) {
+ var tmp = from;
+ from = to;
+ to = tmp;
+ }
+
+ return { from: from, to: to, axis: axis };
+ }
+
+ function setSelection(ranges, preventEvent) {
+ var axis, range, o = plot.getOptions();
+
+ if (o.selection.mode == "y") {
+ selection.first.x = 0;
+ selection.second.x = plot.width();
+ }
+ else {
+ range = extractRange(ranges, "x");
+
+ selection.first.x = range.axis.p2c(range.from);
+ selection.second.x = range.axis.p2c(range.to);
+ }
+
+ if (o.selection.mode == "x") {
+ selection.first.y = 0;
+ selection.second.y = plot.height();
+ }
+ else {
+ range = extractRange(ranges, "y");
+
+ selection.first.y = range.axis.p2c(range.from);
+ selection.second.y = range.axis.p2c(range.to);
+ }
+
+ selection.show = true;
+ plot.triggerRedrawOverlay();
+ if (!preventEvent && selectionIsSane())
+ triggerSelectedEvent();
+ }
+
+ function selectionIsSane() {
+ var minSize = plot.getOptions().selection.minSize;
+ return Math.abs(selection.second.x - selection.first.x) >= minSize &&
+ Math.abs(selection.second.y - selection.first.y) >= minSize;
+ }
+
+ plot.clearSelection = clearSelection;
+ plot.setSelection = setSelection;
+ plot.getSelection = getSelection;
+
+ plot.hooks.bindEvents.push(function(plot, eventHolder) {
+ var o = plot.getOptions();
+ if (o.selection.mode != null) {
+ eventHolder.mousemove(onMouseMove);
+ eventHolder.mousedown(onMouseDown);
+ }
+ });
+
+
+ plot.hooks.drawOverlay.push(function (plot, ctx) {
+ // draw selection
+ if (selection.show && selectionIsSane()) {
+ var plotOffset = plot.getPlotOffset();
+ var o = plot.getOptions();
+
+ ctx.save();
+ ctx.translate(plotOffset.left, plotOffset.top);
+
+ var c = $.color.parse(o.selection.color);
+
+ ctx.strokeStyle = c.scale('a', 0.8).toString();
+ ctx.lineWidth = 1;
+ ctx.lineJoin = o.selection.shape;
+ ctx.fillStyle = c.scale('a', 0.4).toString();
+
+ var x = Math.min(selection.first.x, selection.second.x) + 0.5,
+ y = Math.min(selection.first.y, selection.second.y) + 0.5,
+ w = Math.abs(selection.second.x - selection.first.x) - 1,
+ h = Math.abs(selection.second.y - selection.first.y) - 1;
+
+ ctx.fillRect(x, y, w, h);
+ ctx.strokeRect(x, y, w, h);
+
+ ctx.restore();
+ }
+ });
+
+ plot.hooks.shutdown.push(function (plot, eventHolder) {
+ eventHolder.unbind("mousemove", onMouseMove);
+ eventHolder.unbind("mousedown", onMouseDown);
+
+ if (mouseUpHandler)
+ $(document).unbind("mouseup", mouseUpHandler);
+ });
+
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: {
+ selection: {
+ mode: null, // one of null, "x", "y" or "xy"
+ color: "#e8cfac",
+ shape: "round", // one of "round", "miter", or "bevel"
+ minSize: 5 // minimum number of pixels
+ }
+ },
+ name: 'selection',
+ version: '1.1'
+ });
+})(jQuery);
diff --git a/misc/flot/jquery.flot.selection.min.js b/misc/flot/jquery.flot.selection.min.js
new file mode 100644
index 0000000..a0154fb
--- /dev/null
+++ b/misc/flot/jquery.flot.selection.min.js
@@ -0,0 +1,7 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function($){function init(plot){var selection={first:{x:-1,y:-1},second:{x:-1,y:-1},show:false,active:false};var savedhandlers={};var mouseUpHandler=null;function onMouseMove(e){if(selection.active){updateSelection(e);plot.getPlaceholder().trigger("plotselecting",[getSelection()])}}function onMouseDown(e){if(e.which!=1)return;document.body.focus();if(document.onselectstart!==undefined&&savedhandlers.onselectstart==null){savedhandlers.onselectstart=document.onselectstart;document.onselectstart=function(){return false}}if(document.ondrag!==undefined&&savedhandlers.ondrag==null){savedhandlers.ondrag=document.ondrag;document.ondrag=function(){return false}}setSelectionPos(selection.first,e);selection.active=true;mouseUpHandler=function(e){onMouseUp(e)};$(document).one("mouseup",mouseUpHandler)}function onMouseUp(e){mouseUpHandler=null;if(document.onselectstart!==undefined)document.onselectstart=savedhandlers.onselectstart;if(document.ondrag!==undefined)document.ondrag=savedhandlers.ondrag;selection.active=false;updateSelection(e);if(selectionIsSane())triggerSelectedEvent();else{plot.getPlaceholder().trigger("plotunselected",[]);plot.getPlaceholder().trigger("plotselecting",[null])}return false}function getSelection(){if(!selectionIsSane())return null;if(!selection.show)return null;var r={},c1=selection.first,c2=selection.second;$.each(plot.getAxes(),function(name,axis){if(axis.used){var p1=axis.c2p(c1[axis.direction]),p2=axis.c2p(c2[axis.direction]);r[name]={from:Math.min(p1,p2),to:Math.max(p1,p2)}}});return r}function triggerSelectedEvent(){var r=getSelection();plot.getPlaceholder().trigger("plotselected",[r]);if(r.xaxis&&r.yaxis)plot.getPlaceholder().trigger("selected",[{x1:r.xaxis.from,y1:r.yaxis.from,x2:r.xaxis.to,y2:r.yaxis.to}])}function clamp(min,value,max){return value<min?min:value>max?max:value}function setSelectionPos(pos,e){var o=plot.getOptions();var offset=plot.getPlaceholder().offset();var plotOffset=plot.getPlotOffset();pos.x=clamp(0,e.pageX-offset.left-plotOffset.left,plot.width());pos.y=clamp(0,e.pageY-offset.top-plotOffset.top,plot.height());if(o.selection.mode=="y")pos.x=pos==selection.first?0:plot.width();if(o.selection.mode=="x")pos.y=pos==selection.first?0:plot.height()}function updateSelection(pos){if(pos.pageX==null)return;setSelectionPos(selection.second,pos);if(selectionIsSane()){selection.show=true;plot.triggerRedrawOverlay()}else clearSelection(true)}function clearSelection(preventEvent){if(selection.show){selection.show=false;plot.triggerRedrawOverlay();if(!preventEvent)plot.getPlaceholder().trigger("plotunselected",[])}}function extractRange(ranges,coord){var axis,from,to,key,axes=plot.getAxes();for(var k in axes){axis=axes[k];if(axis.direction==coord){key=coord+axis.n+"axis";if(!ranges[key]&&axis.n==1)key=coord+"axis";if(ranges[key]){from=ranges[key].from;to=ranges[key].to;break}}}if(!ranges[key]){axis=coord=="x"?plot.getXAxes()[0]:plot.getYAxes()[0];from=ranges[coord+"1"];to=ranges[coord+"2"]}if(from!=null&&to!=null&&from>to){var tmp=from;from=to;to=tmp}return{from:from,to:to,axis:axis}}function setSelection(ranges,preventEvent){var axis,range,o=plot.getOptions();if(o.selection.mode=="y"){selection.first.x=0;selection.second.x=plot.width()}else{range=extractRange(ranges,"x");selection.first.x=range.axis.p2c(range.from);selection.second.x=range.axis.p2c(range.to)}if(o.selection.mode=="x"){selection.first.y=0;selection.second.y=plot.height()}else{range=extractRange(ranges,"y");selection.first.y=range.axis.p2c(range.from);selection.second.y=range.axis.p2c(range.to)}selection.show=true;plot.triggerRedrawOverlay();if(!preventEvent&&selectionIsSane())triggerSelectedEvent()}function selectionIsSane(){var minSize=plot.getOptions().selection.minSize;return Math.abs(selection.second.x-selection.first.x)>=minSize&&Math.abs(selection.second.y-selection.first.y)>=minSize}plot.clearSelection=clearSelection;plot.setSelection=setSelection;plot.getSelection=getSelection;plot.hooks.bindEvents.push(function(plot,eventHolder){var o=plot.getOptions();if(o.selection.mode!=null){eventHolder.mousemove(onMouseMove);eventHolder.mousedown(onMouseDown)}});plot.hooks.drawOverlay.push(function(plot,ctx){if(selection.show&&selectionIsSane()){var plotOffset=plot.getPlotOffset();var o=plot.getOptions();ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var c=$.color.parse(o.selection.color);ctx.strokeStyle=c.scale("a",.8).toString();ctx.lineWidth=1;ctx.lineJoin=o.selection.shape;ctx.fillStyle=c.scale("a",.4).toString();var x=Math.min(selection.first.x,selection.second.x)+.5,y=Math.min(selection.first.y,selection.second.y)+.5,w=Math.abs(selection.second.x-selection.first.x)-1,h=Math.abs(selection.second.y-selection.first.y)-1;ctx.fillRect(x,y,w,h);ctx.strokeRect(x,y,w,h);ctx.restore()}});plot.hooks.shutdown.push(function(plot,eventHolder){eventHolder.unbind("mousemove",onMouseMove);eventHolder.unbind("mousedown",onMouseDown);if(mouseUpHandler)$(document).unbind("mouseup",mouseUpHandler)})}$.plot.plugins.push({init:init,options:{selection:{mode:null,color:"#e8cfac",shape:"round",minSize:5}},name:"selection",version:"1.1"})})(jQuery); \ No newline at end of file
diff --git a/misc/flot/jquery.flot.stack.js b/misc/flot/jquery.flot.stack.js
new file mode 100644
index 0000000..e75a7df
--- /dev/null
+++ b/misc/flot/jquery.flot.stack.js
@@ -0,0 +1,188 @@
+/* Flot plugin for stacking data sets rather than overlyaing them.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The plugin assumes the data is sorted on x (or y if stacking horizontally).
+For line charts, it is assumed that if a line has an undefined gap (from a
+null point), then the line above it should have the same gap - insert zeros
+instead of "null" if you want another behaviour. This also holds for the start
+and end of the chart. Note that stacking a mix of positive and negative values
+in most instances doesn't make sense (so it looks weird).
+
+Two or more series are stacked when their "stack" attribute is set to the same
+key (which can be any number or string or just "true"). To specify the default
+stack, you can set the stack option like this:
+
+ series: {
+ stack: null/false, true, or a key (number/string)
+ }
+
+You can also specify it for a single series, like this:
+
+ $.plot( $("#placeholder"), [{
+ data: [ ... ],
+ stack: true
+ }])
+
+The stacking order is determined by the order of the data series in the array
+(later series end up on top of the previous).
+
+Internally, the plugin modifies the datapoints in each series, adding an
+offset to the y value. For line series, extra data points are inserted through
+interpolation. If there's a second y value, it's also adjusted (e.g for bar
+charts or filled areas).
+
+*/
+
+(function ($) {
+ var options = {
+ series: { stack: null } // or number/string
+ };
+
+ function init(plot) {
+ function findMatchingSeries(s, allseries) {
+ var res = null;
+ for (var i = 0; i < allseries.length; ++i) {
+ if (s == allseries[i])
+ break;
+
+ if (allseries[i].stack == s.stack)
+ res = allseries[i];
+ }
+
+ return res;
+ }
+
+ function stackData(plot, s, datapoints) {
+ if (s.stack == null || s.stack === false)
+ return;
+
+ var other = findMatchingSeries(s, plot.getData());
+ if (!other)
+ return;
+
+ var ps = datapoints.pointsize,
+ points = datapoints.points,
+ otherps = other.datapoints.pointsize,
+ otherpoints = other.datapoints.points,
+ newpoints = [],
+ px, py, intery, qx, qy, bottom,
+ withlines = s.lines.show,
+ horizontal = s.bars.horizontal,
+ withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y),
+ withsteps = withlines && s.lines.steps,
+ fromgap = true,
+ keyOffset = horizontal ? 1 : 0,
+ accumulateOffset = horizontal ? 0 : 1,
+ i = 0, j = 0, l, m;
+
+ while (true) {
+ if (i >= points.length)
+ break;
+
+ l = newpoints.length;
+
+ if (points[i] == null) {
+ // copy gaps
+ for (m = 0; m < ps; ++m)
+ newpoints.push(points[i + m]);
+ i += ps;
+ }
+ else if (j >= otherpoints.length) {
+ // for lines, we can't use the rest of the points
+ if (!withlines) {
+ for (m = 0; m < ps; ++m)
+ newpoints.push(points[i + m]);
+ }
+ i += ps;
+ }
+ else if (otherpoints[j] == null) {
+ // oops, got a gap
+ for (m = 0; m < ps; ++m)
+ newpoints.push(null);
+ fromgap = true;
+ j += otherps;
+ }
+ else {
+ // cases where we actually got two points
+ px = points[i + keyOffset];
+ py = points[i + accumulateOffset];
+ qx = otherpoints[j + keyOffset];
+ qy = otherpoints[j + accumulateOffset];
+ bottom = 0;
+
+ if (px == qx) {
+ for (m = 0; m < ps; ++m)
+ newpoints.push(points[i + m]);
+
+ newpoints[l + accumulateOffset] += qy;
+ bottom = qy;
+
+ i += ps;
+ j += otherps;
+ }
+ else if (px > qx) {
+ // we got past point below, might need to
+ // insert interpolated extra point
+ if (withlines && i > 0 && points[i - ps] != null) {
+ intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
+ newpoints.push(qx);
+ newpoints.push(intery + qy);
+ for (m = 2; m < ps; ++m)
+ newpoints.push(points[i + m]);
+ bottom = qy;
+ }
+
+ j += otherps;
+ }
+ else { // px < qx
+ if (fromgap && withlines) {
+ // if we come from a gap, we just skip this point
+ i += ps;
+ continue;
+ }
+
+ for (m = 0; m < ps; ++m)
+ newpoints.push(points[i + m]);
+
+ // we might be able to interpolate a point below,
+ // this can give us a better y
+ if (withlines && j > 0 && otherpoints[j - otherps] != null)
+ bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);
+
+ newpoints[l + accumulateOffset] += bottom;
+
+ i += ps;
+ }
+
+ fromgap = false;
+
+ if (l != newpoints.length && withbottom)
+ newpoints[l + 2] += bottom;
+ }
+
+ // maintain the line steps invariant
+ if (withsteps && l != newpoints.length && l > 0
+ && newpoints[l] != null
+ && newpoints[l] != newpoints[l - ps]
+ && newpoints[l + 1] != newpoints[l - ps + 1]) {
+ for (m = 0; m < ps; ++m)
+ newpoints[l + ps + m] = newpoints[l + m];
+ newpoints[l + 1] = newpoints[l - ps + 1];
+ }
+ }
+
+ datapoints.points = newpoints;
+ }
+
+ plot.hooks.processDatapoints.push(stackData);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'stack',
+ version: '1.2'
+ });
+})(jQuery);
diff --git a/misc/flot/jquery.flot.stack.min.js b/misc/flot/jquery.flot.stack.min.js
new file mode 100644
index 0000000..920764f
--- /dev/null
+++ b/misc/flot/jquery.flot.stack.min.js
@@ -0,0 +1,7 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function($){var options={series:{stack:null}};function init(plot){function findMatchingSeries(s,allseries){var res=null;for(var i=0;i<allseries.length;++i){if(s==allseries[i])break;if(allseries[i].stack==s.stack)res=allseries[i]}return res}function stackData(plot,s,datapoints){if(s.stack==null||s.stack===false)return;var other=findMatchingSeries(s,plot.getData());if(!other)return;var ps=datapoints.pointsize,points=datapoints.points,otherps=other.datapoints.pointsize,otherpoints=other.datapoints.points,newpoints=[],px,py,intery,qx,qy,bottom,withlines=s.lines.show,horizontal=s.bars.horizontal,withbottom=ps>2&&(horizontal?datapoints.format[2].x:datapoints.format[2].y),withsteps=withlines&&s.lines.steps,fromgap=true,keyOffset=horizontal?1:0,accumulateOffset=horizontal?0:1,i=0,j=0,l,m;while(true){if(i>=points.length)break;l=newpoints.length;if(points[i]==null){for(m=0;m<ps;++m)newpoints.push(points[i+m]);i+=ps}else if(j>=otherpoints.length){if(!withlines){for(m=0;m<ps;++m)newpoints.push(points[i+m])}i+=ps}else if(otherpoints[j]==null){for(m=0;m<ps;++m)newpoints.push(null);fromgap=true;j+=otherps}else{px=points[i+keyOffset];py=points[i+accumulateOffset];qx=otherpoints[j+keyOffset];qy=otherpoints[j+accumulateOffset];bottom=0;if(px==qx){for(m=0;m<ps;++m)newpoints.push(points[i+m]);newpoints[l+accumulateOffset]+=qy;bottom=qy;i+=ps;j+=otherps}else if(px>qx){if(withlines&&i>0&&points[i-ps]!=null){intery=py+(points[i-ps+accumulateOffset]-py)*(qx-px)/(points[i-ps+keyOffset]-px);newpoints.push(qx);newpoints.push(intery+qy);for(m=2;m<ps;++m)newpoints.push(points[i+m]);bottom=qy}j+=otherps}else{if(fromgap&&withlines){i+=ps;continue}for(m=0;m<ps;++m)newpoints.push(points[i+m]);if(withlines&&j>0&&otherpoints[j-otherps]!=null)bottom=qy+(otherpoints[j-otherps+accumulateOffset]-qy)*(px-qx)/(otherpoints[j-otherps+keyOffset]-qx);newpoints[l+accumulateOffset]+=bottom;i+=ps}fromgap=false;if(l!=newpoints.length&&withbottom)newpoints[l+2]+=bottom}if(withsteps&&l!=newpoints.length&&l>0&&newpoints[l]!=null&&newpoints[l]!=newpoints[l-ps]&&newpoints[l+1]!=newpoints[l-ps+1]){for(m=0;m<ps;++m)newpoints[l+ps+m]=newpoints[l+m];newpoints[l+1]=newpoints[l-ps+1]}}datapoints.points=newpoints}plot.hooks.processDatapoints.push(stackData)}$.plot.plugins.push({init:init,options:options,name:"stack",version:"1.2"})})(jQuery); \ No newline at end of file
diff --git a/misc/flot/jquery.flot.symbol.js b/misc/flot/jquery.flot.symbol.js
new file mode 100644
index 0000000..79f6349
--- /dev/null
+++ b/misc/flot/jquery.flot.symbol.js
@@ -0,0 +1,71 @@
+/* Flot plugin that adds some extra symbols for plotting points.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The symbols are accessed as strings through the standard symbol options:
+
+ series: {
+ points: {
+ symbol: "square" // or "diamond", "triangle", "cross"
+ }
+ }
+
+*/
+
+(function ($) {
+ function processRawData(plot, series, datapoints) {
+ // we normalize the area of each symbol so it is approximately the
+ // same as a circle of the given radius
+
+ var handlers = {
+ square: function (ctx, x, y, radius, shadow) {
+ // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2
+ var size = radius * Math.sqrt(Math.PI) / 2;
+ ctx.rect(x - size, y - size, size + size, size + size);
+ },
+ diamond: function (ctx, x, y, radius, shadow) {
+ // pi * r^2 = 2s^2 => s = r * sqrt(pi/2)
+ var size = radius * Math.sqrt(Math.PI / 2);
+ ctx.moveTo(x - size, y);
+ ctx.lineTo(x, y - size);
+ ctx.lineTo(x + size, y);
+ ctx.lineTo(x, y + size);
+ ctx.lineTo(x - size, y);
+ },
+ triangle: function (ctx, x, y, radius, shadow) {
+ // pi * r^2 = 1/2 * s^2 * sin (pi / 3) => s = r * sqrt(2 * pi / sin(pi / 3))
+ var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3));
+ var height = size * Math.sin(Math.PI / 3);
+ ctx.moveTo(x - size/2, y + height/2);
+ ctx.lineTo(x + size/2, y + height/2);
+ if (!shadow) {
+ ctx.lineTo(x, y - height/2);
+ ctx.lineTo(x - size/2, y + height/2);
+ }
+ },
+ cross: function (ctx, x, y, radius, shadow) {
+ // pi * r^2 = (2s)^2 => s = r * sqrt(pi)/2
+ var size = radius * Math.sqrt(Math.PI) / 2;
+ ctx.moveTo(x - size, y - size);
+ ctx.lineTo(x + size, y + size);
+ ctx.moveTo(x - size, y + size);
+ ctx.lineTo(x + size, y - size);
+ }
+ };
+
+ var s = series.points.symbol;
+ if (handlers[s])
+ series.points.symbol = handlers[s];
+ }
+
+ function init(plot) {
+ plot.hooks.processDatapoints.push(processRawData);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ name: 'symbols',
+ version: '1.0'
+ });
+})(jQuery);
diff --git a/misc/flot/jquery.flot.symbol.min.js b/misc/flot/jquery.flot.symbol.min.js
new file mode 100644
index 0000000..f4a3430
--- /dev/null
+++ b/misc/flot/jquery.flot.symbol.min.js
@@ -0,0 +1,7 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function($){function processRawData(plot,series,datapoints){var handlers={square:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI)/2;ctx.rect(x-size,y-size,size+size,size+size)},diamond:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI/2);ctx.moveTo(x-size,y);ctx.lineTo(x,y-size);ctx.lineTo(x+size,y);ctx.lineTo(x,y+size);ctx.lineTo(x-size,y)},triangle:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(2*Math.PI/Math.sin(Math.PI/3));var height=size*Math.sin(Math.PI/3);ctx.moveTo(x-size/2,y+height/2);ctx.lineTo(x+size/2,y+height/2);if(!shadow){ctx.lineTo(x,y-height/2);ctx.lineTo(x-size/2,y+height/2)}},cross:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI)/2;ctx.moveTo(x-size,y-size);ctx.lineTo(x+size,y+size);ctx.moveTo(x-size,y+size);ctx.lineTo(x+size,y-size)}};var s=series.points.symbol;if(handlers[s])series.points.symbol=handlers[s]}function init(plot){plot.hooks.processDatapoints.push(processRawData)}$.plot.plugins.push({init:init,name:"symbols",version:"1.0"})})(jQuery); \ No newline at end of file
diff --git a/misc/flot/jquery.flot.threshold.js b/misc/flot/jquery.flot.threshold.js
new file mode 100644
index 0000000..8c99c40
--- /dev/null
+++ b/misc/flot/jquery.flot.threshold.js
@@ -0,0 +1,142 @@
+/* Flot plugin for thresholding data.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+The plugin supports these options:
+
+ series: {
+ threshold: {
+ below: number
+ color: colorspec
+ }
+ }
+
+It can also be applied to a single series, like this:
+
+ $.plot( $("#placeholder"), [{
+ data: [ ... ],
+ threshold: { ... }
+ }])
+
+An array can be passed for multiple thresholding, like this:
+
+ threshold: [{
+ below: number1
+ color: color1
+ },{
+ below: number2
+ color: color2
+ }]
+
+These multiple threshold objects can be passed in any order since they are
+sorted by the processing function.
+
+The data points below "below" are drawn with the specified color. This makes
+it easy to mark points below 0, e.g. for budget data.
+
+Internally, the plugin works by splitting the data into two series, above and
+below the threshold. The extra series below the threshold will have its label
+cleared and the special "originSeries" attribute set to the original series.
+You may need to check for this in hover events.
+
+*/
+
+(function ($) {
+ var options = {
+ series: { threshold: null } // or { below: number, color: color spec}
+ };
+
+ function init(plot) {
+ function thresholdData(plot, s, datapoints, below, color) {
+ var ps = datapoints.pointsize, i, x, y, p, prevp,
+ thresholded = $.extend({}, s); // note: shallow copy
+
+ thresholded.datapoints = { points: [], pointsize: ps, format: datapoints.format };
+ thresholded.label = null;
+ thresholded.color = color;
+ thresholded.threshold = null;
+ thresholded.originSeries = s;
+ thresholded.data = [];
+
+ var origpoints = datapoints.points,
+ addCrossingPoints = s.lines.show;
+
+ var threspoints = [];
+ var newpoints = [];
+ var m;
+
+ for (i = 0; i < origpoints.length; i += ps) {
+ x = origpoints[i];
+ y = origpoints[i + 1];
+
+ prevp = p;
+ if (y < below)
+ p = threspoints;
+ else
+ p = newpoints;
+
+ if (addCrossingPoints && prevp != p && x != null
+ && i > 0 && origpoints[i - ps] != null) {
+ var interx = x + (below - y) * (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]);
+ prevp.push(interx);
+ prevp.push(below);
+ for (m = 2; m < ps; ++m)
+ prevp.push(origpoints[i + m]);
+
+ p.push(null); // start new segment
+ p.push(null);
+ for (m = 2; m < ps; ++m)
+ p.push(origpoints[i + m]);
+ p.push(interx);
+ p.push(below);
+ for (m = 2; m < ps; ++m)
+ p.push(origpoints[i + m]);
+ }
+
+ p.push(x);
+ p.push(y);
+ for (m = 2; m < ps; ++m)
+ p.push(origpoints[i + m]);
+ }
+
+ datapoints.points = newpoints;
+ thresholded.datapoints.points = threspoints;
+
+ if (thresholded.datapoints.points.length > 0) {
+ var origIndex = $.inArray(s, plot.getData());
+ // Insert newly-generated series right after original one (to prevent it from becoming top-most)
+ plot.getData().splice(origIndex + 1, 0, thresholded);
+ }
+
+ // FIXME: there are probably some edge cases left in bars
+ }
+
+ function processThresholds(plot, s, datapoints) {
+ if (!s.threshold)
+ return;
+
+ if (s.threshold instanceof Array) {
+ s.threshold.sort(function(a, b) {
+ return a.below - b.below;
+ });
+
+ $(s.threshold).each(function(i, th) {
+ thresholdData(plot, s, datapoints, th.below, th.color);
+ });
+ }
+ else {
+ thresholdData(plot, s, datapoints, s.threshold.below, s.threshold.color);
+ }
+ }
+
+ plot.hooks.processDatapoints.push(processThresholds);
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'threshold',
+ version: '1.2'
+ });
+})(jQuery);
diff --git a/misc/flot/jquery.flot.threshold.min.js b/misc/flot/jquery.flot.threshold.min.js
new file mode 100644
index 0000000..ce93e0f
--- /dev/null
+++ b/misc/flot/jquery.flot.threshold.min.js
@@ -0,0 +1,7 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function($){var options={series:{threshold:null}};function init(plot){function thresholdData(plot,s,datapoints,below,color){var ps=datapoints.pointsize,i,x,y,p,prevp,thresholded=$.extend({},s);thresholded.datapoints={points:[],pointsize:ps,format:datapoints.format};thresholded.label=null;thresholded.color=color;thresholded.threshold=null;thresholded.originSeries=s;thresholded.data=[];var origpoints=datapoints.points,addCrossingPoints=s.lines.show;var threspoints=[];var newpoints=[];var m;for(i=0;i<origpoints.length;i+=ps){x=origpoints[i];y=origpoints[i+1];prevp=p;if(y<below)p=threspoints;else p=newpoints;if(addCrossingPoints&&prevp!=p&&x!=null&&i>0&&origpoints[i-ps]!=null){var interx=x+(below-y)*(x-origpoints[i-ps])/(y-origpoints[i-ps+1]);prevp.push(interx);prevp.push(below);for(m=2;m<ps;++m)prevp.push(origpoints[i+m]);p.push(null);p.push(null);for(m=2;m<ps;++m)p.push(origpoints[i+m]);p.push(interx);p.push(below);for(m=2;m<ps;++m)p.push(origpoints[i+m])}p.push(x);p.push(y);for(m=2;m<ps;++m)p.push(origpoints[i+m])}datapoints.points=newpoints;thresholded.datapoints.points=threspoints;if(thresholded.datapoints.points.length>0){var origIndex=$.inArray(s,plot.getData());plot.getData().splice(origIndex+1,0,thresholded)}}function processThresholds(plot,s,datapoints){if(!s.threshold)return;if(s.threshold instanceof Array){s.threshold.sort(function(a,b){return a.below-b.below});$(s.threshold).each(function(i,th){thresholdData(plot,s,datapoints,th.below,th.color)})}else{thresholdData(plot,s,datapoints,s.threshold.below,s.threshold.color)}}plot.hooks.processDatapoints.push(processThresholds)}$.plot.plugins.push({init:init,options:options,name:"threshold",version:"1.2"})})(jQuery); \ No newline at end of file
diff --git a/misc/flot/jquery.flot.time.js b/misc/flot/jquery.flot.time.js
new file mode 100644
index 0000000..34c1d12
--- /dev/null
+++ b/misc/flot/jquery.flot.time.js
@@ -0,0 +1,432 @@
+/* Pretty handling of time axes.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+Set axis.mode to "time" to enable. See the section "Time series data" in
+API.txt for details.
+
+*/
+
+(function($) {
+
+ var options = {
+ xaxis: {
+ timezone: null, // "browser" for local to the client or timezone for timezone-js
+ timeformat: null, // format string to use
+ twelveHourClock: false, // 12 or 24 time in time mode
+ monthNames: null // list of names of months
+ }
+ };
+
+ // round to nearby lower multiple of base
+
+ function floorInBase(n, base) {
+ return base * Math.floor(n / base);
+ }
+
+ // Returns a string with the date d formatted according to fmt.
+ // A subset of the Open Group's strftime format is supported.
+
+ function formatDate(d, fmt, monthNames, dayNames) {
+
+ if (typeof d.strftime == "function") {
+ return d.strftime(fmt);
+ }
+
+ var leftPad = function(n, pad) {
+ n = "" + n;
+ pad = "" + (pad == null ? "0" : pad);
+ return n.length == 1 ? pad + n : n;
+ };
+
+ var r = [];
+ var escape = false;
+ var hours = d.getHours();
+ var isAM = hours < 12;
+
+ if (monthNames == null) {
+ monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+ }
+
+ if (dayNames == null) {
+ dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+ }
+
+ var hours12;
+
+ if (hours > 12) {
+ hours12 = hours - 12;
+ } else if (hours == 0) {
+ hours12 = 12;
+ } else {
+ hours12 = hours;
+ }
+
+ for (var i = 0; i < fmt.length; ++i) {
+
+ var c = fmt.charAt(i);
+
+ if (escape) {
+ switch (c) {
+ case 'a': c = "" + dayNames[d.getDay()]; break;
+ case 'b': c = "" + monthNames[d.getMonth()]; break;
+ case 'd': c = leftPad(d.getDate()); break;
+ case 'e': c = leftPad(d.getDate(), " "); break;
+ case 'h': // For back-compat with 0.7; remove in 1.0
+ case 'H': c = leftPad(hours); break;
+ case 'I': c = leftPad(hours12); break;
+ case 'l': c = leftPad(hours12, " "); break;
+ case 'm': c = leftPad(d.getMonth() + 1); break;
+ case 'M': c = leftPad(d.getMinutes()); break;
+ // quarters not in Open Group's strftime specification
+ case 'q':
+ c = "" + (Math.floor(d.getMonth() / 3) + 1); break;
+ case 'S': c = leftPad(d.getSeconds()); break;
+ case 'y': c = leftPad(d.getFullYear() % 100); break;
+ case 'Y': c = "" + d.getFullYear(); break;
+ case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
+ case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
+ case 'w': c = "" + d.getDay(); break;
+ }
+ r.push(c);
+ escape = false;
+ } else {
+ if (c == "%") {
+ escape = true;
+ } else {
+ r.push(c);
+ }
+ }
+ }
+
+ return r.join("");
+ }
+
+ // To have a consistent view of time-based data independent of which time
+ // zone the client happens to be in we need a date-like object independent
+ // of time zones. This is done through a wrapper that only calls the UTC
+ // versions of the accessor methods.
+
+ function makeUtcWrapper(d) {
+
+ function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
+ sourceObj[sourceMethod] = function() {
+ return targetObj[targetMethod].apply(targetObj, arguments);
+ };
+ };
+
+ var utc = {
+ date: d
+ };
+
+ // support strftime, if found
+
+ if (d.strftime != undefined) {
+ addProxyMethod(utc, "strftime", d, "strftime");
+ }
+
+ addProxyMethod(utc, "getTime", d, "getTime");
+ addProxyMethod(utc, "setTime", d, "setTime");
+
+ var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"];
+
+ for (var p = 0; p < props.length; p++) {
+ addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
+ addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
+ }
+
+ return utc;
+ };
+
+ // select time zone strategy. This returns a date-like object tied to the
+ // desired timezone
+
+ function dateGenerator(ts, opts) {
+ if (opts.timezone == "browser") {
+ return new Date(ts);
+ } else if (!opts.timezone || opts.timezone == "utc") {
+ return makeUtcWrapper(new Date(ts));
+ } else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") {
+ var d = new timezoneJS.Date();
+ // timezone-js is fickle, so be sure to set the time zone before
+ // setting the time.
+ d.setTimezone(opts.timezone);
+ d.setTime(ts);
+ return d;
+ } else {
+ return makeUtcWrapper(new Date(ts));
+ }
+ }
+
+ // map of app. size of time units in milliseconds
+
+ var timeUnitSize = {
+ "second": 1000,
+ "minute": 60 * 1000,
+ "hour": 60 * 60 * 1000,
+ "day": 24 * 60 * 60 * 1000,
+ "month": 30 * 24 * 60 * 60 * 1000,
+ "quarter": 3 * 30 * 24 * 60 * 60 * 1000,
+ "year": 365.2425 * 24 * 60 * 60 * 1000
+ };
+
+ // the allowed tick sizes, after 1 year we use
+ // an integer algorithm
+
+ var baseSpec = [
+ [1, "second"], [2, "second"], [5, "second"], [10, "second"],
+ [30, "second"],
+ [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
+ [30, "minute"],
+ [1, "hour"], [2, "hour"], [4, "hour"],
+ [8, "hour"], [12, "hour"],
+ [1, "day"], [2, "day"], [3, "day"],
+ [0.25, "month"], [0.5, "month"], [1, "month"],
+ [2, "month"]
+ ];
+
+ // we don't know which variant(s) we'll need yet, but generating both is
+ // cheap
+
+ var specMonths = baseSpec.concat([[3, "month"], [6, "month"],
+ [1, "year"]]);
+ var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"],
+ [1, "year"]]);
+
+ function init(plot) {
+ plot.hooks.processOptions.push(function (plot, options) {
+ $.each(plot.getAxes(), function(axisName, axis) {
+
+ var opts = axis.options;
+
+ if (opts.mode == "time") {
+ axis.tickGenerator = function(axis) {
+
+ var ticks = [];
+ var d = dateGenerator(axis.min, opts);
+ var minSize = 0;
+
+ // make quarter use a possibility if quarters are
+ // mentioned in either of these options
+
+ var spec = (opts.tickSize && opts.tickSize[1] ===
+ "quarter") ||
+ (opts.minTickSize && opts.minTickSize[1] ===
+ "quarter") ? specQuarters : specMonths;
+
+ if (opts.minTickSize != null) {
+ if (typeof opts.tickSize == "number") {
+ minSize = opts.tickSize;
+ } else {
+ minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
+ }
+ }
+
+ for (var i = 0; i < spec.length - 1; ++i) {
+ if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]]
+ + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
+ && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
+ break;
+ }
+ }
+
+ var size = spec[i][0];
+ var unit = spec[i][1];
+
+ // special-case the possibility of several years
+
+ if (unit == "year") {
+
+ // if given a minTickSize in years, just use it,
+ // ensuring that it's an integer
+
+ if (opts.minTickSize != null && opts.minTickSize[1] == "year") {
+ size = Math.floor(opts.minTickSize[0]);
+ } else {
+
+ var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));
+ var norm = (axis.delta / timeUnitSize.year) / magn;
+
+ if (norm < 1.5) {
+ size = 1;
+ } else if (norm < 3) {
+ size = 2;
+ } else if (norm < 7.5) {
+ size = 5;
+ } else {
+ size = 10;
+ }
+
+ size *= magn;
+ }
+
+ // minimum size for years is 1
+
+ if (size < 1) {
+ size = 1;
+ }
+ }
+
+ axis.tickSize = opts.tickSize || [size, unit];
+ var tickSize = axis.tickSize[0];
+ unit = axis.tickSize[1];
+
+ var step = tickSize * timeUnitSize[unit];
+
+ if (unit == "second") {
+ d.setSeconds(floorInBase(d.getSeconds(), tickSize));
+ } else if (unit == "minute") {
+ d.setMinutes(floorInBase(d.getMinutes(), tickSize));
+ } else if (unit == "hour") {
+ d.setHours(floorInBase(d.getHours(), tickSize));
+ } else if (unit == "month") {
+ d.setMonth(floorInBase(d.getMonth(), tickSize));
+ } else if (unit == "quarter") {
+ d.setMonth(3 * floorInBase(d.getMonth() / 3,
+ tickSize));
+ } else if (unit == "year") {
+ d.setFullYear(floorInBase(d.getFullYear(), tickSize));
+ }
+
+ // reset smaller components
+
+ d.setMilliseconds(0);
+
+ if (step >= timeUnitSize.minute) {
+ d.setSeconds(0);
+ }
+ if (step >= timeUnitSize.hour) {
+ d.setMinutes(0);
+ }
+ if (step >= timeUnitSize.day) {
+ d.setHours(0);
+ }
+ if (step >= timeUnitSize.day * 4) {
+ d.setDate(1);
+ }
+ if (step >= timeUnitSize.month * 2) {
+ d.setMonth(floorInBase(d.getMonth(), 3));
+ }
+ if (step >= timeUnitSize.quarter * 2) {
+ d.setMonth(floorInBase(d.getMonth(), 6));
+ }
+ if (step >= timeUnitSize.year) {
+ d.setMonth(0);
+ }
+
+ var carry = 0;
+ var v = Number.NaN;
+ var prev;
+
+ do {
+
+ prev = v;
+ v = d.getTime();
+ ticks.push(v);
+
+ if (unit == "month" || unit == "quarter") {
+ if (tickSize < 1) {
+
+ // a bit complicated - we'll divide the
+ // month/quarter up but we need to take
+ // care of fractions so we don't end up in
+ // the middle of a day
+
+ d.setDate(1);
+ var start = d.getTime();
+ d.setMonth(d.getMonth() +
+ (unit == "quarter" ? 3 : 1));
+ var end = d.getTime();
+ d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
+ carry = d.getHours();
+ d.setHours(0);
+ } else {
+ d.setMonth(d.getMonth() +
+ tickSize * (unit == "quarter" ? 3 : 1));
+ }
+ } else if (unit == "year") {
+ d.setFullYear(d.getFullYear() + tickSize);
+ } else {
+ d.setTime(v + step);
+ }
+ } while (v < axis.max && v != prev);
+
+ return ticks;
+ };
+
+ axis.tickFormatter = function (v, axis) {
+
+ var d = dateGenerator(v, axis.options);
+
+ // first check global format
+
+ if (opts.timeformat != null) {
+ return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames);
+ }
+
+ // possibly use quarters if quarters are mentioned in
+ // any of these places
+
+ var useQuarters = (axis.options.tickSize &&
+ axis.options.tickSize[1] == "quarter") ||
+ (axis.options.minTickSize &&
+ axis.options.minTickSize[1] == "quarter");
+
+ var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
+ var span = axis.max - axis.min;
+ var suffix = (opts.twelveHourClock) ? " %p" : "";
+ var hourCode = (opts.twelveHourClock) ? "%I" : "%H";
+ var fmt;
+
+ if (t < timeUnitSize.minute) {
+ fmt = hourCode + ":%M:%S" + suffix;
+ } else if (t < timeUnitSize.day) {
+ if (span < 2 * timeUnitSize.day) {
+ fmt = hourCode + ":%M" + suffix;
+ } else {
+ fmt = "%b %d " + hourCode + ":%M" + suffix;
+ }
+ } else if (t < timeUnitSize.month) {
+ fmt = "%b %d";
+ } else if ((useQuarters && t < timeUnitSize.quarter) ||
+ (!useQuarters && t < timeUnitSize.year)) {
+ if (span < timeUnitSize.year) {
+ fmt = "%b";
+ } else {
+ fmt = "%b %Y";
+ }
+ } else if (useQuarters && t < timeUnitSize.year) {
+ if (span < timeUnitSize.year) {
+ fmt = "Q%q";
+ } else {
+ fmt = "Q%q %Y";
+ }
+ } else {
+ fmt = "%Y";
+ }
+
+ var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames);
+
+ return rt;
+ };
+ }
+ });
+ });
+ }
+
+ $.plot.plugins.push({
+ init: init,
+ options: options,
+ name: 'time',
+ version: '1.0'
+ });
+
+ // Time-axis support used to be in Flot core, which exposed the
+ // formatDate function on the plot object. Various plugins depend
+ // on the function, so we need to re-expose it here.
+
+ $.plot.formatDate = formatDate;
+ $.plot.dateGenerator = dateGenerator;
+
+})(jQuery);
diff --git a/misc/flot/jquery.flot.time.min.js b/misc/flot/jquery.flot.time.min.js
new file mode 100644
index 0000000..690eb68
--- /dev/null
+++ b/misc/flot/jquery.flot.time.min.js
@@ -0,0 +1,7 @@
+/* Javascript plotting library for jQuery, version 0.8.3.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Licensed under the MIT license.
+
+*/
+(function($){var options={xaxis:{timezone:null,timeformat:null,twelveHourClock:false,monthNames:null}};function floorInBase(n,base){return base*Math.floor(n/base)}function formatDate(d,fmt,monthNames,dayNames){if(typeof d.strftime=="function"){return d.strftime(fmt)}var leftPad=function(n,pad){n=""+n;pad=""+(pad==null?"0":pad);return n.length==1?pad+n:n};var r=[];var escape=false;var hours=d.getHours();var isAM=hours<12;if(monthNames==null){monthNames=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]}if(dayNames==null){dayNames=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]}var hours12;if(hours>12){hours12=hours-12}else if(hours==0){hours12=12}else{hours12=hours}for(var i=0;i<fmt.length;++i){var c=fmt.charAt(i);if(escape){switch(c){case"a":c=""+dayNames[d.getDay()];break;case"b":c=""+monthNames[d.getMonth()];break;case"d":c=leftPad(d.getDate());break;case"e":c=leftPad(d.getDate()," ");break;case"h":case"H":c=leftPad(hours);break;case"I":c=leftPad(hours12);break;case"l":c=leftPad(hours12," ");break;case"m":c=leftPad(d.getMonth()+1);break;case"M":c=leftPad(d.getMinutes());break;case"q":c=""+(Math.floor(d.getMonth()/3)+1);break;case"S":c=leftPad(d.getSeconds());break;case"y":c=leftPad(d.getFullYear()%100);break;case"Y":c=""+d.getFullYear();break;case"p":c=isAM?""+"am":""+"pm";break;case"P":c=isAM?""+"AM":""+"PM";break;case"w":c=""+d.getDay();break}r.push(c);escape=false}else{if(c=="%"){escape=true}else{r.push(c)}}}return r.join("")}function makeUtcWrapper(d){function addProxyMethod(sourceObj,sourceMethod,targetObj,targetMethod){sourceObj[sourceMethod]=function(){return targetObj[targetMethod].apply(targetObj,arguments)}}var utc={date:d};if(d.strftime!=undefined){addProxyMethod(utc,"strftime",d,"strftime")}addProxyMethod(utc,"getTime",d,"getTime");addProxyMethod(utc,"setTime",d,"setTime");var props=["Date","Day","FullYear","Hours","Milliseconds","Minutes","Month","Seconds"];for(var p=0;p<props.length;p++){addProxyMethod(utc,"get"+props[p],d,"getUTC"+props[p]);addProxyMethod(utc,"set"+props[p],d,"setUTC"+props[p])}return utc}function dateGenerator(ts,opts){if(opts.timezone=="browser"){return new Date(ts)}else if(!opts.timezone||opts.timezone=="utc"){return makeUtcWrapper(new Date(ts))}else if(typeof timezoneJS!="undefined"&&typeof timezoneJS.Date!="undefined"){var d=new timezoneJS.Date;d.setTimezone(opts.timezone);d.setTime(ts);return d}else{return makeUtcWrapper(new Date(ts))}}var timeUnitSize={second:1e3,minute:60*1e3,hour:60*60*1e3,day:24*60*60*1e3,month:30*24*60*60*1e3,quarter:3*30*24*60*60*1e3,year:365.2425*24*60*60*1e3};var baseSpec=[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[.25,"month"],[.5,"month"],[1,"month"],[2,"month"]];var specMonths=baseSpec.concat([[3,"month"],[6,"month"],[1,"year"]]);var specQuarters=baseSpec.concat([[1,"quarter"],[2,"quarter"],[1,"year"]]);function init(plot){plot.hooks.processOptions.push(function(plot,options){$.each(plot.getAxes(),function(axisName,axis){var opts=axis.options;if(opts.mode=="time"){axis.tickGenerator=function(axis){var ticks=[];var d=dateGenerator(axis.min,opts);var minSize=0;var spec=opts.tickSize&&opts.tickSize[1]==="quarter"||opts.minTickSize&&opts.minTickSize[1]==="quarter"?specQuarters:specMonths;if(opts.minTickSize!=null){if(typeof opts.tickSize=="number"){minSize=opts.tickSize}else{minSize=opts.minTickSize[0]*timeUnitSize[opts.minTickSize[1]]}}for(var i=0;i<spec.length-1;++i){if(axis.delta<(spec[i][0]*timeUnitSize[spec[i][1]]+spec[i+1][0]*timeUnitSize[spec[i+1][1]])/2&&spec[i][0]*timeUnitSize[spec[i][1]]>=minSize){break}}var size=spec[i][0];var unit=spec[i][1];if(unit=="year"){if(opts.minTickSize!=null&&opts.minTickSize[1]=="year"){size=Math.floor(opts.minTickSize[0])}else{var magn=Math.pow(10,Math.floor(Math.log(axis.delta/timeUnitSize.year)/Math.LN10));var norm=axis.delta/timeUnitSize.year/magn;if(norm<1.5){size=1}else if(norm<3){size=2}else if(norm<7.5){size=5}else{size=10}size*=magn}if(size<1){size=1}}axis.tickSize=opts.tickSize||[size,unit];var tickSize=axis.tickSize[0];unit=axis.tickSize[1];var step=tickSize*timeUnitSize[unit];if(unit=="second"){d.setSeconds(floorInBase(d.getSeconds(),tickSize))}else if(unit=="minute"){d.setMinutes(floorInBase(d.getMinutes(),tickSize))}else if(unit=="hour"){d.setHours(floorInBase(d.getHours(),tickSize))}else if(unit=="month"){d.setMonth(floorInBase(d.getMonth(),tickSize))}else if(unit=="quarter"){d.setMonth(3*floorInBase(d.getMonth()/3,tickSize))}else if(unit=="year"){d.setFullYear(floorInBase(d.getFullYear(),tickSize))}d.setMilliseconds(0);if(step>=timeUnitSize.minute){d.setSeconds(0)}if(step>=timeUnitSize.hour){d.setMinutes(0)}if(step>=timeUnitSize.day){d.setHours(0)}if(step>=timeUnitSize.day*4){d.setDate(1)}if(step>=timeUnitSize.month*2){d.setMonth(floorInBase(d.getMonth(),3))}if(step>=timeUnitSize.quarter*2){d.setMonth(floorInBase(d.getMonth(),6))}if(step>=timeUnitSize.year){d.setMonth(0)}var carry=0;var v=Number.NaN;var prev;do{prev=v;v=d.getTime();ticks.push(v);if(unit=="month"||unit=="quarter"){if(tickSize<1){d.setDate(1);var start=d.getTime();d.setMonth(d.getMonth()+(unit=="quarter"?3:1));var end=d.getTime();d.setTime(v+carry*timeUnitSize.hour+(end-start)*tickSize);carry=d.getHours();d.setHours(0)}else{d.setMonth(d.getMonth()+tickSize*(unit=="quarter"?3:1))}}else if(unit=="year"){d.setFullYear(d.getFullYear()+tickSize)}else{d.setTime(v+step)}}while(v<axis.max&&v!=prev);return ticks};axis.tickFormatter=function(v,axis){var d=dateGenerator(v,axis.options);if(opts.timeformat!=null){return formatDate(d,opts.timeformat,opts.monthNames,opts.dayNames)}var useQuarters=axis.options.tickSize&&axis.options.tickSize[1]=="quarter"||axis.options.minTickSize&&axis.options.minTickSize[1]=="quarter";var t=axis.tickSize[0]*timeUnitSize[axis.tickSize[1]];var span=axis.max-axis.min;var suffix=opts.twelveHourClock?" %p":"";var hourCode=opts.twelveHourClock?"%I":"%H";var fmt;if(t<timeUnitSize.minute){fmt=hourCode+":%M:%S"+suffix}else if(t<timeUnitSize.day){if(span<2*timeUnitSize.day){fmt=hourCode+":%M"+suffix}else{fmt="%b %d "+hourCode+":%M"+suffix}}else if(t<timeUnitSize.month){fmt="%b %d"}else if(useQuarters&&t<timeUnitSize.quarter||!useQuarters&&t<timeUnitSize.year){if(span<timeUnitSize.year){fmt="%b"}else{fmt="%b %Y"}}else if(useQuarters&&t<timeUnitSize.year){if(span<timeUnitSize.year){fmt="Q%q"}else{fmt="Q%q %Y"}}else{fmt="%Y"}var rt=formatDate(d,fmt,opts.monthNames,opts.dayNames);return rt}}})})}$.plot.plugins.push({init:init,options:options,name:"time",version:"1.0"});$.plot.formatDate=formatDate;$.plot.dateGenerator=dateGenerator})(jQuery); \ No newline at end of file
diff --git a/misc/flot/jquery.js b/misc/flot/jquery.js
new file mode 100644
index 0000000..8c24ffc
--- /dev/null
+++ b/misc/flot/jquery.js
@@ -0,0 +1,9472 @@
+/*!
+ * jQuery JavaScript Library v1.8.3
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: Tue Nov 13 2012 08:20:33 GMT-0500 (Eastern Standard Time)
+ */
+(function( window, undefined ) {
+var
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // Use the correct document accordingly with window argument (sandbox)
+ document = window.document,
+ location = window.location,
+ navigator = window.navigator,
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // Save a reference to some core methods
+ core_push = Array.prototype.push,
+ core_slice = Array.prototype.slice,
+ core_indexOf = Array.prototype.indexOf,
+ core_toString = Object.prototype.toString,
+ core_hasOwn = Object.prototype.hasOwnProperty,
+ core_trim = String.prototype.trim,
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Used for matching numbers
+ core_pnum = /[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,
+
+ // Used for detecting and trimming whitespace
+ core_rnotwhite = /\S/,
+ core_rspace = /\s+/,
+
+ // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)
+ rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+ rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
+
+ // JSON RegExp
+ rvalidchars = /^[\],:{}\s]*$/,
+ rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+ rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
+ rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,
+
+ // Matches dashed string for camelizing
+ rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([\da-z])/gi,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return ( letter + "" ).toUpperCase();
+ },
+
+ // The ready event handler and self cleanup method
+ DOMContentLoaded = function() {
+ if ( document.addEventListener ) {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ } else if ( document.readyState === "complete" ) {
+ // we're here because readyState === "complete" in oldIE
+ // which is good enough for us to call the dom ready!
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ },
+
+ // [[Class]] -> type pairs
+ class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem, ret, doc;
+
+ // Handle $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+ doc = ( context && context.nodeType ? context.ownerDocument || context : document );
+
+ // scripts is true for back-compat
+ selector = jQuery.parseHTML( match[1], doc, true );
+ if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+ this.attr.call( selector, context, true );
+ }
+
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.8.3",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return core_slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems, name, selector ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" ) {
+ ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
+ } else if ( name ) {
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+ }
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Add the callback
+ jQuery.ready.promise().done( fn );
+
+ return this;
+ },
+
+ eq: function( i ) {
+ i = +i;
+ return i === -1 ?
+ this.slice( i ) :
+ this.slice( i, i + 1 );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ slice: function() {
+ return this.pushStack( core_slice.apply( this, arguments ),
+ "slice", core_slice.call(arguments).join(",") );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: core_push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
+
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger("ready").off("ready");
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ isWindow: function( obj ) {
+ return obj != null && obj == obj.window;
+ },
+
+ isNumeric: function( obj ) {
+ return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ },
+
+ type: function( obj ) {
+ return obj == null ?
+ String( obj ) :
+ class2type[ core_toString.call(obj) ] || "object";
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ try {
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !core_hasOwn.call(obj, "constructor") &&
+ !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+ } catch ( e ) {
+ // IE8,9 Will throw exceptions on certain host objects #9897
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || core_hasOwn.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ // data: string of html
+ // context (optional): If specified, the fragment will be created in this context, defaults to document
+ // scripts (optional): If true, will include scripts passed in the html string
+ parseHTML: function( data, context, scripts ) {
+ var parsed;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ if ( typeof context === "boolean" ) {
+ scripts = context;
+ context = 0;
+ }
+ context = context || document;
+
+ // Single tag
+ if ( (parsed = rsingleTag.exec( data )) ) {
+ return [ context.createElement( parsed[1] ) ];
+ }
+
+ parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] );
+ return jQuery.merge( [],
+ (parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes );
+ },
+
+ parseJSON: function( data ) {
+ if ( !data || typeof data !== "string") {
+ return null;
+ }
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ // Attempt to parse using the native JSON parser first
+ if ( window.JSON && window.JSON.parse ) {
+ return window.JSON.parse( data );
+ }
+
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+ .replace( rvalidtokens, "]" )
+ .replace( rvalidbraces, "")) ) {
+
+ return ( new Function( "return " + data ) )();
+
+ }
+ jQuery.error( "Invalid JSON: " + data );
+ },
+
+ // Cross-browser xml parsing
+ parseXML: function( data ) {
+ var xml, tmp;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ try {
+ if ( window.DOMParser ) { // Standard
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } else { // IE
+ xml = new ActiveXObject( "Microsoft.XMLDOM" );
+ xml.async = "false";
+ xml.loadXML( data );
+ }
+ } catch( e ) {
+ xml = undefined;
+ }
+ if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evaluates a script in a global context
+ // Workarounds based on findings by Jim Driscoll
+ // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+ globalEval: function( data ) {
+ if ( data && core_rnotwhite.test( data ) ) {
+ // We use execScript on Internet Explorer
+ // We use an anonymous function so that context is window
+ // rather than jQuery in Firefox
+ ( window.execScript || function( data ) {
+ window[ "eval" ].call( window, data );
+ } )( data );
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+ },
+
+ // args is for internal usage only
+ each: function( obj, callback, args ) {
+ var name,
+ i = 0,
+ length = obj.length,
+ isObj = length === undefined || jQuery.isFunction( obj );
+
+ if ( args ) {
+ if ( isObj ) {
+ for ( name in obj ) {
+ if ( callback.apply( obj[ name ], args ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.apply( obj[ i++ ], args ) === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isObj ) {
+ for ( name in obj ) {
+ if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return obj;
+ },
+
+ // Use native String.trim function wherever possible
+ trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
+ function( text ) {
+ return text == null ?
+ "" :
+ core_trim.call( text );
+ } :
+
+ // Otherwise use our own trimming functionality
+ function( text ) {
+ return text == null ?
+ "" :
+ ( text + "" ).replace( rtrim, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var type,
+ ret = results || [];
+
+ if ( arr != null ) {
+ // The window, strings (and functions) also have 'length'
+ // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+ type = jQuery.type( arr );
+
+ if ( arr.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( arr ) ) {
+ core_push.call( ret, arr );
+ } else {
+ jQuery.merge( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ var len;
+
+ if ( arr ) {
+ if ( core_indexOf ) {
+ return core_indexOf.call( arr, elem, i );
+ }
+
+ len = arr.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+ // Skip accessing in sparse arrays
+ if ( i in arr && arr[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var l = second.length,
+ i = first.length,
+ j = 0;
+
+ if ( typeof l === "number" ) {
+ for ( ; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var retVal,
+ ret = [],
+ i = 0,
+ length = elems.length;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value, key,
+ ret = [],
+ i = 0,
+ length = elems.length,
+ // jquery objects are treated as arrays
+ isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( key in elems ) {
+ value = callback( elems[ key ], key, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return ret.concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ var tmp, args, proxy;
+
+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ args = core_slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context, args.concat( core_slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ // Multifunctional method to get and set values of a collection
+ // The value/s can optionally be executed if it's a function
+ access: function( elems, fn, key, value, chainable, emptyGet, pass ) {
+ var exec,
+ bulk = key == null,
+ i = 0,
+ length = elems.length;
+
+ // Sets many values
+ if ( key && typeof key === "object" ) {
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], 1, emptyGet, value );
+ }
+ chainable = 1;
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = pass === undefined && jQuery.isFunction( value );
+
+ if ( bulk ) {
+ // Bulk operations only iterate when executing function values
+ if ( exec ) {
+ exec = fn;
+ fn = function( elem, key, value ) {
+ return exec.call( jQuery( elem ), value );
+ };
+
+ // Otherwise they run against the entire set
+ } else {
+ fn.call( elems, value );
+ fn = null;
+ }
+ }
+
+ if ( fn ) {
+ for (; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+ }
+
+ chainable = 1;
+ }
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ length ? fn( elems[0], key ) : emptyGet;
+ },
+
+ now: function() {
+ return ( new Date() ).getTime();
+ }
+});
+
+jQuery.ready.promise = function( obj ) {
+ if ( !readyList ) {
+
+ readyList = jQuery.Deferred();
+
+ // Catch cases where $(document).ready() is called after the browser event has already occurred.
+ // we once tried to use readyState "interactive" here, but it caused issues like the one
+ // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ setTimeout( jQuery.ready, 1 );
+
+ // Standards-based browsers support DOMContentLoaded
+ } else if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else {
+ // Ensure firing before onload, maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var top = false;
+
+ try {
+ top = window.frameElement == null && document.documentElement;
+ } catch(e) {}
+
+ if ( top && top.doScroll ) {
+ (function doScrollCheck() {
+ if ( !jQuery.isReady ) {
+
+ try {
+ // Use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ top.doScroll("left");
+ } catch(e) {
+ return setTimeout( doScrollCheck, 50 );
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+ }
+ })();
+ }
+ }
+ }
+ return readyList.promise( obj );
+};
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+// String to Object options format cache
+var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+ var object = optionsCache[ options ] = {};
+ jQuery.each( options.split( core_rspace ), function( _, flag ) {
+ object[ flag ] = true;
+ });
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ ( optionsCache[ options ] || createOptions( options ) ) :
+ jQuery.extend( {}, options );
+
+ var // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = !options.once && [],
+ // Fire callbacks
+ fire = function( data ) {
+ memory = options.memory && data;
+ fired = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ firing = true;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+ memory = false; // To prevent further calls using add
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( stack ) {
+ if ( stack.length ) {
+ fire( stack.shift() );
+ }
+ } else if ( memory ) {
+ list = [];
+ } else {
+ self.disable();
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ // First, we save the current length
+ var start = list.length;
+ (function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ var type = jQuery.type( arg );
+ if ( type === "function" ) {
+ if ( !options.unique || !self.has( arg ) ) {
+ list.push( arg );
+ }
+ } else if ( arg && arg.length && type !== "string" ) {
+ // Inspect recursively
+ add( arg );
+ }
+ });
+ })( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away
+ } else if ( memory ) {
+ firingStart = start;
+ fire( memory );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+ // Handle firing indexes
+ if ( firing ) {
+ if ( index <= firingLength ) {
+ firingLength--;
+ }
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ });
+ }
+ return this;
+ },
+ // Control if a given callback is in the list
+ has: function( fn ) {
+ return jQuery.inArray( fn, list ) > -1;
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ if ( list && ( !fired || stack ) ) {
+ if ( firing ) {
+ stack.push( args );
+ } else {
+ fire( args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+};
+jQuery.extend({
+
+ Deferred: function( func ) {
+ var tuples = [
+ // action, add listener, listener list, final state
+ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+ [ "notify", "progress", jQuery.Callbacks("memory") ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ then: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( tuples, function( i, tuple ) {
+ var action = tuple[ 0 ],
+ fn = fns[ i ];
+ // deferred[ done | fail | progress ] for forwarding actions to newDefer
+ deferred[ tuple[1] ]( jQuery.isFunction( fn ) ?
+ function() {
+ var returned = fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise()
+ .done( newDefer.resolve )
+ .fail( newDefer.reject )
+ .progress( newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+ }
+ } :
+ newDefer[ action ]
+ );
+ });
+ fns = null;
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
+
+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[1] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(function() {
+ // state = [ resolved | rejected ]
+ state = stateString;
+
+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
+
+ // deferred[ resolve | reject | notify ] = list.fire
+ deferred[ tuple[0] ] = list.fire;
+ deferred[ tuple[0] + "With" ] = list.fireWith;
+ });
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
+ resolveValues = core_slice.call( arguments ),
+ length = resolveValues.length,
+
+ // the count of uncompleted subordinates
+ remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+ // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+ // Update function for both resolve and progress values
+ updateFunc = function( i, contexts, values ) {
+ return function( value ) {
+ contexts[ i ] = this;
+ values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
+ if( values === progressValues ) {
+ deferred.notifyWith( contexts, values );
+ } else if ( !( --remaining ) ) {
+ deferred.resolveWith( contexts, values );
+ }
+ };
+ },
+
+ progressValues, progressContexts, resolveContexts;
+
+ // add listeners to Deferred subordinates; treat others as resolved
+ if ( length > 1 ) {
+ progressValues = new Array( length );
+ progressContexts = new Array( length );
+ resolveContexts = new Array( length );
+ for ( ; i < length; i++ ) {
+ if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+ resolveValues[ i ].promise()
+ .done( updateFunc( i, resolveContexts, resolveValues ) )
+ .fail( deferred.reject )
+ .progress( updateFunc( i, progressContexts, progressValues ) );
+ } else {
+ --remaining;
+ }
+ }
+ }
+
+ // if we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
+ }
+
+ return deferred.promise();
+ }
+});
+jQuery.support = (function() {
+
+ var support,
+ all,
+ a,
+ select,
+ opt,
+ input,
+ fragment,
+ eventName,
+ i,
+ isSupported,
+ clickFn,
+ div = document.createElement("div");
+
+ // Setup
+ div.setAttribute( "className", "t" );
+ div.innerHTML = " <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
+
+ // Support tests won't run in some limited or non-browser environments
+ all = div.getElementsByTagName("*");
+ a = div.getElementsByTagName("a")[ 0 ];
+ if ( !all || !a || !all.length ) {
+ return {};
+ }
+
+ // First batch of tests
+ select = document.createElement("select");
+ opt = select.appendChild( document.createElement("option") );
+ input = div.getElementsByTagName("input")[ 0 ];
+
+ a.style.cssText = "top:1px;float:left;opacity:.5";
+ support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText instead)
+ style: /top/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: ( a.getAttribute("href") === "/a" ),
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.5/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Make sure that if no value is specified for a checkbox
+ // that it defaults to "on".
+ // (WebKit defaults to "" instead)
+ checkOn: ( input.value === "on" ),
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: opt.selected,
+
+ // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+ getSetAttribute: div.className !== "t",
+
+ // Tests for enctype support on a form (#6743)
+ enctype: !!document.createElement("form").enctype,
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
+
+ // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode
+ boxModel: ( document.compatMode === "CSS1Compat" ),
+
+ // Will be defined later
+ submitBubbles: true,
+ changeBubbles: true,
+ focusinBubbles: false,
+ deleteExpando: true,
+ noCloneEvent: true,
+ inlineBlockNeedsLayout: false,
+ shrinkWrapBlocks: false,
+ reliableMarginRight: true,
+ boxSizingReliable: true,
+ pixelPosition: false
+ };
+
+ // Make sure checked status is properly cloned
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Test to see if it's possible to delete an expando from an element
+ // Fails in Internet Explorer
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+
+ if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+ div.attachEvent( "onclick", clickFn = function() {
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ support.noCloneEvent = false;
+ });
+ div.cloneNode( true ).fireEvent("onclick");
+ div.detachEvent( "onclick", clickFn );
+ }
+
+ // Check if a radio maintains its value
+ // after being appended to the DOM
+ input = document.createElement("input");
+ input.value = "t";
+ input.setAttribute( "type", "radio" );
+ support.radioValue = input.value === "t";
+
+ input.setAttribute( "checked", "checked" );
+
+ // #11217 - WebKit loses check when the name is after the checked attribute
+ input.setAttribute( "name", "t" );
+
+ div.appendChild( input );
+ fragment = document.createDocumentFragment();
+ fragment.appendChild( div.lastChild );
+
+ // WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ support.appendChecked = input.checked;
+
+ fragment.removeChild( input );
+ fragment.appendChild( div );
+
+ // Technique from Juriy Zaytsev
+ // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+ // We only care about the case where non-standard event systems
+ // are used, namely in IE. Short-circuiting here helps us to
+ // avoid an eval call (in setAttribute) which can cause CSP
+ // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+ if ( div.attachEvent ) {
+ for ( i in {
+ submit: true,
+ change: true,
+ focusin: true
+ }) {
+ eventName = "on" + i;
+ isSupported = ( eventName in div );
+ if ( !isSupported ) {
+ div.setAttribute( eventName, "return;" );
+ isSupported = ( typeof div[ eventName ] === "function" );
+ }
+ support[ i + "Bubbles" ] = isSupported;
+ }
+ }
+
+ // Run tests that need a body at doc ready
+ jQuery(function() {
+ var container, div, tds, marginDiv,
+ divReset = "padding:0;margin:0;border:0;display:block;overflow:hidden;",
+ body = document.getElementsByTagName("body")[0];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ container = document.createElement("div");
+ container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px";
+ body.insertBefore( container, body.firstChild );
+
+ // Construct the test element
+ div = document.createElement("div");
+ container.appendChild( div );
+
+ // Check if table cells still have offsetWidth/Height when they are set
+ // to display:none and there are still other visible table cells in a
+ // table row; if so, offsetWidth/Height are not reliable for use when
+ // determining if an element has been hidden directly using
+ // display:none (it is still safe to use offsets if a parent element is
+ // hidden; don safety goggles and see bug #4512 for more information).
+ // (only IE 8 fails this test)
+ div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
+ tds = div.getElementsByTagName("td");
+ tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
+ isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+ tds[ 0 ].style.display = "";
+ tds[ 1 ].style.display = "none";
+
+ // Check if empty table cells still have offsetWidth/Height
+ // (IE <= 8 fail this test)
+ support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+ // Check box-sizing and margin behavior
+ div.innerHTML = "";
+ div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
+ support.boxSizing = ( div.offsetWidth === 4 );
+ support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 );
+
+ // NOTE: To any future maintainer, we've window.getComputedStyle
+ // because jsdom on node.js will break without it.
+ if ( window.getComputedStyle ) {
+ support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
+ support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
+
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. For more
+ // info see bug #3333
+ // Fails in WebKit before Feb 2011 nightlies
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ marginDiv = document.createElement("div");
+ marginDiv.style.cssText = div.style.cssText = divReset;
+ marginDiv.style.marginRight = marginDiv.style.width = "0";
+ div.style.width = "1px";
+ div.appendChild( marginDiv );
+ support.reliableMarginRight =
+ !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
+ }
+
+ if ( typeof div.style.zoom !== "undefined" ) {
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ // (IE < 8 does this)
+ div.innerHTML = "";
+ div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
+ support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
+
+ // Check if elements with layout shrink-wrap their children
+ // (IE 6 does this)
+ div.style.display = "block";
+ div.style.overflow = "visible";
+ div.innerHTML = "<div></div>";
+ div.firstChild.style.width = "5px";
+ support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
+
+ container.style.zoom = 1;
+ }
+
+ // Null elements to avoid leaks in IE
+ body.removeChild( container );
+ container = div = tds = marginDiv = null;
+ });
+
+ // Null elements to avoid leaks in IE
+ fragment.removeChild( div );
+ all = a = select = opt = input = fragment = div = null;
+
+ return support;
+})();
+var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+ cache: {},
+
+ deletedIds: [],
+
+ // Remove at next major release (1.9/2.0)
+ uuid: 0,
+
+ // Unique for each copy of jQuery on the page
+ // Non-digits removed to match rinlinejQuery
+ expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ // Ban all objects except for Flash (which handle expandos)
+ "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+ "applet": true
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, ret,
+ internalKey = jQuery.expando,
+ getByName = typeof name === "string",
+
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
+
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
+
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
+
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ elem[ internalKey ] = id = jQuery.deletedIds.pop() || jQuery.guid++;
+ } else {
+ id = internalKey;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ cache[ id ] = {};
+
+ // Avoids exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+ }
+
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ] = jQuery.extend( cache[ id ], name );
+ } else {
+ cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+ }
+ }
+
+ thisCache = cache[ id ];
+
+ // jQuery data() is stored in a separate object inside the object's internal data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data.
+ if ( !pvt ) {
+ if ( !thisCache.data ) {
+ thisCache.data = {};
+ }
+
+ thisCache = thisCache.data;
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
+
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( getByName ) {
+
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
+
+ // Test for null|undefined property data
+ if ( ret == null ) {
+
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
+ }
+ } else {
+ ret = thisCache;
+ }
+
+ return ret;
+ },
+
+ removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, i, l,
+
+ isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+ id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
+
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
+
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+ if ( thisCache ) {
+
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
+
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+
+ // split the camel cased version by spaces unless a key with the spaces exists
+ name = jQuery.camelCase( name );
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+ name = name.split(" ");
+ }
+ }
+ }
+
+ for ( i = 0, l = name.length; i < l; i++ ) {
+ delete thisCache[ name[i] ];
+ }
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( !pvt ) {
+ delete cache[ id ].data;
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject( cache[ id ] ) ) {
+ return;
+ }
+ }
+
+ // Destroy the cache
+ if ( isNode ) {
+ jQuery.cleanData( [ elem ], true );
+
+ // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
+ } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
+ delete cache[ id ];
+
+ // When all else fails, null
+ } else {
+ cache[ id ] = null;
+ }
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return jQuery.data( elem, name, data, true );
+ },
+
+ // A method for determining if a DOM node can handle the data expando
+ acceptData: function( elem ) {
+ var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+ // nodes accept data unless otherwise specified; rejection can be conditional
+ return !noData || noData !== true && elem.getAttribute("classid") === noData;
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var parts, part, attr, name, l,
+ elem = this[0],
+ i = 0,
+ data = null;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = jQuery.data( elem );
+
+ if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+ attr = elem.attributes;
+ for ( l = attr.length; i < l; i++ ) {
+ name = attr[i].name;
+
+ if ( !name.indexOf( "data-" ) ) {
+ name = jQuery.camelCase( name.substring(5) );
+
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ jQuery._data( elem, "parsedAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ parts = key.split( ".", 2 );
+ parts[1] = parts[1] ? "." + parts[1] : "";
+ part = parts[1] + "!";
+
+ return jQuery.access( this, function( value ) {
+
+ if ( value === undefined ) {
+ data = this.triggerHandler( "getData" + part, [ parts[0] ] );
+
+ // Try to fetch any internally stored data first
+ if ( data === undefined && elem ) {
+ data = jQuery.data( elem, key );
+ data = dataAttr( elem, key, data );
+ }
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+ }
+
+ parts[1] = value;
+ this.each(function() {
+ var self = jQuery( this );
+
+ self.triggerHandler( "setData" + part, parts );
+ jQuery.data( this, key, value );
+ self.triggerHandler( "changeData" + part, parts );
+ });
+ }, null, value, arguments.length > 1, null, false );
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+
+function dataAttr( elem, key, data ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ // Only convert to a number if it doesn't change the string
+ +data + "" === data ? +data :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+ var name;
+ for ( name in obj ) {
+
+ // if the public data object is empty, the private is still empty
+ if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+ continue;
+ }
+ if ( name !== "toJSON" ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+jQuery.extend({
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = jQuery._data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || jQuery.isArray(data) ) {
+ queue = jQuery._data( elem, type, jQuery.makeArray(data) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
+
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // not intended for public consumption - generates a queueHooks object, or returns the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return jQuery._data( elem, key ) || jQuery._data( elem, key, {
+ empty: jQuery.Callbacks("once memory").add(function() {
+ jQuery.removeData( elem, type + "queue", true );
+ jQuery.removeData( elem, key, true );
+ })
+ });
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while( i-- ) {
+ tmp = jQuery._data( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
+ }
+});
+var nodeHook, boolHook, fixSpecified,
+ rclass = /[\t\r\n]/g,
+ rreturn = /\r/g,
+ rtype = /^(?:button|input)$/i,
+ rfocusable = /^(?:button|input|object|select|textarea)$/i,
+ rclickable = /^a(?:rea|)$/i,
+ rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+ getSetAttribute = jQuery.support.getSetAttribute;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ removeProp: function( name ) {
+ name = jQuery.propFix[ name ] || name;
+ return this.each(function() {
+ // try/catch handles cases where IE balks (such as removing a property on window)
+ try {
+ this[ name ] = undefined;
+ delete this[ name ];
+ } catch( e ) {}
+ });
+ },
+
+ addClass: function( value ) {
+ var classNames, i, l, elem,
+ setClass, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( value && typeof value === "string" ) {
+ classNames = value.split( core_rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 ) {
+ if ( !elem.className && classNames.length === 1 ) {
+ elem.className = value;
+
+ } else {
+ setClass = " " + elem.className + " ";
+
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ if ( setClass.indexOf( " " + classNames[ c ] + " " ) < 0 ) {
+ setClass += classNames[ c ] + " ";
+ }
+ }
+ elem.className = jQuery.trim( setClass );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var removes, className, elem, c, cl, i, l;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call(this, j, this.className) );
+ });
+ }
+ if ( (value && typeof value === "string") || value === undefined ) {
+ removes = ( value || "" ).split( core_rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+ if ( elem.nodeType === 1 && elem.className ) {
+
+ className = (" " + elem.className + " ").replace( rclass, " " );
+
+ // loop over each item in the removal list
+ for ( c = 0, cl = removes.length; c < cl; c++ ) {
+ // Remove until there is nothing to remove,
+ while ( className.indexOf(" " + removes[ c ] + " ") >= 0 ) {
+ className = className.replace( " " + removes[ c ] + " " , " " );
+ }
+ }
+ elem.className = value ? jQuery.trim( className ) : "";
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ state = stateVal,
+ classNames = value.split( core_rspace );
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space separated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( this, "__className__", this.className );
+ }
+
+ // toggle whole className
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var val,
+ self = jQuery(this);
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, self.val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map(val, function ( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, option,
+ options = elem.options,
+ index = elem.selectedIndex,
+ one = elem.type === "select-one" || index < 0,
+ values = one ? null : [],
+ max = one ? index + 1 : options.length,
+ i = index < 0 ?
+ max :
+ one ? index : 0;
+
+ // Loop through all the selected options
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // oldIE doesn't update selected after form reset (#2551)
+ if ( ( option.selected || i === index ) &&
+ // Don't return options that are disabled or in a disabled optgroup
+ ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
+ ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var values = jQuery.makeArray( value );
+
+ jQuery(elem).find("option").each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ },
+
+ // Unused in 1.8, left in so attrFn-stabbers won't die; remove in 1.9
+ attrFn: {},
+
+ attr: function( elem, name, value, pass ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) {
+ return jQuery( elem )[ name ]( value );
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === "undefined" ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( notxml ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+ return;
+
+ } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, value + "" );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+
+ ret = elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret === null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var propName, attrNames, name, isBool,
+ i = 0;
+
+ if ( value && elem.nodeType === 1 ) {
+
+ attrNames = value.split( core_rspace );
+
+ for ( ; i < attrNames.length; i++ ) {
+ name = attrNames[ i ];
+
+ if ( name ) {
+ propName = jQuery.propFix[ name ] || name;
+ isBool = rboolean.test( name );
+
+ // See #9699 for explanation of this approach (setting first, then removal)
+ // Do not do this for boolean attributes (see #10870)
+ if ( !isBool ) {
+ jQuery.attr( elem, name, "" );
+ }
+ elem.removeAttribute( getSetAttribute ? name : propName );
+
+ // Set corresponding property to false for boolean attributes
+ if ( isBool && propName in elem ) {
+ elem[ propName ] = false;
+ }
+ }
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+ jQuery.error( "type property can't be changed" );
+ } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to it's default in case type is set after value
+ // This is for element creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ },
+ // Use the value property for back compat
+ // Use the nodeHook for button elements in IE6/7 (#1954)
+ value: {
+ get: function( elem, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.get( elem, name );
+ }
+ return name in elem ?
+ elem.value :
+ null;
+ },
+ set: function( elem, value, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.set( elem, value, name );
+ }
+ // Does not return so that setAttribute is also used
+ elem.value = value;
+ }
+ }
+ },
+
+ propFix: {
+ tabindex: "tabIndex",
+ readonly: "readOnly",
+ "for": "htmlFor",
+ "class": "className",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing",
+ cellpadding: "cellPadding",
+ rowspan: "rowSpan",
+ colspan: "colSpan",
+ usemap: "useMap",
+ frameborder: "frameBorder",
+ contenteditable: "contentEditable"
+ },
+
+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ return ( elem[ name ] = value );
+ }
+
+ } else {
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ return elem[ name ];
+ }
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ var attributeNode = elem.getAttributeNode("tabindex");
+
+ return attributeNode && attributeNode.specified ?
+ parseInt( attributeNode.value, 10 ) :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+ }
+ }
+});
+
+// Hook for boolean attributes
+boolHook = {
+ get: function( elem, name ) {
+ // Align boolean attributes with corresponding properties
+ // Fall back to attribute presence where some booleans are not supported
+ var attrNode,
+ property = jQuery.prop( elem, name );
+ return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+ name.toLowerCase() :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ var propName;
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ // value is true since we know at this point it's type boolean and not false
+ // Set boolean attributes to the same name and set the DOM property
+ propName = jQuery.propFix[ name ] || name;
+ if ( propName in elem ) {
+ // Only set the IDL specifically if it already exists on the element
+ elem[ propName ] = true;
+ }
+
+ elem.setAttribute( name, name.toLowerCase() );
+ }
+ return name;
+ }
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+ fixSpecified = {
+ name: true,
+ id: true,
+ coords: true
+ };
+
+ // Use this for any attribute in IE6/7
+ // This fixes almost every IE6/7 issue
+ nodeHook = jQuery.valHooks.button = {
+ get: function( elem, name ) {
+ var ret;
+ ret = elem.getAttributeNode( name );
+ return ret && ( fixSpecified[ name ] ? ret.value !== "" : ret.specified ) ?
+ ret.value :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ // Set the existing or create a new attribute node
+ var ret = elem.getAttributeNode( name );
+ if ( !ret ) {
+ ret = document.createAttribute( name );
+ elem.setAttributeNode( ret );
+ }
+ return ( ret.value = value + "" );
+ }
+ };
+
+ // Set width and height to auto instead of 0 on empty string( Bug #8150 )
+ // This is for removals
+ jQuery.each([ "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ set: function( elem, value ) {
+ if ( value === "" ) {
+ elem.setAttribute( name, "auto" );
+ return value;
+ }
+ }
+ });
+ });
+
+ // Set contenteditable to false on removals(#10429)
+ // Setting to empty string throws an error as an invalid value
+ jQuery.attrHooks.contenteditable = {
+ get: nodeHook.get,
+ set: function( elem, value, name ) {
+ if ( value === "" ) {
+ value = "false";
+ }
+ nodeHook.set( elem, value, name );
+ }
+ };
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+ jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ get: function( elem ) {
+ var ret = elem.getAttribute( name, 2 );
+ return ret === null ? undefined : ret;
+ }
+ });
+ });
+}
+
+if ( !jQuery.support.style ) {
+ jQuery.attrHooks.style = {
+ get: function( elem ) {
+ // Return undefined in the case of empty string
+ // Normalize to lowercase since IE uppercases css property names
+ return elem.style.cssText.toLowerCase() || undefined;
+ },
+ set: function( elem, value ) {
+ return ( elem.style.cssText = value + "" );
+ }
+ };
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+ jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ return null;
+ }
+ });
+}
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+ jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+ jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ get: function( elem ) {
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+ };
+ });
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ });
+});
+var rformElems = /^(?:textarea|input|select)$/i,
+ rtypenamespace = /^([^\.]*|)(?:\.(.+)|)$/,
+ rhoverHack = /(?:^|\s)hover(\.\S+|)\b/,
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ hoverHack = function( events ) {
+ return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+ };
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var elemData, eventHandle, events,
+ t, tns, type, namespaces, handleObj,
+ handleObjIn, handlers, special;
+
+ // Don't attach events to noData or text/comment nodes (allow plain objects tho)
+ if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ events = elemData.events;
+ if ( !events ) {
+ elemData.events = events = {};
+ }
+ eventHandle = elemData.handle;
+ if ( !eventHandle ) {
+ elemData.handle = eventHandle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+ eventHandle.elem = elem;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = jQuery.trim( hoverHack(types) ).split( " " );
+ for ( t = 0; t < types.length; t++ ) {
+
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = tns[1];
+ namespaces = ( tns[2] || "" ).split( "." ).sort();
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: tns[1],
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ handlers = events[ type ];
+ if ( !handlers ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener/attachEvent if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var t, tns, type, origType, namespaces, origCount,
+ j, events, special, eventType, handleObj,
+ elemData = jQuery.hasData( elem ) && jQuery._data( elem );
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
+ for ( t = 0; t < types.length; t++ ) {
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tns[1];
+ namespaces = tns[2];
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector? special.delegateType : special.bindType ) || type;
+ eventType = events[ type ] || [];
+ origCount = eventType.length;
+ namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
+
+ // Remove matching events
+ for ( j = 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ eventType.splice( j--, 1 );
+
+ if ( handleObj.selector ) {
+ eventType.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( eventType.length === 0 && origCount !== eventType.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ delete elemData.handle;
+
+ // removeData also checks for emptiness and clears the expando if empty
+ // so use it instead of delete
+ jQuery.removeData( elem, "events", true );
+ }
+ },
+
+ // Events that are safe to short-circuit if no handlers are attached.
+ // Native DOM events should not be added, they may have inline handlers.
+ customEvent: {
+ "getData": true,
+ "setData": true,
+ "changeData": true
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+ // Don't do events on text and comment nodes
+ if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+ return;
+ }
+
+ // Event object or event type
+ var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType,
+ type = event.type || event,
+ namespaces = [];
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf( "!" ) >= 0 ) {
+ // Exclusive events trigger only for the exact event (no namespaces)
+ type = type.slice(0, -1);
+ exclusive = true;
+ }
+
+ if ( type.indexOf( "." ) >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+
+ if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+ // No jQuery handlers for this event type, and it can't have inline handlers
+ return;
+ }
+
+ // Caller can pass in an Event, Object, or just an event type string
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[ jQuery.expando ] ? event :
+ // Object literal
+ new jQuery.Event( type, event ) :
+ // Just the event type (string)
+ new jQuery.Event( type );
+
+ event.type = type;
+ event.isTrigger = true;
+ event.exclusive = exclusive;
+ event.namespace = namespaces.join( "." );
+ event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
+ ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
+
+ // Handle a global trigger
+ if ( !elem ) {
+
+ // TODO: Stop taunting the data cache; remove global events and always attach to document
+ cache = jQuery.cache;
+ for ( i in cache ) {
+ if ( cache[ i ].events && cache[ i ].events[ type ] ) {
+ jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
+ }
+ }
+ return;
+ }
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data != null ? jQuery.makeArray( data ) : [];
+ data.unshift( event );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ eventPath = [[ elem, special.bindType || type ]];
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
+ for ( old = elem; cur; cur = cur.parentNode ) {
+ eventPath.push([ cur, bubbleType ]);
+ old = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( old === (elem.ownerDocument || document) ) {
+ eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+ }
+ }
+
+ // Fire handlers on the event path
+ for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+
+ cur = eventPath[i][0];
+ event.type = eventPath[i][1];
+
+ handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+ // Note that this is a bare JS function and not a jQuery handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
+ event.preventDefault();
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+ !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Can't use an .isFunction() check here because IE6/7 fails that test.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ // IE<9 dies on focus/blur to hidden element (#1486)
+ if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ old = elem[ ontype ];
+
+ if ( old ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
+
+ if ( old ) {
+ elem[ ontype ] = old;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event || window.event );
+
+ var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related,
+ handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+ delegateCount = handlers.delegateCount,
+ args = core_slice.call( arguments ),
+ run_all = !event.exclusive && !event.namespace,
+ special = jQuery.event.special[ event.type ] || {},
+ handlerQueue = [];
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers that should run if there are delegated events
+ // Avoid non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && !(event.button && event.type === "click") ) {
+
+ for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
+
+ // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #11382, #11764)
+ if ( cur.disabled !== true || event.type !== "click" ) {
+ selMatch = {};
+ matches = [];
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+ sel = handleObj.selector;
+
+ if ( selMatch[ sel ] === undefined ) {
+ selMatch[ sel ] = handleObj.needsContext ?
+ jQuery( sel, this ).index( cur ) >= 0 :
+ jQuery.find( sel, this, null, [ cur ] ).length;
+ }
+ if ( selMatch[ sel ] ) {
+ matches.push( handleObj );
+ }
+ }
+ if ( matches.length ) {
+ handlerQueue.push({ elem: cur, matches: matches });
+ }
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ if ( handlers.length > delegateCount ) {
+ handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
+ }
+
+ // Run delegates first; they may want to stop propagation beneath us
+ for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
+ matched = handlerQueue[ i ];
+ event.currentTarget = matched.elem;
+
+ for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
+ handleObj = matched.matches[ j ];
+
+ // Triggered event must either 1) be non-exclusive and have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.data = handleObj.data;
+ event.handleObj = handleObj;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
+ props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
+
+ return event;
+ }
+ },
+
+ mouseHooks: {
+ props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ filter: function( event, original ) {
+ var eventDoc, doc, body,
+ button = original.button,
+ fromElement = original.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
+
+ event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && fromElement ) {
+ event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
+
+ return event;
+ }
+ },
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // Create a writable copy of the event object and normalize some properties
+ var i, prop,
+ originalEvent = event,
+ fixHook = jQuery.event.fixHooks[ event.type ] || {},
+ copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+ event = jQuery.Event( originalEvent );
+
+ for ( i = copy.length; i; ) {
+ prop = copy[ --i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
+ if ( !event.target ) {
+ event.target = originalEvent.srcElement || document;
+ }
+
+ // Target should not be a text node (#504, Safari)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8)
+ event.metaKey = !!event.metaKey;
+
+ return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+ },
+
+ special: {
+ load: {
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+
+ focus: {
+ delegateType: "focusin"
+ },
+ blur: {
+ delegateType: "focusout"
+ },
+
+ beforeunload: {
+ setup: function( data, namespaces, eventHandle ) {
+ // We only want to do this special case on windows
+ if ( jQuery.isWindow( this ) ) {
+ this.onbeforeunload = eventHandle;
+ }
+ },
+
+ teardown: function( namespaces, eventHandle ) {
+ if ( this.onbeforeunload === eventHandle ) {
+ this.onbeforeunload = null;
+ }
+ }
+ }
+ },
+
+ simulate: function( type, elem, event, bubble ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ { type: type,
+ isSimulated: true,
+ originalEvent: {}
+ }
+ );
+ if ( bubble ) {
+ jQuery.event.trigger( e, null, elem );
+ } else {
+ jQuery.event.dispatch.call( elem, e );
+ }
+ if ( e.isDefaultPrevented() ) {
+ event.preventDefault();
+ }
+ }
+};
+
+// Some plugins are using, but it's undocumented/deprecated and will be removed.
+// The 1.7 special event interface should provide all the hooks needed now.
+jQuery.event.handle = jQuery.event.dispatch;
+
+jQuery.removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+ } :
+ function( elem, type, handle ) {
+ var name = "on" + type;
+
+ if ( elem.detachEvent ) {
+
+ // #8545, #7054, preventing memory leaks for custom events in IE6-8
+ // detachEvent needed property on element, by name of that event, to properly expose it to GC
+ if ( typeof elem[ name ] === "undefined" ) {
+ elem[ name ] = null;
+ }
+
+ elem.detachEvent( name, handle );
+ }
+ };
+
+jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !(this instanceof jQuery.Event) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
+ src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+ return false;
+}
+function returnTrue() {
+ return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ preventDefault: function() {
+ this.isDefaultPrevented = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+
+ // if preventDefault exists run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+
+ // otherwise set the returnValue property of the original event to false (IE)
+ } else {
+ e.returnValue = false;
+ }
+ },
+ stopPropagation: function() {
+ this.isPropagationStopped = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+ // if stopPropagation exists run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ },
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var ret,
+ target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj,
+ selector = handleObj.selector;
+
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+});
+
+// IE submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Lazy-add a submit handler when a descendant form may potentially be submitted
+ jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+ // Node name check avoids a VML-related crash in IE (#9807)
+ var elem = e.target,
+ form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
+ if ( form && !jQuery._data( form, "_submit_attached" ) ) {
+ jQuery.event.add( form, "submit._submit", function( event ) {
+ event._submit_bubble = true;
+ });
+ jQuery._data( form, "_submit_attached", true );
+ }
+ });
+ // return undefined since we don't need an event listener
+ },
+
+ postDispatch: function( event ) {
+ // If form was submitted by the user, bubble the event up the tree
+ if ( event._submit_bubble ) {
+ delete event._submit_bubble;
+ if ( this.parentNode && !event.isTrigger ) {
+ jQuery.event.simulate( "submit", this.parentNode, event, true );
+ }
+ }
+ },
+
+ teardown: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+ jQuery.event.remove( this, "._submit" );
+ }
+ };
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !jQuery.support.changeBubbles ) {
+
+ jQuery.event.special.change = {
+
+ setup: function() {
+
+ if ( rformElems.test( this.nodeName ) ) {
+ // IE doesn't fire change on a check/radio until blur; trigger it on click
+ // after a propertychange. Eat the blur-change in special.change.handle.
+ // This still fires onchange a second time for check/radio after blur.
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ jQuery.event.add( this, "propertychange._change", function( event ) {
+ if ( event.originalEvent.propertyName === "checked" ) {
+ this._just_changed = true;
+ }
+ });
+ jQuery.event.add( this, "click._change", function( event ) {
+ if ( this._just_changed && !event.isTrigger ) {
+ this._just_changed = false;
+ }
+ // Allow triggered, simulated change events (#11500)
+ jQuery.event.simulate( "change", this, event, true );
+ });
+ }
+ return false;
+ }
+ // Delegated event; lazy-add a change handler on descendant inputs
+ jQuery.event.add( this, "beforeactivate._change", function( e ) {
+ var elem = e.target;
+
+ if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "_change_attached" ) ) {
+ jQuery.event.add( elem, "change._change", function( event ) {
+ if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+ jQuery.event.simulate( "change", this.parentNode, event, true );
+ }
+ });
+ jQuery._data( elem, "_change_attached", true );
+ }
+ });
+ },
+
+ handle: function( event ) {
+ var elem = event.target;
+
+ // Swallow native change events from checkbox/radio, we already triggered them above
+ if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
+ return event.handleObj.handler.apply( this, arguments );
+ }
+ },
+
+ teardown: function() {
+ jQuery.event.remove( this, "._change" );
+
+ return !rformElems.test( this.nodeName );
+ }
+ };
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler while someone wants focusin/focusout
+ var attaches = 0,
+ handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ if ( attaches++ === 0 ) {
+ document.addEventListener( orig, handler, true );
+ }
+ },
+ teardown: function() {
+ if ( --attaches === 0 ) {
+ document.removeEventListener( orig, handler, true );
+ }
+ }
+ };
+ });
+}
+
+jQuery.fn.extend({
+
+ on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) { // && selector != null
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ this.on( type, selector, data, types[ type ], one );
+ }
+ return this;
+ }
+
+ if ( data == null && fn == null ) {
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return this.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ });
+ },
+ one: function( types, selector, data, fn ) {
+ return this.on( types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ var handleObj, type;
+ if ( types && types.preventDefault && types.handleObj ) {
+ // ( event ) dispatched jQuery.Event
+ handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+ // ( types-object [, selector] )
+ for ( type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each(function() {
+ jQuery.event.remove( this, types, fn, selector );
+ });
+ },
+
+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ live: function( types, data, fn ) {
+ jQuery( this.context ).on( types, this.selector, data, fn );
+ return this;
+ },
+ die: function( types, fn ) {
+ jQuery( this.context ).off( types, this.selector || "**", fn );
+ return this;
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+ triggerHandler: function( type, data ) {
+ if ( this[0] ) {
+ return jQuery.event.trigger( type, data, this[0], true );
+ }
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments,
+ guid = fn.guid || jQuery.guid++,
+ i = 0,
+ toggler = function( event ) {
+ // Figure out which function to execute
+ var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ lastToggle ].apply( this, arguments ) || false;
+ };
+
+ // link all the functions, so any of them can unbind this click handler
+ toggler.guid = guid;
+ while ( i < args.length ) {
+ args[ i++ ].guid = guid;
+ }
+
+ return this.click( toggler );
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ }
+});
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+ "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+ "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ if ( fn == null ) {
+ fn = data;
+ data = null;
+ }
+
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+
+ if ( rkeyEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
+ }
+
+ if ( rmouseEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
+ }
+});
+/*!
+ * Sizzle CSS Selector Engine
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://sizzlejs.com/
+ */
+(function( window, undefined ) {
+
+var cachedruns,
+ assertGetIdNotName,
+ Expr,
+ getText,
+ isXML,
+ contains,
+ compile,
+ sortOrder,
+ hasDuplicate,
+ outermostContext,
+
+ baseHasDuplicate = true,
+ strundefined = "undefined",
+
+ expando = ( "sizcache" + Math.random() ).replace( ".", "" ),
+
+ Token = String,
+ document = window.document,
+ docElem = document.documentElement,
+ dirruns = 0,
+ done = 0,
+ pop = [].pop,
+ push = [].push,
+ slice = [].slice,
+ // Use a stripped-down indexOf if a native one is unavailable
+ indexOf = [].indexOf || function( elem ) {
+ var i = 0,
+ len = this.length;
+ for ( ; i < len; i++ ) {
+ if ( this[i] === elem ) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ // Augment a function for special use by Sizzle
+ markFunction = function( fn, value ) {
+ fn[ expando ] = value == null || value;
+ return fn;
+ },
+
+ createCache = function() {
+ var cache = {},
+ keys = [];
+
+ return markFunction(function( key, value ) {
+ // Only keep the most recent entries
+ if ( keys.push( key ) > Expr.cacheLength ) {
+ delete cache[ keys.shift() ];
+ }
+
+ // Retrieve with (key + " ") to avoid collision with native Object.prototype properties (see Issue #157)
+ return (cache[ key + " " ] = value);
+ }, cache );
+ },
+
+ classCache = createCache(),
+ tokenCache = createCache(),
+ compilerCache = createCache(),
+
+ // Regex
+
+ // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+ whitespace = "[\\x20\\t\\r\\n\\f]",
+ // http://www.w3.org/TR/css3-syntax/#characters
+ characterEncoding = "(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",
+
+ // Loosely modeled on CSS identifier characters
+ // An unquoted value should be a CSS identifier (http://www.w3.org/TR/css3-selectors/#attribute-selectors)
+ // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+ identifier = characterEncoding.replace( "w", "w#" ),
+
+ // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
+ operators = "([*^$|!~]?=)",
+ attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
+ "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
+
+ // Prefer arguments not in parens/brackets,
+ // then attribute selectors and non-pseudos (denoted by :),
+ // then anything else
+ // These preferences are here to reduce the number of selectors
+ // needing tokenize in the PSEUDO preFilter
+ pseudos = ":(" + characterEncoding + ")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:" + attributes + ")|[^:]|\\\\.)*|.*))\\)|)",
+
+ // For matchExpr.POS and matchExpr.needsContext
+ pos = ":(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
+ "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)",
+
+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+ rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+ rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ),
+ rpseudo = new RegExp( pseudos ),
+
+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,
+
+ rnot = /^:not/,
+ rsibling = /[\x20\t\r\n\f]*[+~]/,
+ rendsWithNot = /:not\($/,
+
+ rheader = /h\d/i,
+ rinputs = /input|select|textarea|button/i,
+
+ rbackslash = /\\(?!\\)/g,
+
+ matchExpr = {
+ "ID": new RegExp( "^#(" + characterEncoding + ")" ),
+ "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
+ "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ),
+ "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
+ "ATTR": new RegExp( "^" + attributes ),
+ "PSEUDO": new RegExp( "^" + pseudos ),
+ "POS": new RegExp( pos, "i" ),
+ "CHILD": new RegExp( "^:(only|nth|first|last)-child(?:\\(" + whitespace +
+ "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+ "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+ // For use in libraries implementing .is()
+ "needsContext": new RegExp( "^" + whitespace + "*[>+~]|" + pos, "i" )
+ },
+
+ // Support
+
+ // Used for testing something on an element
+ assert = function( fn ) {
+ var div = document.createElement("div");
+
+ try {
+ return fn( div );
+ } catch (e) {
+ return false;
+ } finally {
+ // release memory in IE
+ div = null;
+ }
+ },
+
+ // Check if getElementsByTagName("*") returns only elements
+ assertTagNameNoComments = assert(function( div ) {
+ div.appendChild( document.createComment("") );
+ return !div.getElementsByTagName("*").length;
+ }),
+
+ // Check if getAttribute returns normalized href attributes
+ assertHrefNotNormalized = assert(function( div ) {
+ div.innerHTML = "<a href='#'></a>";
+ return div.firstChild && typeof div.firstChild.getAttribute !== strundefined &&
+ div.firstChild.getAttribute("href") === "#";
+ }),
+
+ // Check if attributes should be retrieved by attribute nodes
+ assertAttributes = assert(function( div ) {
+ div.innerHTML = "<select></select>";
+ var type = typeof div.lastChild.getAttribute("multiple");
+ // IE8 returns a string for some attributes even when not present
+ return type !== "boolean" && type !== "string";
+ }),
+
+ // Check if getElementsByClassName can be trusted
+ assertUsableClassName = assert(function( div ) {
+ // Opera can't find a second classname (in 9.6)
+ div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>";
+ if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) {
+ return false;
+ }
+
+ // Safari 3.2 caches class attributes and doesn't catch changes
+ div.lastChild.className = "e";
+ return div.getElementsByClassName("e").length === 2;
+ }),
+
+ // Check if getElementById returns elements by name
+ // Check if getElementsByName privileges form controls or returns elements by ID
+ assertUsableName = assert(function( div ) {
+ // Inject content
+ div.id = expando + 0;
+ div.innerHTML = "<a name='" + expando + "'></a><div name='" + expando + "'></div>";
+ docElem.insertBefore( div, docElem.firstChild );
+
+ // Test
+ var pass = document.getElementsByName &&
+ // buggy browsers will return fewer than the correct 2
+ document.getElementsByName( expando ).length === 2 +
+ // buggy browsers will return more than the correct 0
+ document.getElementsByName( expando + 0 ).length;
+ assertGetIdNotName = !document.getElementById( expando );
+
+ // Cleanup
+ docElem.removeChild( div );
+
+ return pass;
+ });
+
+// If slice is not available, provide a backup
+try {
+ slice.call( docElem.childNodes, 0 )[0].nodeType;
+} catch ( e ) {
+ slice = function( i ) {
+ var elem,
+ results = [];
+ for ( ; (elem = this[i]); i++ ) {
+ results.push( elem );
+ }
+ return results;
+ };
+}
+
+function Sizzle( selector, context, results, seed ) {
+ results = results || [];
+ context = context || document;
+ var match, elem, xml, m,
+ nodeType = context.nodeType;
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ if ( nodeType !== 1 && nodeType !== 9 ) {
+ return [];
+ }
+
+ xml = isXML( context );
+
+ if ( !xml && !seed ) {
+ if ( (match = rquickExpr.exec( selector )) ) {
+ // Speed-up: Sizzle("#ID")
+ if ( (m = match[1]) ) {
+ if ( nodeType === 9 ) {
+ elem = context.getElementById( m );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE, Opera, and Webkit return items
+ // by name instead of ID
+ if ( elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ } else {
+ return results;
+ }
+ } else {
+ // Context is not a document
+ if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
+ contains( context, elem ) && elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ }
+
+ // Speed-up: Sizzle("TAG")
+ } else if ( match[2] ) {
+ push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) );
+ return results;
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( (m = match[3]) && assertUsableClassName && context.getElementsByClassName ) {
+ push.apply( results, slice.call(context.getElementsByClassName( m ), 0) );
+ return results;
+ }
+ }
+ }
+
+ // All others
+ return select( selector.replace( rtrim, "$1" ), context, results, seed, xml );
+}
+
+Sizzle.matches = function( expr, elements ) {
+ return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+ return Sizzle( expr, null, null, [ elem ] ).length > 0;
+};
+
+// Returns a function to use in pseudos for input types
+function createInputPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === type;
+ };
+}
+
+// Returns a function to use in pseudos for buttons
+function createButtonPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && elem.type === type;
+ };
+}
+
+// Returns a function to use in pseudos for positionals
+function createPositionalPseudo( fn ) {
+ return markFunction(function( argument ) {
+ argument = +argument;
+ return markFunction(function( seed, matches ) {
+ var j,
+ matchIndexes = fn( [], seed.length, argument ),
+ i = matchIndexes.length;
+
+ // Match elements found at the specified indexes
+ while ( i-- ) {
+ if ( seed[ (j = matchIndexes[i]) ] ) {
+ seed[j] = !(matches[j] = seed[j]);
+ }
+ }
+ });
+ });
+}
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+getText = Sizzle.getText = function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
+
+ if ( nodeType ) {
+ if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+ // Use textContent for elements
+ // innerText usage removed for consistency of new lines (see #11153)
+ if ( typeof elem.textContent === "string" ) {
+ return elem.textContent;
+ } else {
+ // Traverse its children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ // Do not include comment or processing instruction nodes
+ } else {
+
+ // If no nodeType, this is expected to be an array
+ for ( ; (node = elem[i]); i++ ) {
+ // Do not traverse comment nodes
+ ret += getText( node );
+ }
+ }
+ return ret;
+};
+
+isXML = Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = elem && (elem.ownerDocument || elem).documentElement;
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+// Element contains another
+contains = Sizzle.contains = docElem.contains ?
+ function( a, b ) {
+ var adown = a.nodeType === 9 ? a.documentElement : a,
+ bup = b && b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && adown.contains && adown.contains(bup) );
+ } :
+ docElem.compareDocumentPosition ?
+ function( a, b ) {
+ return b && !!( a.compareDocumentPosition( b ) & 16 );
+ } :
+ function( a, b ) {
+ while ( (b = b.parentNode) ) {
+ if ( b === a ) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+Sizzle.attr = function( elem, name ) {
+ var val,
+ xml = isXML( elem );
+
+ if ( !xml ) {
+ name = name.toLowerCase();
+ }
+ if ( (val = Expr.attrHandle[ name ]) ) {
+ return val( elem );
+ }
+ if ( xml || assertAttributes ) {
+ return elem.getAttribute( name );
+ }
+ val = elem.getAttributeNode( name );
+ return val ?
+ typeof elem[ name ] === "boolean" ?
+ elem[ name ] ? name : null :
+ val.specified ? val.value : null :
+ null;
+};
+
+Expr = Sizzle.selectors = {
+
+ // Can be adjusted by the user
+ cacheLength: 50,
+
+ createPseudo: markFunction,
+
+ match: matchExpr,
+
+ // IE6/7 return a modified href
+ attrHandle: assertHrefNotNormalized ?
+ {} :
+ {
+ "href": function( elem ) {
+ return elem.getAttribute( "href", 2 );
+ },
+ "type": function( elem ) {
+ return elem.getAttribute("type");
+ }
+ },
+
+ find: {
+ "ID": assertGetIdNotName ?
+ function( id, context, xml ) {
+ if ( typeof context.getElementById !== strundefined && !xml ) {
+ var m = context.getElementById( id );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ } :
+ function( id, context, xml ) {
+ if ( typeof context.getElementById !== strundefined && !xml ) {
+ var m = context.getElementById( id );
+
+ return m ?
+ m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ?
+ [m] :
+ undefined :
+ [];
+ }
+ },
+
+ "TAG": assertTagNameNoComments ?
+ function( tag, context ) {
+ if ( typeof context.getElementsByTagName !== strundefined ) {
+ return context.getElementsByTagName( tag );
+ }
+ } :
+ function( tag, context ) {
+ var results = context.getElementsByTagName( tag );
+
+ // Filter out possible comments
+ if ( tag === "*" ) {
+ var elem,
+ tmp = [],
+ i = 0;
+
+ for ( ; (elem = results[i]); i++ ) {
+ if ( elem.nodeType === 1 ) {
+ tmp.push( elem );
+ }
+ }
+
+ return tmp;
+ }
+ return results;
+ },
+
+ "NAME": assertUsableName && function( tag, context ) {
+ if ( typeof context.getElementsByName !== strundefined ) {
+ return context.getElementsByName( name );
+ }
+ },
+
+ "CLASS": assertUsableClassName && function( className, context, xml ) {
+ if ( typeof context.getElementsByClassName !== strundefined && !xml ) {
+ return context.getElementsByClassName( className );
+ }
+ }
+ },
+
+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
+ },
+
+ preFilter: {
+ "ATTR": function( match ) {
+ match[1] = match[1].replace( rbackslash, "" );
+
+ // Move the given value to match[3] whether quoted or unquoted
+ match[3] = ( match[4] || match[5] || "" ).replace( rbackslash, "" );
+
+ if ( match[2] === "~=" ) {
+ match[3] = " " + match[3] + " ";
+ }
+
+ return match.slice( 0, 4 );
+ },
+
+ "CHILD": function( match ) {
+ /* matches from matchExpr["CHILD"]
+ 1 type (only|nth|...)
+ 2 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+ 3 xn-component of xn+y argument ([+-]?\d*n|)
+ 4 sign of xn-component
+ 5 x of xn-component
+ 6 sign of y-component
+ 7 y of y-component
+ */
+ match[1] = match[1].toLowerCase();
+
+ if ( match[1] === "nth" ) {
+ // nth-child requires argument
+ if ( !match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // numeric x and y parameters for Expr.filter.CHILD
+ // remember that false/true cast respectively to 0/1
+ match[3] = +( match[3] ? match[4] + (match[5] || 1) : 2 * ( match[2] === "even" || match[2] === "odd" ) );
+ match[4] = +( ( match[6] + match[7] ) || match[2] === "odd" );
+
+ // other types prohibit arguments
+ } else if ( match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ return match;
+ },
+
+ "PSEUDO": function( match ) {
+ var unquoted, excess;
+ if ( matchExpr["CHILD"].test( match[0] ) ) {
+ return null;
+ }
+
+ if ( match[3] ) {
+ match[2] = match[3];
+ } else if ( (unquoted = match[4]) ) {
+ // Only check arguments that contain a pseudo
+ if ( rpseudo.test(unquoted) &&
+ // Get excess from tokenize (recursively)
+ (excess = tokenize( unquoted, true )) &&
+ // advance to the next closing parenthesis
+ (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
+
+ // excess is a negative index
+ unquoted = unquoted.slice( 0, excess );
+ match[0] = match[0].slice( 0, excess );
+ }
+ match[2] = unquoted;
+ }
+
+ // Return only captures needed by the pseudo filter method (type and argument)
+ return match.slice( 0, 3 );
+ }
+ },
+
+ filter: {
+ "ID": assertGetIdNotName ?
+ function( id ) {
+ id = id.replace( rbackslash, "" );
+ return function( elem ) {
+ return elem.getAttribute("id") === id;
+ };
+ } :
+ function( id ) {
+ id = id.replace( rbackslash, "" );
+ return function( elem ) {
+ var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
+ return node && node.value === id;
+ };
+ },
+
+ "TAG": function( nodeName ) {
+ if ( nodeName === "*" ) {
+ return function() { return true; };
+ }
+ nodeName = nodeName.replace( rbackslash, "" ).toLowerCase();
+
+ return function( elem ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+ };
+ },
+
+ "CLASS": function( className ) {
+ var pattern = classCache[ expando ][ className + " " ];
+
+ return pattern ||
+ (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
+ classCache( className, function( elem ) {
+ return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" );
+ });
+ },
+
+ "ATTR": function( name, operator, check ) {
+ return function( elem, context ) {
+ var result = Sizzle.attr( elem, name );
+
+ if ( result == null ) {
+ return operator === "!=";
+ }
+ if ( !operator ) {
+ return true;
+ }
+
+ result += "";
+
+ return operator === "=" ? result === check :
+ operator === "!=" ? result !== check :
+ operator === "^=" ? check && result.indexOf( check ) === 0 :
+ operator === "*=" ? check && result.indexOf( check ) > -1 :
+ operator === "$=" ? check && result.substr( result.length - check.length ) === check :
+ operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
+ operator === "|=" ? result === check || result.substr( 0, check.length + 1 ) === check + "-" :
+ false;
+ };
+ },
+
+ "CHILD": function( type, argument, first, last ) {
+
+ if ( type === "nth" ) {
+ return function( elem ) {
+ var node, diff,
+ parent = elem.parentNode;
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ if ( parent ) {
+ diff = 0;
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ diff++;
+ if ( elem === node ) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Incorporate the offset (or cast to NaN), then check against cycle size
+ diff -= last;
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );
+ };
+ }
+
+ return function( elem ) {
+ var node = elem;
+
+ switch ( type ) {
+ case "only":
+ case "first":
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ if ( type === "first" ) {
+ return true;
+ }
+
+ node = elem;
+
+ /* falls through */
+ case "last":
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ };
+ },
+
+ "PSEUDO": function( pseudo, argument ) {
+ // pseudo-class names are case-insensitive
+ // http://www.w3.org/TR/selectors/#pseudo-classes
+ // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+ // Remember that setFilters inherits from pseudos
+ var args,
+ fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+ Sizzle.error( "unsupported pseudo: " + pseudo );
+
+ // The user may use createPseudo to indicate that
+ // arguments are needed to create the filter function
+ // just as Sizzle does
+ if ( fn[ expando ] ) {
+ return fn( argument );
+ }
+
+ // But maintain support for old signatures
+ if ( fn.length > 1 ) {
+ args = [ pseudo, pseudo, "", argument ];
+ return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+ markFunction(function( seed, matches ) {
+ var idx,
+ matched = fn( seed, argument ),
+ i = matched.length;
+ while ( i-- ) {
+ idx = indexOf.call( seed, matched[i] );
+ seed[ idx ] = !( matches[ idx ] = matched[i] );
+ }
+ }) :
+ function( elem ) {
+ return fn( elem, 0, args );
+ };
+ }
+
+ return fn;
+ }
+ },
+
+ pseudos: {
+ "not": markFunction(function( selector ) {
+ // Trim the selector passed to compile
+ // to avoid treating leading and trailing
+ // spaces as combinators
+ var input = [],
+ results = [],
+ matcher = compile( selector.replace( rtrim, "$1" ) );
+
+ return matcher[ expando ] ?
+ markFunction(function( seed, matches, context, xml ) {
+ var elem,
+ unmatched = matcher( seed, null, xml, [] ),
+ i = seed.length;
+
+ // Match elements unmatched by `matcher`
+ while ( i-- ) {
+ if ( (elem = unmatched[i]) ) {
+ seed[i] = !(matches[i] = elem);
+ }
+ }
+ }) :
+ function( elem, context, xml ) {
+ input[0] = elem;
+ matcher( input, null, xml, results );
+ return !results.pop();
+ };
+ }),
+
+ "has": markFunction(function( selector ) {
+ return function( elem ) {
+ return Sizzle( selector, elem ).length > 0;
+ };
+ }),
+
+ "contains": markFunction(function( text ) {
+ return function( elem ) {
+ return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+ };
+ }),
+
+ "enabled": function( elem ) {
+ return elem.disabled === false;
+ },
+
+ "disabled": function( elem ) {
+ return elem.disabled === true;
+ },
+
+ "checked": function( elem ) {
+ // In CSS3, :checked should return both checked and selected elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ var nodeName = elem.nodeName.toLowerCase();
+ return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+ },
+
+ "selected": function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ "parent": function( elem ) {
+ return !Expr.pseudos["empty"]( elem );
+ },
+
+ "empty": function( elem ) {
+ // http://www.w3.org/TR/selectors/#empty-pseudo
+ // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
+ // not comment, processing instructions, or others
+ // Thanks to Diego Perini for the nodeName shortcut
+ // Greater than "@" means alpha characters (specifically not starting with "#" or "?")
+ var nodeType;
+ elem = elem.firstChild;
+ while ( elem ) {
+ if ( elem.nodeName > "@" || (nodeType = elem.nodeType) === 3 || nodeType === 4 ) {
+ return false;
+ }
+ elem = elem.nextSibling;
+ }
+ return true;
+ },
+
+ "header": function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
+
+ "text": function( elem ) {
+ var type, attr;
+ // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+ // use getAttribute instead to test this case
+ return elem.nodeName.toLowerCase() === "input" &&
+ (type = elem.type) === "text" &&
+ ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === type );
+ },
+
+ // Input types
+ "radio": createInputPseudo("radio"),
+ "checkbox": createInputPseudo("checkbox"),
+ "file": createInputPseudo("file"),
+ "password": createInputPseudo("password"),
+ "image": createInputPseudo("image"),
+
+ "submit": createButtonPseudo("submit"),
+ "reset": createButtonPseudo("reset"),
+
+ "button": function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === "button" || name === "button";
+ },
+
+ "input": function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
+
+ "focus": function( elem ) {
+ var doc = elem.ownerDocument;
+ return elem === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
+ },
+
+ "active": function( elem ) {
+ return elem === elem.ownerDocument.activeElement;
+ },
+
+ // Positional types
+ "first": createPositionalPseudo(function() {
+ return [ 0 ];
+ }),
+
+ "last": createPositionalPseudo(function( matchIndexes, length ) {
+ return [ length - 1 ];
+ }),
+
+ "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ argument < 0 ? argument + length : argument ];
+ }),
+
+ "even": createPositionalPseudo(function( matchIndexes, length ) {
+ for ( var i = 0; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "odd": createPositionalPseudo(function( matchIndexes, length ) {
+ for ( var i = 1; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ for ( var i = argument < 0 ? argument + length : argument; --i >= 0; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ for ( var i = argument < 0 ? argument + length : argument; ++i < length; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ })
+ }
+};
+
+function siblingCheck( a, b, ret ) {
+ if ( a === b ) {
+ return ret;
+ }
+
+ var cur = a.nextSibling;
+
+ while ( cur ) {
+ if ( cur === b ) {
+ return -1;
+ }
+
+ cur = cur.nextSibling;
+ }
+
+ return 1;
+}
+
+sortOrder = docElem.compareDocumentPosition ?
+ function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ return ( !a.compareDocumentPosition || !b.compareDocumentPosition ?
+ a.compareDocumentPosition :
+ a.compareDocumentPosition(b) & 4
+ ) ? -1 : 1;
+ } :
+ function( a, b ) {
+ // The nodes are identical, we can exit early
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // Fallback to using sourceIndex (in IE) if it's available on both nodes
+ } else if ( a.sourceIndex && b.sourceIndex ) {
+ return a.sourceIndex - b.sourceIndex;
+ }
+
+ var al, bl,
+ ap = [],
+ bp = [],
+ aup = a.parentNode,
+ bup = b.parentNode,
+ cur = aup;
+
+ // If the nodes are siblings (or identical) we can do a quick check
+ if ( aup === bup ) {
+ return siblingCheck( a, b );
+
+ // If no parents were found then the nodes are disconnected
+ } else if ( !aup ) {
+ return -1;
+
+ } else if ( !bup ) {
+ return 1;
+ }
+
+ // Otherwise they're somewhere else in the tree so we need
+ // to build up a full list of the parentNodes for comparison
+ while ( cur ) {
+ ap.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ cur = bup;
+
+ while ( cur ) {
+ bp.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ al = ap.length;
+ bl = bp.length;
+
+ // Start walking down the tree looking for a discrepancy
+ for ( var i = 0; i < al && i < bl; i++ ) {
+ if ( ap[i] !== bp[i] ) {
+ return siblingCheck( ap[i], bp[i] );
+ }
+ }
+
+ // We ended someplace up the tree so do a sibling check
+ return i === al ?
+ siblingCheck( a, bp[i], -1 ) :
+ siblingCheck( ap[i], b, 1 );
+ };
+
+// Always assume the presence of duplicates if sort doesn't
+// pass them to our comparison function (as in Google Chrome).
+[0, 0].sort( sortOrder );
+baseHasDuplicate = !hasDuplicate;
+
+// Document sorting and removing duplicates
+Sizzle.uniqueSort = function( results ) {
+ var elem,
+ duplicates = [],
+ i = 1,
+ j = 0;
+
+ hasDuplicate = baseHasDuplicate;
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ for ( ; (elem = results[i]); i++ ) {
+ if ( elem === results[ i - 1 ] ) {
+ j = duplicates.push( i );
+ }
+ }
+ while ( j-- ) {
+ results.splice( duplicates[ j ], 1 );
+ }
+ }
+
+ return results;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+function tokenize( selector, parseOnly ) {
+ var matched, match, tokens, type,
+ soFar, groups, preFilters,
+ cached = tokenCache[ expando ][ selector + " " ];
+
+ if ( cached ) {
+ return parseOnly ? 0 : cached.slice( 0 );
+ }
+
+ soFar = selector;
+ groups = [];
+ preFilters = Expr.preFilter;
+
+ while ( soFar ) {
+
+ // Comma and first run
+ if ( !matched || (match = rcomma.exec( soFar )) ) {
+ if ( match ) {
+ // Don't consume trailing commas as valid
+ soFar = soFar.slice( match[0].length ) || soFar;
+ }
+ groups.push( tokens = [] );
+ }
+
+ matched = false;
+
+ // Combinators
+ if ( (match = rcombinators.exec( soFar )) ) {
+ tokens.push( matched = new Token( match.shift() ) );
+ soFar = soFar.slice( matched.length );
+
+ // Cast descendant combinators to space
+ matched.type = match[0].replace( rtrim, " " );
+ }
+
+ // Filters
+ for ( type in Expr.filter ) {
+ if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+ (match = preFilters[ type ]( match ))) ) {
+
+ tokens.push( matched = new Token( match.shift() ) );
+ soFar = soFar.slice( matched.length );
+ matched.type = type;
+ matched.matches = match;
+ }
+ }
+
+ if ( !matched ) {
+ break;
+ }
+ }
+
+ // Return the length of the invalid excess
+ // if we're just parsing
+ // Otherwise, throw an error or return tokens
+ return parseOnly ?
+ soFar.length :
+ soFar ?
+ Sizzle.error( selector ) :
+ // Cache the tokens
+ tokenCache( selector, groups ).slice( 0 );
+}
+
+function addCombinator( matcher, combinator, base ) {
+ var dir = combinator.dir,
+ checkNonElements = base && combinator.dir === "parentNode",
+ doneName = done++;
+
+ return combinator.first ?
+ // Check against closest ancestor/preceding element
+ function( elem, context, xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( checkNonElements || elem.nodeType === 1 ) {
+ return matcher( elem, context, xml );
+ }
+ }
+ } :
+
+ // Check against all ancestor/preceding elements
+ function( elem, context, xml ) {
+ // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
+ if ( !xml ) {
+ var cache,
+ dirkey = dirruns + " " + doneName + " ",
+ cachedkey = dirkey + cachedruns;
+ while ( (elem = elem[ dir ]) ) {
+ if ( checkNonElements || elem.nodeType === 1 ) {
+ if ( (cache = elem[ expando ]) === cachedkey ) {
+ return elem.sizset;
+ } else if ( typeof cache === "string" && cache.indexOf(dirkey) === 0 ) {
+ if ( elem.sizset ) {
+ return elem;
+ }
+ } else {
+ elem[ expando ] = cachedkey;
+ if ( matcher( elem, context, xml ) ) {
+ elem.sizset = true;
+ return elem;
+ }
+ elem.sizset = false;
+ }
+ }
+ }
+ } else {
+ while ( (elem = elem[ dir ]) ) {
+ if ( checkNonElements || elem.nodeType === 1 ) {
+ if ( matcher( elem, context, xml ) ) {
+ return elem;
+ }
+ }
+ }
+ }
+ };
+}
+
+function elementMatcher( matchers ) {
+ return matchers.length > 1 ?
+ function( elem, context, xml ) {
+ var i = matchers.length;
+ while ( i-- ) {
+ if ( !matchers[i]( elem, context, xml ) ) {
+ return false;
+ }
+ }
+ return true;
+ } :
+ matchers[0];
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+ var elem,
+ newUnmatched = [],
+ i = 0,
+ len = unmatched.length,
+ mapped = map != null;
+
+ for ( ; i < len; i++ ) {
+ if ( (elem = unmatched[i]) ) {
+ if ( !filter || filter( elem, context, xml ) ) {
+ newUnmatched.push( elem );
+ if ( mapped ) {
+ map.push( i );
+ }
+ }
+ }
+ }
+
+ return newUnmatched;
+}
+
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+ if ( postFilter && !postFilter[ expando ] ) {
+ postFilter = setMatcher( postFilter );
+ }
+ if ( postFinder && !postFinder[ expando ] ) {
+ postFinder = setMatcher( postFinder, postSelector );
+ }
+ return markFunction(function( seed, results, context, xml ) {
+ var temp, i, elem,
+ preMap = [],
+ postMap = [],
+ preexisting = results.length,
+
+ // Get initial elements from seed or context
+ elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
+
+ // Prefilter to get matcher input, preserving a map for seed-results synchronization
+ matcherIn = preFilter && ( seed || !selector ) ?
+ condense( elems, preMap, preFilter, context, xml ) :
+ elems,
+
+ matcherOut = matcher ?
+ // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+ postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+ // ...intermediate processing is necessary
+ [] :
+
+ // ...otherwise use results directly
+ results :
+ matcherIn;
+
+ // Find primary matches
+ if ( matcher ) {
+ matcher( matcherIn, matcherOut, context, xml );
+ }
+
+ // Apply postFilter
+ if ( postFilter ) {
+ temp = condense( matcherOut, postMap );
+ postFilter( temp, [], context, xml );
+
+ // Un-match failing elements by moving them back to matcherIn
+ i = temp.length;
+ while ( i-- ) {
+ if ( (elem = temp[i]) ) {
+ matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
+ }
+ }
+ }
+
+ if ( seed ) {
+ if ( postFinder || preFilter ) {
+ if ( postFinder ) {
+ // Get the final matcherOut by condensing this intermediate into postFinder contexts
+ temp = [];
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) ) {
+ // Restore matcherIn since elem is not yet a final match
+ temp.push( (matcherIn[i] = elem) );
+ }
+ }
+ postFinder( null, (matcherOut = []), temp, xml );
+ }
+
+ // Move matched elements from seed to results to keep them synchronized
+ i = matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) &&
+ (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
+
+ seed[temp] = !(results[temp] = elem);
+ }
+ }
+ }
+
+ // Add elements to results, through postFinder if defined
+ } else {
+ matcherOut = condense(
+ matcherOut === results ?
+ matcherOut.splice( preexisting, matcherOut.length ) :
+ matcherOut
+ );
+ if ( postFinder ) {
+ postFinder( null, results, matcherOut, xml );
+ } else {
+ push.apply( results, matcherOut );
+ }
+ }
+ });
+}
+
+function matcherFromTokens( tokens ) {
+ var checkContext, matcher, j,
+ len = tokens.length,
+ leadingRelative = Expr.relative[ tokens[0].type ],
+ implicitRelative = leadingRelative || Expr.relative[" "],
+ i = leadingRelative ? 1 : 0,
+
+ // The foundational matcher ensures that elements are reachable from top-level context(s)
+ matchContext = addCombinator( function( elem ) {
+ return elem === checkContext;
+ }, implicitRelative, true ),
+ matchAnyContext = addCombinator( function( elem ) {
+ return indexOf.call( checkContext, elem ) > -1;
+ }, implicitRelative, true ),
+ matchers = [ function( elem, context, xml ) {
+ return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+ (checkContext = context).nodeType ?
+ matchContext( elem, context, xml ) :
+ matchAnyContext( elem, context, xml ) );
+ } ];
+
+ for ( ; i < len; i++ ) {
+ if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
+ matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
+ } else {
+ matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
+
+ // Return special upon seeing a positional matcher
+ if ( matcher[ expando ] ) {
+ // Find the next relative operator (if any) for proper handling
+ j = ++i;
+ for ( ; j < len; j++ ) {
+ if ( Expr.relative[ tokens[j].type ] ) {
+ break;
+ }
+ }
+ return setMatcher(
+ i > 1 && elementMatcher( matchers ),
+ i > 1 && tokens.slice( 0, i - 1 ).join("").replace( rtrim, "$1" ),
+ matcher,
+ i < j && matcherFromTokens( tokens.slice( i, j ) ),
+ j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
+ j < len && tokens.join("")
+ );
+ }
+ matchers.push( matcher );
+ }
+ }
+
+ return elementMatcher( matchers );
+}
+
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+ var bySet = setMatchers.length > 0,
+ byElement = elementMatchers.length > 0,
+ superMatcher = function( seed, context, xml, results, expandContext ) {
+ var elem, j, matcher,
+ setMatched = [],
+ matchedCount = 0,
+ i = "0",
+ unmatched = seed && [],
+ outermost = expandContext != null,
+ contextBackup = outermostContext,
+ // We must always have either seed elements or context
+ elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ),
+ // Nested matchers should use non-integer dirruns
+ dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.E);
+
+ if ( outermost ) {
+ outermostContext = context !== document && context;
+ cachedruns = superMatcher.el;
+ }
+
+ // Add elements passing elementMatchers directly to results
+ for ( ; (elem = elems[i]) != null; i++ ) {
+ if ( byElement && elem ) {
+ for ( j = 0; (matcher = elementMatchers[j]); j++ ) {
+ if ( matcher( elem, context, xml ) ) {
+ results.push( elem );
+ break;
+ }
+ }
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ cachedruns = ++superMatcher.el;
+ }
+ }
+
+ // Track unmatched elements for set filters
+ if ( bySet ) {
+ // They will have gone through all possible matchers
+ if ( (elem = !matcher && elem) ) {
+ matchedCount--;
+ }
+
+ // Lengthen the array for every element, matched or not
+ if ( seed ) {
+ unmatched.push( elem );
+ }
+ }
+ }
+
+ // Apply set filters to unmatched elements
+ matchedCount += i;
+ if ( bySet && i !== matchedCount ) {
+ for ( j = 0; (matcher = setMatchers[j]); j++ ) {
+ matcher( unmatched, setMatched, context, xml );
+ }
+
+ if ( seed ) {
+ // Reintegrate element matches to eliminate the need for sorting
+ if ( matchedCount > 0 ) {
+ while ( i-- ) {
+ if ( !(unmatched[i] || setMatched[i]) ) {
+ setMatched[i] = pop.call( results );
+ }
+ }
+ }
+
+ // Discard index placeholder values to get only actual matches
+ setMatched = condense( setMatched );
+ }
+
+ // Add matches to results
+ push.apply( results, setMatched );
+
+ // Seedless set matches succeeding multiple successful matchers stipulate sorting
+ if ( outermost && !seed && setMatched.length > 0 &&
+ ( matchedCount + setMatchers.length ) > 1 ) {
+
+ Sizzle.uniqueSort( results );
+ }
+ }
+
+ // Override manipulation of globals by nested matchers
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ outermostContext = contextBackup;
+ }
+
+ return unmatched;
+ };
+
+ superMatcher.el = 0;
+ return bySet ?
+ markFunction( superMatcher ) :
+ superMatcher;
+}
+
+compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
+ var i,
+ setMatchers = [],
+ elementMatchers = [],
+ cached = compilerCache[ expando ][ selector + " " ];
+
+ if ( !cached ) {
+ // Generate a function of recursive functions that can be used to check each element
+ if ( !group ) {
+ group = tokenize( selector );
+ }
+ i = group.length;
+ while ( i-- ) {
+ cached = matcherFromTokens( group[i] );
+ if ( cached[ expando ] ) {
+ setMatchers.push( cached );
+ } else {
+ elementMatchers.push( cached );
+ }
+ }
+
+ // Cache the compiled function
+ cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+ }
+ return cached;
+};
+
+function multipleContexts( selector, contexts, results ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ Sizzle( selector, contexts[i], results );
+ }
+ return results;
+}
+
+function select( selector, context, results, seed, xml ) {
+ var i, tokens, token, type, find,
+ match = tokenize( selector ),
+ j = match.length;
+
+ if ( !seed ) {
+ // Try to minimize operations if there is only one group
+ if ( match.length === 1 ) {
+
+ // Take a shortcut and set the context if the root selector is an ID
+ tokens = match[0] = match[0].slice( 0 );
+ if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
+ context.nodeType === 9 && !xml &&
+ Expr.relative[ tokens[1].type ] ) {
+
+ context = Expr.find["ID"]( token.matches[0].replace( rbackslash, "" ), context, xml )[0];
+ if ( !context ) {
+ return results;
+ }
+
+ selector = selector.slice( tokens.shift().length );
+ }
+
+ // Fetch a seed set for right-to-left matching
+ for ( i = matchExpr["POS"].test( selector ) ? -1 : tokens.length - 1; i >= 0; i-- ) {
+ token = tokens[i];
+
+ // Abort if we hit a combinator
+ if ( Expr.relative[ (type = token.type) ] ) {
+ break;
+ }
+ if ( (find = Expr.find[ type ]) ) {
+ // Search, expanding context for leading sibling combinators
+ if ( (seed = find(
+ token.matches[0].replace( rbackslash, "" ),
+ rsibling.test( tokens[0].type ) && context.parentNode || context,
+ xml
+ )) ) {
+
+ // If seed is empty or no tokens remain, we can return early
+ tokens.splice( i, 1 );
+ selector = seed.length && tokens.join("");
+ if ( !selector ) {
+ push.apply( results, slice.call( seed, 0 ) );
+ return results;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Compile and execute a filtering function
+ // Provide `match` to avoid retokenization if we modified the selector above
+ compile( selector, match )(
+ seed,
+ context,
+ xml,
+ results,
+ rsibling.test( selector )
+ );
+ return results;
+}
+
+if ( document.querySelectorAll ) {
+ (function() {
+ var disconnectedMatch,
+ oldSelect = select,
+ rescape = /'|\\/g,
+ rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,
+
+ // qSa(:focus) reports false when true (Chrome 21), no need to also add to buggyMatches since matches checks buggyQSA
+ // A support test would require too much code (would include document ready)
+ rbuggyQSA = [ ":focus" ],
+
+ // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+ // A support test would require too much code (would include document ready)
+ // just skip matchesSelector for :active
+ rbuggyMatches = [ ":active" ],
+ matches = docElem.matchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.webkitMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector;
+
+ // Build QSA regex
+ // Regex strategy adopted from Diego Perini
+ assert(function( div ) {
+ // Select is set to empty string on purpose
+ // This is to test IE's treatment of not explictly
+ // setting a boolean content attribute,
+ // since its presence should be enough
+ // http://bugs.jquery.com/ticket/12359
+ div.innerHTML = "<select><option selected=''></option></select>";
+
+ // IE8 - Some boolean attributes are not treated correctly
+ if ( !div.querySelectorAll("[selected]").length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" );
+ }
+
+ // Webkit/Opera - :checked should return selected option elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ // IE8 throws error here (do not put tests after this one)
+ if ( !div.querySelectorAll(":checked").length ) {
+ rbuggyQSA.push(":checked");
+ }
+ });
+
+ assert(function( div ) {
+
+ // Opera 10-12/IE9 - ^= $= *= and empty values
+ // Should not select anything
+ div.innerHTML = "<p test=''></p>";
+ if ( div.querySelectorAll("[test^='']").length ) {
+ rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" );
+ }
+
+ // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+ // IE8 throws error here (do not put tests after this one)
+ div.innerHTML = "<input type='hidden'/>";
+ if ( !div.querySelectorAll(":enabled").length ) {
+ rbuggyQSA.push(":enabled", ":disabled");
+ }
+ });
+
+ // rbuggyQSA always contains :focus, so no need for a length check
+ rbuggyQSA = /* rbuggyQSA.length && */ new RegExp( rbuggyQSA.join("|") );
+
+ select = function( selector, context, results, seed, xml ) {
+ // Only use querySelectorAll when not filtering,
+ // when this is not xml,
+ // and when no QSA bugs apply
+ if ( !seed && !xml && !rbuggyQSA.test( selector ) ) {
+ var groups, i,
+ old = true,
+ nid = expando,
+ newContext = context,
+ newSelector = context.nodeType === 9 && selector;
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ groups = tokenize( selector );
+
+ if ( (old = context.getAttribute("id")) ) {
+ nid = old.replace( rescape, "\\$&" );
+ } else {
+ context.setAttribute( "id", nid );
+ }
+ nid = "[id='" + nid + "'] ";
+
+ i = groups.length;
+ while ( i-- ) {
+ groups[i] = nid + groups[i].join("");
+ }
+ newContext = rsibling.test( selector ) && context.parentNode || context;
+ newSelector = groups.join(",");
+ }
+
+ if ( newSelector ) {
+ try {
+ push.apply( results, slice.call( newContext.querySelectorAll(
+ newSelector
+ ), 0 ) );
+ return results;
+ } catch(qsaError) {
+ } finally {
+ if ( !old ) {
+ context.removeAttribute("id");
+ }
+ }
+ }
+ }
+
+ return oldSelect( selector, context, results, seed, xml );
+ };
+
+ if ( matches ) {
+ assert(function( div ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9)
+ disconnectedMatch = matches.call( div, "div" );
+
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ try {
+ matches.call( div, "[test!='']:sizzle" );
+ rbuggyMatches.push( "!=", pseudos );
+ } catch ( e ) {}
+ });
+
+ // rbuggyMatches always contains :active and :focus, so no need for a length check
+ rbuggyMatches = /* rbuggyMatches.length && */ new RegExp( rbuggyMatches.join("|") );
+
+ Sizzle.matchesSelector = function( elem, expr ) {
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace( rattributeQuotes, "='$1']" );
+
+ // rbuggyMatches always contains :active, so no need for an existence check
+ if ( !isXML( elem ) && !rbuggyMatches.test( expr ) && !rbuggyQSA.test( expr ) ) {
+ try {
+ var ret = matches.call( elem, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9
+ elem.document && elem.document.nodeType !== 11 ) {
+ return ret;
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle( expr, null, null, [ elem ] ).length > 0;
+ };
+ }
+ })();
+}
+
+// Deprecated
+Expr.pseudos["nth"] = Expr.pseudos["eq"];
+
+// Back-compat
+function setFilters() {}
+Expr.filters = setFilters.prototype = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
+// Override sizzle attribute retrieval
+Sizzle.attr = jQuery.attr;
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.pseudos;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})( window );
+var runtil = /Until$/,
+ rparentsprev = /^(?:parents|prev(?:Until|All))/,
+ isSimple = /^.[^:#\[\.,]*$/,
+ rneedsContext = jQuery.expr.match.needsContext,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var i, l, length, n, r, ret,
+ self = this;
+
+ if ( typeof selector !== "string" ) {
+ return jQuery( selector ).filter(function() {
+ for ( i = 0, l = self.length; i < l; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ });
+ }
+
+ ret = this.pushStack( "", "find", selector );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ length = ret.length;
+ jQuery.find( selector, this[i], ret );
+
+ if ( i > 0 ) {
+ // Make sure that the results are unique
+ for ( n = length; n < ret.length; n++ ) {
+ for ( r = 0; r < length; r++ ) {
+ if ( ret[r] === ret[n] ) {
+ ret.splice(n--, 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ has: function( target ) {
+ var i,
+ targets = jQuery( target, this ),
+ len = targets.length;
+
+ return this.filter(function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector, false), "not", selector);
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector, true), "filter", selector );
+ },
+
+ is: function( selector ) {
+ return !!selector && (
+ typeof selector === "string" ?
+ // If this is a positional/relative selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ rneedsContext.test( selector ) ?
+ jQuery( selector, this.context ).index( this[0] ) >= 0 :
+ jQuery.filter( selector, this ).length > 0 :
+ this.filter( selector ).length > 0 );
+ },
+
+ closest: function( selectors, context ) {
+ var cur,
+ i = 0,
+ l = this.length,
+ ret = [],
+ pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( ; i < l; i++ ) {
+ cur = this[i];
+
+ while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) {
+ if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+ ret.push( cur );
+ break;
+ }
+ cur = cur.parentNode;
+ }
+ }
+
+ ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+ return this.pushStack( ret, "closest", selectors );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+ }
+
+ // index in selector
+ if ( typeof elem === "string" ) {
+ return jQuery.inArray( this[0], jQuery( elem ) );
+ }
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[0] : elem, this );
+ },
+
+ add: function( selector, context ) {
+ var set = typeof selector === "string" ?
+ jQuery( selector, context ) :
+ jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+ all = jQuery.merge( this.get(), set );
+
+ return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+ all :
+ jQuery.unique( all ) );
+ },
+
+ addBack: function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter(selector)
+ );
+ }
+});
+
+jQuery.fn.andSelf = jQuery.fn.addBack;
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+ return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+function sibling( cur, dir ) {
+ do {
+ cur = cur[ dir ];
+ } while ( cur && cur.nodeType !== 1 );
+
+ return cur;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return sibling( elem, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return sibling( elem, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.merge( [], elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until );
+
+ if ( !runtil.test( name ) ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+ if ( this.length > 1 && rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+
+ return this.pushStack( ret, name, core_slice.call( arguments ).join(",") );
+ };
+});
+
+jQuery.extend({
+ filter: function( expr, elems, not ) {
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 ?
+ jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+ jQuery.find.matches(expr, elems);
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ cur = elem[ dir ];
+
+ while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+ if ( cur.nodeType === 1 ) {
+ matched.push( cur );
+ }
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+
+ // Can't pass null or undefined to indexOf in Firefox 4
+ // Set to 0 to skip string check
+ qualifier = qualifier || 0;
+
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ var retVal = !!qualifier.call( elem, i, elem );
+ return retVal === keep;
+ });
+
+ } else if ( qualifier.nodeType ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( elem === qualifier ) === keep;
+ });
+
+ } else if ( typeof qualifier === "string" ) {
+ var filtered = jQuery.grep(elements, function( elem ) {
+ return elem.nodeType === 1;
+ });
+
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter(qualifier, filtered, !keep);
+ } else {
+ qualifier = jQuery.filter( qualifier, filtered );
+ }
+ }
+
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
+ });
+}
+function createSafeFragment( document ) {
+ var list = nodeNames.split( "|" ),
+ safeFrag = document.createDocumentFragment();
+
+ if ( safeFrag.createElement ) {
+ while ( list.length ) {
+ safeFrag.createElement(
+ list.pop()
+ );
+ }
+ }
+ return safeFrag;
+}
+
+var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
+ "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+ rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
+ rleadingWhitespace = /^\s+/,
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
+ rtagName = /<([\w:]+)/,
+ rtbody = /<tbody/i,
+ rhtml = /<|&#?\w+;/,
+ rnoInnerhtml = /<(?:script|style|link)/i,
+ rnocache = /<(?:script|object|embed|option|style)/i,
+ rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
+ rcheckableType = /^(?:checkbox|radio)$/,
+ // checked="checked" or checked
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+ rscriptType = /\/(java|ecma)script/i,
+ rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,
+ wrapMap = {
+ option: [ 1, "<select multiple='multiple'>", "</select>" ],
+ legend: [ 1, "<fieldset>", "</fieldset>" ],
+ thead: [ 1, "<table>", "</table>" ],
+ tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+ td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+ col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+ area: [ 1, "<map>", "</map>" ],
+ _default: [ 0, "", "" ]
+ },
+ safeFragment = createSafeFragment( document ),
+ fragmentDiv = safeFragment.appendChild( document.createElement("div") );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
+// unless wrapped in a div with non-breaking characters in front of it.
+if ( !jQuery.support.htmlSerialize ) {
+ wrapMap._default = [ 1, "X<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+ text: function( value ) {
+ return jQuery.access( this, function( value ) {
+ return value === undefined ?
+ jQuery.text( this ) :
+ this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
+ }, null, value, arguments.length );
+ },
+
+ wrapAll: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapAll( html.call(this, i) );
+ });
+ }
+
+ if ( this[0] ) {
+ // The elements to wrap the target around
+ var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+ if ( this[0].parentNode ) {
+ wrap.insertBefore( this[0] );
+ }
+
+ wrap.map(function() {
+ var elem = this;
+
+ while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+ elem = elem.firstChild;
+ }
+
+ return elem;
+ }).append( this );
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapInner( html.call(this, i) );
+ });
+ }
+
+ return this.each(function() {
+ var self = jQuery( this ),
+ contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ });
+ },
+
+ wrap: function( html ) {
+ var isFunction = jQuery.isFunction( html );
+
+ return this.each(function(i) {
+ jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+ });
+ },
+
+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 ) {
+ this.appendChild( elem );
+ }
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 ) {
+ this.insertBefore( elem, this.firstChild );
+ }
+ });
+ },
+
+ before: function() {
+ if ( !isDisconnected( this[0] ) ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this );
+ });
+ }
+
+ if ( arguments.length ) {
+ var set = jQuery.clean( arguments );
+ return this.pushStack( jQuery.merge( set, this ), "before", this.selector );
+ }
+ },
+
+ after: function() {
+ if ( !isDisconnected( this[0] ) ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ }
+
+ if ( arguments.length ) {
+ var set = jQuery.clean( arguments );
+ return this.pushStack( jQuery.merge( this, set ), "after", this.selector );
+ }
+ },
+
+ // keepData is for internal use only--do not document
+ remove: function( selector, keepData ) {
+ var elem,
+ i = 0;
+
+ for ( ; (elem = this[i]) != null; i++ ) {
+ if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ jQuery.cleanData( [ elem ] );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ }
+ }
+
+ return this;
+ },
+
+ empty: function() {
+ var elem,
+ i = 0;
+
+ for ( ; (elem = this[i]) != null; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ }
+
+ // Remove any remaining nodes
+ while ( elem.firstChild ) {
+ elem.removeChild( elem.firstChild );
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+ return this.map( function () {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ });
+ },
+
+ html: function( value ) {
+ return jQuery.access( this, function( value ) {
+ var elem = this[0] || {},
+ i = 0,
+ l = this.length;
+
+ if ( value === undefined ) {
+ return elem.nodeType === 1 ?
+ elem.innerHTML.replace( rinlinejQuery, "" ) :
+ undefined;
+ }
+
+ // See if we can take a shortcut and just use innerHTML
+ if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+ ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) &&
+ ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
+ !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
+
+ value = value.replace( rxhtmlTag, "<$1></$2>" );
+
+ try {
+ for (; i < l; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ elem = this[i] || {};
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName( "*" ) );
+ elem.innerHTML = value;
+ }
+ }
+
+ elem = 0;
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch(e) {}
+ }
+
+ if ( elem ) {
+ this.empty().append( value );
+ }
+ }, null, value, arguments.length );
+ },
+
+ replaceWith: function( value ) {
+ if ( !isDisconnected( this[0] ) ) {
+ // Make sure that the elements are removed from the DOM before they are inserted
+ // this can help fix replacing a parent with child elements
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function(i) {
+ var self = jQuery(this), old = self.html();
+ self.replaceWith( value.call( this, i, old ) );
+ });
+ }
+
+ if ( typeof value !== "string" ) {
+ value = jQuery( value ).detach();
+ }
+
+ return this.each(function() {
+ var next = this.nextSibling,
+ parent = this.parentNode;
+
+ jQuery( this ).remove();
+
+ if ( next ) {
+ jQuery(next).before( value );
+ } else {
+ jQuery(parent).append( value );
+ }
+ });
+ }
+
+ return this.length ?
+ this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+ this;
+ },
+
+ detach: function( selector ) {
+ return this.remove( selector, true );
+ },
+
+ domManip: function( args, table, callback ) {
+
+ // Flatten any nested arrays
+ args = [].concat.apply( [], args );
+
+ var results, first, fragment, iNoClone,
+ i = 0,
+ value = args[0],
+ scripts = [],
+ l = this.length;
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( !jQuery.support.checkClone && l > 1 && typeof value === "string" && rchecked.test( value ) ) {
+ return this.each(function() {
+ jQuery(this).domManip( args, table, callback );
+ });
+ }
+
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ args[0] = value.call( this, i, table ? self.html() : undefined );
+ self.domManip( args, table, callback );
+ });
+ }
+
+ if ( this[0] ) {
+ results = jQuery.buildFragment( args, this, scripts );
+ fragment = results.fragment;
+ first = fragment.firstChild;
+
+ if ( fragment.childNodes.length === 1 ) {
+ fragment = first;
+ }
+
+ if ( first ) {
+ table = table && jQuery.nodeName( first, "tr" );
+
+ // Use the original fragment for the last item instead of the first because it can end up
+ // being emptied incorrectly in certain situations (#8070).
+ // Fragments from the fragment cache must always be cloned and never used in place.
+ for ( iNoClone = results.cacheable || l - 1; i < l; i++ ) {
+ callback.call(
+ table && jQuery.nodeName( this[i], "table" ) ?
+ findOrAppend( this[i], "tbody" ) :
+ this[i],
+ i === iNoClone ?
+ fragment :
+ jQuery.clone( fragment, true, true )
+ );
+ }
+ }
+
+ // Fix #11809: Avoid leaking memory
+ fragment = first = null;
+
+ if ( scripts.length ) {
+ jQuery.each( scripts, function( i, elem ) {
+ if ( elem.src ) {
+ if ( jQuery.ajax ) {
+ jQuery.ajax({
+ url: elem.src,
+ type: "GET",
+ dataType: "script",
+ async: false,
+ global: false,
+ "throws": true
+ });
+ } else {
+ jQuery.error("no ajax");
+ }
+ } else {
+ jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "" ) );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ });
+ }
+ }
+
+ return this;
+ }
+});
+
+function findOrAppend( elem, tag ) {
+ return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) );
+}
+
+function cloneCopyEvent( src, dest ) {
+
+ if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+ return;
+ }
+
+ var type, i, l,
+ oldData = jQuery._data( src ),
+ curData = jQuery._data( dest, oldData ),
+ events = oldData.events;
+
+ if ( events ) {
+ delete curData.handle;
+ curData.events = {};
+
+ for ( type in events ) {
+ for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type, events[ type ][ i ] );
+ }
+ }
+ }
+
+ // make the cloned public data object a copy from the original
+ if ( curData.data ) {
+ curData.data = jQuery.extend( {}, curData.data );
+ }
+}
+
+function cloneFixAttributes( src, dest ) {
+ var nodeName;
+
+ // We do not need to do anything for non-Elements
+ if ( dest.nodeType !== 1 ) {
+ return;
+ }
+
+ // clearAttributes removes the attributes, which we don't want,
+ // but also removes the attachEvent events, which we *do* want
+ if ( dest.clearAttributes ) {
+ dest.clearAttributes();
+ }
+
+ // mergeAttributes, in contrast, only merges back on the
+ // original attributes, not the events
+ if ( dest.mergeAttributes ) {
+ dest.mergeAttributes( src );
+ }
+
+ nodeName = dest.nodeName.toLowerCase();
+
+ if ( nodeName === "object" ) {
+ // IE6-10 improperly clones children of object elements using classid.
+ // IE10 throws NoModificationAllowedError if parent is null, #12132.
+ if ( dest.parentNode ) {
+ dest.outerHTML = src.outerHTML;
+ }
+
+ // This path appears unavoidable for IE9. When cloning an object
+ // element in IE9, the outerHTML strategy above is not sufficient.
+ // If the src has innerHTML and the destination does not,
+ // copy the src.innerHTML into the dest.innerHTML. #10324
+ if ( jQuery.support.html5Clone && (src.innerHTML && !jQuery.trim(dest.innerHTML)) ) {
+ dest.innerHTML = src.innerHTML;
+ }
+
+ } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
+ // IE6-8 fails to persist the checked state of a cloned checkbox
+ // or radio button. Worse, IE6-7 fail to give the cloned element
+ // a checked appearance if the defaultChecked value isn't also set
+
+ dest.defaultChecked = dest.checked = src.checked;
+
+ // IE6-7 get confused and end up setting the value of a cloned
+ // checkbox/radio button to an empty string instead of "on"
+ if ( dest.value !== src.value ) {
+ dest.value = src.value;
+ }
+
+ // IE6-8 fails to return the selected option to the default selected
+ // state when cloning options
+ } else if ( nodeName === "option" ) {
+ dest.selected = src.defaultSelected;
+
+ // IE6-8 fails to set the defaultValue to the correct value when
+ // cloning other types of input fields
+ } else if ( nodeName === "input" || nodeName === "textarea" ) {
+ dest.defaultValue = src.defaultValue;
+
+ // IE blanks contents when cloning scripts
+ } else if ( nodeName === "script" && dest.text !== src.text ) {
+ dest.text = src.text;
+ }
+
+ // Event data gets referenced instead of copied if the expando
+ // gets copied too
+ dest.removeAttribute( jQuery.expando );
+}
+
+jQuery.buildFragment = function( args, context, scripts ) {
+ var fragment, cacheable, cachehit,
+ first = args[ 0 ];
+
+ // Set context from what may come in as undefined or a jQuery collection or a node
+ // Updated to fix #12266 where accessing context[0] could throw an exception in IE9/10 &
+ // also doubles as fix for #8950 where plain objects caused createDocumentFragment exception
+ context = context || document;
+ context = !context.nodeType && context[0] || context;
+ context = context.ownerDocument || context;
+
+ // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+ // Cloning options loses the selected state, so don't cache them
+ // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+ // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+ // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
+ if ( args.length === 1 && typeof first === "string" && first.length < 512 && context === document &&
+ first.charAt(0) === "<" && !rnocache.test( first ) &&
+ (jQuery.support.checkClone || !rchecked.test( first )) &&
+ (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
+
+ // Mark cacheable and look for a hit
+ cacheable = true;
+ fragment = jQuery.fragments[ first ];
+ cachehit = fragment !== undefined;
+ }
+
+ if ( !fragment ) {
+ fragment = context.createDocumentFragment();
+ jQuery.clean( args, context, fragment, scripts );
+
+ // Update the cache, but only store false
+ // unless this is a second parsing of the same content
+ if ( cacheable ) {
+ jQuery.fragments[ first ] = cachehit && fragment;
+ }
+ }
+
+ return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var elems,
+ i = 0,
+ ret = [],
+ insert = jQuery( selector ),
+ l = insert.length,
+ parent = this.length === 1 && this[0].parentNode;
+
+ if ( (parent == null || parent && parent.nodeType === 11 && parent.childNodes.length === 1) && l === 1 ) {
+ insert[ original ]( this[0] );
+ return this;
+ } else {
+ for ( ; i < l; i++ ) {
+ elems = ( i > 0 ? this.clone(true) : this ).get();
+ jQuery( insert[i] )[ original ]( elems );
+ ret = ret.concat( elems );
+ }
+
+ return this.pushStack( ret, name, insert.selector );
+ }
+ };
+});
+
+function getAll( elem ) {
+ if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ return elem.getElementsByTagName( "*" );
+
+ } else if ( typeof elem.querySelectorAll !== "undefined" ) {
+ return elem.querySelectorAll( "*" );
+
+ } else {
+ return [];
+ }
+}
+
+// Used in clean, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+ if ( rcheckableType.test( elem.type ) ) {
+ elem.defaultChecked = elem.checked;
+ }
+}
+
+jQuery.extend({
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var srcElements,
+ destElements,
+ i,
+ clone;
+
+ if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
+ clone = elem.cloneNode( true );
+
+ // IE<=8 does not properly clone detached, unknown element nodes
+ } else {
+ fragmentDiv.innerHTML = elem.outerHTML;
+ fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
+ }
+
+ if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+ (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+ // IE copies events bound via attachEvent when using cloneNode.
+ // Calling detachEvent on the clone will also remove the events
+ // from the original. In order to get around this, we use some
+ // proprietary methods to clear the events. Thanks to MooTools
+ // guys for this hotness.
+
+ cloneFixAttributes( elem, clone );
+
+ // Using Sizzle here is crazy slow, so we use getElementsByTagName instead
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ // Weird iteration because IE will replace the length property
+ // with an element if you are cloning the body and one of the
+ // elements on the page has a name or id of "length"
+ for ( i = 0; srcElements[i]; ++i ) {
+ // Ensure that the destination node is not null; Fixes #9587
+ if ( destElements[i] ) {
+ cloneFixAttributes( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ cloneCopyEvent( elem, clone );
+
+ if ( deepDataAndEvents ) {
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ for ( i = 0; srcElements[i]; ++i ) {
+ cloneCopyEvent( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ srcElements = destElements = null;
+
+ // Return the cloned set
+ return clone;
+ },
+
+ clean: function( elems, context, fragment, scripts ) {
+ var i, j, elem, tag, wrap, depth, div, hasBody, tbody, len, handleScript, jsTags,
+ safe = context === document && safeFragment,
+ ret = [];
+
+ // Ensure that context is a document
+ if ( !context || typeof context.createDocumentFragment === "undefined" ) {
+ context = document;
+ }
+
+ // Use the already-created safe fragment if context permits
+ for ( i = 0; (elem = elems[i]) != null; i++ ) {
+ if ( typeof elem === "number" ) {
+ elem += "";
+ }
+
+ if ( !elem ) {
+ continue;
+ }
+
+ // Convert html string into DOM nodes
+ if ( typeof elem === "string" ) {
+ if ( !rhtml.test( elem ) ) {
+ elem = context.createTextNode( elem );
+ } else {
+ // Ensure a safe container in which to render the html
+ safe = safe || createSafeFragment( context );
+ div = context.createElement("div");
+ safe.appendChild( div );
+
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(rxhtmlTag, "<$1></$2>");
+
+ // Go to html and back, then peel off extra wrappers
+ tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();
+ wrap = wrapMap[ tag ] || wrapMap._default;
+ depth = wrap[0];
+ div.innerHTML = wrap[1] + elem + wrap[2];
+
+ // Move to the right depth
+ while ( depth-- ) {
+ div = div.lastChild;
+ }
+
+ // Remove IE's autoinserted <tbody> from table fragments
+ if ( !jQuery.support.tbody ) {
+
+ // String was a <table>, *may* have spurious <tbody>
+ hasBody = rtbody.test(elem);
+ tbody = tag === "table" && !hasBody ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare <thead> or <tfoot>
+ wrap[1] === "<table>" && !hasBody ?
+ div.childNodes :
+ [];
+
+ for ( j = tbody.length - 1; j >= 0 ; --j ) {
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+ }
+ }
+ }
+
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+ div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+ }
+
+ elem = div.childNodes;
+
+ // Take out of fragment container (we need a fresh div each time)
+ div.parentNode.removeChild( div );
+ }
+ }
+
+ if ( elem.nodeType ) {
+ ret.push( elem );
+ } else {
+ jQuery.merge( ret, elem );
+ }
+ }
+
+ // Fix #11356: Clear elements from safeFragment
+ if ( div ) {
+ elem = div = safe = null;
+ }
+
+ // Reset defaultChecked for any radios and checkboxes
+ // about to be appended to the DOM in IE 6/7 (#8060)
+ if ( !jQuery.support.appendChecked ) {
+ for ( i = 0; (elem = ret[i]) != null; i++ ) {
+ if ( jQuery.nodeName( elem, "input" ) ) {
+ fixDefaultChecked( elem );
+ } else if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+ }
+ }
+ }
+
+ // Append elements to a provided document fragment
+ if ( fragment ) {
+ // Special handling of each script element
+ handleScript = function( elem ) {
+ // Check if we consider it executable
+ if ( !elem.type || rscriptType.test( elem.type ) ) {
+ // Detach the script and store it in the scripts array (if provided) or the fragment
+ // Return truthy to indicate that it has been handled
+ return scripts ?
+ scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) :
+ fragment.appendChild( elem );
+ }
+ };
+
+ for ( i = 0; (elem = ret[i]) != null; i++ ) {
+ // Check if we're done after handling an executable script
+ if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) {
+ // Append to fragment and handle embedded scripts
+ fragment.appendChild( elem );
+ if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ // handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration
+ jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript );
+
+ // Splice the scripts into ret after their former ancestor and advance our index beyond them
+ ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+ i += jsTags.length;
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ cleanData: function( elems, /* internal */ acceptData ) {
+ var data, id, elem, type,
+ i = 0,
+ internalKey = jQuery.expando,
+ cache = jQuery.cache,
+ deleteExpando = jQuery.support.deleteExpando,
+ special = jQuery.event.special;
+
+ for ( ; (elem = elems[i]) != null; i++ ) {
+
+ if ( acceptData || jQuery.acceptData( elem ) ) {
+
+ id = elem[ internalKey ];
+ data = id && cache[ id ];
+
+ if ( data ) {
+ if ( data.events ) {
+ for ( type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+ }
+
+ // Remove cache only if it was not already removed by jQuery.event.remove
+ if ( cache[ id ] ) {
+
+ delete cache[ id ];
+
+ // IE does not allow us to delete expando properties from nodes,
+ // nor does it have a removeAttribute function on Document nodes;
+ // we must handle all of these cases
+ if ( deleteExpando ) {
+ delete elem[ internalKey ];
+
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( internalKey );
+
+ } else {
+ elem[ internalKey ] = null;
+ }
+
+ jQuery.deletedIds.push( id );
+ }
+ }
+ }
+ }
+ }
+});
+// Limit scope pollution from any deprecated API
+(function() {
+
+var matched, browser;
+
+// Use of jQuery.browser is frowned upon.
+// More details: http://api.jquery.com/jQuery.browser
+// jQuery.uaMatch maintained for back-compat
+jQuery.uaMatch = function( ua ) {
+ ua = ua.toLowerCase();
+
+ var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
+ /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
+ /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
+ /(msie) ([\w.]+)/.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
+ [];
+
+ return {
+ browser: match[ 1 ] || "",
+ version: match[ 2 ] || "0"
+ };
+};
+
+matched = jQuery.uaMatch( navigator.userAgent );
+browser = {};
+
+if ( matched.browser ) {
+ browser[ matched.browser ] = true;
+ browser.version = matched.version;
+}
+
+// Chrome is Webkit, but Webkit is also Safari.
+if ( browser.chrome ) {
+ browser.webkit = true;
+} else if ( browser.webkit ) {
+ browser.safari = true;
+}
+
+jQuery.browser = browser;
+
+jQuery.sub = function() {
+ function jQuerySub( selector, context ) {
+ return new jQuerySub.fn.init( selector, context );
+ }
+ jQuery.extend( true, jQuerySub, this );
+ jQuerySub.superclass = this;
+ jQuerySub.fn = jQuerySub.prototype = this();
+ jQuerySub.fn.constructor = jQuerySub;
+ jQuerySub.sub = this.sub;
+ jQuerySub.fn.init = function init( selector, context ) {
+ if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+ context = jQuerySub( context );
+ }
+
+ return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+ };
+ jQuerySub.fn.init.prototype = jQuerySub.fn;
+ var rootjQuerySub = jQuerySub(document);
+ return jQuerySub;
+};
+
+})();
+var curCSS, iframe, iframeDoc,
+ ralpha = /alpha\([^)]*\)/i,
+ ropacity = /opacity=([^)]*)/,
+ rposition = /^(top|right|bottom|left)$/,
+ // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
+ // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+ rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+ rmargin = /^margin/,
+ rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
+ rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
+ rrelNum = new RegExp( "^([-+])=(" + core_pnum + ")", "i" ),
+ elemdisplay = { BODY: "block" },
+
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+ cssNormalTransform = {
+ letterSpacing: 0,
+ fontWeight: 400
+ },
+
+ cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+ cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
+
+ eventsToggle = jQuery.fn.toggle;
+
+// return a css property mapped to a potentially vendor prefixed property
+function vendorPropName( style, name ) {
+
+ // shortcut for names that are not vendor prefixed
+ if ( name in style ) {
+ return name;
+ }
+
+ // check for vendor prefixed names
+ var capName = name.charAt(0).toUpperCase() + name.slice(1),
+ origName = name,
+ i = cssPrefixes.length;
+
+ while ( i-- ) {
+ name = cssPrefixes[ i ] + capName;
+ if ( name in style ) {
+ return name;
+ }
+ }
+
+ return origName;
+}
+
+function isHidden( elem, el ) {
+ elem = el || elem;
+ return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
+}
+
+function showHide( elements, show ) {
+ var elem, display,
+ values = [],
+ index = 0,
+ length = elements.length;
+
+ for ( ; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+ values[ index ] = jQuery._data( elem, "olddisplay" );
+ if ( show ) {
+ // Reset the inline display of this element to learn if it is
+ // being hidden by cascaded rules or not
+ if ( !values[ index ] && elem.style.display === "none" ) {
+ elem.style.display = "";
+ }
+
+ // Set elements which have been overridden with display: none
+ // in a stylesheet to whatever the default browser style is
+ // for such an element
+ if ( elem.style.display === "" && isHidden( elem ) ) {
+ values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
+ }
+ } else {
+ display = curCSS( elem, "display" );
+
+ if ( !values[ index ] && display !== "none" ) {
+ jQuery._data( elem, "olddisplay", display );
+ }
+ }
+ }
+
+ // Set the display of most of the elements in a second loop
+ // to avoid the constant reflow
+ for ( index = 0; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+ if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+ elem.style.display = show ? values[ index ] || "" : "none";
+ }
+ }
+
+ return elements;
+}
+
+jQuery.fn.extend({
+ css: function( name, value ) {
+ return jQuery.access( this, function( elem, name, value ) {
+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ }, name, value, arguments.length > 1 );
+ },
+ show: function() {
+ return showHide( this, true );
+ },
+ hide: function() {
+ return showHide( this );
+ },
+ toggle: function( state, fn2 ) {
+ var bool = typeof state === "boolean";
+
+ if ( jQuery.isFunction( state ) && jQuery.isFunction( fn2 ) ) {
+ return eventsToggle.apply( this, arguments );
+ }
+
+ return this.each(function() {
+ if ( bool ? state : isHidden( this ) ) {
+ jQuery( this ).show();
+ } else {
+ jQuery( this ).hide();
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity" );
+ return ret === "" ? "1" : ret;
+
+ }
+ }
+ }
+ },
+
+ // Exclude the following css properties to add px
+ cssNumber: {
+ "fillOpacity": true,
+ "fontWeight": true,
+ "lineHeight": true,
+ "opacity": true,
+ "orphans": true,
+ "widows": true,
+ "zIndex": true,
+ "zoom": true
+ },
+
+ // Add in properties whose names you wish to fix before
+ // setting or getting the value
+ cssProps: {
+ // normalize float css property
+ "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+ },
+
+ // Get and set the style property on a DOM Node
+ style: function( elem, name, value, extra ) {
+ // Don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+ return;
+ }
+
+ // Make sure that we're working with the right name
+ var ret, type, hooks,
+ origName = jQuery.camelCase( name ),
+ style = elem.style;
+
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
+
+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ type = typeof value;
+
+ // convert relative number strings (+= or -=) to relative numbers. #7345
+ if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+ value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
+ // Fixes bug #9237
+ type = "number";
+ }
+
+ // Make sure that NaN and null values aren't set. See: #7116
+ if ( value == null || type === "number" && isNaN( value ) ) {
+ return;
+ }
+
+ // If a number was passed in, add 'px' to the (except for certain CSS properties)
+ if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+ value += "px";
+ }
+
+ // If a hook was provided, use that value, otherwise just set the specified value
+ if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
+ // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+ // Fixes bug #5509
+ try {
+ style[ name ] = value;
+ } catch(e) {}
+ }
+
+ } else {
+ // If a hook was provided get the non-computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+ return ret;
+ }
+
+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
+ },
+
+ css: function( elem, name, numeric, extra ) {
+ var val, num, hooks,
+ origName = jQuery.camelCase( name );
+
+ // Make sure that we're working with the right name
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
+
+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks ) {
+ val = hooks.get( elem, true, extra );
+ }
+
+ // Otherwise, if a way to get the computed value exists, use that
+ if ( val === undefined ) {
+ val = curCSS( elem, name );
+ }
+
+ //convert "normal" to computed value
+ if ( val === "normal" && name in cssNormalTransform ) {
+ val = cssNormalTransform[ name ];
+ }
+
+ // Return, converting to number if forced or a qualifier was provided and val looks numeric
+ if ( numeric || extra !== undefined ) {
+ num = parseFloat( val );
+ return numeric || jQuery.isNumeric( num ) ? num || 0 : val;
+ }
+ return val;
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var ret, name,
+ old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ ret = callback.call( elem );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+
+ return ret;
+ }
+});
+
+// NOTE: To any future maintainer, we've window.getComputedStyle
+// because jsdom on node.js will break without it.
+if ( window.getComputedStyle ) {
+ curCSS = function( elem, name ) {
+ var ret, width, minWidth, maxWidth,
+ computed = window.getComputedStyle( elem, null ),
+ style = elem.style;
+
+ if ( computed ) {
+
+ // getPropertyValue is only needed for .css('filter') in IE9, see #12537
+ ret = computed.getPropertyValue( name ) || computed[ name ];
+
+ if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
+
+ // A tribute to the "awesome hack by Dean Edwards"
+ // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
+ // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
+ // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+ if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
+ width = style.width;
+ minWidth = style.minWidth;
+ maxWidth = style.maxWidth;
+
+ style.minWidth = style.maxWidth = style.width = ret;
+ ret = computed.width;
+
+ style.width = width;
+ style.minWidth = minWidth;
+ style.maxWidth = maxWidth;
+ }
+ }
+
+ return ret;
+ };
+} else if ( document.documentElement.currentStyle ) {
+ curCSS = function( elem, name ) {
+ var left, rsLeft,
+ ret = elem.currentStyle && elem.currentStyle[ name ],
+ style = elem.style;
+
+ // Avoid setting ret to empty string here
+ // so we don't default to auto
+ if ( ret == null && style && style[ name ] ) {
+ ret = style[ name ];
+ }
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ // but not position css attributes, as those are proportional to the parent element instead
+ // and we can't measure the parent instead because it might trigger a "stacking dolls" problem
+ if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
+
+ // Remember the original values
+ left = style.left;
+ rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ }
+ style.left = name === "fontSize" ? "1em" : ret;
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret === "" ? "auto" : ret;
+ };
+}
+
+function setPositiveNumber( elem, value, subtract ) {
+ var matches = rnumsplit.exec( value );
+ return matches ?
+ Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
+ value;
+}
+
+function augmentWidthOrHeight( elem, name, extra, isBorderBox ) {
+ var i = extra === ( isBorderBox ? "border" : "content" ) ?
+ // If we already have the right measurement, avoid augmentation
+ 4 :
+ // Otherwise initialize for horizontal or vertical properties
+ name === "width" ? 1 : 0,
+
+ val = 0;
+
+ for ( ; i < 4; i += 2 ) {
+ // both box models exclude margin, so add it if we want it
+ if ( extra === "margin" ) {
+ // we use jQuery.css instead of curCSS here
+ // because of the reliableMarginRight CSS hook!
+ val += jQuery.css( elem, extra + cssExpand[ i ], true );
+ }
+
+ // From this point on we use curCSS for maximum performance (relevant in animations)
+ if ( isBorderBox ) {
+ // border-box includes padding, so remove it if we want content
+ if ( extra === "content" ) {
+ val -= parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
+ }
+
+ // at this point, extra isn't border nor margin, so remove border
+ if ( extra !== "margin" ) {
+ val -= parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+ }
+ } else {
+ // at this point, extra isn't content, so add padding
+ val += parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
+
+ // at this point, extra isn't content nor padding, so add border
+ if ( extra !== "padding" ) {
+ val += parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+ }
+ }
+ }
+
+ return val;
+}
+
+function getWidthOrHeight( elem, name, extra ) {
+
+ // Start with offset property, which is equivalent to the border-box value
+ var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+ valueIsBorderBox = true,
+ isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box";
+
+ // some non-html elements return undefined for offsetWidth, so check for null/undefined
+ // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
+ // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
+ if ( val <= 0 || val == null ) {
+ // Fall back to computed then uncomputed css if necessary
+ val = curCSS( elem, name );
+ if ( val < 0 || val == null ) {
+ val = elem.style[ name ];
+ }
+
+ // Computed unit is not pixels. Stop here and return.
+ if ( rnumnonpx.test(val) ) {
+ return val;
+ }
+
+ // we need the check for style in case a browser which returns unreliable values
+ // for getComputedStyle silently falls back to the reliable elem.style
+ valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
+
+ // Normalize "", auto, and prepare for extra
+ val = parseFloat( val ) || 0;
+ }
+
+ // use the active box-sizing model to add/subtract irrelevant styles
+ return ( val +
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra || ( isBorderBox ? "border" : "content" ),
+ valueIsBorderBox
+ )
+ ) + "px";
+}
+
+
+// Try to determine the default display value of an element
+function css_defaultDisplay( nodeName ) {
+ if ( elemdisplay[ nodeName ] ) {
+ return elemdisplay[ nodeName ];
+ }
+
+ var elem = jQuery( "<" + nodeName + ">" ).appendTo( document.body ),
+ display = elem.css("display");
+ elem.remove();
+
+ // If the simple way fails,
+ // get element's real default display by attaching it to a temp iframe
+ if ( display === "none" || display === "" ) {
+ // Use the already-created iframe if possible
+ iframe = document.body.appendChild(
+ iframe || jQuery.extend( document.createElement("iframe"), {
+ frameBorder: 0,
+ width: 0,
+ height: 0
+ })
+ );
+
+ // Create a cacheable copy of the iframe document on first call.
+ // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+ // document to it; WebKit & Firefox won't allow reusing the iframe document.
+ if ( !iframeDoc || !iframe.createElement ) {
+ iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+ iframeDoc.write("<!doctype html><html><body>");
+ iframeDoc.close();
+ }
+
+ elem = iframeDoc.body.appendChild( iframeDoc.createElement(nodeName) );
+
+ display = curCSS( elem, "display" );
+ document.body.removeChild( iframe );
+ }
+
+ // Store the correct default display
+ elemdisplay[ nodeName ] = display;
+
+ return display;
+}
+
+jQuery.each([ "height", "width" ], function( i, name ) {
+ jQuery.cssHooks[ name ] = {
+ get: function( elem, computed, extra ) {
+ if ( computed ) {
+ // certain elements can have dimension info if we invisibly show them
+ // however, it must have a current display style that would benefit from this
+ if ( elem.offsetWidth === 0 && rdisplayswap.test( curCSS( elem, "display" ) ) ) {
+ return jQuery.swap( elem, cssShow, function() {
+ return getWidthOrHeight( elem, name, extra );
+ });
+ } else {
+ return getWidthOrHeight( elem, name, extra );
+ }
+ }
+ },
+
+ set: function( elem, value, extra ) {
+ return setPositiveNumber( elem, value, extra ?
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra,
+ jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box"
+ ) : 0
+ );
+ }
+ };
+});
+
+if ( !jQuery.support.opacity ) {
+ jQuery.cssHooks.opacity = {
+ get: function( elem, computed ) {
+ // IE uses filters for opacity
+ return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+ ( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
+ computed ? "1" : "";
+ },
+
+ set: function( elem, value ) {
+ var style = elem.style,
+ currentStyle = elem.currentStyle,
+ opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
+ filter = currentStyle && currentStyle.filter || style.filter || "";
+
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ style.zoom = 1;
+
+ // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+ if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
+ style.removeAttribute ) {
+
+ // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+ // if "filter:" is present at all, clearType is disabled, we want to avoid this
+ // style.removeAttribute is IE Only, but so apparently is this code path...
+ style.removeAttribute( "filter" );
+
+ // if there there is no filter style applied in a css rule, we are done
+ if ( currentStyle && !currentStyle.filter ) {
+ return;
+ }
+ }
+
+ // otherwise, set new filter values
+ style.filter = ralpha.test( filter ) ?
+ filter.replace( ralpha, opacity ) :
+ filter + " " + opacity;
+ }
+ };
+}
+
+// These hooks cannot be added until DOM ready because the support test
+// for it is not run until after DOM ready
+jQuery(function() {
+ if ( !jQuery.support.reliableMarginRight ) {
+ jQuery.cssHooks.marginRight = {
+ get: function( elem, computed ) {
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ // Work around by temporarily setting element display to inline-block
+ return jQuery.swap( elem, { "display": "inline-block" }, function() {
+ if ( computed ) {
+ return curCSS( elem, "marginRight" );
+ }
+ });
+ }
+ };
+ }
+
+ // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+ // getComputedStyle returns percent when specified for top/left/bottom/right
+ // rather than make the css module depend on the offset module, we just check for it here
+ if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
+ jQuery.each( [ "top", "left" ], function( i, prop ) {
+ jQuery.cssHooks[ prop ] = {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ var ret = curCSS( elem, prop );
+ // if curCSS returns percentage, fallback to offset
+ return rnumnonpx.test( ret ) ? jQuery( elem ).position()[ prop ] + "px" : ret;
+ }
+ }
+ };
+ });
+ }
+
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.hidden = function( elem ) {
+ return ( elem.offsetWidth === 0 && elem.offsetHeight === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || curCSS( elem, "display" )) === "none");
+ };
+
+ jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+ };
+}
+
+// These hooks are used by animate to expand properties
+jQuery.each({
+ margin: "",
+ padding: "",
+ border: "Width"
+}, function( prefix, suffix ) {
+ jQuery.cssHooks[ prefix + suffix ] = {
+ expand: function( value ) {
+ var i,
+
+ // assumes a single number if not a string
+ parts = typeof value === "string" ? value.split(" ") : [ value ],
+ expanded = {};
+
+ for ( i = 0; i < 4; i++ ) {
+ expanded[ prefix + cssExpand[ i ] + suffix ] =
+ parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+ }
+
+ return expanded;
+ }
+ };
+
+ if ( !rmargin.test( prefix ) ) {
+ jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+ }
+});
+var r20 = /%20/g,
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+ rselectTextarea = /^(?:select|textarea)/i;
+
+jQuery.fn.extend({
+ serialize: function() {
+ return jQuery.param( this.serializeArray() );
+ },
+ serializeArray: function() {
+ return this.map(function(){
+ return this.elements ? jQuery.makeArray( this.elements ) : this;
+ })
+ .filter(function(){
+ return this.name && !this.disabled &&
+ ( this.checked || rselectTextarea.test( this.nodeName ) ||
+ rinput.test( this.type ) );
+ })
+ .map(function( i, elem ){
+ var val = jQuery( this ).val();
+
+ return val == null ?
+ null :
+ jQuery.isArray( val ) ?
+ jQuery.map( val, function( val, i ){
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }) :
+ { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }).get();
+ }
+});
+
+//Serialize an array of form elements or a set of
+//key/values into a query string
+jQuery.param = function( a, traditional ) {
+ var prefix,
+ s = [],
+ add = function( key, value ) {
+ // If value is a function, invoke it and return its value
+ value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
+ s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+ };
+
+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ });
+
+ } else {
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( prefix in a ) {
+ buildParams( prefix, a[ prefix ], traditional, add );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join( "&" ).replace( r20, "+" );
+};
+
+function buildParams( prefix, obj, traditional, add ) {
+ var name;
+
+ if ( jQuery.isArray( obj ) ) {
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || rbracket.test( prefix ) ) {
+ // Treat each array item as a scalar.
+ add( prefix, v );
+
+ } else {
+ // If array item is non-scalar (array or object), encode its
+ // numeric index to resolve deserialization ambiguity issues.
+ // Note that rack (as of 1.0.0) can't currently deserialize
+ // nested arrays properly, and attempting to do so may cause
+ // a server error. Possible fixes are to modify rack's
+ // deserialization algorithm or to provide an option or flag
+ // to force array serialization to be shallow.
+ buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+ }
+ });
+
+ } else if ( !traditional && jQuery.type( obj ) === "object" ) {
+ // Serialize object item.
+ for ( name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
+
+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+}
+var
+ // Document location
+ ajaxLocParts,
+ ajaxLocation,
+
+ rhash = /#.*$/,
+ rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+ // #7653, #8125, #8152: local protocol detection
+ rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
+ rnoContent = /^(?:GET|HEAD)$/,
+ rprotocol = /^\/\//,
+ rquery = /\?/,
+ rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+ rts = /([?&])_=[^&]*/,
+ rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,
+
+ // Keep a copy of the old load method
+ _load = jQuery.fn.load,
+
+ /* Prefilters
+ * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+ * 2) These are called:
+ * - BEFORE asking for a transport
+ * - AFTER param serialization (s.data is a string if s.processData is true)
+ * 3) key is the dataType
+ * 4) the catchall symbol "*" can be used
+ * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+ */
+ prefilters = {},
+
+ /* Transports bindings
+ * 1) key is the dataType
+ * 2) the catchall symbol "*" can be used
+ * 3) selection will start with transport dataType and THEN go to "*" if needed
+ */
+ transports = {},
+
+ // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+ allTypes = ["*/"] + ["*"];
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+ ajaxLocation = location.href;
+} catch( e ) {
+ // Use the href attribute of an A element
+ // since IE will modify it given document.location
+ ajaxLocation = document.createElement( "a" );
+ ajaxLocation.href = "";
+ ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+ // dataTypeExpression is optional and defaults to "*"
+ return function( dataTypeExpression, func ) {
+
+ if ( typeof dataTypeExpression !== "string" ) {
+ func = dataTypeExpression;
+ dataTypeExpression = "*";
+ }
+
+ var dataType, list, placeBefore,
+ dataTypes = dataTypeExpression.toLowerCase().split( core_rspace ),
+ i = 0,
+ length = dataTypes.length;
+
+ if ( jQuery.isFunction( func ) ) {
+ // For each dataType in the dataTypeExpression
+ for ( ; i < length; i++ ) {
+ dataType = dataTypes[ i ];
+ // We control if we're asked to add before
+ // any existing element
+ placeBefore = /^\+/.test( dataType );
+ if ( placeBefore ) {
+ dataType = dataType.substr( 1 ) || "*";
+ }
+ list = structure[ dataType ] = structure[ dataType ] || [];
+ // then we add to the structure accordingly
+ list[ placeBefore ? "unshift" : "push" ]( func );
+ }
+ }
+ };
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
+ dataType /* internal */, inspected /* internal */ ) {
+
+ dataType = dataType || options.dataTypes[ 0 ];
+ inspected = inspected || {};
+
+ inspected[ dataType ] = true;
+
+ var selection,
+ list = structure[ dataType ],
+ i = 0,
+ length = list ? list.length : 0,
+ executeOnly = ( structure === prefilters );
+
+ for ( ; i < length && ( executeOnly || !selection ); i++ ) {
+ selection = list[ i ]( options, originalOptions, jqXHR );
+ // If we got redirected to another dataType
+ // we try there if executing only and not done already
+ if ( typeof selection === "string" ) {
+ if ( !executeOnly || inspected[ selection ] ) {
+ selection = undefined;
+ } else {
+ options.dataTypes.unshift( selection );
+ selection = inspectPrefiltersOrTransports(
+ structure, options, originalOptions, jqXHR, selection, inspected );
+ }
+ }
+ }
+ // If we're only executing or nothing was selected
+ // we try the catchall dataType if not done already
+ if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
+ selection = inspectPrefiltersOrTransports(
+ structure, options, originalOptions, jqXHR, "*", inspected );
+ }
+ // unnecessary when only executing (prefilters)
+ // but it'll be ignored by the caller in that case
+ return selection;
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+ var key, deep,
+ flatOptions = jQuery.ajaxSettings.flatOptions || {};
+ for ( key in src ) {
+ if ( src[ key ] !== undefined ) {
+ ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+ }
+ }
+ if ( deep ) {
+ jQuery.extend( true, target, deep );
+ }
+}
+
+jQuery.fn.load = function( url, params, callback ) {
+ if ( typeof url !== "string" && _load ) {
+ return _load.apply( this, arguments );
+ }
+
+ // Don't do a request if no elements are being requested
+ if ( !this.length ) {
+ return this;
+ }
+
+ var selector, type, response,
+ self = this,
+ off = url.indexOf(" ");
+
+ if ( off >= 0 ) {
+ selector = url.slice( off, url.length );
+ url = url.slice( 0, off );
+ }
+
+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
+
+ // We assume that it's the callback
+ callback = params;
+ params = undefined;
+
+ // Otherwise, build a param string
+ } else if ( params && typeof params === "object" ) {
+ type = "POST";
+ }
+
+ // Request the remote document
+ jQuery.ajax({
+ url: url,
+
+ // if "type" variable is undefined, then "GET" method will be used
+ type: type,
+ dataType: "html",
+ data: params,
+ complete: function( jqXHR, status ) {
+ if ( callback ) {
+ self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
+ }
+ }
+ }).done(function( responseText ) {
+
+ // Save response for use in complete callback
+ response = arguments;
+
+ // See if a selector was specified
+ self.html( selector ?
+
+ // Create a dummy div to hold the results
+ jQuery("<div>")
+
+ // inject the contents of the document in, removing the scripts
+ // to avoid any 'Permission Denied' errors in IE
+ .append( responseText.replace( rscript, "" ) )
+
+ // Locate the specified elements
+ .find( selector ) :
+
+ // If not, just inject the full result
+ responseText );
+
+ });
+
+ return this;
+};
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
+ jQuery.fn[ o ] = function( f ){
+ return this.on( o, f );
+ };
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+ // shift arguments if data argument was omitted
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
+
+ return jQuery.ajax({
+ type: method,
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ };
+});
+
+jQuery.extend({
+
+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
+ },
+
+ // Creates a full fledged settings object into target
+ // with both ajaxSettings and settings fields.
+ // If target is omitted, writes into ajaxSettings.
+ ajaxSetup: function( target, settings ) {
+ if ( settings ) {
+ // Building a settings object
+ ajaxExtend( target, jQuery.ajaxSettings );
+ } else {
+ // Extending ajaxSettings
+ settings = target;
+ target = jQuery.ajaxSettings;
+ }
+ ajaxExtend( target, settings );
+ return target;
+ },
+
+ ajaxSettings: {
+ url: ajaxLocation,
+ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+ global: true,
+ type: "GET",
+ contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+ processData: true,
+ async: true,
+ /*
+ timeout: 0,
+ data: null,
+ dataType: null,
+ username: null,
+ password: null,
+ cache: null,
+ throws: false,
+ traditional: false,
+ headers: {},
+ */
+
+ accepts: {
+ xml: "application/xml, text/xml",
+ html: "text/html",
+ text: "text/plain",
+ json: "application/json, text/javascript",
+ "*": allTypes
+ },
+
+ contents: {
+ xml: /xml/,
+ html: /html/,
+ json: /json/
+ },
+
+ responseFields: {
+ xml: "responseXML",
+ text: "responseText"
+ },
+
+ // List of data converters
+ // 1) key format is "source_type destination_type" (a single space in-between)
+ // 2) the catchall symbol "*" can be used for source_type
+ converters: {
+
+ // Convert anything to text
+ "* text": window.String,
+
+ // Text to html (true = no transformation)
+ "text html": true,
+
+ // Evaluate text as a json expression
+ "text json": jQuery.parseJSON,
+
+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ },
+
+ // For options that shouldn't be deep extended:
+ // you can add your own custom options here if
+ // and when you create one that shouldn't be
+ // deep extended (see ajaxExtend)
+ flatOptions: {
+ context: true,
+ url: true
+ }
+ },
+
+ ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+ ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+ // Main method
+ ajax: function( url, options ) {
+
+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
+
+ // Force options to be an object
+ options = options || {};
+
+ var // ifModified key
+ ifModifiedKey,
+ // Response headers
+ responseHeadersString,
+ responseHeaders,
+ // transport
+ transport,
+ // timeout handle
+ timeoutTimer,
+ // Cross-domain detection vars
+ parts,
+ // To know if global events are to be dispatched
+ fireGlobals,
+ // Loop variable
+ i,
+ // Create the final options object
+ s = jQuery.ajaxSetup( {}, options ),
+ // Callbacks context
+ callbackContext = s.context || s,
+ // Context for global events
+ // It's the callbackContext if one was provided in the options
+ // and if it's a DOM node or a jQuery collection
+ globalEventContext = callbackContext !== s &&
+ ( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+ jQuery( callbackContext ) : jQuery.event,
+ // Deferreds
+ deferred = jQuery.Deferred(),
+ completeDeferred = jQuery.Callbacks( "once memory" ),
+ // Status-dependent callbacks
+ statusCode = s.statusCode || {},
+ // Headers (they are sent all at once)
+ requestHeaders = {},
+ requestHeadersNames = {},
+ // The jqXHR state
+ state = 0,
+ // Default abort message
+ strAbort = "canceled",
+ // Fake xhr
+ jqXHR = {
+
+ readyState: 0,
+
+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ if ( !state ) {
+ var lname = name.toLowerCase();
+ name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+ requestHeaders[ name ] = value;
+ }
+ return this;
+ },
+
+ // Raw string
+ getAllResponseHeaders: function() {
+ return state === 2 ? responseHeadersString : null;
+ },
+
+ // Builds headers hashtable if needed
+ getResponseHeader: function( key ) {
+ var match;
+ if ( state === 2 ) {
+ if ( !responseHeaders ) {
+ responseHeaders = {};
+ while( ( match = rheaders.exec( responseHeadersString ) ) ) {
+ responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+ }
+ }
+ match = responseHeaders[ key.toLowerCase() ];
+ }
+ return match === undefined ? null : match;
+ },
+
+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( !state ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
+
+ // Cancel the request
+ abort: function( statusText ) {
+ statusText = statusText || strAbort;
+ if ( transport ) {
+ transport.abort( statusText );
+ }
+ done( 0, statusText );
+ return this;
+ }
+ };
+
+ // Callback for when everything is done
+ // It is defined here because jslint complains if it is declared
+ // at the end of the function (which would be more logical and readable)
+ function done( status, nativeStatusText, responses, headers ) {
+ var isSuccess, success, error, response, modified,
+ statusText = nativeStatusText;
+
+ // Called once
+ if ( state === 2 ) {
+ return;
+ }
+
+ // State is "done" now
+ state = 2;
+
+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ clearTimeout( timeoutTimer );
+ }
+
+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
+
+ // Cache response headers
+ responseHeadersString = headers || "";
+
+ // Set readyState
+ jqXHR.readyState = status > 0 ? 4 : 0;
+
+ // Get response data
+ if ( responses ) {
+ response = ajaxHandleResponses( s, jqXHR, responses );
+ }
+
+ // If successful, handle type chaining
+ if ( status >= 200 && status < 300 || status === 304 ) {
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+
+ modified = jqXHR.getResponseHeader("Last-Modified");
+ if ( modified ) {
+ jQuery.lastModified[ ifModifiedKey ] = modified;
+ }
+ modified = jqXHR.getResponseHeader("Etag");
+ if ( modified ) {
+ jQuery.etag[ ifModifiedKey ] = modified;
+ }
+ }
+
+ // If not modified
+ if ( status === 304 ) {
+
+ statusText = "notmodified";
+ isSuccess = true;
+
+ // If we have data
+ } else {
+
+ isSuccess = ajaxConvert( s, response );
+ statusText = isSuccess.state;
+ success = isSuccess.data;
+ error = isSuccess.error;
+ isSuccess = !error;
+ }
+ } else {
+ // We extract error from statusText
+ // then normalize statusText and status for non-aborts
+ error = statusText;
+ if ( !statusText || status ) {
+ statusText = "error";
+ if ( status < 0 ) {
+ status = 0;
+ }
+ }
+ }
+
+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
+
+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
+
+ // Complete
+ completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger( "ajaxStop" );
+ }
+ }
+ }
+
+ // Attach deferreds
+ deferred.promise( jqXHR );
+ jqXHR.success = jqXHR.done;
+ jqXHR.error = jqXHR.fail;
+ jqXHR.complete = completeDeferred.add;
+
+ // Status-dependent callbacks
+ jqXHR.statusCode = function( map ) {
+ if ( map ) {
+ var tmp;
+ if ( state < 2 ) {
+ for ( tmp in map ) {
+ statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
+ }
+ } else {
+ tmp = map[ jqXHR.status ];
+ jqXHR.always( tmp );
+ }
+ }
+ return this;
+ };
+
+ // Remove hash character (#7531: and string promotion)
+ // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+ // We also use the url parameter if available
+ s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+ // Extract dataTypes list
+ s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( core_rspace );
+
+ // A cross-domain request is in order when we have a protocol:host:port mismatch
+ if ( s.crossDomain == null ) {
+ parts = rurl.exec( s.url.toLowerCase() );
+ s.crossDomain = !!( parts &&
+ ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
+ ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+ ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
+ );
+ }
+
+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+ // If request was aborted inside a prefilter, stop there
+ if ( state === 2 ) {
+ return jqXHR;
+ }
+
+ // We can fire global events as of now if asked to
+ fireGlobals = s.global;
+
+ // Uppercase the type
+ s.type = s.type.toUpperCase();
+
+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
+
+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger( "ajaxStart" );
+ }
+
+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
+
+ // If data is available, append data to url
+ if ( s.data ) {
+ s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
+ // #9682: remove data so that it's not used in an eventual retry
+ delete s.data;
+ }
+
+ // Get ifModifiedKey before adding the anti-cache parameter
+ ifModifiedKey = s.url;
+
+ // Add anti-cache in url if needed
+ if ( s.cache === false ) {
+
+ var ts = jQuery.now(),
+ // try replacing _= if it is there
+ ret = s.url.replace( rts, "$1_=" + ts );
+
+ // if nothing was replaced, add timestamp to the end
+ s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
+ }
+ }
+
+ // Set the correct header, if data is being sent
+ if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+ jqXHR.setRequestHeader( "Content-Type", s.contentType );
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ ifModifiedKey = ifModifiedKey || s.url;
+ if ( jQuery.lastModified[ ifModifiedKey ] ) {
+ jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
+ }
+ if ( jQuery.etag[ ifModifiedKey ] ) {
+ jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
+ }
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ jqXHR.setRequestHeader(
+ "Accept",
+ s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+ s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+ s.accepts[ "*" ]
+ );
+
+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+ // Abort if not done already and return
+ return jqXHR.abort();
+
+ }
+
+ // aborting is no longer a cancellation
+ strAbort = "abort";
+
+ // Install callbacks on deferreds
+ for ( i in { success: 1, error: 1, complete: 1 } ) {
+ jqXHR[ i ]( s[ i ] );
+ }
+
+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
+ // Send global event
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+ }
+ // Timeout
+ if ( s.async && s.timeout > 0 ) {
+ timeoutTimer = setTimeout( function(){
+ jqXHR.abort( "timeout" );
+ }, s.timeout );
+ }
+
+ try {
+ state = 1;
+ transport.send( requestHeaders, done );
+ } catch (e) {
+ // Propagate exception as error if not done
+ if ( state < 2 ) {
+ done( -1, e );
+ // Simply rethrow otherwise
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ return jqXHR;
+ },
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {}
+
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+ var ct, type, finalDataType, firstDataType,
+ contents = s.contents,
+ dataTypes = s.dataTypes,
+ responseFields = s.responseFields;
+
+ // Fill responseXXX fields
+ for ( type in responseFields ) {
+ if ( type in responses ) {
+ jqXHR[ responseFields[type] ] = responses[ type ];
+ }
+ }
+
+ // Remove auto dataType and get content-type in the process
+ while( dataTypes[ 0 ] === "*" ) {
+ dataTypes.shift();
+ if ( ct === undefined ) {
+ ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
+ }
+ }
+
+ // Check if we're dealing with a known content-type
+ if ( ct ) {
+ for ( type in contents ) {
+ if ( contents[ type ] && contents[ type ].test( ct ) ) {
+ dataTypes.unshift( type );
+ break;
+ }
+ }
+ }
+
+ // Check to see if we have a response for the expected dataType
+ if ( dataTypes[ 0 ] in responses ) {
+ finalDataType = dataTypes[ 0 ];
+ } else {
+ // Try convertible dataTypes
+ for ( type in responses ) {
+ if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+ finalDataType = type;
+ break;
+ }
+ if ( !firstDataType ) {
+ firstDataType = type;
+ }
+ }
+ // Or just use first one
+ finalDataType = finalDataType || firstDataType;
+ }
+
+ // If we found a dataType
+ // We add the dataType to the list if needed
+ // and return the corresponding response
+ if ( finalDataType ) {
+ if ( finalDataType !== dataTypes[ 0 ] ) {
+ dataTypes.unshift( finalDataType );
+ }
+ return responses[ finalDataType ];
+ }
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+ var conv, conv2, current, tmp,
+ // Work with a copy of dataTypes in case we need to modify it for conversion
+ dataTypes = s.dataTypes.slice(),
+ prev = dataTypes[ 0 ],
+ converters = {},
+ i = 0;
+
+ // Apply the dataFilter if provided
+ if ( s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
+
+ // Create converters map with lowercased keys
+ if ( dataTypes[ 1 ] ) {
+ for ( conv in s.converters ) {
+ converters[ conv.toLowerCase() ] = s.converters[ conv ];
+ }
+ }
+
+ // Convert to each sequential dataType, tolerating list modification
+ for ( ; (current = dataTypes[++i]); ) {
+
+ // There's only work to do if current dataType is non-auto
+ if ( current !== "*" ) {
+
+ // Convert response if prev dataType is non-auto and differs from current
+ if ( prev !== "*" && prev !== current ) {
+
+ // Seek a direct converter
+ conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+ // If none found, seek a pair
+ if ( !conv ) {
+ for ( conv2 in converters ) {
+
+ // If conv2 outputs current
+ tmp = conv2.split(" ");
+ if ( tmp[ 1 ] === current ) {
+
+ // If prev can be converted to accepted input
+ conv = converters[ prev + " " + tmp[ 0 ] ] ||
+ converters[ "* " + tmp[ 0 ] ];
+ if ( conv ) {
+ // Condense equivalence converters
+ if ( conv === true ) {
+ conv = converters[ conv2 ];
+
+ // Otherwise, insert the intermediate dataType
+ } else if ( converters[ conv2 ] !== true ) {
+ current = tmp[ 0 ];
+ dataTypes.splice( i--, 0, current );
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Apply converter (if not an equivalence)
+ if ( conv !== true ) {
+
+ // Unless errors are allowed to bubble, catch and return them
+ if ( conv && s["throws"] ) {
+ response = conv( response );
+ } else {
+ try {
+ response = conv( response );
+ } catch ( e ) {
+ return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
+ }
+ }
+ }
+ }
+
+ // Update prev for next iteration
+ prev = current;
+ }
+ }
+
+ return { state: "success", data: response };
+}
+var oldCallbacks = [],
+ rquestion = /\?/,
+ rjsonp = /(=)\?(?=&|$)|\?\?/,
+ nonce = jQuery.now();
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+ jsonp: "callback",
+ jsonpCallback: function() {
+ var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
+ this[ callback ] = true;
+ return callback;
+ }
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+ var callbackName, overwritten, responseContainer,
+ data = s.data,
+ url = s.url,
+ hasCallback = s.jsonp !== false,
+ replaceInUrl = hasCallback && rjsonp.test( url ),
+ replaceInData = hasCallback && !replaceInUrl && typeof data === "string" &&
+ !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") &&
+ rjsonp.test( data );
+
+ // Handle iff the expected data type is "jsonp" or we have a parameter to set
+ if ( s.dataTypes[ 0 ] === "jsonp" || replaceInUrl || replaceInData ) {
+
+ // Get callback name, remembering preexisting value associated with it
+ callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
+ s.jsonpCallback() :
+ s.jsonpCallback;
+ overwritten = window[ callbackName ];
+
+ // Insert callback into url or form data
+ if ( replaceInUrl ) {
+ s.url = url.replace( rjsonp, "$1" + callbackName );
+ } else if ( replaceInData ) {
+ s.data = data.replace( rjsonp, "$1" + callbackName );
+ } else if ( hasCallback ) {
+ s.url += ( rquestion.test( url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+ }
+
+ // Use data converter to retrieve json after script execution
+ s.converters["script json"] = function() {
+ if ( !responseContainer ) {
+ jQuery.error( callbackName + " was not called" );
+ }
+ return responseContainer[ 0 ];
+ };
+
+ // force json dataType
+ s.dataTypes[ 0 ] = "json";
+
+ // Install callback
+ window[ callbackName ] = function() {
+ responseContainer = arguments;
+ };
+
+ // Clean-up function (fires after converters)
+ jqXHR.always(function() {
+ // Restore preexisting value
+ window[ callbackName ] = overwritten;
+
+ // Save back as free
+ if ( s[ callbackName ] ) {
+ // make sure that re-using the options doesn't screw things around
+ s.jsonpCallback = originalSettings.jsonpCallback;
+
+ // save the callback name for future use
+ oldCallbacks.push( callbackName );
+ }
+
+ // Call if it was a function and we have a response
+ if ( responseContainer && jQuery.isFunction( overwritten ) ) {
+ overwritten( responseContainer[ 0 ] );
+ }
+
+ responseContainer = overwritten = undefined;
+ });
+
+ // Delegate to script
+ return "script";
+ }
+});
+// Install script dataType
+jQuery.ajaxSetup({
+ accepts: {
+ script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+ },
+ contents: {
+ script: /javascript|ecmascript/
+ },
+ converters: {
+ "text script": function( text ) {
+ jQuery.globalEval( text );
+ return text;
+ }
+ }
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
+ }
+ if ( s.crossDomain ) {
+ s.type = "GET";
+ s.global = false;
+ }
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+ // This transport only deals with cross domain requests
+ if ( s.crossDomain ) {
+
+ var script,
+ head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
+
+ return {
+
+ send: function( _, callback ) {
+
+ script = document.createElement( "script" );
+
+ script.async = "async";
+
+ if ( s.scriptCharset ) {
+ script.charset = s.scriptCharset;
+ }
+
+ script.src = s.url;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+ if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+
+ // Remove the script
+ if ( head && script.parentNode ) {
+ head.removeChild( script );
+ }
+
+ // Dereference the script
+ script = undefined;
+
+ // Callback if not abort
+ if ( !isAbort ) {
+ callback( 200, "success" );
+ }
+ }
+ };
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709 and #4378).
+ head.insertBefore( script, head.firstChild );
+ },
+
+ abort: function() {
+ if ( script ) {
+ script.onload( 0, 1 );
+ }
+ }
+ };
+ }
+});
+var xhrCallbacks,
+ // #5280: Internet Explorer will keep connections alive if we don't abort on unload
+ xhrOnUnloadAbort = window.ActiveXObject ? function() {
+ // Abort all pending requests
+ for ( var key in xhrCallbacks ) {
+ xhrCallbacks[ key ]( 0, 1 );
+ }
+ } : false,
+ xhrId = 0;
+
+// Functions to create xhrs
+function createStandardXHR() {
+ try {
+ return new window.XMLHttpRequest();
+ } catch( e ) {}
+}
+
+function createActiveXHR() {
+ try {
+ return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+ } catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+ /* Microsoft failed to properly
+ * implement the XMLHttpRequest in IE7 (can't request local files),
+ * so we use the ActiveXObject when it is available
+ * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+ * we need a fallback.
+ */
+ function() {
+ return !this.isLocal && createStandardXHR() || createActiveXHR();
+ } :
+ // For all other browsers, use the standard XMLHttpRequest object
+ createStandardXHR;
+
+// Determine support properties
+(function( xhr ) {
+ jQuery.extend( jQuery.support, {
+ ajax: !!xhr,
+ cors: !!xhr && ( "withCredentials" in xhr )
+ });
+})( jQuery.ajaxSettings.xhr() );
+
+// Create transport if the browser can provide an xhr
+if ( jQuery.support.ajax ) {
+
+ jQuery.ajaxTransport(function( s ) {
+ // Cross domain only allowed if supported through XMLHttpRequest
+ if ( !s.crossDomain || jQuery.support.cors ) {
+
+ var callback;
+
+ return {
+ send: function( headers, complete ) {
+
+ // Get a new xhr
+ var handle, i,
+ xhr = s.xhr();
+
+ // Open the socket
+ // Passing null username, generates a login popup on Opera (#2865)
+ if ( s.username ) {
+ xhr.open( s.type, s.url, s.async, s.username, s.password );
+ } else {
+ xhr.open( s.type, s.url, s.async );
+ }
+
+ // Apply custom fields if provided
+ if ( s.xhrFields ) {
+ for ( i in s.xhrFields ) {
+ xhr[ i ] = s.xhrFields[ i ];
+ }
+ }
+
+ // Override mime type if needed
+ if ( s.mimeType && xhr.overrideMimeType ) {
+ xhr.overrideMimeType( s.mimeType );
+ }
+
+ // X-Requested-With header
+ // For cross-domain requests, seeing as conditions for a preflight are
+ // akin to a jigsaw puzzle, we simply never set it to be sure.
+ // (it can always be set on a per-request basis or even using ajaxSetup)
+ // For same-domain requests, won't change header if already provided.
+ if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+ headers[ "X-Requested-With" ] = "XMLHttpRequest";
+ }
+
+ // Need an extra try/catch for cross domain requests in Firefox 3
+ try {
+ for ( i in headers ) {
+ xhr.setRequestHeader( i, headers[ i ] );
+ }
+ } catch( _ ) {}
+
+ // Do send the request
+ // This may raise an exception which is actually
+ // handled in jQuery.ajax (so no try/catch here)
+ xhr.send( ( s.hasContent && s.data ) || null );
+
+ // Listener
+ callback = function( _, isAbort ) {
+
+ var status,
+ statusText,
+ responseHeaders,
+ responses,
+ xml;
+
+ // Firefox throws exceptions when accessing properties
+ // of an xhr when a network error occurred
+ // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+ try {
+
+ // Was never called and is aborted or complete
+ if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+ // Only called once
+ callback = undefined;
+
+ // Do not keep as active anymore
+ if ( handle ) {
+ xhr.onreadystatechange = jQuery.noop;
+ if ( xhrOnUnloadAbort ) {
+ delete xhrCallbacks[ handle ];
+ }
+ }
+
+ // If it's an abort
+ if ( isAbort ) {
+ // Abort it manually if needed
+ if ( xhr.readyState !== 4 ) {
+ xhr.abort();
+ }
+ } else {
+ status = xhr.status;
+ responseHeaders = xhr.getAllResponseHeaders();
+ responses = {};
+ xml = xhr.responseXML;
+
+ // Construct response list
+ if ( xml && xml.documentElement /* #4958 */ ) {
+ responses.xml = xml;
+ }
+
+ // When requesting binary data, IE6-9 will throw an exception
+ // on any attempt to access responseText (#11426)
+ try {
+ responses.text = xhr.responseText;
+ } catch( e ) {
+ }
+
+ // Firefox throws an exception when accessing
+ // statusText for faulty cross-domain requests
+ try {
+ statusText = xhr.statusText;
+ } catch( e ) {
+ // We normalize with Webkit giving an empty statusText
+ statusText = "";
+ }
+
+ // Filter status for non standard behaviors
+
+ // If the request is local and we have data: assume a success
+ // (success with no data won't get notified, that's the best we
+ // can do given current implementations)
+ if ( !status && s.isLocal && !s.crossDomain ) {
+ status = responses.text ? 200 : 404;
+ // IE - #1450: sometimes returns 1223 when it should be 204
+ } else if ( status === 1223 ) {
+ status = 204;
+ }
+ }
+ }
+ } catch( firefoxAccessException ) {
+ if ( !isAbort ) {
+ complete( -1, firefoxAccessException );
+ }
+ }
+
+ // Call complete if needed
+ if ( responses ) {
+ complete( status, statusText, responses, responseHeaders );
+ }
+ };
+
+ if ( !s.async ) {
+ // if we're in sync mode we fire the callback
+ callback();
+ } else if ( xhr.readyState === 4 ) {
+ // (IE6 & IE7) if it's in cache and has been
+ // retrieved directly we need to fire the callback
+ setTimeout( callback, 0 );
+ } else {
+ handle = ++xhrId;
+ if ( xhrOnUnloadAbort ) {
+ // Create the active xhrs callbacks list if needed
+ // and attach the unload handler
+ if ( !xhrCallbacks ) {
+ xhrCallbacks = {};
+ jQuery( window ).unload( xhrOnUnloadAbort );
+ }
+ // Add to list of active xhrs callbacks
+ xhrCallbacks[ handle ] = callback;
+ }
+ xhr.onreadystatechange = callback;
+ }
+ },
+
+ abort: function() {
+ if ( callback ) {
+ callback(0,1);
+ }
+ }
+ };
+ }
+ });
+}
+var fxNow, timerId,
+ rfxtypes = /^(?:toggle|show|hide)$/,
+ rfxnum = new RegExp( "^(?:([-+])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
+ rrun = /queueHooks$/,
+ animationPrefilters = [ defaultPrefilter ],
+ tweeners = {
+ "*": [function( prop, value ) {
+ var end, unit,
+ tween = this.createTween( prop, value ),
+ parts = rfxnum.exec( value ),
+ target = tween.cur(),
+ start = +target || 0,
+ scale = 1,
+ maxIterations = 20;
+
+ if ( parts ) {
+ end = +parts[2];
+ unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+
+ // We need to compute starting value
+ if ( unit !== "px" && start ) {
+ // Iteratively approximate from a nonzero starting point
+ // Prefer the current property, because this process will be trivial if it uses the same units
+ // Fallback to end or a simple constant
+ start = jQuery.css( tween.elem, prop, true ) || end || 1;
+
+ do {
+ // If previous iteration zeroed out, double until we get *something*
+ // Use a string for doubling factor so we don't accidentally see scale as unchanged below
+ scale = scale || ".5";
+
+ // Adjust and apply
+ start = start / scale;
+ jQuery.style( tween.elem, prop, start + unit );
+
+ // Update scale, tolerating zero or NaN from tween.cur()
+ // And breaking the loop if scale is unchanged or perfect, or if we've just had enough
+ } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
+ }
+
+ tween.unit = unit;
+ tween.start = start;
+ // If a +=/-= token was provided, we're doing a relative animation
+ tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end;
+ }
+ return tween;
+ }]
+ };
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+ setTimeout(function() {
+ fxNow = undefined;
+ }, 0 );
+ return ( fxNow = jQuery.now() );
+}
+
+function createTweens( animation, props ) {
+ jQuery.each( props, function( prop, value ) {
+ var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
+ index = 0,
+ length = collection.length;
+ for ( ; index < length; index++ ) {
+ if ( collection[ index ].call( animation, prop, value ) ) {
+
+ // we're done with this property
+ return;
+ }
+ }
+ });
+}
+
+function Animation( elem, properties, options ) {
+ var result,
+ index = 0,
+ tweenerIndex = 0,
+ length = animationPrefilters.length,
+ deferred = jQuery.Deferred().always( function() {
+ // don't match elem in the :animated selector
+ delete tick.elem;
+ }),
+ tick = function() {
+ var currentTime = fxNow || createFxNow(),
+ remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+ // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
+ temp = remaining / animation.duration || 0,
+ percent = 1 - temp,
+ index = 0,
+ length = animation.tweens.length;
+
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( percent );
+ }
+
+ deferred.notifyWith( elem, [ animation, percent, remaining ]);
+
+ if ( percent < 1 && length ) {
+ return remaining;
+ } else {
+ deferred.resolveWith( elem, [ animation ] );
+ return false;
+ }
+ },
+ animation = deferred.promise({
+ elem: elem,
+ props: jQuery.extend( {}, properties ),
+ opts: jQuery.extend( true, { specialEasing: {} }, options ),
+ originalProperties: properties,
+ originalOptions: options,
+ startTime: fxNow || createFxNow(),
+ duration: options.duration,
+ tweens: [],
+ createTween: function( prop, end, easing ) {
+ var tween = jQuery.Tween( elem, animation.opts, prop, end,
+ animation.opts.specialEasing[ prop ] || animation.opts.easing );
+ animation.tweens.push( tween );
+ return tween;
+ },
+ stop: function( gotoEnd ) {
+ var index = 0,
+ // if we are going to the end, we want to run all the tweens
+ // otherwise we skip this part
+ length = gotoEnd ? animation.tweens.length : 0;
+
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( 1 );
+ }
+
+ // resolve when we played the last frame
+ // otherwise, reject
+ if ( gotoEnd ) {
+ deferred.resolveWith( elem, [ animation, gotoEnd ] );
+ } else {
+ deferred.rejectWith( elem, [ animation, gotoEnd ] );
+ }
+ return this;
+ }
+ }),
+ props = animation.props;
+
+ propFilter( props, animation.opts.specialEasing );
+
+ for ( ; index < length ; index++ ) {
+ result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
+ if ( result ) {
+ return result;
+ }
+ }
+
+ createTweens( animation, props );
+
+ if ( jQuery.isFunction( animation.opts.start ) ) {
+ animation.opts.start.call( elem, animation );
+ }
+
+ jQuery.fx.timer(
+ jQuery.extend( tick, {
+ anim: animation,
+ queue: animation.opts.queue,
+ elem: elem
+ })
+ );
+
+ // attach callbacks from options
+ return animation.progress( animation.opts.progress )
+ .done( animation.opts.done, animation.opts.complete )
+ .fail( animation.opts.fail )
+ .always( animation.opts.always );
+}
+
+function propFilter( props, specialEasing ) {
+ var index, name, easing, value, hooks;
+
+ // camelCase, specialEasing and expand cssHook pass
+ for ( index in props ) {
+ name = jQuery.camelCase( index );
+ easing = specialEasing[ name ];
+ value = props[ index ];
+ if ( jQuery.isArray( value ) ) {
+ easing = value[ 1 ];
+ value = props[ index ] = value[ 0 ];
+ }
+
+ if ( index !== name ) {
+ props[ name ] = value;
+ delete props[ index ];
+ }
+
+ hooks = jQuery.cssHooks[ name ];
+ if ( hooks && "expand" in hooks ) {
+ value = hooks.expand( value );
+ delete props[ name ];
+
+ // not quite $.extend, this wont overwrite keys already present.
+ // also - reusing 'index' from above because we have the correct "name"
+ for ( index in value ) {
+ if ( !( index in props ) ) {
+ props[ index ] = value[ index ];
+ specialEasing[ index ] = easing;
+ }
+ }
+ } else {
+ specialEasing[ name ] = easing;
+ }
+ }
+}
+
+jQuery.Animation = jQuery.extend( Animation, {
+
+ tweener: function( props, callback ) {
+ if ( jQuery.isFunction( props ) ) {
+ callback = props;
+ props = [ "*" ];
+ } else {
+ props = props.split(" ");
+ }
+
+ var prop,
+ index = 0,
+ length = props.length;
+
+ for ( ; index < length ; index++ ) {
+ prop = props[ index ];
+ tweeners[ prop ] = tweeners[ prop ] || [];
+ tweeners[ prop ].unshift( callback );
+ }
+ },
+
+ prefilter: function( callback, prepend ) {
+ if ( prepend ) {
+ animationPrefilters.unshift( callback );
+ } else {
+ animationPrefilters.push( callback );
+ }
+ }
+});
+
+function defaultPrefilter( elem, props, opts ) {
+ var index, prop, value, length, dataShow, toggle, tween, hooks, oldfire,
+ anim = this,
+ style = elem.style,
+ orig = {},
+ handled = [],
+ hidden = elem.nodeType && isHidden( elem );
+
+ // handle queue: false promises
+ if ( !opts.queue ) {
+ hooks = jQuery._queueHooks( elem, "fx" );
+ if ( hooks.unqueued == null ) {
+ hooks.unqueued = 0;
+ oldfire = hooks.empty.fire;
+ hooks.empty.fire = function() {
+ if ( !hooks.unqueued ) {
+ oldfire();
+ }
+ };
+ }
+ hooks.unqueued++;
+
+ anim.always(function() {
+ // doing this makes sure that the complete handler will be called
+ // before this completes
+ anim.always(function() {
+ hooks.unqueued--;
+ if ( !jQuery.queue( elem, "fx" ).length ) {
+ hooks.empty.fire();
+ }
+ });
+ });
+ }
+
+ // height/width overflow pass
+ if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
+ // Make sure that nothing sneaks out
+ // Record all 3 overflow attributes because IE does not
+ // change the overflow attribute when overflowX and
+ // overflowY are set to the same value
+ opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+ // Set display property to inline-block for height/width
+ // animations on inline elements that are having width/height animated
+ if ( jQuery.css( elem, "display" ) === "inline" &&
+ jQuery.css( elem, "float" ) === "none" ) {
+
+ // inline-level elements accept inline-block;
+ // block-level elements need to be inline with layout
+ if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) {
+ style.display = "inline-block";
+
+ } else {
+ style.zoom = 1;
+ }
+ }
+ }
+
+ if ( opts.overflow ) {
+ style.overflow = "hidden";
+ if ( !jQuery.support.shrinkWrapBlocks ) {
+ anim.done(function() {
+ style.overflow = opts.overflow[ 0 ];
+ style.overflowX = opts.overflow[ 1 ];
+ style.overflowY = opts.overflow[ 2 ];
+ });
+ }
+ }
+
+
+ // show/hide pass
+ for ( index in props ) {
+ value = props[ index ];
+ if ( rfxtypes.exec( value ) ) {
+ delete props[ index ];
+ toggle = toggle || value === "toggle";
+ if ( value === ( hidden ? "hide" : "show" ) ) {
+ continue;
+ }
+ handled.push( index );
+ }
+ }
+
+ length = handled.length;
+ if ( length ) {
+ dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} );
+ if ( "hidden" in dataShow ) {
+ hidden = dataShow.hidden;
+ }
+
+ // store state if its toggle - enables .stop().toggle() to "reverse"
+ if ( toggle ) {
+ dataShow.hidden = !hidden;
+ }
+ if ( hidden ) {
+ jQuery( elem ).show();
+ } else {
+ anim.done(function() {
+ jQuery( elem ).hide();
+ });
+ }
+ anim.done(function() {
+ var prop;
+ jQuery.removeData( elem, "fxshow", true );
+ for ( prop in orig ) {
+ jQuery.style( elem, prop, orig[ prop ] );
+ }
+ });
+ for ( index = 0 ; index < length ; index++ ) {
+ prop = handled[ index ];
+ tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 );
+ orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );
+
+ if ( !( prop in dataShow ) ) {
+ dataShow[ prop ] = tween.start;
+ if ( hidden ) {
+ tween.end = tween.start;
+ tween.start = prop === "width" || prop === "height" ? 1 : 0;
+ }
+ }
+ }
+ }
+}
+
+function Tween( elem, options, prop, end, easing ) {
+ return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+Tween.prototype = {
+ constructor: Tween,
+ init: function( elem, options, prop, end, easing, unit ) {
+ this.elem = elem;
+ this.prop = prop;
+ this.easing = easing || "swing";
+ this.options = options;
+ this.start = this.now = this.cur();
+ this.end = end;
+ this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+ },
+ cur: function() {
+ var hooks = Tween.propHooks[ this.prop ];
+
+ return hooks && hooks.get ?
+ hooks.get( this ) :
+ Tween.propHooks._default.get( this );
+ },
+ run: function( percent ) {
+ var eased,
+ hooks = Tween.propHooks[ this.prop ];
+
+ if ( this.options.duration ) {
+ this.pos = eased = jQuery.easing[ this.easing ](
+ percent, this.options.duration * percent, 0, 1, this.options.duration
+ );
+ } else {
+ this.pos = eased = percent;
+ }
+ this.now = ( this.end - this.start ) * eased + this.start;
+
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ if ( hooks && hooks.set ) {
+ hooks.set( this );
+ } else {
+ Tween.propHooks._default.set( this );
+ }
+ return this;
+ }
+};
+
+Tween.prototype.init.prototype = Tween.prototype;
+
+Tween.propHooks = {
+ _default: {
+ get: function( tween ) {
+ var result;
+
+ if ( tween.elem[ tween.prop ] != null &&
+ (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
+ return tween.elem[ tween.prop ];
+ }
+
+ // passing any value as a 4th parameter to .css will automatically
+ // attempt a parseFloat and fallback to a string if the parse fails
+ // so, simple values such as "10px" are parsed to Float.
+ // complex values such as "rotate(1rad)" are returned as is.
+ result = jQuery.css( tween.elem, tween.prop, false, "" );
+ // Empty strings, null, undefined and "auto" are converted to 0.
+ return !result || result === "auto" ? 0 : result;
+ },
+ set: function( tween ) {
+ // use step hook for back compat - use cssHook if its there - use .style if its
+ // available and use plain properties where available
+ if ( jQuery.fx.step[ tween.prop ] ) {
+ jQuery.fx.step[ tween.prop ]( tween );
+ } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
+ jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+ } else {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+ }
+};
+
+// Remove in 2.0 - this supports IE8's panic based approach
+// to setting things on disconnected nodes
+
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+ set: function( tween ) {
+ if ( tween.elem.nodeType && tween.elem.parentNode ) {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+};
+
+jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
+ var cssFn = jQuery.fn[ name ];
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return speed == null || typeof speed === "boolean" ||
+ // special check for .toggle( handler, handler, ... )
+ ( !i && jQuery.isFunction( speed ) && jQuery.isFunction( easing ) ) ?
+ cssFn.apply( this, arguments ) :
+ this.animate( genFx( name, true ), speed, easing, callback );
+ };
+});
+
+jQuery.fn.extend({
+ fadeTo: function( speed, to, easing, callback ) {
+
+ // show any hidden elements after setting opacity to 0
+ return this.filter( isHidden ).css( "opacity", 0 ).show()
+
+ // animate to the value specified
+ .end().animate({ opacity: to }, speed, easing, callback );
+ },
+ animate: function( prop, speed, easing, callback ) {
+ var empty = jQuery.isEmptyObject( prop ),
+ optall = jQuery.speed( speed, easing, callback ),
+ doAnimation = function() {
+ // Operate on a copy of prop so per-property easing won't be lost
+ var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+
+ // Empty animations resolve immediately
+ if ( empty ) {
+ anim.stop( true );
+ }
+ };
+
+ return empty || optall.queue === false ?
+ this.each( doAnimation ) :
+ this.queue( optall.queue, doAnimation );
+ },
+ stop: function( type, clearQueue, gotoEnd ) {
+ var stopQueue = function( hooks ) {
+ var stop = hooks.stop;
+ delete hooks.stop;
+ stop( gotoEnd );
+ };
+
+ if ( typeof type !== "string" ) {
+ gotoEnd = clearQueue;
+ clearQueue = type;
+ type = undefined;
+ }
+ if ( clearQueue && type !== false ) {
+ this.queue( type || "fx", [] );
+ }
+
+ return this.each(function() {
+ var dequeue = true,
+ index = type != null && type + "queueHooks",
+ timers = jQuery.timers,
+ data = jQuery._data( this );
+
+ if ( index ) {
+ if ( data[ index ] && data[ index ].stop ) {
+ stopQueue( data[ index ] );
+ }
+ } else {
+ for ( index in data ) {
+ if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+ stopQueue( data[ index ] );
+ }
+ }
+ }
+
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+ timers[ index ].anim.stop( gotoEnd );
+ dequeue = false;
+ timers.splice( index, 1 );
+ }
+ }
+
+ // start the next in the queue if the last step wasn't forced
+ // timers currently will call their complete callbacks, which will dequeue
+ // but only if they were gotoEnd
+ if ( dequeue || !gotoEnd ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ }
+});
+
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+ var which,
+ attrs = { height: type },
+ i = 0;
+
+ // if we include width, step value is 1 to do all cssExpand values,
+ // if we don't include width, step value is 2 to skip over Left and Right
+ includeWidth = includeWidth? 1 : 0;
+ for( ; i < 4 ; i += 2 - includeWidth ) {
+ which = cssExpand[ i ];
+ attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+ }
+
+ if ( includeWidth ) {
+ attrs.opacity = attrs.width = type;
+ }
+
+ return attrs;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx("show"),
+ slideUp: genFx("hide"),
+ slideToggle: genFx("toggle"),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" },
+ fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return this.animate( props, speed, easing, callback );
+ };
+});
+
+jQuery.speed = function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+ };
+
+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+ // normalize opt.queue - true/undefined/null -> "fx"
+ if ( opt.queue == null || opt.queue === true ) {
+ opt.queue = "fx";
+ }
+
+ // Queueing
+ opt.old = opt.complete;
+
+ opt.complete = function() {
+ if ( jQuery.isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
+
+ if ( opt.queue ) {
+ jQuery.dequeue( this, opt.queue );
+ }
+ };
+
+ return opt;
+};
+
+jQuery.easing = {
+ linear: function( p ) {
+ return p;
+ },
+ swing: function( p ) {
+ return 0.5 - Math.cos( p*Math.PI ) / 2;
+ }
+};
+
+jQuery.timers = [];
+jQuery.fx = Tween.prototype.init;
+jQuery.fx.tick = function() {
+ var timer,
+ timers = jQuery.timers,
+ i = 0;
+
+ fxNow = jQuery.now();
+
+ for ( ; i < timers.length; i++ ) {
+ timer = timers[ i ];
+ // Checks the timer has not already been removed
+ if ( !timer() && timers[ i ] === timer ) {
+ timers.splice( i--, 1 );
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+ fxNow = undefined;
+};
+
+jQuery.fx.timer = function( timer ) {
+ if ( timer() && jQuery.timers.push( timer ) && !timerId ) {
+ timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
+ }
+};
+
+jQuery.fx.interval = 13;
+
+jQuery.fx.stop = function() {
+ clearInterval( timerId );
+ timerId = null;
+};
+
+jQuery.fx.speeds = {
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+};
+
+// Back Compat <1.8 extension point
+jQuery.fx.step = {};
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.animated = function( elem ) {
+ return jQuery.grep(jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ }).length;
+ };
+}
+var rroot = /^(?:body|html)$/i;
+
+jQuery.fn.offset = function( options ) {
+ if ( arguments.length ) {
+ return options === undefined ?
+ this :
+ this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ var docElem, body, win, clientTop, clientLeft, scrollTop, scrollLeft,
+ box = { top: 0, left: 0 },
+ elem = this[ 0 ],
+ doc = elem && elem.ownerDocument;
+
+ if ( !doc ) {
+ return;
+ }
+
+ if ( (body = doc.body) === elem ) {
+ return jQuery.offset.bodyOffset( elem );
+ }
+
+ docElem = doc.documentElement;
+
+ // Make sure it's not a disconnected DOM node
+ if ( !jQuery.contains( docElem, elem ) ) {
+ return box;
+ }
+
+ // If we don't have gBCR, just use 0,0 rather than error
+ // BlackBerry 5, iOS 3 (original iPhone)
+ if ( typeof elem.getBoundingClientRect !== "undefined" ) {
+ box = elem.getBoundingClientRect();
+ }
+ win = getWindow( doc );
+ clientTop = docElem.clientTop || body.clientTop || 0;
+ clientLeft = docElem.clientLeft || body.clientLeft || 0;
+ scrollTop = win.pageYOffset || docElem.scrollTop;
+ scrollLeft = win.pageXOffset || docElem.scrollLeft;
+ return {
+ top: box.top + scrollTop - clientTop,
+ left: box.left + scrollLeft - clientLeft
+ };
+};
+
+jQuery.offset = {
+
+ bodyOffset: function( body ) {
+ var top = body.offsetTop,
+ left = body.offsetLeft;
+
+ if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) {
+ top += parseFloat( jQuery.css(body, "marginTop") ) || 0;
+ left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
+ }
+
+ return { top: top, left: left };
+ },
+
+ setOffset: function( elem, options, i ) {
+ var position = jQuery.css( elem, "position" );
+
+ // set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
+
+ var curElem = jQuery( elem ),
+ curOffset = curElem.offset(),
+ curCSSTop = jQuery.css( elem, "top" ),
+ curCSSLeft = jQuery.css( elem, "left" ),
+ calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
+ props = {}, curPosition = {}, curTop, curLeft;
+
+ // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+ if ( calculatePosition ) {
+ curPosition = curElem.position();
+ curTop = curPosition.top;
+ curLeft = curPosition.left;
+ } else {
+ curTop = parseFloat( curCSSTop ) || 0;
+ curLeft = parseFloat( curCSSLeft ) || 0;
+ }
+
+ if ( jQuery.isFunction( options ) ) {
+ options = options.call( elem, i, curOffset );
+ }
+
+ if ( options.top != null ) {
+ props.top = ( options.top - curOffset.top ) + curTop;
+ }
+ if ( options.left != null ) {
+ props.left = ( options.left - curOffset.left ) + curLeft;
+ }
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ }
+};
+
+
+jQuery.fn.extend({
+
+ position: function() {
+ if ( !this[0] ) {
+ return;
+ }
+
+ var elem = this[0],
+
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent(),
+
+ // Get correct offsets
+ offset = this.offset(),
+ parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+ // Subtract element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
+ offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
+
+ // Add offsetParent borders
+ parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
+ parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
+
+ // Subtract the two offsets
+ return {
+ top: offset.top - parentOffset.top,
+ left: offset.left - parentOffset.left
+ };
+ },
+
+ offsetParent: function() {
+ return this.map(function() {
+ var offsetParent = this.offsetParent || document.body;
+ while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent || document.body;
+ });
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
+ var top = /Y/.test( prop );
+
+ jQuery.fn[ method ] = function( val ) {
+ return jQuery.access( this, function( elem, method, val ) {
+ var win = getWindow( elem );
+
+ if ( val === undefined ) {
+ return win ? (prop in win) ? win[ prop ] :
+ win.document.documentElement[ method ] :
+ elem[ method ];
+ }
+
+ if ( win ) {
+ win.scrollTo(
+ !top ? val : jQuery( win ).scrollLeft(),
+ top ? val : jQuery( win ).scrollTop()
+ );
+
+ } else {
+ elem[ method ] = val;
+ }
+ }, method, val, arguments.length, null );
+ };
+});
+
+function getWindow( elem ) {
+ return jQuery.isWindow( elem ) ?
+ elem :
+ elem.nodeType === 9 ?
+ elem.defaultView || elem.parentWindow :
+ false;
+}
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+ jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
+ // margin is only for outerHeight, outerWidth
+ jQuery.fn[ funcName ] = function( margin, value ) {
+ var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+ extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+ return jQuery.access( this, function( elem, type, value ) {
+ var doc;
+
+ if ( jQuery.isWindow( elem ) ) {
+ // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+ // isn't a whole lot we can do. See pull request at this URL for discussion:
+ // https://github.com/jquery/jquery/pull/764
+ return elem.document.documentElement[ "client" + name ];
+ }
+
+ // Get document width or height
+ if ( elem.nodeType === 9 ) {
+ doc = elem.documentElement;
+
+ // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
+ // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
+ return Math.max(
+ elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+ elem.body[ "offset" + name ], doc[ "offset" + name ],
+ doc[ "client" + name ]
+ );
+ }
+
+ return value === undefined ?
+ // Get width or height on the element, requesting but not forcing parseFloat
+ jQuery.css( elem, type, value, extra ) :
+
+ // Set width or height on the element
+ jQuery.style( elem, type, value, extra );
+ }, type, chainable ? margin : undefined, chainable, null );
+ };
+ });
+});
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+
+// Expose jQuery as an AMD module, but only for AMD loaders that
+// understand the issues with loading multiple versions of jQuery
+// in a page that all might call define(). The loader will indicate
+// they have special allowances for multiple jQuery versions by
+// specifying define.amd.jQuery = true. Register as a named module,
+// since jQuery can be concatenated with other files that may use define,
+// but not use a proper concatenation script that understands anonymous
+// AMD modules. A named AMD is safest and most robust way to register.
+// Lowercase jquery is used because AMD module names are derived from
+// file names, and jQuery is normally delivered in a lowercase file name.
+// Do this after creating the global so that if an AMD module wants to call
+// noConflict to hide this version of jQuery, it will work.
+if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
+ define( "jquery", [], function () { return jQuery; } );
+}
+
+})( window );
diff --git a/misc/flot/jquery.min.js b/misc/flot/jquery.min.js
new file mode 100644
index 0000000..ee48790
--- /dev/null
+++ b/misc/flot/jquery.min.js
@@ -0,0 +1,5 @@
+(function(window,undefined){var rootjQuery,readyList,document=window.document,location=window.location,navigator=window.navigator,_jQuery=window.jQuery,_$=window.$,core_push=Array.prototype.push,core_slice=Array.prototype.slice,core_indexOf=Array.prototype.indexOf,core_toString=Object.prototype.toString,core_hasOwn=Object.prototype.hasOwnProperty,core_trim=String.prototype.trim,jQuery=function(selector,context){return new jQuery.fn.init(selector,context,rootjQuery)},core_pnum=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,core_rnotwhite=/\S/,core_rspace=/\s+/,rtrim=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,rquickExpr=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,rsingleTag=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,rvalidchars=/^[\],:{}\s]*$/,rvalidbraces=/(?:^|:|,)(?:\s*\[)+/g,rvalidescape=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,rvalidtokens=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,rmsPrefix=/^-ms-/,rdashAlpha=/-([\da-z])/gi,fcamelCase=function(all,letter){return(letter+"").toUpperCase()},DOMContentLoaded=function(){if(document.addEventListener){document.removeEventListener("DOMContentLoaded",DOMContentLoaded,false);jQuery.ready()}else if(document.readyState==="complete"){document.detachEvent("onreadystatechange",DOMContentLoaded);jQuery.ready()}},class2type={};jQuery.fn=jQuery.prototype={constructor:jQuery,init:function(selector,context,rootjQuery){var match,elem,ret,doc;if(!selector){return this}if(selector.nodeType){this.context=this[0]=selector;this.length=1;return this}if(typeof selector==="string"){if(selector.charAt(0)==="<"&&selector.charAt(selector.length-1)===">"&&selector.length>=3){match=[null,selector,null]}else{match=rquickExpr.exec(selector)}if(match&&(match[1]||!context)){if(match[1]){context=context instanceof jQuery?context[0]:context;doc=context&&context.nodeType?context.ownerDocument||context:document;selector=jQuery.parseHTML(match[1],doc,true);if(rsingleTag.test(match[1])&&jQuery.isPlainObject(context)){this.attr.call(selector,context,true)}return jQuery.merge(this,selector)}else{elem=document.getElementById(match[2]);if(elem&&elem.parentNode){if(elem.id!==match[2]){return rootjQuery.find(selector)}this.length=1;this[0]=elem}this.context=document;this.selector=selector;return this}}else if(!context||context.jquery){return(context||rootjQuery).find(selector)}else{return this.constructor(context).find(selector)}}else if(jQuery.isFunction(selector)){return rootjQuery.ready(selector)}if(selector.selector!==undefined){this.selector=selector.selector;this.context=selector.context}return jQuery.makeArray(selector,this)},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return core_slice.call(this)},get:function(num){return num==null?this.toArray():num<0?this[this.length+num]:this[num]},pushStack:function(elems,name,selector){var ret=jQuery.merge(this.constructor(),elems);ret.prevObject=this;ret.context=this.context;if(name==="find"){ret.selector=this.selector+(this.selector?" ":"")+selector}else if(name){ret.selector=this.selector+"."+name+"("+selector+")"}return ret},each:function(callback,args){return jQuery.each(this,callback,args)},ready:function(fn){jQuery.ready.promise().done(fn);return this},eq:function(i){i=+i;return i===-1?this.slice(i):this.slice(i,i+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(core_slice.apply(this,arguments),"slice",core_slice.call(arguments).join(","))},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem)}))},end:function(){return this.prevObject||this.constructor(null)},push:core_push,sort:[].sort,splice:[].splice};jQuery.fn.init.prototype=jQuery.fn;jQuery.extend=jQuery.fn.extend=function(){var options,name,src,copy,copyIsArray,clone,target=arguments[0]||{},i=1,length=arguments.length,deep=false;if(typeof target==="boolean"){deep=target;target=arguments[1]||{};i=2}if(typeof target!=="object"&&!jQuery.isFunction(target)){target={}}if(length===i){target=this;--i}for(;i<length;i++){if((options=arguments[i])!=null){for(name in options){src=target[name];copy=options[name];if(target===copy){continue}if(deep&&copy&&(jQuery.isPlainObject(copy)||(copyIsArray=jQuery.isArray(copy)))){if(copyIsArray){copyIsArray=false;clone=src&&jQuery.isArray(src)?src:[]}else{clone=src&&jQuery.isPlainObject(src)?src:{}}target[name]=jQuery.extend(deep,clone,copy)}else if(copy!==undefined){target[name]=copy}}}}return target};jQuery.extend({noConflict:function(deep){if(window.$===jQuery){window.$=_$}if(deep&&window.jQuery===jQuery){window.jQuery=_jQuery}return jQuery},isReady:false,readyWait:1,holdReady:function(hold){if(hold){jQuery.readyWait++}else{jQuery.ready(true)}},ready:function(wait){if(wait===true?--jQuery.readyWait:jQuery.isReady){return}if(!document.body){return setTimeout(jQuery.ready,1)}jQuery.isReady=true;if(wait!==true&&--jQuery.readyWait>0){return}readyList.resolveWith(document,[jQuery]);if(jQuery.fn.trigger){jQuery(document).trigger("ready").off("ready")}},isFunction:function(obj){return jQuery.type(obj)==="function"},isArray:Array.isArray||function(obj){return jQuery.type(obj)==="array"},isWindow:function(obj){return obj!=null&&obj==obj.window},isNumeric:function(obj){return!isNaN(parseFloat(obj))&&isFinite(obj)},type:function(obj){return obj==null?String(obj):class2type[core_toString.call(obj)]||"object"},isPlainObject:function(obj){if(!obj||jQuery.type(obj)!=="object"||obj.nodeType||jQuery.isWindow(obj)){return false}try{if(obj.constructor&&!core_hasOwn.call(obj,"constructor")&&!core_hasOwn.call(obj.constructor.prototype,"isPrototypeOf")){return false}}catch(e){return false}var key;for(key in obj){}return key===undefined||core_hasOwn.call(obj,key)},isEmptyObject:function(obj){var name;for(name in obj){return false}return true},error:function(msg){throw new Error(msg)},parseHTML:function(data,context,scripts){var parsed;if(!data||typeof data!=="string"){return null}if(typeof context==="boolean"){scripts=context;context=0}context=context||document;if(parsed=rsingleTag.exec(data)){return[context.createElement(parsed[1])]}parsed=jQuery.buildFragment([data],context,scripts?null:[]);return jQuery.merge([],(parsed.cacheable?jQuery.clone(parsed.fragment):parsed.fragment).childNodes)},parseJSON:function(data){if(!data||typeof data!=="string"){return null}data=jQuery.trim(data);if(window.JSON&&window.JSON.parse){return window.JSON.parse(data)}if(rvalidchars.test(data.replace(rvalidescape,"@").replace(rvalidtokens,"]").replace(rvalidbraces,""))){return new Function("return "+data)()}jQuery.error("Invalid JSON: "+data)},parseXML:function(data){var xml,tmp;if(!data||typeof data!=="string"){return null}try{if(window.DOMParser){tmp=new DOMParser;xml=tmp.parseFromString(data,"text/xml")}else{xml=new ActiveXObject("Microsoft.XMLDOM");xml.async="false";xml.loadXML(data)}}catch(e){xml=undefined}if(!xml||!xml.documentElement||xml.getElementsByTagName("parsererror").length){jQuery.error("Invalid XML: "+data)}return xml},noop:function(){},globalEval:function(data){if(data&&core_rnotwhite.test(data)){(window.execScript||function(data){window["eval"].call(window,data)})(data)}},camelCase:function(string){return string.replace(rmsPrefix,"ms-").replace(rdashAlpha,fcamelCase)},nodeName:function(elem,name){return elem.nodeName&&elem.nodeName.toLowerCase()===name.toLowerCase()},each:function(obj,callback,args){var name,i=0,length=obj.length,isObj=length===undefined||jQuery.isFunction(obj);if(args){if(isObj){for(name in obj){if(callback.apply(obj[name],args)===false){break}}}else{for(;i<length;){if(callback.apply(obj[i++],args)===false){break}}}}else{if(isObj){for(name in obj){if(callback.call(obj[name],name,obj[name])===false){break}}}else{for(;i<length;){if(callback.call(obj[i],i,obj[i++])===false){break}}}}return obj},trim:core_trim&&!core_trim.call(" ")?function(text){return text==null?"":core_trim.call(text)}:function(text){return text==null?"":(text+"").replace(rtrim,"")},makeArray:function(arr,results){var type,ret=results||[];if(arr!=null){type=jQuery.type(arr);if(arr.length==null||type==="string"||type==="function"||type==="regexp"||jQuery.isWindow(arr)){core_push.call(ret,arr)}else{jQuery.merge(ret,arr)}}return ret},inArray:function(elem,arr,i){var len;if(arr){if(core_indexOf){return core_indexOf.call(arr,elem,i)}len=arr.length;i=i?i<0?Math.max(0,len+i):i:0;for(;i<len;i++){if(i in arr&&arr[i]===elem){return i}}}return-1},merge:function(first,second){var l=second.length,i=first.length,j=0;if(typeof l==="number"){for(;j<l;j++){first[i++]=second[j]}}else{while(second[j]!==undefined){first[i++]=second[j++]}}first.length=i;return first},grep:function(elems,callback,inv){var retVal,ret=[],i=0,length=elems.length;inv=!!inv;for(;i<length;i++){retVal=!!callback(elems[i],i);if(inv!==retVal){ret.push(elems[i])}}return ret},map:function(elems,callback,arg){var value,key,ret=[],i=0,length=elems.length,isArray=elems instanceof jQuery||length!==undefined&&typeof length==="number"&&(length>0&&elems[0]&&elems[length-1]||length===0||jQuery.isArray(elems));if(isArray){for(;i<length;i++){value=callback(elems[i],i,arg);if(value!=null){ret[ret.length]=value}}}else{for(key in elems){value=callback(elems[key],key,arg);if(value!=null){ret[ret.length]=value}}}return ret.concat.apply([],ret)},guid:1,proxy:function(fn,context){var tmp,args,proxy;if(typeof context==="string"){tmp=fn[context];context=fn;fn=tmp}if(!jQuery.isFunction(fn)){return undefined}args=core_slice.call(arguments,2);proxy=function(){return fn.apply(context,args.concat(core_slice.call(arguments)))};proxy.guid=fn.guid=fn.guid||jQuery.guid++;return proxy},access:function(elems,fn,key,value,chainable,emptyGet,pass){var exec,bulk=key==null,i=0,length=elems.length;if(key&&typeof key==="object"){for(i in key){jQuery.access(elems,fn,i,key[i],1,emptyGet,value)}chainable=1}else if(value!==undefined){exec=pass===undefined&&jQuery.isFunction(value);if(bulk){if(exec){exec=fn;fn=function(elem,key,value){return exec.call(jQuery(elem),value)}}else{fn.call(elems,value);fn=null}}if(fn){for(;i<length;i++){fn(elems[i],key,exec?value.call(elems[i],i,fn(elems[i],key)):value,pass)}}chainable=1}return chainable?elems:bulk?fn.call(elems):length?fn(elems[0],key):emptyGet},now:function(){return(new Date).getTime()}});jQuery.ready.promise=function(obj){if(!readyList){readyList=jQuery.Deferred();if(document.readyState==="complete"){setTimeout(jQuery.ready,1)}else if(document.addEventListener){document.addEventListener("DOMContentLoaded",DOMContentLoaded,false);window.addEventListener("load",jQuery.ready,false)}else{document.attachEvent("onreadystatechange",DOMContentLoaded);window.attachEvent("onload",jQuery.ready);var top=false;try{top=window.frameElement==null&&document.documentElement}catch(e){}if(top&&top.doScroll){(function doScrollCheck(){if(!jQuery.isReady){try{top.doScroll("left")}catch(e){return setTimeout(doScrollCheck,50)}jQuery.ready()}})()}}}return readyList.promise(obj)};jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(i,name){class2type["[object "+name+"]"]=name.toLowerCase()});rootjQuery=jQuery(document);var optionsCache={};function createOptions(options){var object=optionsCache[options]={};jQuery.each(options.split(core_rspace),function(_,flag){object[flag]=true});return object}jQuery.Callbacks=function(options){options=typeof options==="string"?optionsCache[options]||createOptions(options):jQuery.extend({},options);var memory,fired,firing,firingStart,firingLength,firingIndex,list=[],stack=!options.once&&[],fire=function(data){memory=options.memory&&data;fired=true;firingIndex=firingStart||0;firingStart=0;firingLength=list.length;firing=true;for(;list&&firingIndex<firingLength;firingIndex++){if(list[firingIndex].apply(data[0],data[1])===false&&options.stopOnFalse){memory=false;break}}firing=false;if(list){if(stack){if(stack.length){fire(stack.shift())}}else if(memory){list=[]}else{self.disable()}}},self={add:function(){if(list){var start=list.length;(function add(args){jQuery.each(args,function(_,arg){var type=jQuery.type(arg);if(type==="function"){if(!options.unique||!self.has(arg)){list.push(arg)}}else if(arg&&arg.length&&type!=="string"){add(arg)}})})(arguments);if(firing){firingLength=list.length}else if(memory){firingStart=start;fire(memory)}}return this},remove:function(){if(list){jQuery.each(arguments,function(_,arg){var index;while((index=jQuery.inArray(arg,list,index))>-1){list.splice(index,1);if(firing){if(index<=firingLength){firingLength--}if(index<=firingIndex){firingIndex--}}}})}return this},has:function(fn){return jQuery.inArray(fn,list)>-1},empty:function(){list=[];return this},disable:function(){list=stack=memory=undefined;return this},disabled:function(){return!list},lock:function(){stack=undefined;if(!memory){self.disable()}return this},locked:function(){return!stack},fireWith:function(context,args){args=args||[];args=[context,args.slice?args.slice():args];if(list&&(!fired||stack)){if(firing){stack.push(args)}else{fire(args)}}return this},fire:function(){self.fireWith(this,arguments);return this},fired:function(){return!!fired}};return self};jQuery.extend({Deferred:function(func){var tuples=[["resolve","done",jQuery.Callbacks("once memory"),"resolved"],["reject","fail",jQuery.Callbacks("once memory"),"rejected"],["notify","progress",jQuery.Callbacks("memory")]],state="pending",promise={state:function(){return state},always:function(){deferred.done(arguments).fail(arguments);return this},then:function(){var fns=arguments;return jQuery.Deferred(function(newDefer){jQuery.each(tuples,function(i,tuple){var action=tuple[0],fn=fns[i];deferred[tuple[1]](jQuery.isFunction(fn)?function(){var returned=fn.apply(this,arguments);if(returned&&jQuery.isFunction(returned.promise)){returned.promise().done(newDefer.resolve).fail(newDefer.reject).progress(newDefer.notify)}else{newDefer[action+"With"](this===deferred?newDefer:this,[returned])}}:newDefer[action])});fns=null}).promise()},promise:function(obj){return obj!=null?jQuery.extend(obj,promise):promise}},deferred={};promise.pipe=promise.then;jQuery.each(tuples,function(i,tuple){var list=tuple[2],stateString=tuple[3];promise[tuple[1]]=list.add;if(stateString){list.add(function(){state=stateString},tuples[i^1][2].disable,tuples[2][2].lock)}deferred[tuple[0]]=list.fire;deferred[tuple[0]+"With"]=list.fireWith});promise.promise(deferred);if(func){func.call(deferred,deferred)}return deferred},when:function(subordinate){var i=0,resolveValues=core_slice.call(arguments),length=resolveValues.length,remaining=length!==1||subordinate&&jQuery.isFunction(subordinate.promise)?length:0,deferred=remaining===1?subordinate:jQuery.Deferred(),updateFunc=function(i,contexts,values){return function(value){contexts[i]=this;values[i]=arguments.length>1?core_slice.call(arguments):value;if(values===progressValues){deferred.notifyWith(contexts,values)}else if(!--remaining){deferred.resolveWith(contexts,values)}}},progressValues,progressContexts,resolveContexts;if(length>1){progressValues=new Array(length);progressContexts=new Array(length);resolveContexts=new Array(length);for(;i<length;i++){if(resolveValues[i]&&jQuery.isFunction(resolveValues[i].promise)){resolveValues[i].promise().done(updateFunc(i,resolveContexts,resolveValues)).fail(deferred.reject).progress(updateFunc(i,progressContexts,progressValues))}else{--remaining}}}if(!remaining){deferred.resolveWith(resolveContexts,resolveValues)}return deferred.promise()}});jQuery.support=function(){var support,all,a,select,opt,input,fragment,eventName,i,isSupported,clickFn,div=document.createElement("div");div.setAttribute("className","t");div.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";all=div.getElementsByTagName("*");a=div.getElementsByTagName("a")[0];if(!all||!a||!all.length){return{}}select=document.createElement("select");opt=select.appendChild(document.createElement("option"));input=div.getElementsByTagName("input")[0];a.style.cssText="top:1px;float:left;opacity:.5";support={leadingWhitespace:div.firstChild.nodeType===3,tbody:!div.getElementsByTagName("tbody").length,htmlSerialize:!!div.getElementsByTagName("link").length,style:/top/.test(a.getAttribute("style")),hrefNormalized:a.getAttribute("href")==="/a",opacity:/^0.5/.test(a.style.opacity),cssFloat:!!a.style.cssFloat,checkOn:input.value==="on",optSelected:opt.selected,getSetAttribute:div.className!=="t",enctype:!!document.createElement("form").enctype,html5Clone:document.createElement("nav").cloneNode(true).outerHTML!=="<:nav></:nav>",boxModel:document.compatMode==="CSS1Compat",submitBubbles:true,changeBubbles:true,focusinBubbles:false,deleteExpando:true,noCloneEvent:true,inlineBlockNeedsLayout:false,shrinkWrapBlocks:false,reliableMarginRight:true,boxSizingReliable:true,pixelPosition:false};input.checked=true;support.noCloneChecked=input.cloneNode(true).checked;select.disabled=true;support.optDisabled=!opt.disabled;try{delete div.test}catch(e){support.deleteExpando=false}if(!div.addEventListener&&div.attachEvent&&div.fireEvent){div.attachEvent("onclick",clickFn=function(){support.noCloneEvent=false});div.cloneNode(true).fireEvent("onclick");div.detachEvent("onclick",clickFn)}input=document.createElement("input");input.value="t";input.setAttribute("type","radio");support.radioValue=input.value==="t";input.setAttribute("checked","checked");input.setAttribute("name","t");div.appendChild(input);fragment=document.createDocumentFragment();fragment.appendChild(div.lastChild);support.checkClone=fragment.cloneNode(true).cloneNode(true).lastChild.checked;support.appendChecked=input.checked;fragment.removeChild(input);fragment.appendChild(div);if(div.attachEvent){for(i in{submit:true,change:true,focusin:true}){eventName="on"+i;isSupported=eventName in div;if(!isSupported){div.setAttribute(eventName,"return;");isSupported=typeof div[eventName]==="function"}support[i+"Bubbles"]=isSupported}}jQuery(function(){var container,div,tds,marginDiv,divReset="padding:0;margin:0;border:0;display:block;overflow:hidden;",body=document.getElementsByTagName("body")[0];if(!body){return}container=document.createElement("div");container.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px";body.insertBefore(container,body.firstChild);div=document.createElement("div");container.appendChild(div);div.innerHTML="<table><tr><td></td><td>t</td></tr></table>";tds=div.getElementsByTagName("td");tds[0].style.cssText="padding:0;margin:0;border:0;display:none";isSupported=tds[0].offsetHeight===0;tds[0].style.display="";tds[1].style.display="none";support.reliableHiddenOffsets=isSupported&&tds[0].offsetHeight===0;div.innerHTML="";div.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";support.boxSizing=div.offsetWidth===4;support.doesNotIncludeMarginInBodyOffset=body.offsetTop!==1;if(window.getComputedStyle){support.pixelPosition=(window.getComputedStyle(div,null)||{}).top!=="1%";support.boxSizingReliable=(window.getComputedStyle(div,null)||{width:"4px"}).width==="4px";marginDiv=document.createElement("div");marginDiv.style.cssText=div.style.cssText=divReset;marginDiv.style.marginRight=marginDiv.style.width="0";div.style.width="1px";div.appendChild(marginDiv);support.reliableMarginRight=!parseFloat((window.getComputedStyle(marginDiv,null)||{}).marginRight)}if(typeof div.style.zoom!=="undefined"){div.innerHTML="";div.style.cssText=divReset+"width:1px;padding:1px;display:inline;zoom:1";support.inlineBlockNeedsLayout=div.offsetWidth===3;div.style.display="block";div.style.overflow="visible";div.innerHTML="<div></div>";div.firstChild.style.width="5px";support.shrinkWrapBlocks=div.offsetWidth!==3;container.style.zoom=1}body.removeChild(container);container=div=tds=marginDiv=null});fragment.removeChild(div);all=a=select=opt=input=fragment=div=null;return support}();var rbrace=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,rmultiDash=/([A-Z])/g;jQuery.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(jQuery.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},hasData:function(elem){elem=elem.nodeType?jQuery.cache[elem[jQuery.expando]]:elem[jQuery.expando];return!!elem&&!isEmptyDataObject(elem)},data:function(elem,name,data,pvt){if(!jQuery.acceptData(elem)){return}var thisCache,ret,internalKey=jQuery.expando,getByName=typeof name==="string",isNode=elem.nodeType,cache=isNode?jQuery.cache:elem,id=isNode?elem[internalKey]:elem[internalKey]&&internalKey;if((!id||!cache[id]||!pvt&&!cache[id].data)&&getByName&&data===undefined){return}if(!id){if(isNode){elem[internalKey]=id=jQuery.deletedIds.pop()||jQuery.guid++}else{id=internalKey}}if(!cache[id]){cache[id]={};if(!isNode){cache[id].toJSON=jQuery.noop}}if(typeof name==="object"||typeof name==="function"){if(pvt){cache[id]=jQuery.extend(cache[id],name)}else{cache[id].data=jQuery.extend(cache[id].data,name)}}thisCache=cache[id];if(!pvt){if(!thisCache.data){thisCache.data={}}thisCache=thisCache.data}if(data!==undefined){thisCache[jQuery.camelCase(name)]=data}if(getByName){ret=thisCache[name];if(ret==null){ret=thisCache[jQuery.camelCase(name)]}}else{ret=thisCache}return ret},removeData:function(elem,name,pvt){if(!jQuery.acceptData(elem)){return}var thisCache,i,l,isNode=elem.nodeType,cache=isNode?jQuery.cache:elem,id=isNode?elem[jQuery.expando]:jQuery.expando;if(!cache[id]){return}if(name){thisCache=pvt?cache[id]:cache[id].data;if(thisCache){if(!jQuery.isArray(name)){if(name in thisCache){name=[name]}else{name=jQuery.camelCase(name);if(name in thisCache){name=[name]}else{name=name.split(" ")}}}for(i=0,l=name.length;i<l;i++){delete thisCache[name[i]]}if(!(pvt?isEmptyDataObject:jQuery.isEmptyObject)(thisCache)){return}}}if(!pvt){delete cache[id].data;if(!isEmptyDataObject(cache[id])){return}}if(isNode){jQuery.cleanData([elem],true)}else if(jQuery.support.deleteExpando||cache!=cache.window){delete cache[id]}else{cache[id]=null}},_data:function(elem,name,data){return jQuery.data(elem,name,data,true)},acceptData:function(elem){var noData=elem.nodeName&&jQuery.noData[elem.nodeName.toLowerCase()];return!noData||noData!==true&&elem.getAttribute("classid")===noData}});jQuery.fn.extend({data:function(key,value){var parts,part,attr,name,l,elem=this[0],i=0,data=null;if(key===undefined){if(this.length){data=jQuery.data(elem);if(elem.nodeType===1&&!jQuery._data(elem,"parsedAttrs")){attr=elem.attributes;for(l=attr.length;i<l;i++){name=attr[i].name;if(!name.indexOf("data-")){name=jQuery.camelCase(name.substring(5));dataAttr(elem,name,data[name])}}jQuery._data(elem,"parsedAttrs",true)}}return data}if(typeof key==="object"){return this.each(function(){jQuery.data(this,key)})}parts=key.split(".",2);parts[1]=parts[1]?"."+parts[1]:"";part=parts[1]+"!";return jQuery.access(this,function(value){if(value===undefined){data=this.triggerHandler("getData"+part,[parts[0]]);if(data===undefined&&elem){data=jQuery.data(elem,key);data=dataAttr(elem,key,data)}return data===undefined&&parts[1]?this.data(parts[0]):data}parts[1]=value;this.each(function(){var self=jQuery(this);self.triggerHandler("setData"+part,parts);jQuery.data(this,key,value);self.triggerHandler("changeData"+part,parts)})},null,value,arguments.length>1,null,false)},removeData:function(key){return this.each(function(){jQuery.removeData(this,key)})}});function dataAttr(elem,key,data){if(data===undefined&&elem.nodeType===1){var name="data-"+key.replace(rmultiDash,"-$1").toLowerCase();data=elem.getAttribute(name);if(typeof data==="string"){try{data=data==="true"?true:data==="false"?false:data==="null"?null:+data+""===data?+data:rbrace.test(data)?jQuery.parseJSON(data):data}catch(e){}jQuery.data(elem,key,data)}else{data=undefined}}return data}function isEmptyDataObject(obj){var name;for(name in obj){if(name==="data"&&jQuery.isEmptyObject(obj[name])){continue}if(name!=="toJSON"){return false}}return true}jQuery.extend({queue:function(elem,type,data){var queue;if(elem){type=(type||"fx")+"queue";queue=jQuery._data(elem,type);if(data){if(!queue||jQuery.isArray(data)){queue=jQuery._data(elem,type,jQuery.makeArray(data))}else{queue.push(data)}}return queue||[]}},dequeue:function(elem,type){type=type||"fx";var queue=jQuery.queue(elem,type),startLength=queue.length,fn=queue.shift(),hooks=jQuery._queueHooks(elem,type),next=function(){jQuery.dequeue(elem,type)};if(fn==="inprogress"){fn=queue.shift();startLength--}if(fn){if(type==="fx"){queue.unshift("inprogress")}delete hooks.stop;fn.call(elem,next,hooks)}if(!startLength&&hooks){hooks.empty.fire()}},_queueHooks:function(elem,type){var key=type+"queueHooks";return jQuery._data(elem,key)||jQuery._data(elem,key,{empty:jQuery.Callbacks("once memory").add(function(){jQuery.removeData(elem,type+"queue",true);jQuery.removeData(elem,key,true)})})}});jQuery.fn.extend({queue:function(type,data){var setter=2;if(typeof type!=="string"){data=type;type="fx";setter--}if(arguments.length<setter){return jQuery.queue(this[0],type)}return data===undefined?this:this.each(function(){var queue=jQuery.queue(this,type,data);jQuery._queueHooks(this,type);if(type==="fx"&&queue[0]!=="inprogress"){jQuery.dequeue(this,type)}})},dequeue:function(type){return this.each(function(){jQuery.dequeue(this,type)})},delay:function(time,type){time=jQuery.fx?jQuery.fx.speeds[time]||time:time;type=type||"fx";return this.queue(type,function(next,hooks){var timeout=setTimeout(next,time);hooks.stop=function(){clearTimeout(timeout)}})},clearQueue:function(type){return this.queue(type||"fx",[])},promise:function(type,obj){var tmp,count=1,defer=jQuery.Deferred(),elements=this,i=this.length,resolve=function(){if(!--count){defer.resolveWith(elements,[elements])}};if(typeof type!=="string"){obj=type;type=undefined}type=type||"fx";while(i--){tmp=jQuery._data(elements[i],type+"queueHooks");if(tmp&&tmp.empty){count++;tmp.empty.add(resolve)}}resolve();return defer.promise(obj)}});var nodeHook,boolHook,fixSpecified,rclass=/[\t\r\n]/g,rreturn=/\r/g,rtype=/^(?:button|input)$/i,rfocusable=/^(?:button|input|object|select|textarea)$/i,rclickable=/^a(?:rea|)$/i,rboolean=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,getSetAttribute=jQuery.support.getSetAttribute;jQuery.fn.extend({attr:function(name,value){return jQuery.access(this,jQuery.attr,name,value,arguments.length>1)},removeAttr:function(name){return this.each(function(){jQuery.removeAttr(this,name)})},prop:function(name,value){return jQuery.access(this,jQuery.prop,name,value,arguments.length>1)},removeProp:function(name){name=jQuery.propFix[name]||name;return this.each(function(){try{this[name]=undefined;delete this[name]}catch(e){}})},addClass:function(value){var classNames,i,l,elem,setClass,c,cl;if(jQuery.isFunction(value)){return this.each(function(j){jQuery(this).addClass(value.call(this,j,this.className))})}if(value&&typeof value==="string"){classNames=value.split(core_rspace);for(i=0,l=this.length;i<l;i++){elem=this[i];if(elem.nodeType===1){if(!elem.className&&classNames.length===1){elem.className=value}else{setClass=" "+elem.className+" ";for(c=0,cl=classNames.length;c<cl;c++){if(setClass.indexOf(" "+classNames[c]+" ")<0){setClass+=classNames[c]+" "}}elem.className=jQuery.trim(setClass)}}}}return this},removeClass:function(value){var removes,className,elem,c,cl,i,l;if(jQuery.isFunction(value)){return this.each(function(j){jQuery(this).removeClass(value.call(this,j,this.className))})}if(value&&typeof value==="string"||value===undefined){removes=(value||"").split(core_rspace);for(i=0,l=this.length;i<l;i++){elem=this[i];if(elem.nodeType===1&&elem.className){className=(" "+elem.className+" ").replace(rclass," ");for(c=0,cl=removes.length;c<cl;c++){while(className.indexOf(" "+removes[c]+" ")>=0){className=className.replace(" "+removes[c]+" "," ")}}elem.className=value?jQuery.trim(className):""}}}return this},toggleClass:function(value,stateVal){var type=typeof value,isBool=typeof stateVal==="boolean";if(jQuery.isFunction(value)){return this.each(function(i){jQuery(this).toggleClass(value.call(this,i,this.className,stateVal),stateVal)})}return this.each(function(){if(type==="string"){var className,i=0,self=jQuery(this),state=stateVal,classNames=value.split(core_rspace);while(className=classNames[i++]){state=isBool?state:!self.hasClass(className);self[state?"addClass":"removeClass"](className)}}else if(type==="undefined"||type==="boolean"){if(this.className){jQuery._data(this,"__className__",this.className)}this.className=this.className||value===false?"":jQuery._data(this,"__className__")||""}})},hasClass:function(selector){var className=" "+selector+" ",i=0,l=this.length;for(;i<l;i++){if(this[i].nodeType===1&&(" "+this[i].className+" ").replace(rclass," ").indexOf(className)>=0){return true}}return false},val:function(value){var hooks,ret,isFunction,elem=this[0];if(!arguments.length){if(elem){hooks=jQuery.valHooks[elem.type]||jQuery.valHooks[elem.nodeName.toLowerCase()];if(hooks&&"get"in hooks&&(ret=hooks.get(elem,"value"))!==undefined){return ret}ret=elem.value;return typeof ret==="string"?ret.replace(rreturn,""):ret==null?"":ret}return}isFunction=jQuery.isFunction(value);return this.each(function(i){var val,self=jQuery(this);if(this.nodeType!==1){return}if(isFunction){val=value.call(this,i,self.val())}else{val=value}if(val==null){val=""}else if(typeof val==="number"){val+=""}else if(jQuery.isArray(val)){val=jQuery.map(val,function(value){return value==null?"":value+""})}hooks=jQuery.valHooks[this.type]||jQuery.valHooks[this.nodeName.toLowerCase()];if(!hooks||!("set"in hooks)||hooks.set(this,val,"value")===undefined){this.value=val}})}});jQuery.extend({valHooks:{option:{get:function(elem){var val=elem.attributes.value;return!val||val.specified?elem.value:elem.text}},select:{get:function(elem){var value,option,options=elem.options,index=elem.selectedIndex,one=elem.type==="select-one"||index<0,values=one?null:[],max=one?index+1:options.length,i=index<0?max:one?index:0;for(;i<max;i++){option=options[i];if((option.selected||i===index)&&(jQuery.support.optDisabled?!option.disabled:option.getAttribute("disabled")===null)&&(!option.parentNode.disabled||!jQuery.nodeName(option.parentNode,"optgroup"))){value=jQuery(option).val();if(one){return value}values.push(value)}}return values},set:function(elem,value){var values=jQuery.makeArray(value);jQuery(elem).find("option").each(function(){this.selected=jQuery.inArray(jQuery(this).val(),values)>=0});if(!values.length){elem.selectedIndex=-1}return values}}},attrFn:{},attr:function(elem,name,value,pass){var ret,hooks,notxml,nType=elem.nodeType;if(!elem||nType===3||nType===8||nType===2){return}if(pass&&jQuery.isFunction(jQuery.fn[name])){return jQuery(elem)[name](value)}if(typeof elem.getAttribute==="undefined"){return jQuery.prop(elem,name,value)}notxml=nType!==1||!jQuery.isXMLDoc(elem);if(notxml){name=name.toLowerCase();hooks=jQuery.attrHooks[name]||(rboolean.test(name)?boolHook:nodeHook)}if(value!==undefined){if(value===null){jQuery.removeAttr(elem,name);return}else if(hooks&&"set"in hooks&&notxml&&(ret=hooks.set(elem,value,name))!==undefined){return ret}else{elem.setAttribute(name,value+"");return value}}else if(hooks&&"get"in hooks&&notxml&&(ret=hooks.get(elem,name))!==null){return ret}else{ret=elem.getAttribute(name);return ret===null?undefined:ret}},removeAttr:function(elem,value){var propName,attrNames,name,isBool,i=0;if(value&&elem.nodeType===1){attrNames=value.split(core_rspace);for(;i<attrNames.length;i++){name=attrNames[i];if(name){propName=jQuery.propFix[name]||name;isBool=rboolean.test(name);if(!isBool){jQuery.attr(elem,name,"")}elem.removeAttribute(getSetAttribute?name:propName);if(isBool&&propName in elem){elem[propName]=false}}}}},attrHooks:{type:{set:function(elem,value){if(rtype.test(elem.nodeName)&&elem.parentNode){jQuery.error("type property can't be changed")
+}else if(!jQuery.support.radioValue&&value==="radio"&&jQuery.nodeName(elem,"input")){var val=elem.value;elem.setAttribute("type",value);if(val){elem.value=val}return value}}},value:{get:function(elem,name){if(nodeHook&&jQuery.nodeName(elem,"button")){return nodeHook.get(elem,name)}return name in elem?elem.value:null},set:function(elem,value,name){if(nodeHook&&jQuery.nodeName(elem,"button")){return nodeHook.set(elem,value,name)}elem.value=value}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(elem,name,value){var ret,hooks,notxml,nType=elem.nodeType;if(!elem||nType===3||nType===8||nType===2){return}notxml=nType!==1||!jQuery.isXMLDoc(elem);if(notxml){name=jQuery.propFix[name]||name;hooks=jQuery.propHooks[name]}if(value!==undefined){if(hooks&&"set"in hooks&&(ret=hooks.set(elem,value,name))!==undefined){return ret}else{return elem[name]=value}}else{if(hooks&&"get"in hooks&&(ret=hooks.get(elem,name))!==null){return ret}else{return elem[name]}}},propHooks:{tabIndex:{get:function(elem){var attributeNode=elem.getAttributeNode("tabindex");return attributeNode&&attributeNode.specified?parseInt(attributeNode.value,10):rfocusable.test(elem.nodeName)||rclickable.test(elem.nodeName)&&elem.href?0:undefined}}}});boolHook={get:function(elem,name){var attrNode,property=jQuery.prop(elem,name);return property===true||typeof property!=="boolean"&&(attrNode=elem.getAttributeNode(name))&&attrNode.nodeValue!==false?name.toLowerCase():undefined},set:function(elem,value,name){var propName;if(value===false){jQuery.removeAttr(elem,name)}else{propName=jQuery.propFix[name]||name;if(propName in elem){elem[propName]=true}elem.setAttribute(name,name.toLowerCase())}return name}};if(!getSetAttribute){fixSpecified={name:true,id:true,coords:true};nodeHook=jQuery.valHooks.button={get:function(elem,name){var ret;ret=elem.getAttributeNode(name);return ret&&(fixSpecified[name]?ret.value!=="":ret.specified)?ret.value:undefined},set:function(elem,value,name){var ret=elem.getAttributeNode(name);if(!ret){ret=document.createAttribute(name);elem.setAttributeNode(ret)}return ret.value=value+""}};jQuery.each(["width","height"],function(i,name){jQuery.attrHooks[name]=jQuery.extend(jQuery.attrHooks[name],{set:function(elem,value){if(value===""){elem.setAttribute(name,"auto");return value}}})});jQuery.attrHooks.contenteditable={get:nodeHook.get,set:function(elem,value,name){if(value===""){value="false"}nodeHook.set(elem,value,name)}}}if(!jQuery.support.hrefNormalized){jQuery.each(["href","src","width","height"],function(i,name){jQuery.attrHooks[name]=jQuery.extend(jQuery.attrHooks[name],{get:function(elem){var ret=elem.getAttribute(name,2);return ret===null?undefined:ret}})})}if(!jQuery.support.style){jQuery.attrHooks.style={get:function(elem){return elem.style.cssText.toLowerCase()||undefined},set:function(elem,value){return elem.style.cssText=value+""}}}if(!jQuery.support.optSelected){jQuery.propHooks.selected=jQuery.extend(jQuery.propHooks.selected,{get:function(elem){var parent=elem.parentNode;if(parent){parent.selectedIndex;if(parent.parentNode){parent.parentNode.selectedIndex}}return null}})}if(!jQuery.support.enctype){jQuery.propFix.enctype="encoding"}if(!jQuery.support.checkOn){jQuery.each(["radio","checkbox"],function(){jQuery.valHooks[this]={get:function(elem){return elem.getAttribute("value")===null?"on":elem.value}}})}jQuery.each(["radio","checkbox"],function(){jQuery.valHooks[this]=jQuery.extend(jQuery.valHooks[this],{set:function(elem,value){if(jQuery.isArray(value)){return elem.checked=jQuery.inArray(jQuery(elem).val(),value)>=0}}})});var rformElems=/^(?:textarea|input|select)$/i,rtypenamespace=/^([^\.]*|)(?:\.(.+)|)$/,rhoverHack=/(?:^|\s)hover(\.\S+|)\b/,rkeyEvent=/^key/,rmouseEvent=/^(?:mouse|contextmenu)|click/,rfocusMorph=/^(?:focusinfocus|focusoutblur)$/,hoverHack=function(events){return jQuery.event.special.hover?events:events.replace(rhoverHack,"mouseenter$1 mouseleave$1")};jQuery.event={add:function(elem,types,handler,data,selector){var elemData,eventHandle,events,t,tns,type,namespaces,handleObj,handleObjIn,handlers,special;if(elem.nodeType===3||elem.nodeType===8||!types||!handler||!(elemData=jQuery._data(elem))){return}if(handler.handler){handleObjIn=handler;handler=handleObjIn.handler;selector=handleObjIn.selector}if(!handler.guid){handler.guid=jQuery.guid++}events=elemData.events;if(!events){elemData.events=events={}}eventHandle=elemData.handle;if(!eventHandle){elemData.handle=eventHandle=function(e){return typeof jQuery!=="undefined"&&(!e||jQuery.event.triggered!==e.type)?jQuery.event.dispatch.apply(eventHandle.elem,arguments):undefined};eventHandle.elem=elem}types=jQuery.trim(hoverHack(types)).split(" ");for(t=0;t<types.length;t++){tns=rtypenamespace.exec(types[t])||[];type=tns[1];namespaces=(tns[2]||"").split(".").sort();special=jQuery.event.special[type]||{};type=(selector?special.delegateType:special.bindType)||type;special=jQuery.event.special[type]||{};handleObj=jQuery.extend({type:type,origType:tns[1],data:data,handler:handler,guid:handler.guid,selector:selector,needsContext:selector&&jQuery.expr.match.needsContext.test(selector),namespace:namespaces.join(".")},handleObjIn);handlers=events[type];if(!handlers){handlers=events[type]=[];handlers.delegateCount=0;if(!special.setup||special.setup.call(elem,data,namespaces,eventHandle)===false){if(elem.addEventListener){elem.addEventListener(type,eventHandle,false)}else if(elem.attachEvent){elem.attachEvent("on"+type,eventHandle)}}}if(special.add){special.add.call(elem,handleObj);if(!handleObj.handler.guid){handleObj.handler.guid=handler.guid}}if(selector){handlers.splice(handlers.delegateCount++,0,handleObj)}else{handlers.push(handleObj)}jQuery.event.global[type]=true}elem=null},global:{},remove:function(elem,types,handler,selector,mappedTypes){var t,tns,type,origType,namespaces,origCount,j,events,special,eventType,handleObj,elemData=jQuery.hasData(elem)&&jQuery._data(elem);if(!elemData||!(events=elemData.events)){return}types=jQuery.trim(hoverHack(types||"")).split(" ");for(t=0;t<types.length;t++){tns=rtypenamespace.exec(types[t])||[];type=origType=tns[1];namespaces=tns[2];if(!type){for(type in events){jQuery.event.remove(elem,type+types[t],handler,selector,true)}continue}special=jQuery.event.special[type]||{};type=(selector?special.delegateType:special.bindType)||type;eventType=events[type]||[];origCount=eventType.length;namespaces=namespaces?new RegExp("(^|\\.)"+namespaces.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(j=0;j<eventType.length;j++){handleObj=eventType[j];if((mappedTypes||origType===handleObj.origType)&&(!handler||handler.guid===handleObj.guid)&&(!namespaces||namespaces.test(handleObj.namespace))&&(!selector||selector===handleObj.selector||selector==="**"&&handleObj.selector)){eventType.splice(j--,1);if(handleObj.selector){eventType.delegateCount--}if(special.remove){special.remove.call(elem,handleObj)}}}if(eventType.length===0&&origCount!==eventType.length){if(!special.teardown||special.teardown.call(elem,namespaces,elemData.handle)===false){jQuery.removeEvent(elem,type,elemData.handle)}delete events[type]}}if(jQuery.isEmptyObject(events)){delete elemData.handle;jQuery.removeData(elem,"events",true)}},customEvent:{getData:true,setData:true,changeData:true},trigger:function(event,data,elem,onlyHandlers){if(elem&&(elem.nodeType===3||elem.nodeType===8)){return}var cache,exclusive,i,cur,old,ontype,special,handle,eventPath,bubbleType,type=event.type||event,namespaces=[];if(rfocusMorph.test(type+jQuery.event.triggered)){return}if(type.indexOf("!")>=0){type=type.slice(0,-1);exclusive=true}if(type.indexOf(".")>=0){namespaces=type.split(".");type=namespaces.shift();namespaces.sort()}if((!elem||jQuery.event.customEvent[type])&&!jQuery.event.global[type]){return}event=typeof event==="object"?event[jQuery.expando]?event:new jQuery.Event(type,event):new jQuery.Event(type);event.type=type;event.isTrigger=true;event.exclusive=exclusive;event.namespace=namespaces.join(".");event.namespace_re=event.namespace?new RegExp("(^|\\.)"+namespaces.join("\\.(?:.*\\.|)")+"(\\.|$)"):null;ontype=type.indexOf(":")<0?"on"+type:"";if(!elem){cache=jQuery.cache;for(i in cache){if(cache[i].events&&cache[i].events[type]){jQuery.event.trigger(event,data,cache[i].handle.elem,true)}}return}event.result=undefined;if(!event.target){event.target=elem}data=data!=null?jQuery.makeArray(data):[];data.unshift(event);special=jQuery.event.special[type]||{};if(special.trigger&&special.trigger.apply(elem,data)===false){return}eventPath=[[elem,special.bindType||type]];if(!onlyHandlers&&!special.noBubble&&!jQuery.isWindow(elem)){bubbleType=special.delegateType||type;cur=rfocusMorph.test(bubbleType+type)?elem:elem.parentNode;for(old=elem;cur;cur=cur.parentNode){eventPath.push([cur,bubbleType]);old=cur}if(old===(elem.ownerDocument||document)){eventPath.push([old.defaultView||old.parentWindow||window,bubbleType])}}for(i=0;i<eventPath.length&&!event.isPropagationStopped();i++){cur=eventPath[i][0];event.type=eventPath[i][1];handle=(jQuery._data(cur,"events")||{})[event.type]&&jQuery._data(cur,"handle");if(handle){handle.apply(cur,data)}handle=ontype&&cur[ontype];if(handle&&jQuery.acceptData(cur)&&handle.apply&&handle.apply(cur,data)===false){event.preventDefault()}}event.type=type;if(!onlyHandlers&&!event.isDefaultPrevented()){if((!special._default||special._default.apply(elem.ownerDocument,data)===false)&&!(type==="click"&&jQuery.nodeName(elem,"a"))&&jQuery.acceptData(elem)){if(ontype&&elem[type]&&(type!=="focus"&&type!=="blur"||event.target.offsetWidth!==0)&&!jQuery.isWindow(elem)){old=elem[ontype];if(old){elem[ontype]=null}jQuery.event.triggered=type;elem[type]();jQuery.event.triggered=undefined;if(old){elem[ontype]=old}}}}return event.result},dispatch:function(event){event=jQuery.event.fix(event||window.event);var i,j,cur,ret,selMatch,matched,matches,handleObj,sel,related,handlers=(jQuery._data(this,"events")||{})[event.type]||[],delegateCount=handlers.delegateCount,args=core_slice.call(arguments),run_all=!event.exclusive&&!event.namespace,special=jQuery.event.special[event.type]||{},handlerQueue=[];args[0]=event;event.delegateTarget=this;if(special.preDispatch&&special.preDispatch.call(this,event)===false){return}if(delegateCount&&!(event.button&&event.type==="click")){for(cur=event.target;cur!=this;cur=cur.parentNode||this){if(cur.disabled!==true||event.type!=="click"){selMatch={};matches=[];for(i=0;i<delegateCount;i++){handleObj=handlers[i];sel=handleObj.selector;if(selMatch[sel]===undefined){selMatch[sel]=handleObj.needsContext?jQuery(sel,this).index(cur)>=0:jQuery.find(sel,this,null,[cur]).length}if(selMatch[sel]){matches.push(handleObj)}}if(matches.length){handlerQueue.push({elem:cur,matches:matches})}}}}if(handlers.length>delegateCount){handlerQueue.push({elem:this,matches:handlers.slice(delegateCount)})}for(i=0;i<handlerQueue.length&&!event.isPropagationStopped();i++){matched=handlerQueue[i];event.currentTarget=matched.elem;for(j=0;j<matched.matches.length&&!event.isImmediatePropagationStopped();j++){handleObj=matched.matches[j];if(run_all||!event.namespace&&!handleObj.namespace||event.namespace_re&&event.namespace_re.test(handleObj.namespace)){event.data=handleObj.data;event.handleObj=handleObj;ret=((jQuery.event.special[handleObj.origType]||{}).handle||handleObj.handler).apply(matched.elem,args);if(ret!==undefined){event.result=ret;if(ret===false){event.preventDefault();event.stopPropagation()}}}}}if(special.postDispatch){special.postDispatch.call(this,event)}return event.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(event,original){if(event.which==null){event.which=original.charCode!=null?original.charCode:original.keyCode}return event}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(event,original){var eventDoc,doc,body,button=original.button,fromElement=original.fromElement;if(event.pageX==null&&original.clientX!=null){eventDoc=event.target.ownerDocument||document;doc=eventDoc.documentElement;body=eventDoc.body;event.pageX=original.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc&&doc.clientLeft||body&&body.clientLeft||0);event.pageY=original.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc&&doc.clientTop||body&&body.clientTop||0)}if(!event.relatedTarget&&fromElement){event.relatedTarget=fromElement===event.target?original.toElement:fromElement}if(!event.which&&button!==undefined){event.which=button&1?1:button&2?3:button&4?2:0}return event}},fix:function(event){if(event[jQuery.expando]){return event}var i,prop,originalEvent=event,fixHook=jQuery.event.fixHooks[event.type]||{},copy=fixHook.props?this.props.concat(fixHook.props):this.props;event=jQuery.Event(originalEvent);for(i=copy.length;i;){prop=copy[--i];event[prop]=originalEvent[prop]}if(!event.target){event.target=originalEvent.srcElement||document}if(event.target.nodeType===3){event.target=event.target.parentNode}event.metaKey=!!event.metaKey;return fixHook.filter?fixHook.filter(event,originalEvent):event},special:{load:{noBubble:true},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(data,namespaces,eventHandle){if(jQuery.isWindow(this)){this.onbeforeunload=eventHandle}},teardown:function(namespaces,eventHandle){if(this.onbeforeunload===eventHandle){this.onbeforeunload=null}}}},simulate:function(type,elem,event,bubble){var e=jQuery.extend(new jQuery.Event,event,{type:type,isSimulated:true,originalEvent:{}});if(bubble){jQuery.event.trigger(e,null,elem)}else{jQuery.event.dispatch.call(elem,e)}if(e.isDefaultPrevented()){event.preventDefault()}}};jQuery.event.handle=jQuery.event.dispatch;jQuery.removeEvent=document.removeEventListener?function(elem,type,handle){if(elem.removeEventListener){elem.removeEventListener(type,handle,false)}}:function(elem,type,handle){var name="on"+type;if(elem.detachEvent){if(typeof elem[name]==="undefined"){elem[name]=null}elem.detachEvent(name,handle)}};jQuery.Event=function(src,props){if(!(this instanceof jQuery.Event)){return new jQuery.Event(src,props)}if(src&&src.type){this.originalEvent=src;this.type=src.type;this.isDefaultPrevented=src.defaultPrevented||src.returnValue===false||src.getPreventDefault&&src.getPreventDefault()?returnTrue:returnFalse}else{this.type=src}if(props){jQuery.extend(this,props)}this.timeStamp=src&&src.timeStamp||jQuery.now();this[jQuery.expando]=true};function returnFalse(){return false}function returnTrue(){return true}jQuery.Event.prototype={preventDefault:function(){this.isDefaultPrevented=returnTrue;var e=this.originalEvent;if(!e){return}if(e.preventDefault){e.preventDefault()}else{e.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=returnTrue;var e=this.originalEvent;if(!e){return}if(e.stopPropagation){e.stopPropagation()}e.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=returnTrue;this.stopPropagation()},isDefaultPrevented:returnFalse,isPropagationStopped:returnFalse,isImmediatePropagationStopped:returnFalse};jQuery.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(orig,fix){jQuery.event.special[orig]={delegateType:fix,bindType:fix,handle:function(event){var ret,target=this,related=event.relatedTarget,handleObj=event.handleObj,selector=handleObj.selector;if(!related||related!==target&&!jQuery.contains(target,related)){event.type=handleObj.origType;ret=handleObj.handler.apply(this,arguments);event.type=fix}return ret}}});if(!jQuery.support.submitBubbles){jQuery.event.special.submit={setup:function(){if(jQuery.nodeName(this,"form")){return false}jQuery.event.add(this,"click._submit keypress._submit",function(e){var elem=e.target,form=jQuery.nodeName(elem,"input")||jQuery.nodeName(elem,"button")?elem.form:undefined;if(form&&!jQuery._data(form,"_submit_attached")){jQuery.event.add(form,"submit._submit",function(event){event._submit_bubble=true});jQuery._data(form,"_submit_attached",true)}})},postDispatch:function(event){if(event._submit_bubble){delete event._submit_bubble;if(this.parentNode&&!event.isTrigger){jQuery.event.simulate("submit",this.parentNode,event,true)}}},teardown:function(){if(jQuery.nodeName(this,"form")){return false}jQuery.event.remove(this,"._submit")}}}if(!jQuery.support.changeBubbles){jQuery.event.special.change={setup:function(){if(rformElems.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio"){jQuery.event.add(this,"propertychange._change",function(event){if(event.originalEvent.propertyName==="checked"){this._just_changed=true}});jQuery.event.add(this,"click._change",function(event){if(this._just_changed&&!event.isTrigger){this._just_changed=false}jQuery.event.simulate("change",this,event,true)})}return false}jQuery.event.add(this,"beforeactivate._change",function(e){var elem=e.target;if(rformElems.test(elem.nodeName)&&!jQuery._data(elem,"_change_attached")){jQuery.event.add(elem,"change._change",function(event){if(this.parentNode&&!event.isSimulated&&!event.isTrigger){jQuery.event.simulate("change",this.parentNode,event,true)}});jQuery._data(elem,"_change_attached",true)}})},handle:function(event){var elem=event.target;if(this!==elem||event.isSimulated||event.isTrigger||elem.type!=="radio"&&elem.type!=="checkbox"){return event.handleObj.handler.apply(this,arguments)}},teardown:function(){jQuery.event.remove(this,"._change");return!rformElems.test(this.nodeName)}}}if(!jQuery.support.focusinBubbles){jQuery.each({focus:"focusin",blur:"focusout"},function(orig,fix){var attaches=0,handler=function(event){jQuery.event.simulate(fix,event.target,jQuery.event.fix(event),true)};jQuery.event.special[fix]={setup:function(){if(attaches++===0){document.addEventListener(orig,handler,true)}},teardown:function(){if(--attaches===0){document.removeEventListener(orig,handler,true)}}}})}jQuery.fn.extend({on:function(types,selector,data,fn,one){var origFn,type;if(typeof types==="object"){if(typeof selector!=="string"){data=data||selector;selector=undefined}for(type in types){this.on(type,selector,data,types[type],one)}return this}if(data==null&&fn==null){fn=selector;data=selector=undefined}else if(fn==null){if(typeof selector==="string"){fn=data;data=undefined}else{fn=data;data=selector;selector=undefined}}if(fn===false){fn=returnFalse}else if(!fn){return this}if(one===1){origFn=fn;fn=function(event){jQuery().off(event);return origFn.apply(this,arguments)};fn.guid=origFn.guid||(origFn.guid=jQuery.guid++)}return this.each(function(){jQuery.event.add(this,types,fn,data,selector)})},one:function(types,selector,data,fn){return this.on(types,selector,data,fn,1)},off:function(types,selector,fn){var handleObj,type;if(types&&types.preventDefault&&types.handleObj){handleObj=types.handleObj;jQuery(types.delegateTarget).off(handleObj.namespace?handleObj.origType+"."+handleObj.namespace:handleObj.origType,handleObj.selector,handleObj.handler);return this}if(typeof types==="object"){for(type in types){this.off(type,selector,types[type])}return this}if(selector===false||typeof selector==="function"){fn=selector;selector=undefined}if(fn===false){fn=returnFalse}return this.each(function(){jQuery.event.remove(this,types,fn,selector)})},bind:function(types,data,fn){return this.on(types,null,data,fn)},unbind:function(types,fn){return this.off(types,null,fn)},live:function(types,data,fn){jQuery(this.context).on(types,this.selector,data,fn);return this},die:function(types,fn){jQuery(this.context).off(types,this.selector||"**",fn);return this},delegate:function(selector,types,data,fn){return this.on(types,selector,data,fn)},undelegate:function(selector,types,fn){return arguments.length===1?this.off(selector,"**"):this.off(types,selector||"**",fn)},trigger:function(type,data){return this.each(function(){jQuery.event.trigger(type,data,this)})},triggerHandler:function(type,data){if(this[0]){return jQuery.event.trigger(type,data,this[0],true)}},toggle:function(fn){var args=arguments,guid=fn.guid||jQuery.guid++,i=0,toggler=function(event){var lastToggle=(jQuery._data(this,"lastToggle"+fn.guid)||0)%i;jQuery._data(this,"lastToggle"+fn.guid,lastToggle+1);event.preventDefault();return args[lastToggle].apply(this,arguments)||false};toggler.guid=guid;while(i<args.length){args[i++].guid=guid}return this.click(toggler)},hover:function(fnOver,fnOut){return this.mouseenter(fnOver).mouseleave(fnOut||fnOver)}});jQuery.each(("blur focus focusin focusout load resize scroll unload click dblclick "+"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave "+"change select submit keydown keypress keyup error contextmenu").split(" "),function(i,name){jQuery.fn[name]=function(data,fn){if(fn==null){fn=data;data=null}return arguments.length>0?this.on(name,null,data,fn):this.trigger(name)};if(rkeyEvent.test(name)){jQuery.event.fixHooks[name]=jQuery.event.keyHooks}if(rmouseEvent.test(name)){jQuery.event.fixHooks[name]=jQuery.event.mouseHooks}});(function(window,undefined){var cachedruns,assertGetIdNotName,Expr,getText,isXML,contains,compile,sortOrder,hasDuplicate,outermostContext,baseHasDuplicate=true,strundefined="undefined",expando=("sizcache"+Math.random()).replace(".",""),Token=String,document=window.document,docElem=document.documentElement,dirruns=0,done=0,pop=[].pop,push=[].push,slice=[].slice,indexOf=[].indexOf||function(elem){var i=0,len=this.length;for(;i<len;i++){if(this[i]===elem){return i}}return-1},markFunction=function(fn,value){fn[expando]=value==null||value;return fn},createCache=function(){var cache={},keys=[];return markFunction(function(key,value){if(keys.push(key)>Expr.cacheLength){delete cache[keys.shift()]}return cache[key+" "]=value},cache)},classCache=createCache(),tokenCache=createCache(),compilerCache=createCache(),whitespace="[\\x20\\t\\r\\n\\f]",characterEncoding="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",identifier=characterEncoding.replace("w","w#"),operators="([*^$|!~]?=)",attributes="\\["+whitespace+"*("+characterEncoding+")"+whitespace+"*(?:"+operators+whitespace+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+identifier+")|)|)"+whitespace+"*\\]",pseudos=":("+characterEncoding+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+attributes+")|[^:]|\\\\.)*|.*))\\)|)",pos=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+whitespace+"*((?:-\\d)?\\d*)"+whitespace+"*\\)|)(?=[^-]|$)",rtrim=new RegExp("^"+whitespace+"+|((?:^|[^\\\\])(?:\\\\.)*)"+whitespace+"+$","g"),rcomma=new RegExp("^"+whitespace+"*,"+whitespace+"*"),rcombinators=new RegExp("^"+whitespace+"*([\\x20\\t\\r\\n\\f>+~])"+whitespace+"*"),rpseudo=new RegExp(pseudos),rquickExpr=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,rnot=/^:not/,rsibling=/[\x20\t\r\n\f]*[+~]/,rendsWithNot=/:not\($/,rheader=/h\d/i,rinputs=/input|select|textarea|button/i,rbackslash=/\\(?!\\)/g,matchExpr={ID:new RegExp("^#("+characterEncoding+")"),CLASS:new RegExp("^\\.("+characterEncoding+")"),NAME:new RegExp("^\\[name=['\"]?("+characterEncoding+")['\"]?\\]"),TAG:new RegExp("^("+characterEncoding.replace("w","w*")+")"),ATTR:new RegExp("^"+attributes),PSEUDO:new RegExp("^"+pseudos),POS:new RegExp(pos,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+whitespace+"*(even|odd|(([+-]|)(\\d*)n|)"+whitespace+"*(?:([+-]|)"+whitespace+"*(\\d+)|))"+whitespace+"*\\)|)","i"),needsContext:new RegExp("^"+whitespace+"*[>+~]|"+pos,"i")},assert=function(fn){var div=document.createElement("div");try{return fn(div)}catch(e){return false}finally{div=null}},assertTagNameNoComments=assert(function(div){div.appendChild(document.createComment(""));return!div.getElementsByTagName("*").length}),assertHrefNotNormalized=assert(function(div){div.innerHTML="<a href='#'></a>";return div.firstChild&&typeof div.firstChild.getAttribute!==strundefined&&div.firstChild.getAttribute("href")==="#"}),assertAttributes=assert(function(div){div.innerHTML="<select></select>";var type=typeof div.lastChild.getAttribute("multiple");return type!=="boolean"&&type!=="string"}),assertUsableClassName=assert(function(div){div.innerHTML="<div class='hidden e'></div><div class='hidden'></div>";if(!div.getElementsByClassName||!div.getElementsByClassName("e").length){return false}div.lastChild.className="e";return div.getElementsByClassName("e").length===2}),assertUsableName=assert(function(div){div.id=expando+0;div.innerHTML="<a name='"+expando+"'></a><div name='"+expando+"'></div>";docElem.insertBefore(div,docElem.firstChild);var pass=document.getElementsByName&&document.getElementsByName(expando).length===2+document.getElementsByName(expando+0).length;assertGetIdNotName=!document.getElementById(expando);docElem.removeChild(div);return pass});try{slice.call(docElem.childNodes,0)[0].nodeType}catch(e){slice=function(i){var elem,results=[];for(;elem=this[i];i++){results.push(elem)}return results}}function Sizzle(selector,context,results,seed){results=results||[];context=context||document;var match,elem,xml,m,nodeType=context.nodeType;if(!selector||typeof selector!=="string"){return results}if(nodeType!==1&&nodeType!==9){return[]}xml=isXML(context);if(!xml&&!seed){if(match=rquickExpr.exec(selector)){if(m=match[1]){if(nodeType===9){elem=context.getElementById(m);if(elem&&elem.parentNode){if(elem.id===m){results.push(elem);return results}}else{return results}}else{if(context.ownerDocument&&(elem=context.ownerDocument.getElementById(m))&&contains(context,elem)&&elem.id===m){results.push(elem);return results}}}else if(match[2]){push.apply(results,slice.call(context.getElementsByTagName(selector),0));return results}else if((m=match[3])&&assertUsableClassName&&context.getElementsByClassName){push.apply(results,slice.call(context.getElementsByClassName(m),0));return results}}}return select(selector.replace(rtrim,"$1"),context,results,seed,xml)}Sizzle.matches=function(expr,elements){return Sizzle(expr,null,null,elements)};Sizzle.matchesSelector=function(elem,expr){return Sizzle(expr,null,null,[elem]).length>0};function createInputPseudo(type){return function(elem){var name=elem.nodeName.toLowerCase();return name==="input"&&elem.type===type}}function createButtonPseudo(type){return function(elem){var name=elem.nodeName.toLowerCase();return(name==="input"||name==="button")&&elem.type===type}}function createPositionalPseudo(fn){return markFunction(function(argument){argument=+argument;return markFunction(function(seed,matches){var j,matchIndexes=fn([],seed.length,argument),i=matchIndexes.length;while(i--){if(seed[j=matchIndexes[i]]){seed[j]=!(matches[j]=seed[j])}}})})}getText=Sizzle.getText=function(elem){var node,ret="",i=0,nodeType=elem.nodeType;if(nodeType){if(nodeType===1||nodeType===9||nodeType===11){if(typeof elem.textContent==="string"){return elem.textContent}else{for(elem=elem.firstChild;elem;elem=elem.nextSibling){ret+=getText(elem)}}}else if(nodeType===3||nodeType===4){return elem.nodeValue}}else{for(;node=elem[i];i++){ret+=getText(node)}}return ret};isXML=Sizzle.isXML=function(elem){var documentElement=elem&&(elem.ownerDocument||elem).documentElement;return documentElement?documentElement.nodeName!=="HTML":false};contains=Sizzle.contains=docElem.contains?function(a,b){var adown=a.nodeType===9?a.documentElement:a,bup=b&&b.parentNode;return a===bup||!!(bup&&bup.nodeType===1&&adown.contains&&adown.contains(bup))}:docElem.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode){if(b===a){return true}}return false};Sizzle.attr=function(elem,name){var val,xml=isXML(elem);if(!xml){name=name.toLowerCase()}if(val=Expr.attrHandle[name]){return val(elem)}if(xml||assertAttributes){return elem.getAttribute(name)}val=elem.getAttributeNode(name);return val?typeof elem[name]==="boolean"?elem[name]?name:null:val.specified?val.value:null:null};Expr=Sizzle.selectors={cacheLength:50,createPseudo:markFunction,match:matchExpr,attrHandle:assertHrefNotNormalized?{}:{href:function(elem){return elem.getAttribute("href",2)},type:function(elem){return elem.getAttribute("type")}},find:{ID:assertGetIdNotName?function(id,context,xml){if(typeof context.getElementById!==strundefined&&!xml){var m=context.getElementById(id);return m&&m.parentNode?[m]:[]}}:function(id,context,xml){if(typeof context.getElementById!==strundefined&&!xml){var m=context.getElementById(id);return m?m.id===id||typeof m.getAttributeNode!==strundefined&&m.getAttributeNode("id").value===id?[m]:undefined:[]}},TAG:assertTagNameNoComments?function(tag,context){if(typeof context.getElementsByTagName!==strundefined){return context.getElementsByTagName(tag)}}:function(tag,context){var results=context.getElementsByTagName(tag);if(tag==="*"){var elem,tmp=[],i=0;for(;elem=results[i];i++){if(elem.nodeType===1){tmp.push(elem)}}return tmp}return results},NAME:assertUsableName&&function(tag,context){if(typeof context.getElementsByName!==strundefined){return context.getElementsByName(name)}},CLASS:assertUsableClassName&&function(className,context,xml){if(typeof context.getElementsByClassName!==strundefined&&!xml){return context.getElementsByClassName(className)}}},relative:{">":{dir:"parentNode",first:true}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:true},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(match){match[1]=match[1].replace(rbackslash,"");match[3]=(match[4]||match[5]||"").replace(rbackslash,"");if(match[2]==="~="){match[3]=" "+match[3]+" "}return match.slice(0,4)},CHILD:function(match){match[1]=match[1].toLowerCase();if(match[1]==="nth"){if(!match[2]){Sizzle.error(match[0])}match[3]=+(match[3]?match[4]+(match[5]||1):2*(match[2]==="even"||match[2]==="odd"));match[4]=+(match[6]+match[7]||match[2]==="odd")}else if(match[2]){Sizzle.error(match[0])}return match},PSEUDO:function(match){var unquoted,excess;if(matchExpr["CHILD"].test(match[0])){return null}if(match[3]){match[2]=match[3]}else if(unquoted=match[4]){if(rpseudo.test(unquoted)&&(excess=tokenize(unquoted,true))&&(excess=unquoted.indexOf(")",unquoted.length-excess)-unquoted.length)){unquoted=unquoted.slice(0,excess);match[0]=match[0].slice(0,excess)}match[2]=unquoted}return match.slice(0,3)}},filter:{ID:assertGetIdNotName?function(id){id=id.replace(rbackslash,"");return function(elem){return elem.getAttribute("id")===id}}:function(id){id=id.replace(rbackslash,"");return function(elem){var node=typeof elem.getAttributeNode!==strundefined&&elem.getAttributeNode("id");return node&&node.value===id}},TAG:function(nodeName){if(nodeName==="*"){return function(){return true}}nodeName=nodeName.replace(rbackslash,"").toLowerCase();return function(elem){return elem.nodeName&&elem.nodeName.toLowerCase()===nodeName}},CLASS:function(className){var pattern=classCache[expando][className+" "];return pattern||(pattern=new RegExp("(^|"+whitespace+")"+className+"("+whitespace+"|$)"))&&classCache(className,function(elem){return pattern.test(elem.className||typeof elem.getAttribute!==strundefined&&elem.getAttribute("class")||"")})},ATTR:function(name,operator,check){return function(elem,context){var result=Sizzle.attr(elem,name);if(result==null){return operator==="!="}if(!operator){return true}result+="";return operator==="="?result===check:operator==="!="?result!==check:operator==="^="?check&&result.indexOf(check)===0:operator==="*="?check&&result.indexOf(check)>-1:operator==="$="?check&&result.substr(result.length-check.length)===check:operator==="~="?(" "+result+" ").indexOf(check)>-1:operator==="|="?result===check||result.substr(0,check.length+1)===check+"-":false}},CHILD:function(type,argument,first,last){if(type==="nth"){return function(elem){var node,diff,parent=elem.parentNode;if(first===1&&last===0){return true}if(parent){diff=0;for(node=parent.firstChild;node;node=node.nextSibling){if(node.nodeType===1){diff++;
+if(elem===node){break}}}}diff-=last;return diff===first||diff%first===0&&diff/first>=0}}return function(elem){var node=elem;switch(type){case"only":case"first":while(node=node.previousSibling){if(node.nodeType===1){return false}}if(type==="first"){return true}node=elem;case"last":while(node=node.nextSibling){if(node.nodeType===1){return false}}return true}}},PSEUDO:function(pseudo,argument){var args,fn=Expr.pseudos[pseudo]||Expr.setFilters[pseudo.toLowerCase()]||Sizzle.error("unsupported pseudo: "+pseudo);if(fn[expando]){return fn(argument)}if(fn.length>1){args=[pseudo,pseudo,"",argument];return Expr.setFilters.hasOwnProperty(pseudo.toLowerCase())?markFunction(function(seed,matches){var idx,matched=fn(seed,argument),i=matched.length;while(i--){idx=indexOf.call(seed,matched[i]);seed[idx]=!(matches[idx]=matched[i])}}):function(elem){return fn(elem,0,args)}}return fn}},pseudos:{not:markFunction(function(selector){var input=[],results=[],matcher=compile(selector.replace(rtrim,"$1"));return matcher[expando]?markFunction(function(seed,matches,context,xml){var elem,unmatched=matcher(seed,null,xml,[]),i=seed.length;while(i--){if(elem=unmatched[i]){seed[i]=!(matches[i]=elem)}}}):function(elem,context,xml){input[0]=elem;matcher(input,null,xml,results);return!results.pop()}}),has:markFunction(function(selector){return function(elem){return Sizzle(selector,elem).length>0}}),contains:markFunction(function(text){return function(elem){return(elem.textContent||elem.innerText||getText(elem)).indexOf(text)>-1}}),enabled:function(elem){return elem.disabled===false},disabled:function(elem){return elem.disabled===true},checked:function(elem){var nodeName=elem.nodeName.toLowerCase();return nodeName==="input"&&!!elem.checked||nodeName==="option"&&!!elem.selected},selected:function(elem){if(elem.parentNode){elem.parentNode.selectedIndex}return elem.selected===true},parent:function(elem){return!Expr.pseudos["empty"](elem)},empty:function(elem){var nodeType;elem=elem.firstChild;while(elem){if(elem.nodeName>"@"||(nodeType=elem.nodeType)===3||nodeType===4){return false}elem=elem.nextSibling}return true},header:function(elem){return rheader.test(elem.nodeName)},text:function(elem){var type,attr;return elem.nodeName.toLowerCase()==="input"&&(type=elem.type)==="text"&&((attr=elem.getAttribute("type"))==null||attr.toLowerCase()===type)},radio:createInputPseudo("radio"),checkbox:createInputPseudo("checkbox"),file:createInputPseudo("file"),password:createInputPseudo("password"),image:createInputPseudo("image"),submit:createButtonPseudo("submit"),reset:createButtonPseudo("reset"),button:function(elem){var name=elem.nodeName.toLowerCase();return name==="input"&&elem.type==="button"||name==="button"},input:function(elem){return rinputs.test(elem.nodeName)},focus:function(elem){var doc=elem.ownerDocument;return elem===doc.activeElement&&(!doc.hasFocus||doc.hasFocus())&&!!(elem.type||elem.href||~elem.tabIndex)},active:function(elem){return elem===elem.ownerDocument.activeElement},first:createPositionalPseudo(function(){return[0]}),last:createPositionalPseudo(function(matchIndexes,length){return[length-1]}),eq:createPositionalPseudo(function(matchIndexes,length,argument){return[argument<0?argument+length:argument]}),even:createPositionalPseudo(function(matchIndexes,length){for(var i=0;i<length;i+=2){matchIndexes.push(i)}return matchIndexes}),odd:createPositionalPseudo(function(matchIndexes,length){for(var i=1;i<length;i+=2){matchIndexes.push(i)}return matchIndexes}),lt:createPositionalPseudo(function(matchIndexes,length,argument){for(var i=argument<0?argument+length:argument;--i>=0;){matchIndexes.push(i)}return matchIndexes}),gt:createPositionalPseudo(function(matchIndexes,length,argument){for(var i=argument<0?argument+length:argument;++i<length;){matchIndexes.push(i)}return matchIndexes})}};function siblingCheck(a,b,ret){if(a===b){return ret}var cur=a.nextSibling;while(cur){if(cur===b){return-1}cur=cur.nextSibling}return 1}sortOrder=docElem.compareDocumentPosition?function(a,b){if(a===b){hasDuplicate=true;return 0}return(!a.compareDocumentPosition||!b.compareDocumentPosition?a.compareDocumentPosition:a.compareDocumentPosition(b)&4)?-1:1}:function(a,b){if(a===b){hasDuplicate=true;return 0}else if(a.sourceIndex&&b.sourceIndex){return a.sourceIndex-b.sourceIndex}var al,bl,ap=[],bp=[],aup=a.parentNode,bup=b.parentNode,cur=aup;if(aup===bup){return siblingCheck(a,b)}else if(!aup){return-1}else if(!bup){return 1}while(cur){ap.unshift(cur);cur=cur.parentNode}cur=bup;while(cur){bp.unshift(cur);cur=cur.parentNode}al=ap.length;bl=bp.length;for(var i=0;i<al&&i<bl;i++){if(ap[i]!==bp[i]){return siblingCheck(ap[i],bp[i])}}return i===al?siblingCheck(a,bp[i],-1):siblingCheck(ap[i],b,1)};[0,0].sort(sortOrder);baseHasDuplicate=!hasDuplicate;Sizzle.uniqueSort=function(results){var elem,duplicates=[],i=1,j=0;hasDuplicate=baseHasDuplicate;results.sort(sortOrder);if(hasDuplicate){for(;elem=results[i];i++){if(elem===results[i-1]){j=duplicates.push(i)}}while(j--){results.splice(duplicates[j],1)}}return results};Sizzle.error=function(msg){throw new Error("Syntax error, unrecognized expression: "+msg)};function tokenize(selector,parseOnly){var matched,match,tokens,type,soFar,groups,preFilters,cached=tokenCache[expando][selector+" "];if(cached){return parseOnly?0:cached.slice(0)}soFar=selector;groups=[];preFilters=Expr.preFilter;while(soFar){if(!matched||(match=rcomma.exec(soFar))){if(match){soFar=soFar.slice(match[0].length)||soFar}groups.push(tokens=[])}matched=false;if(match=rcombinators.exec(soFar)){tokens.push(matched=new Token(match.shift()));soFar=soFar.slice(matched.length);matched.type=match[0].replace(rtrim," ")}for(type in Expr.filter){if((match=matchExpr[type].exec(soFar))&&(!preFilters[type]||(match=preFilters[type](match)))){tokens.push(matched=new Token(match.shift()));soFar=soFar.slice(matched.length);matched.type=type;matched.matches=match}}if(!matched){break}}return parseOnly?soFar.length:soFar?Sizzle.error(selector):tokenCache(selector,groups).slice(0)}function addCombinator(matcher,combinator,base){var dir=combinator.dir,checkNonElements=base&&combinator.dir==="parentNode",doneName=done++;return combinator.first?function(elem,context,xml){while(elem=elem[dir]){if(checkNonElements||elem.nodeType===1){return matcher(elem,context,xml)}}}:function(elem,context,xml){if(!xml){var cache,dirkey=dirruns+" "+doneName+" ",cachedkey=dirkey+cachedruns;while(elem=elem[dir]){if(checkNonElements||elem.nodeType===1){if((cache=elem[expando])===cachedkey){return elem.sizset}else if(typeof cache==="string"&&cache.indexOf(dirkey)===0){if(elem.sizset){return elem}}else{elem[expando]=cachedkey;if(matcher(elem,context,xml)){elem.sizset=true;return elem}elem.sizset=false}}}}else{while(elem=elem[dir]){if(checkNonElements||elem.nodeType===1){if(matcher(elem,context,xml)){return elem}}}}}}function elementMatcher(matchers){return matchers.length>1?function(elem,context,xml){var i=matchers.length;while(i--){if(!matchers[i](elem,context,xml)){return false}}return true}:matchers[0]}function condense(unmatched,map,filter,context,xml){var elem,newUnmatched=[],i=0,len=unmatched.length,mapped=map!=null;for(;i<len;i++){if(elem=unmatched[i]){if(!filter||filter(elem,context,xml)){newUnmatched.push(elem);if(mapped){map.push(i)}}}}return newUnmatched}function setMatcher(preFilter,selector,matcher,postFilter,postFinder,postSelector){if(postFilter&&!postFilter[expando]){postFilter=setMatcher(postFilter)}if(postFinder&&!postFinder[expando]){postFinder=setMatcher(postFinder,postSelector)}return markFunction(function(seed,results,context,xml){var temp,i,elem,preMap=[],postMap=[],preexisting=results.length,elems=seed||multipleContexts(selector||"*",context.nodeType?[context]:context,[]),matcherIn=preFilter&&(seed||!selector)?condense(elems,preMap,preFilter,context,xml):elems,matcherOut=matcher?postFinder||(seed?preFilter:preexisting||postFilter)?[]:results:matcherIn;if(matcher){matcher(matcherIn,matcherOut,context,xml)}if(postFilter){temp=condense(matcherOut,postMap);postFilter(temp,[],context,xml);i=temp.length;while(i--){if(elem=temp[i]){matcherOut[postMap[i]]=!(matcherIn[postMap[i]]=elem)}}}if(seed){if(postFinder||preFilter){if(postFinder){temp=[];i=matcherOut.length;while(i--){if(elem=matcherOut[i]){temp.push(matcherIn[i]=elem)}}postFinder(null,matcherOut=[],temp,xml)}i=matcherOut.length;while(i--){if((elem=matcherOut[i])&&(temp=postFinder?indexOf.call(seed,elem):preMap[i])>-1){seed[temp]=!(results[temp]=elem)}}}}else{matcherOut=condense(matcherOut===results?matcherOut.splice(preexisting,matcherOut.length):matcherOut);if(postFinder){postFinder(null,results,matcherOut,xml)}else{push.apply(results,matcherOut)}}})}function matcherFromTokens(tokens){var checkContext,matcher,j,len=tokens.length,leadingRelative=Expr.relative[tokens[0].type],implicitRelative=leadingRelative||Expr.relative[" "],i=leadingRelative?1:0,matchContext=addCombinator(function(elem){return elem===checkContext},implicitRelative,true),matchAnyContext=addCombinator(function(elem){return indexOf.call(checkContext,elem)>-1},implicitRelative,true),matchers=[function(elem,context,xml){return!leadingRelative&&(xml||context!==outermostContext)||((checkContext=context).nodeType?matchContext(elem,context,xml):matchAnyContext(elem,context,xml))}];for(;i<len;i++){if(matcher=Expr.relative[tokens[i].type]){matchers=[addCombinator(elementMatcher(matchers),matcher)]}else{matcher=Expr.filter[tokens[i].type].apply(null,tokens[i].matches);if(matcher[expando]){j=++i;for(;j<len;j++){if(Expr.relative[tokens[j].type]){break}}return setMatcher(i>1&&elementMatcher(matchers),i>1&&tokens.slice(0,i-1).join("").replace(rtrim,"$1"),matcher,i<j&&matcherFromTokens(tokens.slice(i,j)),j<len&&matcherFromTokens(tokens=tokens.slice(j)),j<len&&tokens.join(""))}matchers.push(matcher)}}return elementMatcher(matchers)}function matcherFromGroupMatchers(elementMatchers,setMatchers){var bySet=setMatchers.length>0,byElement=elementMatchers.length>0,superMatcher=function(seed,context,xml,results,expandContext){var elem,j,matcher,setMatched=[],matchedCount=0,i="0",unmatched=seed&&[],outermost=expandContext!=null,contextBackup=outermostContext,elems=seed||byElement&&Expr.find["TAG"]("*",expandContext&&context.parentNode||context),dirrunsUnique=dirruns+=contextBackup==null?1:Math.E;if(outermost){outermostContext=context!==document&&context;cachedruns=superMatcher.el}for(;(elem=elems[i])!=null;i++){if(byElement&&elem){for(j=0;matcher=elementMatchers[j];j++){if(matcher(elem,context,xml)){results.push(elem);break}}if(outermost){dirruns=dirrunsUnique;cachedruns=++superMatcher.el}}if(bySet){if(elem=!matcher&&elem){matchedCount--}if(seed){unmatched.push(elem)}}}matchedCount+=i;if(bySet&&i!==matchedCount){for(j=0;matcher=setMatchers[j];j++){matcher(unmatched,setMatched,context,xml)}if(seed){if(matchedCount>0){while(i--){if(!(unmatched[i]||setMatched[i])){setMatched[i]=pop.call(results)}}}setMatched=condense(setMatched)}push.apply(results,setMatched);if(outermost&&!seed&&setMatched.length>0&&matchedCount+setMatchers.length>1){Sizzle.uniqueSort(results)}}if(outermost){dirruns=dirrunsUnique;outermostContext=contextBackup}return unmatched};superMatcher.el=0;return bySet?markFunction(superMatcher):superMatcher}compile=Sizzle.compile=function(selector,group){var i,setMatchers=[],elementMatchers=[],cached=compilerCache[expando][selector+" "];if(!cached){if(!group){group=tokenize(selector)}i=group.length;while(i--){cached=matcherFromTokens(group[i]);if(cached[expando]){setMatchers.push(cached)}else{elementMatchers.push(cached)}}cached=compilerCache(selector,matcherFromGroupMatchers(elementMatchers,setMatchers))}return cached};function multipleContexts(selector,contexts,results){var i=0,len=contexts.length;for(;i<len;i++){Sizzle(selector,contexts[i],results)}return results}function select(selector,context,results,seed,xml){var i,tokens,token,type,find,match=tokenize(selector),j=match.length;if(!seed){if(match.length===1){tokens=match[0]=match[0].slice(0);if(tokens.length>2&&(token=tokens[0]).type==="ID"&&context.nodeType===9&&!xml&&Expr.relative[tokens[1].type]){context=Expr.find["ID"](token.matches[0].replace(rbackslash,""),context,xml)[0];if(!context){return results}selector=selector.slice(tokens.shift().length)}for(i=matchExpr["POS"].test(selector)?-1:tokens.length-1;i>=0;i--){token=tokens[i];if(Expr.relative[type=token.type]){break}if(find=Expr.find[type]){if(seed=find(token.matches[0].replace(rbackslash,""),rsibling.test(tokens[0].type)&&context.parentNode||context,xml)){tokens.splice(i,1);selector=seed.length&&tokens.join("");if(!selector){push.apply(results,slice.call(seed,0));return results}break}}}}}compile(selector,match)(seed,context,xml,results,rsibling.test(selector));return results}if(document.querySelectorAll){(function(){var disconnectedMatch,oldSelect=select,rescape=/'|\\/g,rattributeQuotes=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,rbuggyQSA=[":focus"],rbuggyMatches=[":active"],matches=docElem.matchesSelector||docElem.mozMatchesSelector||docElem.webkitMatchesSelector||docElem.oMatchesSelector||docElem.msMatchesSelector;assert(function(div){div.innerHTML="<select><option selected=''></option></select>";if(!div.querySelectorAll("[selected]").length){rbuggyQSA.push("\\["+whitespace+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)")}if(!div.querySelectorAll(":checked").length){rbuggyQSA.push(":checked")}});assert(function(div){div.innerHTML="<p test=''></p>";if(div.querySelectorAll("[test^='']").length){rbuggyQSA.push("[*^$]="+whitespace+"*(?:\"\"|'')")}div.innerHTML="<input type='hidden'/>";if(!div.querySelectorAll(":enabled").length){rbuggyQSA.push(":enabled",":disabled")}});rbuggyQSA=new RegExp(rbuggyQSA.join("|"));select=function(selector,context,results,seed,xml){if(!seed&&!xml&&!rbuggyQSA.test(selector)){var groups,i,old=true,nid=expando,newContext=context,newSelector=context.nodeType===9&&selector;if(context.nodeType===1&&context.nodeName.toLowerCase()!=="object"){groups=tokenize(selector);if(old=context.getAttribute("id")){nid=old.replace(rescape,"\\$&")}else{context.setAttribute("id",nid)}nid="[id='"+nid+"'] ";i=groups.length;while(i--){groups[i]=nid+groups[i].join("")}newContext=rsibling.test(selector)&&context.parentNode||context;newSelector=groups.join(",")}if(newSelector){try{push.apply(results,slice.call(newContext.querySelectorAll(newSelector),0));return results}catch(qsaError){}finally{if(!old){context.removeAttribute("id")}}}}return oldSelect(selector,context,results,seed,xml)};if(matches){assert(function(div){disconnectedMatch=matches.call(div,"div");try{matches.call(div,"[test!='']:sizzle");rbuggyMatches.push("!=",pseudos)}catch(e){}});rbuggyMatches=new RegExp(rbuggyMatches.join("|"));Sizzle.matchesSelector=function(elem,expr){expr=expr.replace(rattributeQuotes,"='$1']");if(!isXML(elem)&&!rbuggyMatches.test(expr)&&!rbuggyQSA.test(expr)){try{var ret=matches.call(elem,expr);if(ret||disconnectedMatch||elem.document&&elem.document.nodeType!==11){return ret}}catch(e){}}return Sizzle(expr,null,null,[elem]).length>0}}})()}Expr.pseudos["nth"]=Expr.pseudos["eq"];function setFilters(){}Expr.filters=setFilters.prototype=Expr.pseudos;Expr.setFilters=new setFilters;Sizzle.attr=jQuery.attr;jQuery.find=Sizzle;jQuery.expr=Sizzle.selectors;jQuery.expr[":"]=jQuery.expr.pseudos;jQuery.unique=Sizzle.uniqueSort;jQuery.text=Sizzle.getText;jQuery.isXMLDoc=Sizzle.isXML;jQuery.contains=Sizzle.contains})(window);var runtil=/Until$/,rparentsprev=/^(?:parents|prev(?:Until|All))/,isSimple=/^.[^:#\[\.,]*$/,rneedsContext=jQuery.expr.match.needsContext,guaranteedUnique={children:true,contents:true,next:true,prev:true};jQuery.fn.extend({find:function(selector){var i,l,length,n,r,ret,self=this;if(typeof selector!=="string"){return jQuery(selector).filter(function(){for(i=0,l=self.length;i<l;i++){if(jQuery.contains(self[i],this)){return true}}})}ret=this.pushStack("","find",selector);for(i=0,l=this.length;i<l;i++){length=ret.length;jQuery.find(selector,this[i],ret);if(i>0){for(n=length;n<ret.length;n++){for(r=0;r<length;r++){if(ret[r]===ret[n]){ret.splice(n--,1);break}}}}}return ret},has:function(target){var i,targets=jQuery(target,this),len=targets.length;return this.filter(function(){for(i=0;i<len;i++){if(jQuery.contains(this,targets[i])){return true}}})},not:function(selector){return this.pushStack(winnow(this,selector,false),"not",selector)},filter:function(selector){return this.pushStack(winnow(this,selector,true),"filter",selector)},is:function(selector){return!!selector&&(typeof selector==="string"?rneedsContext.test(selector)?jQuery(selector,this.context).index(this[0])>=0:jQuery.filter(selector,this).length>0:this.filter(selector).length>0)},closest:function(selectors,context){var cur,i=0,l=this.length,ret=[],pos=rneedsContext.test(selectors)||typeof selectors!=="string"?jQuery(selectors,context||this.context):0;for(;i<l;i++){cur=this[i];while(cur&&cur.ownerDocument&&cur!==context&&cur.nodeType!==11){if(pos?pos.index(cur)>-1:jQuery.find.matchesSelector(cur,selectors)){ret.push(cur);break}cur=cur.parentNode}}ret=ret.length>1?jQuery.unique(ret):ret;return this.pushStack(ret,"closest",selectors)},index:function(elem){if(!elem){return this[0]&&this[0].parentNode?this.prevAll().length:-1}if(typeof elem==="string"){return jQuery.inArray(this[0],jQuery(elem))}return jQuery.inArray(elem.jquery?elem[0]:elem,this)},add:function(selector,context){var set=typeof selector==="string"?jQuery(selector,context):jQuery.makeArray(selector&&selector.nodeType?[selector]:selector),all=jQuery.merge(this.get(),set);return this.pushStack(isDisconnected(set[0])||isDisconnected(all[0])?all:jQuery.unique(all))},addBack:function(selector){return this.add(selector==null?this.prevObject:this.prevObject.filter(selector))}});jQuery.fn.andSelf=jQuery.fn.addBack;function isDisconnected(node){return!node||!node.parentNode||node.parentNode.nodeType===11}function sibling(cur,dir){do{cur=cur[dir]}while(cur&&cur.nodeType!==1);return cur}jQuery.each({parent:function(elem){var parent=elem.parentNode;return parent&&parent.nodeType!==11?parent:null},parents:function(elem){return jQuery.dir(elem,"parentNode")},parentsUntil:function(elem,i,until){return jQuery.dir(elem,"parentNode",until)},next:function(elem){return sibling(elem,"nextSibling")},prev:function(elem){return sibling(elem,"previousSibling")},nextAll:function(elem){return jQuery.dir(elem,"nextSibling")},prevAll:function(elem){return jQuery.dir(elem,"previousSibling")},nextUntil:function(elem,i,until){return jQuery.dir(elem,"nextSibling",until)},prevUntil:function(elem,i,until){return jQuery.dir(elem,"previousSibling",until)},siblings:function(elem){return jQuery.sibling((elem.parentNode||{}).firstChild,elem)},children:function(elem){return jQuery.sibling(elem.firstChild)},contents:function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.merge([],elem.childNodes)}},function(name,fn){jQuery.fn[name]=function(until,selector){var ret=jQuery.map(this,fn,until);if(!runtil.test(name)){selector=until}if(selector&&typeof selector==="string"){ret=jQuery.filter(selector,ret)}ret=this.length>1&&!guaranteedUnique[name]?jQuery.unique(ret):ret;if(this.length>1&&rparentsprev.test(name)){ret=ret.reverse()}return this.pushStack(ret,name,core_slice.call(arguments).join(","))}});jQuery.extend({filter:function(expr,elems,not){if(not){expr=":not("+expr+")"}return elems.length===1?jQuery.find.matchesSelector(elems[0],expr)?[elems[0]]:[]:jQuery.find.matches(expr,elems)},dir:function(elem,dir,until){var matched=[],cur=elem[dir];while(cur&&cur.nodeType!==9&&(until===undefined||cur.nodeType!==1||!jQuery(cur).is(until))){if(cur.nodeType===1){matched.push(cur)}cur=cur[dir]}return matched},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType===1&&n!==elem){r.push(n)}}return r}});function winnow(elements,qualifier,keep){qualifier=qualifier||0;if(jQuery.isFunction(qualifier)){return jQuery.grep(elements,function(elem,i){var retVal=!!qualifier.call(elem,i,elem);return retVal===keep})}else if(qualifier.nodeType){return jQuery.grep(elements,function(elem,i){return elem===qualifier===keep})}else if(typeof qualifier==="string"){var filtered=jQuery.grep(elements,function(elem){return elem.nodeType===1});if(isSimple.test(qualifier)){return jQuery.filter(qualifier,filtered,!keep)}else{qualifier=jQuery.filter(qualifier,filtered)}}return jQuery.grep(elements,function(elem,i){return jQuery.inArray(elem,qualifier)>=0===keep})}function createSafeFragment(document){var list=nodeNames.split("|"),safeFrag=document.createDocumentFragment();if(safeFrag.createElement){while(list.length){safeFrag.createElement(list.pop())}}return safeFrag}var nodeNames="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|"+"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",rinlinejQuery=/ jQuery\d+="(?:null|\d+)"/g,rleadingWhitespace=/^\s+/,rxhtmlTag=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,rtagName=/<([\w:]+)/,rtbody=/<tbody/i,rhtml=/<|&#?\w+;/,rnoInnerhtml=/<(?:script|style|link)/i,rnocache=/<(?:script|object|embed|option|style)/i,rnoshimcache=new RegExp("<(?:"+nodeNames+")[\\s/>]","i"),rcheckableType=/^(?:checkbox|radio)$/,rchecked=/checked\s*(?:[^=]|=\s*.checked.)/i,rscriptType=/\/(java|ecma)script/i,rcleanScript=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,wrapMap={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},safeFragment=createSafeFragment(document),fragmentDiv=safeFragment.appendChild(document.createElement("div"));wrapMap.optgroup=wrapMap.option;wrapMap.tbody=wrapMap.tfoot=wrapMap.colgroup=wrapMap.caption=wrapMap.thead;wrapMap.th=wrapMap.td;if(!jQuery.support.htmlSerialize){wrapMap._default=[1,"X<div>","</div>"]}jQuery.fn.extend({text:function(value){return jQuery.access(this,function(value){return value===undefined?jQuery.text(this):this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(value))},null,value,arguments.length)},wrapAll:function(html){if(jQuery.isFunction(html)){return this.each(function(i){jQuery(this).wrapAll(html.call(this,i))})}if(this[0]){var wrap=jQuery(html,this[0].ownerDocument).eq(0).clone(true);if(this[0].parentNode){wrap.insertBefore(this[0])}wrap.map(function(){var elem=this;while(elem.firstChild&&elem.firstChild.nodeType===1){elem=elem.firstChild}return elem}).append(this)}return this},wrapInner:function(html){if(jQuery.isFunction(html)){return this.each(function(i){jQuery(this).wrapInner(html.call(this,i))})}return this.each(function(){var self=jQuery(this),contents=self.contents();if(contents.length){contents.wrapAll(html)}else{self.append(html)}})},wrap:function(html){var isFunction=jQuery.isFunction(html);return this.each(function(i){jQuery(this).wrapAll(isFunction?html.call(this,i):html)})},unwrap:function(){return this.parent().each(function(){if(!jQuery.nodeName(this,"body")){jQuery(this).replaceWith(this.childNodes)}}).end()},append:function(){return this.domManip(arguments,true,function(elem){if(this.nodeType===1||this.nodeType===11){this.appendChild(elem)}})},prepend:function(){return this.domManip(arguments,true,function(elem){if(this.nodeType===1||this.nodeType===11){this.insertBefore(elem,this.firstChild)}})},before:function(){if(!isDisconnected(this[0])){return this.domManip(arguments,false,function(elem){this.parentNode.insertBefore(elem,this)})}if(arguments.length){var set=jQuery.clean(arguments);return this.pushStack(jQuery.merge(set,this),"before",this.selector)}},after:function(){if(!isDisconnected(this[0])){return this.domManip(arguments,false,function(elem){this.parentNode.insertBefore(elem,this.nextSibling)})}if(arguments.length){var set=jQuery.clean(arguments);return this.pushStack(jQuery.merge(this,set),"after",this.selector)}},remove:function(selector,keepData){var elem,i=0;for(;(elem=this[i])!=null;i++){if(!selector||jQuery.filter(selector,[elem]).length){if(!keepData&&elem.nodeType===1){jQuery.cleanData(elem.getElementsByTagName("*"));jQuery.cleanData([elem])}if(elem.parentNode){elem.parentNode.removeChild(elem)}}}return this},empty:function(){var elem,i=0;for(;(elem=this[i])!=null;i++){if(elem.nodeType===1){jQuery.cleanData(elem.getElementsByTagName("*"))}while(elem.firstChild){elem.removeChild(elem.firstChild)}}return this},clone:function(dataAndEvents,deepDataAndEvents){dataAndEvents=dataAndEvents==null?false:dataAndEvents;deepDataAndEvents=deepDataAndEvents==null?dataAndEvents:deepDataAndEvents;return this.map(function(){return jQuery.clone(this,dataAndEvents,deepDataAndEvents)})},html:function(value){return jQuery.access(this,function(value){var elem=this[0]||{},i=0,l=this.length;if(value===undefined){return elem.nodeType===1?elem.innerHTML.replace(rinlinejQuery,""):undefined}if(typeof value==="string"&&!rnoInnerhtml.test(value)&&(jQuery.support.htmlSerialize||!rnoshimcache.test(value))&&(jQuery.support.leadingWhitespace||!rleadingWhitespace.test(value))&&!wrapMap[(rtagName.exec(value)||["",""])[1].toLowerCase()]){value=value.replace(rxhtmlTag,"<$1></$2>");try{for(;i<l;i++){elem=this[i]||{};if(elem.nodeType===1){jQuery.cleanData(elem.getElementsByTagName("*"));elem.innerHTML=value}}elem=0}catch(e){}}if(elem){this.empty().append(value)}},null,value,arguments.length)},replaceWith:function(value){if(!isDisconnected(this[0])){if(jQuery.isFunction(value)){return this.each(function(i){var self=jQuery(this),old=self.html();self.replaceWith(value.call(this,i,old))})}if(typeof value!=="string"){value=jQuery(value).detach()}return this.each(function(){var next=this.nextSibling,parent=this.parentNode;jQuery(this).remove();if(next){jQuery(next).before(value)}else{jQuery(parent).append(value)}})}return this.length?this.pushStack(jQuery(jQuery.isFunction(value)?value():value),"replaceWith",value):this},detach:function(selector){return this.remove(selector,true)},domManip:function(args,table,callback){args=[].concat.apply([],args);var results,first,fragment,iNoClone,i=0,value=args[0],scripts=[],l=this.length;if(!jQuery.support.checkClone&&l>1&&typeof value==="string"&&rchecked.test(value)){return this.each(function(){jQuery(this).domManip(args,table,callback)})}if(jQuery.isFunction(value)){return this.each(function(i){var self=jQuery(this);args[0]=value.call(this,i,table?self.html():undefined);self.domManip(args,table,callback)})}if(this[0]){results=jQuery.buildFragment(args,this,scripts);fragment=results.fragment;first=fragment.firstChild;if(fragment.childNodes.length===1){fragment=first}if(first){table=table&&jQuery.nodeName(first,"tr");for(iNoClone=results.cacheable||l-1;i<l;i++){callback.call(table&&jQuery.nodeName(this[i],"table")?findOrAppend(this[i],"tbody"):this[i],i===iNoClone?fragment:jQuery.clone(fragment,true,true))}}fragment=first=null;if(scripts.length){jQuery.each(scripts,function(i,elem){if(elem.src){if(jQuery.ajax){jQuery.ajax({url:elem.src,type:"GET",dataType:"script",async:false,global:false,"throws":true})}else{jQuery.error("no ajax")}}else{jQuery.globalEval((elem.text||elem.textContent||elem.innerHTML||"").replace(rcleanScript,""))}if(elem.parentNode){elem.parentNode.removeChild(elem)}})}}return this}});function findOrAppend(elem,tag){return elem.getElementsByTagName(tag)[0]||elem.appendChild(elem.ownerDocument.createElement(tag))}function cloneCopyEvent(src,dest){if(dest.nodeType!==1||!jQuery.hasData(src)){return}var type,i,l,oldData=jQuery._data(src),curData=jQuery._data(dest,oldData),events=oldData.events;if(events){delete curData.handle;curData.events={};for(type in events){for(i=0,l=events[type].length;i<l;i++){jQuery.event.add(dest,type,events[type][i])}}}if(curData.data){curData.data=jQuery.extend({},curData.data)}}function cloneFixAttributes(src,dest){var nodeName;if(dest.nodeType!==1){return}if(dest.clearAttributes){dest.clearAttributes()}if(dest.mergeAttributes){dest.mergeAttributes(src)}nodeName=dest.nodeName.toLowerCase();if(nodeName==="object"){if(dest.parentNode){dest.outerHTML=src.outerHTML}if(jQuery.support.html5Clone&&(src.innerHTML&&!jQuery.trim(dest.innerHTML))){dest.innerHTML=src.innerHTML}}else if(nodeName==="input"&&rcheckableType.test(src.type)){dest.defaultChecked=dest.checked=src.checked;if(dest.value!==src.value){dest.value=src.value}}else if(nodeName==="option"){dest.selected=src.defaultSelected}else if(nodeName==="input"||nodeName==="textarea"){dest.defaultValue=src.defaultValue}else if(nodeName==="script"&&dest.text!==src.text){dest.text=src.text}dest.removeAttribute(jQuery.expando)}jQuery.buildFragment=function(args,context,scripts){var fragment,cacheable,cachehit,first=args[0];context=context||document;context=!context.nodeType&&context[0]||context;context=context.ownerDocument||context;if(args.length===1&&typeof first==="string"&&first.length<512&&context===document&&first.charAt(0)==="<"&&!rnocache.test(first)&&(jQuery.support.checkClone||!rchecked.test(first))&&(jQuery.support.html5Clone||!rnoshimcache.test(first))){cacheable=true;fragment=jQuery.fragments[first];cachehit=fragment!==undefined}if(!fragment){fragment=context.createDocumentFragment();jQuery.clean(args,context,fragment,scripts);if(cacheable){jQuery.fragments[first]=cachehit&&fragment}}return{fragment:fragment,cacheable:cacheable}};jQuery.fragments={};jQuery.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(name,original){jQuery.fn[name]=function(selector){var elems,i=0,ret=[],insert=jQuery(selector),l=insert.length,parent=this.length===1&&this[0].parentNode;if((parent==null||parent&&parent.nodeType===11&&parent.childNodes.length===1)&&l===1){insert[original](this[0]);return this}else{for(;i<l;i++){elems=(i>0?this.clone(true):this).get();jQuery(insert[i])[original](elems);ret=ret.concat(elems)}return this.pushStack(ret,name,insert.selector)}}});function getAll(elem){if(typeof elem.getElementsByTagName!=="undefined"){return elem.getElementsByTagName("*")}else if(typeof elem.querySelectorAll!=="undefined"){return elem.querySelectorAll("*")}else{return[]}}function fixDefaultChecked(elem){if(rcheckableType.test(elem.type)){elem.defaultChecked=elem.checked}}jQuery.extend({clone:function(elem,dataAndEvents,deepDataAndEvents){var srcElements,destElements,i,clone;if(jQuery.support.html5Clone||jQuery.isXMLDoc(elem)||!rnoshimcache.test("<"+elem.nodeName+">")){clone=elem.cloneNode(true)}else{fragmentDiv.innerHTML=elem.outerHTML;fragmentDiv.removeChild(clone=fragmentDiv.firstChild)}if((!jQuery.support.noCloneEvent||!jQuery.support.noCloneChecked)&&(elem.nodeType===1||elem.nodeType===11)&&!jQuery.isXMLDoc(elem)){cloneFixAttributes(elem,clone);srcElements=getAll(elem);destElements=getAll(clone);for(i=0;srcElements[i];++i){if(destElements[i]){cloneFixAttributes(srcElements[i],destElements[i])}}}if(dataAndEvents){cloneCopyEvent(elem,clone);if(deepDataAndEvents){srcElements=getAll(elem);destElements=getAll(clone);for(i=0;srcElements[i];++i){cloneCopyEvent(srcElements[i],destElements[i])}}}srcElements=destElements=null;return clone},clean:function(elems,context,fragment,scripts){var i,j,elem,tag,wrap,depth,div,hasBody,tbody,len,handleScript,jsTags,safe=context===document&&safeFragment,ret=[];if(!context||typeof context.createDocumentFragment==="undefined"){context=document}for(i=0;(elem=elems[i])!=null;i++){if(typeof elem==="number"){elem+=""}if(!elem){continue}if(typeof elem==="string"){if(!rhtml.test(elem)){elem=context.createTextNode(elem)}else{safe=safe||createSafeFragment(context);div=context.createElement("div");safe.appendChild(div);elem=elem.replace(rxhtmlTag,"<$1></$2>");tag=(rtagName.exec(elem)||["",""])[1].toLowerCase();wrap=wrapMap[tag]||wrapMap._default;depth=wrap[0];div.innerHTML=wrap[1]+elem+wrap[2];while(depth--){div=div.lastChild
+}if(!jQuery.support.tbody){hasBody=rtbody.test(elem);tbody=tag==="table"&&!hasBody?div.firstChild&&div.firstChild.childNodes:wrap[1]==="<table>"&&!hasBody?div.childNodes:[];for(j=tbody.length-1;j>=0;--j){if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length){tbody[j].parentNode.removeChild(tbody[j])}}}if(!jQuery.support.leadingWhitespace&&rleadingWhitespace.test(elem)){div.insertBefore(context.createTextNode(rleadingWhitespace.exec(elem)[0]),div.firstChild)}elem=div.childNodes;div.parentNode.removeChild(div)}}if(elem.nodeType){ret.push(elem)}else{jQuery.merge(ret,elem)}}if(div){elem=div=safe=null}if(!jQuery.support.appendChecked){for(i=0;(elem=ret[i])!=null;i++){if(jQuery.nodeName(elem,"input")){fixDefaultChecked(elem)}else if(typeof elem.getElementsByTagName!=="undefined"){jQuery.grep(elem.getElementsByTagName("input"),fixDefaultChecked)}}}if(fragment){handleScript=function(elem){if(!elem.type||rscriptType.test(elem.type)){return scripts?scripts.push(elem.parentNode?elem.parentNode.removeChild(elem):elem):fragment.appendChild(elem)}};for(i=0;(elem=ret[i])!=null;i++){if(!(jQuery.nodeName(elem,"script")&&handleScript(elem))){fragment.appendChild(elem);if(typeof elem.getElementsByTagName!=="undefined"){jsTags=jQuery.grep(jQuery.merge([],elem.getElementsByTagName("script")),handleScript);ret.splice.apply(ret,[i+1,0].concat(jsTags));i+=jsTags.length}}}}return ret},cleanData:function(elems,acceptData){var data,id,elem,type,i=0,internalKey=jQuery.expando,cache=jQuery.cache,deleteExpando=jQuery.support.deleteExpando,special=jQuery.event.special;for(;(elem=elems[i])!=null;i++){if(acceptData||jQuery.acceptData(elem)){id=elem[internalKey];data=id&&cache[id];if(data){if(data.events){for(type in data.events){if(special[type]){jQuery.event.remove(elem,type)}else{jQuery.removeEvent(elem,type,data.handle)}}}if(cache[id]){delete cache[id];if(deleteExpando){delete elem[internalKey]}else if(elem.removeAttribute){elem.removeAttribute(internalKey)}else{elem[internalKey]=null}jQuery.deletedIds.push(id)}}}}}});(function(){var matched,browser;jQuery.uaMatch=function(ua){ua=ua.toLowerCase();var match=/(chrome)[ \/]([\w.]+)/.exec(ua)||/(webkit)[ \/]([\w.]+)/.exec(ua)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua)||/(msie) ([\w.]+)/.exec(ua)||ua.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua)||[];return{browser:match[1]||"",version:match[2]||"0"}};matched=jQuery.uaMatch(navigator.userAgent);browser={};if(matched.browser){browser[matched.browser]=true;browser.version=matched.version}if(browser.chrome){browser.webkit=true}else if(browser.webkit){browser.safari=true}jQuery.browser=browser;jQuery.sub=function(){function jQuerySub(selector,context){return new jQuerySub.fn.init(selector,context)}jQuery.extend(true,jQuerySub,this);jQuerySub.superclass=this;jQuerySub.fn=jQuerySub.prototype=this();jQuerySub.fn.constructor=jQuerySub;jQuerySub.sub=this.sub;jQuerySub.fn.init=function init(selector,context){if(context&&context instanceof jQuery&&!(context instanceof jQuerySub)){context=jQuerySub(context)}return jQuery.fn.init.call(this,selector,context,rootjQuerySub)};jQuerySub.fn.init.prototype=jQuerySub.fn;var rootjQuerySub=jQuerySub(document);return jQuerySub}})();var curCSS,iframe,iframeDoc,ralpha=/alpha\([^)]*\)/i,ropacity=/opacity=([^)]*)/,rposition=/^(top|right|bottom|left)$/,rdisplayswap=/^(none|table(?!-c[ea]).+)/,rmargin=/^margin/,rnumsplit=new RegExp("^("+core_pnum+")(.*)$","i"),rnumnonpx=new RegExp("^("+core_pnum+")(?!px)[a-z%]+$","i"),rrelNum=new RegExp("^([-+])=("+core_pnum+")","i"),elemdisplay={BODY:"block"},cssShow={position:"absolute",visibility:"hidden",display:"block"},cssNormalTransform={letterSpacing:0,fontWeight:400},cssExpand=["Top","Right","Bottom","Left"],cssPrefixes=["Webkit","O","Moz","ms"],eventsToggle=jQuery.fn.toggle;function vendorPropName(style,name){if(name in style){return name}var capName=name.charAt(0).toUpperCase()+name.slice(1),origName=name,i=cssPrefixes.length;while(i--){name=cssPrefixes[i]+capName;if(name in style){return name}}return origName}function isHidden(elem,el){elem=el||elem;return jQuery.css(elem,"display")==="none"||!jQuery.contains(elem.ownerDocument,elem)}function showHide(elements,show){var elem,display,values=[],index=0,length=elements.length;for(;index<length;index++){elem=elements[index];if(!elem.style){continue}values[index]=jQuery._data(elem,"olddisplay");if(show){if(!values[index]&&elem.style.display==="none"){elem.style.display=""}if(elem.style.display===""&&isHidden(elem)){values[index]=jQuery._data(elem,"olddisplay",css_defaultDisplay(elem.nodeName))}}else{display=curCSS(elem,"display");if(!values[index]&&display!=="none"){jQuery._data(elem,"olddisplay",display)}}}for(index=0;index<length;index++){elem=elements[index];if(!elem.style){continue}if(!show||elem.style.display==="none"||elem.style.display===""){elem.style.display=show?values[index]||"":"none"}}return elements}jQuery.fn.extend({css:function(name,value){return jQuery.access(this,function(elem,name,value){return value!==undefined?jQuery.style(elem,name,value):jQuery.css(elem,name)},name,value,arguments.length>1)},show:function(){return showHide(this,true)},hide:function(){return showHide(this)},toggle:function(state,fn2){var bool=typeof state==="boolean";if(jQuery.isFunction(state)&&jQuery.isFunction(fn2)){return eventsToggle.apply(this,arguments)}return this.each(function(){if(bool?state:isHidden(this)){jQuery(this).show()}else{jQuery(this).hide()}})}});jQuery.extend({cssHooks:{opacity:{get:function(elem,computed){if(computed){var ret=curCSS(elem,"opacity");return ret===""?"1":ret}}}},cssNumber:{fillOpacity:true,fontWeight:true,lineHeight:true,opacity:true,orphans:true,widows:true,zIndex:true,zoom:true},cssProps:{"float":jQuery.support.cssFloat?"cssFloat":"styleFloat"},style:function(elem,name,value,extra){if(!elem||elem.nodeType===3||elem.nodeType===8||!elem.style){return}var ret,type,hooks,origName=jQuery.camelCase(name),style=elem.style;name=jQuery.cssProps[origName]||(jQuery.cssProps[origName]=vendorPropName(style,origName));hooks=jQuery.cssHooks[name]||jQuery.cssHooks[origName];if(value!==undefined){type=typeof value;if(type==="string"&&(ret=rrelNum.exec(value))){value=(ret[1]+1)*ret[2]+parseFloat(jQuery.css(elem,name));type="number"}if(value==null||type==="number"&&isNaN(value)){return}if(type==="number"&&!jQuery.cssNumber[origName]){value+="px"}if(!hooks||!("set"in hooks)||(value=hooks.set(elem,value,extra))!==undefined){try{style[name]=value}catch(e){}}}else{if(hooks&&"get"in hooks&&(ret=hooks.get(elem,false,extra))!==undefined){return ret}return style[name]}},css:function(elem,name,numeric,extra){var val,num,hooks,origName=jQuery.camelCase(name);name=jQuery.cssProps[origName]||(jQuery.cssProps[origName]=vendorPropName(elem.style,origName));hooks=jQuery.cssHooks[name]||jQuery.cssHooks[origName];if(hooks&&"get"in hooks){val=hooks.get(elem,true,extra)}if(val===undefined){val=curCSS(elem,name)}if(val==="normal"&&name in cssNormalTransform){val=cssNormalTransform[name]}if(numeric||extra!==undefined){num=parseFloat(val);return numeric||jQuery.isNumeric(num)?num||0:val}return val},swap:function(elem,options,callback){var ret,name,old={};for(name in options){old[name]=elem.style[name];elem.style[name]=options[name]}ret=callback.call(elem);for(name in options){elem.style[name]=old[name]}return ret}});if(window.getComputedStyle){curCSS=function(elem,name){var ret,width,minWidth,maxWidth,computed=window.getComputedStyle(elem,null),style=elem.style;if(computed){ret=computed.getPropertyValue(name)||computed[name];if(ret===""&&!jQuery.contains(elem.ownerDocument,elem)){ret=jQuery.style(elem,name)}if(rnumnonpx.test(ret)&&rmargin.test(name)){width=style.width;minWidth=style.minWidth;maxWidth=style.maxWidth;style.minWidth=style.maxWidth=style.width=ret;ret=computed.width;style.width=width;style.minWidth=minWidth;style.maxWidth=maxWidth}}return ret}}else if(document.documentElement.currentStyle){curCSS=function(elem,name){var left,rsLeft,ret=elem.currentStyle&&elem.currentStyle[name],style=elem.style;if(ret==null&&style&&style[name]){ret=style[name]}if(rnumnonpx.test(ret)&&!rposition.test(name)){left=style.left;rsLeft=elem.runtimeStyle&&elem.runtimeStyle.left;if(rsLeft){elem.runtimeStyle.left=elem.currentStyle.left}style.left=name==="fontSize"?"1em":ret;ret=style.pixelLeft+"px";style.left=left;if(rsLeft){elem.runtimeStyle.left=rsLeft}}return ret===""?"auto":ret}}function setPositiveNumber(elem,value,subtract){var matches=rnumsplit.exec(value);return matches?Math.max(0,matches[1]-(subtract||0))+(matches[2]||"px"):value}function augmentWidthOrHeight(elem,name,extra,isBorderBox){var i=extra===(isBorderBox?"border":"content")?4:name==="width"?1:0,val=0;for(;i<4;i+=2){if(extra==="margin"){val+=jQuery.css(elem,extra+cssExpand[i],true)}if(isBorderBox){if(extra==="content"){val-=parseFloat(curCSS(elem,"padding"+cssExpand[i]))||0}if(extra!=="margin"){val-=parseFloat(curCSS(elem,"border"+cssExpand[i]+"Width"))||0}}else{val+=parseFloat(curCSS(elem,"padding"+cssExpand[i]))||0;if(extra!=="padding"){val+=parseFloat(curCSS(elem,"border"+cssExpand[i]+"Width"))||0}}}return val}function getWidthOrHeight(elem,name,extra){var val=name==="width"?elem.offsetWidth:elem.offsetHeight,valueIsBorderBox=true,isBorderBox=jQuery.support.boxSizing&&jQuery.css(elem,"boxSizing")==="border-box";if(val<=0||val==null){val=curCSS(elem,name);if(val<0||val==null){val=elem.style[name]}if(rnumnonpx.test(val)){return val}valueIsBorderBox=isBorderBox&&(jQuery.support.boxSizingReliable||val===elem.style[name]);val=parseFloat(val)||0}return val+augmentWidthOrHeight(elem,name,extra||(isBorderBox?"border":"content"),valueIsBorderBox)+"px"}function css_defaultDisplay(nodeName){if(elemdisplay[nodeName]){return elemdisplay[nodeName]}var elem=jQuery("<"+nodeName+">").appendTo(document.body),display=elem.css("display");elem.remove();if(display==="none"||display===""){iframe=document.body.appendChild(iframe||jQuery.extend(document.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!iframeDoc||!iframe.createElement){iframeDoc=(iframe.contentWindow||iframe.contentDocument).document;iframeDoc.write("<!doctype html><html><body>");iframeDoc.close()}elem=iframeDoc.body.appendChild(iframeDoc.createElement(nodeName));display=curCSS(elem,"display");document.body.removeChild(iframe)}elemdisplay[nodeName]=display;return display}jQuery.each(["height","width"],function(i,name){jQuery.cssHooks[name]={get:function(elem,computed,extra){if(computed){if(elem.offsetWidth===0&&rdisplayswap.test(curCSS(elem,"display"))){return jQuery.swap(elem,cssShow,function(){return getWidthOrHeight(elem,name,extra)})}else{return getWidthOrHeight(elem,name,extra)}}},set:function(elem,value,extra){return setPositiveNumber(elem,value,extra?augmentWidthOrHeight(elem,name,extra,jQuery.support.boxSizing&&jQuery.css(elem,"boxSizing")==="border-box"):0)}}});if(!jQuery.support.opacity){jQuery.cssHooks.opacity={get:function(elem,computed){return ropacity.test((computed&&elem.currentStyle?elem.currentStyle.filter:elem.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":computed?"1":""},set:function(elem,value){var style=elem.style,currentStyle=elem.currentStyle,opacity=jQuery.isNumeric(value)?"alpha(opacity="+value*100+")":"",filter=currentStyle&&currentStyle.filter||style.filter||"";style.zoom=1;if(value>=1&&jQuery.trim(filter.replace(ralpha,""))===""&&style.removeAttribute){style.removeAttribute("filter");if(currentStyle&&!currentStyle.filter){return}}style.filter=ralpha.test(filter)?filter.replace(ralpha,opacity):filter+" "+opacity}}}jQuery(function(){if(!jQuery.support.reliableMarginRight){jQuery.cssHooks.marginRight={get:function(elem,computed){return jQuery.swap(elem,{display:"inline-block"},function(){if(computed){return curCSS(elem,"marginRight")}})}}}if(!jQuery.support.pixelPosition&&jQuery.fn.position){jQuery.each(["top","left"],function(i,prop){jQuery.cssHooks[prop]={get:function(elem,computed){if(computed){var ret=curCSS(elem,prop);return rnumnonpx.test(ret)?jQuery(elem).position()[prop]+"px":ret}}}})}});if(jQuery.expr&&jQuery.expr.filters){jQuery.expr.filters.hidden=function(elem){return elem.offsetWidth===0&&elem.offsetHeight===0||!jQuery.support.reliableHiddenOffsets&&(elem.style&&elem.style.display||curCSS(elem,"display"))==="none"};jQuery.expr.filters.visible=function(elem){return!jQuery.expr.filters.hidden(elem)}}jQuery.each({margin:"",padding:"",border:"Width"},function(prefix,suffix){jQuery.cssHooks[prefix+suffix]={expand:function(value){var i,parts=typeof value==="string"?value.split(" "):[value],expanded={};for(i=0;i<4;i++){expanded[prefix+cssExpand[i]+suffix]=parts[i]||parts[i-2]||parts[0]}return expanded}};if(!rmargin.test(prefix)){jQuery.cssHooks[prefix+suffix].set=setPositiveNumber}});var r20=/%20/g,rbracket=/\[\]$/,rCRLF=/\r?\n/g,rinput=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,rselectTextarea=/^(?:select|textarea)/i;jQuery.fn.extend({serialize:function(){return jQuery.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?jQuery.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||rselectTextarea.test(this.nodeName)||rinput.test(this.type))}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:jQuery.isArray(val)?jQuery.map(val,function(val,i){return{name:elem.name,value:val.replace(rCRLF,"\r\n")}}):{name:elem.name,value:val.replace(rCRLF,"\r\n")}}).get()}});jQuery.param=function(a,traditional){var prefix,s=[],add=function(key,value){value=jQuery.isFunction(value)?value():value==null?"":value;s[s.length]=encodeURIComponent(key)+"="+encodeURIComponent(value)};if(traditional===undefined){traditional=jQuery.ajaxSettings&&jQuery.ajaxSettings.traditional}if(jQuery.isArray(a)||a.jquery&&!jQuery.isPlainObject(a)){jQuery.each(a,function(){add(this.name,this.value)})}else{for(prefix in a){buildParams(prefix,a[prefix],traditional,add)}}return s.join("&").replace(r20,"+")};function buildParams(prefix,obj,traditional,add){var name;if(jQuery.isArray(obj)){jQuery.each(obj,function(i,v){if(traditional||rbracket.test(prefix)){add(prefix,v)}else{buildParams(prefix+"["+(typeof v==="object"?i:"")+"]",v,traditional,add)}})}else if(!traditional&&jQuery.type(obj)==="object"){for(name in obj){buildParams(prefix+"["+name+"]",obj[name],traditional,add)}}else{add(prefix,obj)}}var ajaxLocParts,ajaxLocation,rhash=/#.*$/,rheaders=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,rlocalProtocol=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,rnoContent=/^(?:GET|HEAD)$/,rprotocol=/^\/\//,rquery=/\?/,rscript=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,rts=/([?&])_=[^&]*/,rurl=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,_load=jQuery.fn.load,prefilters={},transports={},allTypes=["*/"]+["*"];try{ajaxLocation=location.href}catch(e){ajaxLocation=document.createElement("a");ajaxLocation.href="";ajaxLocation=ajaxLocation.href}ajaxLocParts=rurl.exec(ajaxLocation.toLowerCase())||[];function addToPrefiltersOrTransports(structure){return function(dataTypeExpression,func){if(typeof dataTypeExpression!=="string"){func=dataTypeExpression;dataTypeExpression="*"}var dataType,list,placeBefore,dataTypes=dataTypeExpression.toLowerCase().split(core_rspace),i=0,length=dataTypes.length;if(jQuery.isFunction(func)){for(;i<length;i++){dataType=dataTypes[i];placeBefore=/^\+/.test(dataType);if(placeBefore){dataType=dataType.substr(1)||"*"}list=structure[dataType]=structure[dataType]||[];list[placeBefore?"unshift":"push"](func)}}}}function inspectPrefiltersOrTransports(structure,options,originalOptions,jqXHR,dataType,inspected){dataType=dataType||options.dataTypes[0];inspected=inspected||{};inspected[dataType]=true;var selection,list=structure[dataType],i=0,length=list?list.length:0,executeOnly=structure===prefilters;for(;i<length&&(executeOnly||!selection);i++){selection=list[i](options,originalOptions,jqXHR);if(typeof selection==="string"){if(!executeOnly||inspected[selection]){selection=undefined}else{options.dataTypes.unshift(selection);selection=inspectPrefiltersOrTransports(structure,options,originalOptions,jqXHR,selection,inspected)}}}if((executeOnly||!selection)&&!inspected["*"]){selection=inspectPrefiltersOrTransports(structure,options,originalOptions,jqXHR,"*",inspected)}return selection}function ajaxExtend(target,src){var key,deep,flatOptions=jQuery.ajaxSettings.flatOptions||{};for(key in src){if(src[key]!==undefined){(flatOptions[key]?target:deep||(deep={}))[key]=src[key]}}if(deep){jQuery.extend(true,target,deep)}}jQuery.fn.load=function(url,params,callback){if(typeof url!=="string"&&_load){return _load.apply(this,arguments)}if(!this.length){return this}var selector,type,response,self=this,off=url.indexOf(" ");if(off>=0){selector=url.slice(off,url.length);url=url.slice(0,off)}if(jQuery.isFunction(params)){callback=params;params=undefined}else if(params&&typeof params==="object"){type="POST"}jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(jqXHR,status){if(callback){self.each(callback,response||[jqXHR.responseText,status,jqXHR])}}}).done(function(responseText){response=arguments;self.html(selector?jQuery("<div>").append(responseText.replace(rscript,"")).find(selector):responseText)});return this};jQuery.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(i,o){jQuery.fn[o]=function(f){return this.on(o,f)}});jQuery.each(["get","post"],function(i,method){jQuery[method]=function(url,data,callback,type){if(jQuery.isFunction(data)){type=type||callback;callback=data;data=undefined}return jQuery.ajax({type:method,url:url,data:data,success:callback,dataType:type})}});jQuery.extend({getScript:function(url,callback){return jQuery.get(url,undefined,callback,"script")},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json")},ajaxSetup:function(target,settings){if(settings){ajaxExtend(target,jQuery.ajaxSettings)}else{settings=target;target=jQuery.ajaxSettings}ajaxExtend(target,settings);return target},ajaxSettings:{url:ajaxLocation,isLocal:rlocalProtocol.test(ajaxLocParts[1]),global:true,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:true,async:true,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":allTypes},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":window.String,"text html":true,"text json":jQuery.parseJSON,"text xml":jQuery.parseXML},flatOptions:{context:true,url:true}},ajaxPrefilter:addToPrefiltersOrTransports(prefilters),ajaxTransport:addToPrefiltersOrTransports(transports),ajax:function(url,options){if(typeof url==="object"){options=url;url=undefined}options=options||{};var ifModifiedKey,responseHeadersString,responseHeaders,transport,timeoutTimer,parts,fireGlobals,i,s=jQuery.ajaxSetup({},options),callbackContext=s.context||s,globalEventContext=callbackContext!==s&&(callbackContext.nodeType||callbackContext instanceof jQuery)?jQuery(callbackContext):jQuery.event,deferred=jQuery.Deferred(),completeDeferred=jQuery.Callbacks("once memory"),statusCode=s.statusCode||{},requestHeaders={},requestHeadersNames={},state=0,strAbort="canceled",jqXHR={readyState:0,setRequestHeader:function(name,value){if(!state){var lname=name.toLowerCase();name=requestHeadersNames[lname]=requestHeadersNames[lname]||name;requestHeaders[name]=value}return this},getAllResponseHeaders:function(){return state===2?responseHeadersString:null},getResponseHeader:function(key){var match;if(state===2){if(!responseHeaders){responseHeaders={};while(match=rheaders.exec(responseHeadersString)){responseHeaders[match[1].toLowerCase()]=match[2]}}match=responseHeaders[key.toLowerCase()]}return match===undefined?null:match},overrideMimeType:function(type){if(!state){s.mimeType=type}return this},abort:function(statusText){statusText=statusText||strAbort;if(transport){transport.abort(statusText)}done(0,statusText);return this}};function done(status,nativeStatusText,responses,headers){var isSuccess,success,error,response,modified,statusText=nativeStatusText;if(state===2){return}state=2;if(timeoutTimer){clearTimeout(timeoutTimer)}transport=undefined;responseHeadersString=headers||"";jqXHR.readyState=status>0?4:0;if(responses){response=ajaxHandleResponses(s,jqXHR,responses)}if(status>=200&&status<300||status===304){if(s.ifModified){modified=jqXHR.getResponseHeader("Last-Modified");if(modified){jQuery.lastModified[ifModifiedKey]=modified}modified=jqXHR.getResponseHeader("Etag");if(modified){jQuery.etag[ifModifiedKey]=modified}}if(status===304){statusText="notmodified";isSuccess=true}else{isSuccess=ajaxConvert(s,response);statusText=isSuccess.state;success=isSuccess.data;error=isSuccess.error;isSuccess=!error}}else{error=statusText;if(!statusText||status){statusText="error";if(status<0){status=0}}}jqXHR.status=status;jqXHR.statusText=(nativeStatusText||statusText)+"";if(isSuccess){deferred.resolveWith(callbackContext,[success,statusText,jqXHR])}else{deferred.rejectWith(callbackContext,[jqXHR,statusText,error])}jqXHR.statusCode(statusCode);statusCode=undefined;if(fireGlobals){globalEventContext.trigger("ajax"+(isSuccess?"Success":"Error"),[jqXHR,s,isSuccess?success:error])}completeDeferred.fireWith(callbackContext,[jqXHR,statusText]);if(fireGlobals){globalEventContext.trigger("ajaxComplete",[jqXHR,s]);if(!--jQuery.active){jQuery.event.trigger("ajaxStop")}}}deferred.promise(jqXHR);jqXHR.success=jqXHR.done;jqXHR.error=jqXHR.fail;jqXHR.complete=completeDeferred.add;jqXHR.statusCode=function(map){if(map){var tmp;if(state<2){for(tmp in map){statusCode[tmp]=[statusCode[tmp],map[tmp]]}}else{tmp=map[jqXHR.status];jqXHR.always(tmp)}}return this};s.url=((url||s.url)+"").replace(rhash,"").replace(rprotocol,ajaxLocParts[1]+"//");s.dataTypes=jQuery.trim(s.dataType||"*").toLowerCase().split(core_rspace);if(s.crossDomain==null){parts=rurl.exec(s.url.toLowerCase());s.crossDomain=!!(parts&&(parts[1]!==ajaxLocParts[1]||parts[2]!==ajaxLocParts[2]||(parts[3]||(parts[1]==="http:"?80:443))!=(ajaxLocParts[3]||(ajaxLocParts[1]==="http:"?80:443))))}if(s.data&&s.processData&&typeof s.data!=="string"){s.data=jQuery.param(s.data,s.traditional)}inspectPrefiltersOrTransports(prefilters,s,options,jqXHR);if(state===2){return jqXHR}fireGlobals=s.global;s.type=s.type.toUpperCase();s.hasContent=!rnoContent.test(s.type);if(fireGlobals&&jQuery.active++===0){jQuery.event.trigger("ajaxStart")}if(!s.hasContent){if(s.data){s.url+=(rquery.test(s.url)?"&":"?")+s.data;delete s.data}ifModifiedKey=s.url;if(s.cache===false){var ts=jQuery.now(),ret=s.url.replace(rts,"$1_="+ts);s.url=ret+(ret===s.url?(rquery.test(s.url)?"&":"?")+"_="+ts:"")}}if(s.data&&s.hasContent&&s.contentType!==false||options.contentType){jqXHR.setRequestHeader("Content-Type",s.contentType)}if(s.ifModified){ifModifiedKey=ifModifiedKey||s.url;if(jQuery.lastModified[ifModifiedKey]){jqXHR.setRequestHeader("If-Modified-Since",jQuery.lastModified[ifModifiedKey])}if(jQuery.etag[ifModifiedKey]){jqXHR.setRequestHeader("If-None-Match",jQuery.etag[ifModifiedKey])}}jqXHR.setRequestHeader("Accept",s.dataTypes[0]&&s.accepts[s.dataTypes[0]]?s.accepts[s.dataTypes[0]]+(s.dataTypes[0]!=="*"?", "+allTypes+"; q=0.01":""):s.accepts["*"]);for(i in s.headers){jqXHR.setRequestHeader(i,s.headers[i])}if(s.beforeSend&&(s.beforeSend.call(callbackContext,jqXHR,s)===false||state===2)){return jqXHR.abort()}strAbort="abort";for(i in{success:1,error:1,complete:1}){jqXHR[i](s[i])}transport=inspectPrefiltersOrTransports(transports,s,options,jqXHR);if(!transport){done(-1,"No Transport")}else{jqXHR.readyState=1;if(fireGlobals){globalEventContext.trigger("ajaxSend",[jqXHR,s])}if(s.async&&s.timeout>0){timeoutTimer=setTimeout(function(){jqXHR.abort("timeout")},s.timeout)}try{state=1;transport.send(requestHeaders,done)}catch(e){if(state<2){done(-1,e)}else{throw e}}}return jqXHR},active:0,lastModified:{},etag:{}});function ajaxHandleResponses(s,jqXHR,responses){var ct,type,finalDataType,firstDataType,contents=s.contents,dataTypes=s.dataTypes,responseFields=s.responseFields;for(type in responseFields){if(type in responses){jqXHR[responseFields[type]]=responses[type]}}while(dataTypes[0]==="*"){dataTypes.shift();if(ct===undefined){ct=s.mimeType||jqXHR.getResponseHeader("content-type")}}if(ct){for(type in contents){if(contents[type]&&contents[type].test(ct)){dataTypes.unshift(type);break}}}if(dataTypes[0]in responses){finalDataType=dataTypes[0]}else{for(type in responses){if(!dataTypes[0]||s.converters[type+" "+dataTypes[0]]){finalDataType=type;break}if(!firstDataType){firstDataType=type}}finalDataType=finalDataType||firstDataType}if(finalDataType){if(finalDataType!==dataTypes[0]){dataTypes.unshift(finalDataType)}return responses[finalDataType]}}function ajaxConvert(s,response){var conv,conv2,current,tmp,dataTypes=s.dataTypes.slice(),prev=dataTypes[0],converters={},i=0;if(s.dataFilter){response=s.dataFilter(response,s.dataType)}if(dataTypes[1]){for(conv in s.converters){converters[conv.toLowerCase()]=s.converters[conv]}}for(;current=dataTypes[++i];){if(current!=="*"){if(prev!=="*"&&prev!==current){conv=converters[prev+" "+current]||converters["* "+current];if(!conv){for(conv2 in converters){tmp=conv2.split(" ");if(tmp[1]===current){conv=converters[prev+" "+tmp[0]]||converters["* "+tmp[0]];if(conv){if(conv===true){conv=converters[conv2]}else if(converters[conv2]!==true){current=tmp[0];dataTypes.splice(i--,0,current)}break}}}}if(conv!==true){if(conv&&s["throws"]){response=conv(response)}else{try{response=conv(response)}catch(e){return{state:"parsererror",error:conv?e:"No conversion from "+prev+" to "+current}}}}}prev=current}}return{state:"success",data:response}}var oldCallbacks=[],rquestion=/\?/,rjsonp=/(=)\?(?=&|$)|\?\?/,nonce=jQuery.now();jQuery.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var callback=oldCallbacks.pop()||jQuery.expando+"_"+nonce++;this[callback]=true;return callback}});jQuery.ajaxPrefilter("json jsonp",function(s,originalSettings,jqXHR){var callbackName,overwritten,responseContainer,data=s.data,url=s.url,hasCallback=s.jsonp!==false,replaceInUrl=hasCallback&&rjsonp.test(url),replaceInData=hasCallback&&!replaceInUrl&&typeof data==="string"&&!(s.contentType||"").indexOf("application/x-www-form-urlencoded")&&rjsonp.test(data);if(s.dataTypes[0]==="jsonp"||replaceInUrl||replaceInData){callbackName=s.jsonpCallback=jQuery.isFunction(s.jsonpCallback)?s.jsonpCallback():s.jsonpCallback;overwritten=window[callbackName];if(replaceInUrl){s.url=url.replace(rjsonp,"$1"+callbackName)}else if(replaceInData){s.data=data.replace(rjsonp,"$1"+callbackName)}else if(hasCallback){s.url+=(rquestion.test(url)?"&":"?")+s.jsonp+"="+callbackName}s.converters["script json"]=function(){if(!responseContainer){jQuery.error(callbackName+" was not called")}return responseContainer[0]};s.dataTypes[0]="json";window[callbackName]=function(){responseContainer=arguments};jqXHR.always(function(){window[callbackName]=overwritten;if(s[callbackName]){s.jsonpCallback=originalSettings.jsonpCallback;oldCallbacks.push(callbackName)}if(responseContainer&&jQuery.isFunction(overwritten)){overwritten(responseContainer[0])}responseContainer=overwritten=undefined});return"script"}});jQuery.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(text){jQuery.globalEval(text);return text}}});jQuery.ajaxPrefilter("script",function(s){if(s.cache===undefined){s.cache=false}if(s.crossDomain){s.type="GET";s.global=false}});jQuery.ajaxTransport("script",function(s){if(s.crossDomain){var script,head=document.head||document.getElementsByTagName("head")[0]||document.documentElement;return{send:function(_,callback){script=document.createElement("script");script.async="async";if(s.scriptCharset){script.charset=s.scriptCharset}script.src=s.url;script.onload=script.onreadystatechange=function(_,isAbort){if(isAbort||!script.readyState||/loaded|complete/.test(script.readyState)){script.onload=script.onreadystatechange=null;if(head&&script.parentNode){head.removeChild(script)}script=undefined;if(!isAbort){callback(200,"success")}}};head.insertBefore(script,head.firstChild)},abort:function(){if(script){script.onload(0,1)}}}}});var xhrCallbacks,xhrOnUnloadAbort=window.ActiveXObject?function(){for(var key in xhrCallbacks){xhrCallbacks[key](0,1)}}:false,xhrId=0;function createStandardXHR(){try{return new window.XMLHttpRequest}catch(e){}}function createActiveXHR(){try{return new window.ActiveXObject("Microsoft.XMLHTTP")}catch(e){}}jQuery.ajaxSettings.xhr=window.ActiveXObject?function(){return!this.isLocal&&createStandardXHR()||createActiveXHR()}:createStandardXHR;(function(xhr){jQuery.extend(jQuery.support,{ajax:!!xhr,cors:!!xhr&&"withCredentials"in xhr})})(jQuery.ajaxSettings.xhr());if(jQuery.support.ajax){jQuery.ajaxTransport(function(s){if(!s.crossDomain||jQuery.support.cors){var callback;return{send:function(headers,complete){var handle,i,xhr=s.xhr();if(s.username){xhr.open(s.type,s.url,s.async,s.username,s.password)}else{xhr.open(s.type,s.url,s.async)}if(s.xhrFields){for(i in s.xhrFields){xhr[i]=s.xhrFields[i]}}if(s.mimeType&&xhr.overrideMimeType){xhr.overrideMimeType(s.mimeType)}if(!s.crossDomain&&!headers["X-Requested-With"]){headers["X-Requested-With"]="XMLHttpRequest"}try{for(i in headers){xhr.setRequestHeader(i,headers[i])}}catch(_){}xhr.send(s.hasContent&&s.data||null);callback=function(_,isAbort){var status,statusText,responseHeaders,responses,xml;try{if(callback&&(isAbort||xhr.readyState===4)){callback=undefined;if(handle){xhr.onreadystatechange=jQuery.noop;if(xhrOnUnloadAbort){delete xhrCallbacks[handle]}}if(isAbort){if(xhr.readyState!==4){xhr.abort()}}else{status=xhr.status;responseHeaders=xhr.getAllResponseHeaders();responses={};xml=xhr.responseXML;if(xml&&xml.documentElement){responses.xml=xml}try{responses.text=xhr.responseText}catch(e){}try{statusText=xhr.statusText}catch(e){statusText=""}if(!status&&s.isLocal&&!s.crossDomain){status=responses.text?200:404}else if(status===1223){status=204}}}}catch(firefoxAccessException){if(!isAbort){complete(-1,firefoxAccessException)}}if(responses){complete(status,statusText,responses,responseHeaders)}};if(!s.async){callback()}else if(xhr.readyState===4){setTimeout(callback,0)}else{handle=++xhrId;if(xhrOnUnloadAbort){if(!xhrCallbacks){xhrCallbacks={};jQuery(window).unload(xhrOnUnloadAbort)}xhrCallbacks[handle]=callback}xhr.onreadystatechange=callback}},abort:function(){if(callback){callback(0,1)}}}}})}var fxNow,timerId,rfxtypes=/^(?:toggle|show|hide)$/,rfxnum=new RegExp("^(?:([-+])=|)("+core_pnum+")([a-z%]*)$","i"),rrun=/queueHooks$/,animationPrefilters=[defaultPrefilter],tweeners={"*":[function(prop,value){var end,unit,tween=this.createTween(prop,value),parts=rfxnum.exec(value),target=tween.cur(),start=+target||0,scale=1,maxIterations=20;if(parts){end=+parts[2];unit=parts[3]||(jQuery.cssNumber[prop]?"":"px");if(unit!=="px"&&start){start=jQuery.css(tween.elem,prop,true)||end||1;do{scale=scale||".5";start=start/scale;jQuery.style(tween.elem,prop,start+unit)}while(scale!==(scale=tween.cur()/target)&&scale!==1&&--maxIterations)}tween.unit=unit;tween.start=start;tween.end=parts[1]?start+(parts[1]+1)*end:end}return tween}]};function createFxNow(){setTimeout(function(){fxNow=undefined},0);return fxNow=jQuery.now()}function createTweens(animation,props){jQuery.each(props,function(prop,value){var collection=(tweeners[prop]||[]).concat(tweeners["*"]),index=0,length=collection.length;for(;index<length;index++){if(collection[index].call(animation,prop,value)){return}}})}function Animation(elem,properties,options){var result,index=0,tweenerIndex=0,length=animationPrefilters.length,deferred=jQuery.Deferred().always(function(){delete tick.elem}),tick=function(){var currentTime=fxNow||createFxNow(),remaining=Math.max(0,animation.startTime+animation.duration-currentTime),temp=remaining/animation.duration||0,percent=1-temp,index=0,length=animation.tweens.length;
+for(;index<length;index++){animation.tweens[index].run(percent)}deferred.notifyWith(elem,[animation,percent,remaining]);if(percent<1&&length){return remaining}else{deferred.resolveWith(elem,[animation]);return false}},animation=deferred.promise({elem:elem,props:jQuery.extend({},properties),opts:jQuery.extend(true,{specialEasing:{}},options),originalProperties:properties,originalOptions:options,startTime:fxNow||createFxNow(),duration:options.duration,tweens:[],createTween:function(prop,end,easing){var tween=jQuery.Tween(elem,animation.opts,prop,end,animation.opts.specialEasing[prop]||animation.opts.easing);animation.tweens.push(tween);return tween},stop:function(gotoEnd){var index=0,length=gotoEnd?animation.tweens.length:0;for(;index<length;index++){animation.tweens[index].run(1)}if(gotoEnd){deferred.resolveWith(elem,[animation,gotoEnd])}else{deferred.rejectWith(elem,[animation,gotoEnd])}return this}}),props=animation.props;propFilter(props,animation.opts.specialEasing);for(;index<length;index++){result=animationPrefilters[index].call(animation,elem,props,animation.opts);if(result){return result}}createTweens(animation,props);if(jQuery.isFunction(animation.opts.start)){animation.opts.start.call(elem,animation)}jQuery.fx.timer(jQuery.extend(tick,{anim:animation,queue:animation.opts.queue,elem:elem}));return animation.progress(animation.opts.progress).done(animation.opts.done,animation.opts.complete).fail(animation.opts.fail).always(animation.opts.always)}function propFilter(props,specialEasing){var index,name,easing,value,hooks;for(index in props){name=jQuery.camelCase(index);easing=specialEasing[name];value=props[index];if(jQuery.isArray(value)){easing=value[1];value=props[index]=value[0]}if(index!==name){props[name]=value;delete props[index]}hooks=jQuery.cssHooks[name];if(hooks&&"expand"in hooks){value=hooks.expand(value);delete props[name];for(index in value){if(!(index in props)){props[index]=value[index];specialEasing[index]=easing}}}else{specialEasing[name]=easing}}}jQuery.Animation=jQuery.extend(Animation,{tweener:function(props,callback){if(jQuery.isFunction(props)){callback=props;props=["*"]}else{props=props.split(" ")}var prop,index=0,length=props.length;for(;index<length;index++){prop=props[index];tweeners[prop]=tweeners[prop]||[];tweeners[prop].unshift(callback)}},prefilter:function(callback,prepend){if(prepend){animationPrefilters.unshift(callback)}else{animationPrefilters.push(callback)}}});function defaultPrefilter(elem,props,opts){var index,prop,value,length,dataShow,toggle,tween,hooks,oldfire,anim=this,style=elem.style,orig={},handled=[],hidden=elem.nodeType&&isHidden(elem);if(!opts.queue){hooks=jQuery._queueHooks(elem,"fx");if(hooks.unqueued==null){hooks.unqueued=0;oldfire=hooks.empty.fire;hooks.empty.fire=function(){if(!hooks.unqueued){oldfire()}}}hooks.unqueued++;anim.always(function(){anim.always(function(){hooks.unqueued--;if(!jQuery.queue(elem,"fx").length){hooks.empty.fire()}})})}if(elem.nodeType===1&&("height"in props||"width"in props)){opts.overflow=[style.overflow,style.overflowX,style.overflowY];if(jQuery.css(elem,"display")==="inline"&&jQuery.css(elem,"float")==="none"){if(!jQuery.support.inlineBlockNeedsLayout||css_defaultDisplay(elem.nodeName)==="inline"){style.display="inline-block"}else{style.zoom=1}}}if(opts.overflow){style.overflow="hidden";if(!jQuery.support.shrinkWrapBlocks){anim.done(function(){style.overflow=opts.overflow[0];style.overflowX=opts.overflow[1];style.overflowY=opts.overflow[2]})}}for(index in props){value=props[index];if(rfxtypes.exec(value)){delete props[index];toggle=toggle||value==="toggle";if(value===(hidden?"hide":"show")){continue}handled.push(index)}}length=handled.length;if(length){dataShow=jQuery._data(elem,"fxshow")||jQuery._data(elem,"fxshow",{});if("hidden"in dataShow){hidden=dataShow.hidden}if(toggle){dataShow.hidden=!hidden}if(hidden){jQuery(elem).show()}else{anim.done(function(){jQuery(elem).hide()})}anim.done(function(){var prop;jQuery.removeData(elem,"fxshow",true);for(prop in orig){jQuery.style(elem,prop,orig[prop])}});for(index=0;index<length;index++){prop=handled[index];tween=anim.createTween(prop,hidden?dataShow[prop]:0);orig[prop]=dataShow[prop]||jQuery.style(elem,prop);if(!(prop in dataShow)){dataShow[prop]=tween.start;if(hidden){tween.end=tween.start;tween.start=prop==="width"||prop==="height"?1:0}}}}}function Tween(elem,options,prop,end,easing){return new Tween.prototype.init(elem,options,prop,end,easing)}jQuery.Tween=Tween;Tween.prototype={constructor:Tween,init:function(elem,options,prop,end,easing,unit){this.elem=elem;this.prop=prop;this.easing=easing||"swing";this.options=options;this.start=this.now=this.cur();this.end=end;this.unit=unit||(jQuery.cssNumber[prop]?"":"px")},cur:function(){var hooks=Tween.propHooks[this.prop];return hooks&&hooks.get?hooks.get(this):Tween.propHooks._default.get(this)},run:function(percent){var eased,hooks=Tween.propHooks[this.prop];if(this.options.duration){this.pos=eased=jQuery.easing[this.easing](percent,this.options.duration*percent,0,1,this.options.duration)}else{this.pos=eased=percent}this.now=(this.end-this.start)*eased+this.start;if(this.options.step){this.options.step.call(this.elem,this.now,this)}if(hooks&&hooks.set){hooks.set(this)}else{Tween.propHooks._default.set(this)}return this}};Tween.prototype.init.prototype=Tween.prototype;Tween.propHooks={_default:{get:function(tween){var result;if(tween.elem[tween.prop]!=null&&(!tween.elem.style||tween.elem.style[tween.prop]==null)){return tween.elem[tween.prop]}result=jQuery.css(tween.elem,tween.prop,false,"");return!result||result==="auto"?0:result},set:function(tween){if(jQuery.fx.step[tween.prop]){jQuery.fx.step[tween.prop](tween)}else if(tween.elem.style&&(tween.elem.style[jQuery.cssProps[tween.prop]]!=null||jQuery.cssHooks[tween.prop])){jQuery.style(tween.elem,tween.prop,tween.now+tween.unit)}else{tween.elem[tween.prop]=tween.now}}}};Tween.propHooks.scrollTop=Tween.propHooks.scrollLeft={set:function(tween){if(tween.elem.nodeType&&tween.elem.parentNode){tween.elem[tween.prop]=tween.now}}};jQuery.each(["toggle","show","hide"],function(i,name){var cssFn=jQuery.fn[name];jQuery.fn[name]=function(speed,easing,callback){return speed==null||typeof speed==="boolean"||!i&&jQuery.isFunction(speed)&&jQuery.isFunction(easing)?cssFn.apply(this,arguments):this.animate(genFx(name,true),speed,easing,callback)}});jQuery.fn.extend({fadeTo:function(speed,to,easing,callback){return this.filter(isHidden).css("opacity",0).show().end().animate({opacity:to},speed,easing,callback)},animate:function(prop,speed,easing,callback){var empty=jQuery.isEmptyObject(prop),optall=jQuery.speed(speed,easing,callback),doAnimation=function(){var anim=Animation(this,jQuery.extend({},prop),optall);if(empty){anim.stop(true)}};return empty||optall.queue===false?this.each(doAnimation):this.queue(optall.queue,doAnimation)},stop:function(type,clearQueue,gotoEnd){var stopQueue=function(hooks){var stop=hooks.stop;delete hooks.stop;stop(gotoEnd)};if(typeof type!=="string"){gotoEnd=clearQueue;clearQueue=type;type=undefined}if(clearQueue&&type!==false){this.queue(type||"fx",[])}return this.each(function(){var dequeue=true,index=type!=null&&type+"queueHooks",timers=jQuery.timers,data=jQuery._data(this);if(index){if(data[index]&&data[index].stop){stopQueue(data[index])}}else{for(index in data){if(data[index]&&data[index].stop&&rrun.test(index)){stopQueue(data[index])}}}for(index=timers.length;index--;){if(timers[index].elem===this&&(type==null||timers[index].queue===type)){timers[index].anim.stop(gotoEnd);dequeue=false;timers.splice(index,1)}}if(dequeue||!gotoEnd){jQuery.dequeue(this,type)}})}});function genFx(type,includeWidth){var which,attrs={height:type},i=0;includeWidth=includeWidth?1:0;for(;i<4;i+=2-includeWidth){which=cssExpand[i];attrs["margin"+which]=attrs["padding"+which]=type}if(includeWidth){attrs.opacity=attrs.width=type}return attrs}jQuery.each({slideDown:genFx("show"),slideUp:genFx("hide"),slideToggle:genFx("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(name,props){jQuery.fn[name]=function(speed,easing,callback){return this.animate(props,speed,easing,callback)}});jQuery.speed=function(speed,easing,fn){var opt=speed&&typeof speed==="object"?jQuery.extend({},speed):{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&!jQuery.isFunction(easing)&&easing};opt.duration=jQuery.fx.off?0:typeof opt.duration==="number"?opt.duration:opt.duration in jQuery.fx.speeds?jQuery.fx.speeds[opt.duration]:jQuery.fx.speeds._default;if(opt.queue==null||opt.queue===true){opt.queue="fx"}opt.old=opt.complete;opt.complete=function(){if(jQuery.isFunction(opt.old)){opt.old.call(this)}if(opt.queue){jQuery.dequeue(this,opt.queue)}};return opt};jQuery.easing={linear:function(p){return p},swing:function(p){return.5-Math.cos(p*Math.PI)/2}};jQuery.timers=[];jQuery.fx=Tween.prototype.init;jQuery.fx.tick=function(){var timer,timers=jQuery.timers,i=0;fxNow=jQuery.now();for(;i<timers.length;i++){timer=timers[i];if(!timer()&&timers[i]===timer){timers.splice(i--,1)}}if(!timers.length){jQuery.fx.stop()}fxNow=undefined};jQuery.fx.timer=function(timer){if(timer()&&jQuery.timers.push(timer)&&!timerId){timerId=setInterval(jQuery.fx.tick,jQuery.fx.interval)}};jQuery.fx.interval=13;jQuery.fx.stop=function(){clearInterval(timerId);timerId=null};jQuery.fx.speeds={slow:600,fast:200,_default:400};jQuery.fx.step={};if(jQuery.expr&&jQuery.expr.filters){jQuery.expr.filters.animated=function(elem){return jQuery.grep(jQuery.timers,function(fn){return elem===fn.elem}).length}}var rroot=/^(?:body|html)$/i;jQuery.fn.offset=function(options){if(arguments.length){return options===undefined?this:this.each(function(i){jQuery.offset.setOffset(this,options,i)})}var docElem,body,win,clientTop,clientLeft,scrollTop,scrollLeft,box={top:0,left:0},elem=this[0],doc=elem&&elem.ownerDocument;if(!doc){return}if((body=doc.body)===elem){return jQuery.offset.bodyOffset(elem)}docElem=doc.documentElement;if(!jQuery.contains(docElem,elem)){return box}if(typeof elem.getBoundingClientRect!=="undefined"){box=elem.getBoundingClientRect()}win=getWindow(doc);clientTop=docElem.clientTop||body.clientTop||0;clientLeft=docElem.clientLeft||body.clientLeft||0;scrollTop=win.pageYOffset||docElem.scrollTop;scrollLeft=win.pageXOffset||docElem.scrollLeft;return{top:box.top+scrollTop-clientTop,left:box.left+scrollLeft-clientLeft}};jQuery.offset={bodyOffset:function(body){var top=body.offsetTop,left=body.offsetLeft;if(jQuery.support.doesNotIncludeMarginInBodyOffset){top+=parseFloat(jQuery.css(body,"marginTop"))||0;left+=parseFloat(jQuery.css(body,"marginLeft"))||0}return{top:top,left:left}},setOffset:function(elem,options,i){var position=jQuery.css(elem,"position");if(position==="static"){elem.style.position="relative"}var curElem=jQuery(elem),curOffset=curElem.offset(),curCSSTop=jQuery.css(elem,"top"),curCSSLeft=jQuery.css(elem,"left"),calculatePosition=(position==="absolute"||position==="fixed")&&jQuery.inArray("auto",[curCSSTop,curCSSLeft])>-1,props={},curPosition={},curTop,curLeft;if(calculatePosition){curPosition=curElem.position();curTop=curPosition.top;curLeft=curPosition.left}else{curTop=parseFloat(curCSSTop)||0;curLeft=parseFloat(curCSSLeft)||0}if(jQuery.isFunction(options)){options=options.call(elem,i,curOffset)}if(options.top!=null){props.top=options.top-curOffset.top+curTop}if(options.left!=null){props.left=options.left-curOffset.left+curLeft}if("using"in options){options.using.call(elem,props)}else{curElem.css(props)}}};jQuery.fn.extend({position:function(){if(!this[0]){return}var elem=this[0],offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=rroot.test(offsetParent[0].nodeName)?{top:0,left:0}:offsetParent.offset();offset.top-=parseFloat(jQuery.css(elem,"marginTop"))||0;offset.left-=parseFloat(jQuery.css(elem,"marginLeft"))||0;parentOffset.top+=parseFloat(jQuery.css(offsetParent[0],"borderTopWidth"))||0;parentOffset.left+=parseFloat(jQuery.css(offsetParent[0],"borderLeftWidth"))||0;return{top:offset.top-parentOffset.top,left:offset.left-parentOffset.left}},offsetParent:function(){return this.map(function(){var offsetParent=this.offsetParent||document.body;while(offsetParent&&(!rroot.test(offsetParent.nodeName)&&jQuery.css(offsetParent,"position")==="static")){offsetParent=offsetParent.offsetParent}return offsetParent||document.body})}});jQuery.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(method,prop){var top=/Y/.test(prop);jQuery.fn[method]=function(val){return jQuery.access(this,function(elem,method,val){var win=getWindow(elem);if(val===undefined){return win?prop in win?win[prop]:win.document.documentElement[method]:elem[method]}if(win){win.scrollTo(!top?val:jQuery(win).scrollLeft(),top?val:jQuery(win).scrollTop())}else{elem[method]=val}},method,val,arguments.length,null)}});function getWindow(elem){return jQuery.isWindow(elem)?elem:elem.nodeType===9?elem.defaultView||elem.parentWindow:false}jQuery.each({Height:"height",Width:"width"},function(name,type){jQuery.each({padding:"inner"+name,content:type,"":"outer"+name},function(defaultExtra,funcName){jQuery.fn[funcName]=function(margin,value){var chainable=arguments.length&&(defaultExtra||typeof margin!=="boolean"),extra=defaultExtra||(margin===true||value===true?"margin":"border");return jQuery.access(this,function(elem,type,value){var doc;if(jQuery.isWindow(elem)){return elem.document.documentElement["client"+name]}if(elem.nodeType===9){doc=elem.documentElement;return Math.max(elem.body["scroll"+name],doc["scroll"+name],elem.body["offset"+name],doc["offset"+name],doc["client"+name])}return value===undefined?jQuery.css(elem,type,value,extra):jQuery.style(elem,type,value,extra)},type,chainable?margin:undefined,chainable,null)}})});window.jQuery=window.$=jQuery;if(typeof define==="function"&&define.amd&&define.amd.jQuery){define("jquery",[],function(){return jQuery})}})(window); \ No newline at end of file
diff --git a/misc/jquery/jquery-2.1.1.min.js b/misc/jquery/jquery-2.1.1.min.js
new file mode 100644
index 0000000..e5ace11
--- /dev/null
+++ b/misc/jquery/jquery-2.1.1.min.js
@@ -0,0 +1,4 @@
+/*! jQuery v2.1.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */
+!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.1",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="<div class='a'></div><div class='a i'></div>",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="<select msallowclip=''><option selected=''></option></select>",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++d<b;)a.push(d);return a})}},d.pseudos.nth=d.pseudos.eq;for(b in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})d.pseudos[b]=lb(b);for(b in{submit:!0,reset:!0})d.pseudos[b]=mb(b);function pb(){}pb.prototype=d.filters=d.pseudos,d.setFilters=new pb,g=fb.tokenize=function(a,b){var c,e,f,g,h,i,j,k=z[a+" "];if(k)return b?0:k.slice(0);h=a,i=[],j=d.preFilter;while(h){(!c||(e=S.exec(h)))&&(e&&(h=h.slice(e[0].length)||h),i.push(f=[])),c=!1,(e=T.exec(h))&&(c=e.shift(),f.push({value:c,type:e[0].replace(R," ")}),h=h.slice(c.length));for(g in d.filter)!(e=X[g].exec(h))||j[g]&&!(e=j[g](e))||(c=e.shift(),f.push({value:c,type:g,matches:e}),h=h.slice(c.length));if(!c)break}return b?h.length:h?fb.error(a):z(a,i).slice(0)};function qb(a){for(var b=0,c=a.length,d="";c>b;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="<a href='#'></a>","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="<input/>",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+Math.random()}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)
+},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?n.queue(this[0],a):void 0===b?this:this.each(function(){var c=n.queue(this,a,b);n._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&n.dequeue(this,a)})},dequeue:function(a){return this.each(function(){n.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=n.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=L.get(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var Q=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,R=["Top","Right","Bottom","Left"],S=function(a,b){return a=b||a,"none"===n.css(a,"display")||!n.contains(a.ownerDocument,a)},T=/^(?:checkbox|radio)$/i;!function(){var a=l.createDocumentFragment(),b=a.appendChild(l.createElement("div")),c=l.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="<textarea>x</textarea>",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h<b.length&&g.push({elem:this,handlers:b.slice(h)}),g},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return null==a.which&&(a.which=null!=b.charCode?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,b){var c,d,e,f=b.button;return null==a.pageX&&null!=b.clientX&&(c=a.target.ownerDocument||l,d=c.documentElement,e=c.body,a.pageX=b.clientX+(d&&d.scrollLeft||e&&e.scrollLeft||0)-(d&&d.clientLeft||e&&e.clientLeft||0),a.pageY=b.clientY+(d&&d.scrollTop||e&&e.scrollTop||0)-(d&&d.clientTop||e&&e.clientTop||0)),a.which||void 0===f||(a.which=1&f?1:2&f?3:4&f?2:0),a}},fix:function(a){if(a[n.expando])return a;var b,c,d,e=a.type,f=a,g=this.fixHooks[e];g||(this.fixHooks[e]=g=W.test(e)?this.mouseHooks:V.test(e)?this.keyHooks:{}),d=g.props?this.props.concat(g.props):this.props,a=new n.Event(f),b=d.length;while(b--)c=d[b],a[c]=f[c];return a.target||(a.target=l),3===a.target.nodeType&&(a.target=a.target.parentNode),g.filter?g.filter(a,f):a},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==_()&&this.focus?(this.focus(),!1):void 0},delegateType:"focusin"},blur:{trigger:function(){return this===_()&&this.blur?(this.blur(),!1):void 0},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&n.nodeName(this,"input")?(this.click(),!1):void 0},_default:function(a){return n.nodeName(a.target,"a")}},beforeunload:{postDispatch:function(a){void 0!==a.result&&a.originalEvent&&(a.originalEvent.returnValue=a.result)}}},simulate:function(a,b,c,d){var e=n.extend(new n.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?n.event.trigger(e,null,b):n.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},n.removeEvent=function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)},n.Event=function(a,b){return this instanceof n.Event?(a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||void 0===a.defaultPrevented&&a.returnValue===!1?Z:$):this.type=a,b&&n.extend(this,b),this.timeStamp=a&&a.timeStamp||n.now(),void(this[n.expando]=!0)):new n.Event(a,b)},n.Event.prototype={isDefaultPrevented:$,isPropagationStopped:$,isImmediatePropagationStopped:$,preventDefault:function(){var a=this.originalEvent;this.isDefaultPrevented=Z,a&&a.preventDefault&&a.preventDefault()},stopPropagation:function(){var a=this.originalEvent;this.isPropagationStopped=Z,a&&a.stopPropagation&&a.stopPropagation()},stopImmediatePropagation:function(){var a=this.originalEvent;this.isImmediatePropagationStopped=Z,a&&a.stopImmediatePropagation&&a.stopImmediatePropagation(),this.stopPropagation()}},n.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(a,b){n.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj;return(!e||e!==d&&!n.contains(d,e))&&(a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b),c}}}),k.focusinBubbles||n.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){n.event.simulate(b,a.target,n.event.fix(a),!0)};n.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=L.access(d,b);e||d.addEventListener(a,c,!0),L.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=L.access(d,b)-1;e?L.access(d,b,e):(d.removeEventListener(a,c,!0),L.remove(d,b))}}}),n.fn.extend({on:function(a,b,c,d,e){var f,g;if("object"==typeof a){"string"!=typeof b&&(c=c||b,b=void 0);for(g in a)this.on(g,b,c,a[g],e);return this}if(null==c&&null==d?(d=b,c=b=void 0):null==d&&("string"==typeof b?(d=c,c=void 0):(d=c,c=b,b=void 0)),d===!1)d=$;else if(!d)return this;return 1===e&&(f=d,d=function(a){return n().off(a),f.apply(this,arguments)},d.guid=f.guid||(f.guid=n.guid++)),this.each(function(){n.event.add(this,a,d,c,b)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,b,c){var d,e;if(a&&a.preventDefault&&a.handleObj)return d=a.handleObj,n(a.delegateTarget).off(d.namespace?d.origType+"."+d.namespace:d.origType,d.selector,d.handler),this;if("object"==typeof a){for(e in a)this.off(e,b,a[e]);return this}return(b===!1||"function"==typeof b)&&(c=b,b=void 0),c===!1&&(c=$),this.each(function(){n.event.remove(this,a,c,b)})},trigger:function(a,b){return this.each(function(){n.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];return c?n.event.trigger(a,b,c,!0):void 0}});var ab=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,ib={option:[1,"<select multiple='multiple'>","</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1></$2>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(ob(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1></$2>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(ob(c,"script"),kb),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(hb,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function tb(a){var b=l,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||n("<iframe frameborder='0' width='0' height='0'/>")).appendTo(b.documentElement),b=qb[0].contentDocument,b.write(),b.close(),c=sb(a,b),qb.detach()),rb[a]=c),c}var ub=/^margin/,vb=new RegExp("^("+Q+")(?!px)[a-z%]+$","i"),wb=function(a){return a.ownerDocument.defaultView.getComputedStyle(a,null)};function xb(a,b,c){var d,e,f,g,h=a.style;return c=c||wb(a),c&&(g=c.getPropertyValue(b)||c[b]),c&&(""!==g||n.contains(a.ownerDocument,a)||(g=n.style(a,b)),vb.test(g)&&ub.test(b)&&(d=h.width,e=h.minWidth,f=h.maxWidth,h.minWidth=h.maxWidth=h.width=g,g=c.width,h.width=d,h.minWidth=e,h.maxWidth=f)),void 0!==g?g+"":g}function yb(a,b){return{get:function(){return a()?void delete this.get:(this.get=b).apply(this,arguments)}}}!function(){var b,c,d=l.documentElement,e=l.createElement("div"),f=l.createElement("div");if(f.style){f.style.backgroundClip="content-box",f.cloneNode(!0).style.backgroundClip="",k.clearCloneStyle="content-box"===f.style.backgroundClip,e.style.cssText="border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;position:absolute",e.appendChild(f);function g(){f.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;display:block;margin-top:1%;top:1%;border:1px;padding:1px;width:4px;position:absolute",f.innerHTML="",d.appendChild(e);var g=a.getComputedStyle(f,null);b="1%"!==g.top,c="4px"===g.width,d.removeChild(e)}a.getComputedStyle&&n.extend(k,{pixelPosition:function(){return g(),b},boxSizingReliable:function(){return null==c&&g(),c},reliableMarginRight:function(){var b,c=f.appendChild(l.createElement("div"));return c.style.cssText=f.style.cssText="-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;display:block;margin:0;border:0;padding:0",c.style.marginRight=c.style.width="0",f.style.width="1px",d.appendChild(e),b=!parseFloat(a.getComputedStyle(c,null).marginRight),d.removeChild(e),b}})}}(),n.swap=function(a,b,c,d){var e,f,g={};for(f in b)g[f]=a.style[f],a.style[f]=b[f];e=c.apply(a,d||[]);for(f in b)a.style[f]=g[f];return e};var zb=/^(none|table(?!-c[ea]).+)/,Ab=new RegExp("^("+Q+")(.*)$","i"),Bb=new RegExp("^([+-])=("+Q+")","i"),Cb={position:"absolute",visibility:"hidden",display:"block"},Db={letterSpacing:"0",fontWeight:"400"},Eb=["Webkit","O","Moz","ms"];function Fb(a,b){if(b in a)return b;var c=b[0].toUpperCase()+b.slice(1),d=b,e=Eb.length;while(e--)if(b=Eb[e]+c,b in a)return b;return d}function Gb(a,b,c){var d=Ab.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function Hb(a,b,c,d,e){for(var f=c===(d?"border":"content")?4:"width"===b?1:0,g=0;4>f;f+=2)"margin"===c&&(g+=n.css(a,c+R[f],!0,e)),d?("content"===c&&(g-=n.css(a,"padding"+R[f],!0,e)),"margin"!==c&&(g-=n.css(a,"border"+R[f]+"Width",!0,e))):(g+=n.css(a,"padding"+R[f],!0,e),"padding"!==c&&(g+=n.css(a,"border"+R[f]+"Width",!0,e)));return g}function Ib(a,b,c){var d=!0,e="width"===b?a.offsetWidth:a.offsetHeight,f=wb(a),g="border-box"===n.css(a,"boxSizing",!1,f);if(0>=e||null==e){if(e=xb(a,b,f),(0>e||null==e)&&(e=a.style[b]),vb.test(e))return e;d=g&&(k.boxSizingReliable()||e===a.style[b]),e=parseFloat(e)||0}return e+Hb(a,b,c||(g?"border":"content"),d,f)+"px"}function Jb(a,b){for(var c,d,e,f=[],g=0,h=a.length;h>g;g++)d=a[g],d.style&&(f[g]=L.get(d,"olddisplay"),c=d.style.display,b?(f[g]||"none"!==c||(d.style.display=""),""===d.style.display&&S(d)&&(f[g]=L.access(d,"olddisplay",tb(d.nodeName)))):(e=S(d),"none"===c&&e||L.set(d,"olddisplay",e?c:n.css(d,"display"))));for(g=0;h>g;g++)d=a[g],d.style&&(b&&"none"!==d.style.display&&""!==d.style.display||(d.style.display=b?f[g]||"":"none"));return a}n.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=xb(a,"opacity");return""===c?"1":c}}}},cssNumber:{columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(a,b,c,d){if(a&&3!==a.nodeType&&8!==a.nodeType&&a.style){var e,f,g,h=n.camelCase(b),i=a.style;return b=n.cssProps[h]||(n.cssProps[h]=Fb(i,h)),g=n.cssHooks[b]||n.cssHooks[h],void 0===c?g&&"get"in g&&void 0!==(e=g.get(a,!1,d))?e:i[b]:(f=typeof c,"string"===f&&(e=Bb.exec(c))&&(c=(e[1]+1)*e[2]+parseFloat(n.css(a,b)),f="number"),null!=c&&c===c&&("number"!==f||n.cssNumber[h]||(c+="px"),k.clearCloneStyle||""!==c||0!==b.indexOf("background")||(i[b]="inherit"),g&&"set"in g&&void 0===(c=g.set(a,c,d))||(i[b]=c)),void 0)}},css:function(a,b,c,d){var e,f,g,h=n.camelCase(b);return b=n.cssProps[h]||(n.cssProps[h]=Fb(a.style,h)),g=n.cssHooks[b]||n.cssHooks[h],g&&"get"in g&&(e=g.get(a,!0,c)),void 0===e&&(e=xb(a,b,d)),"normal"===e&&b in Db&&(e=Db[b]),""===c||c?(f=parseFloat(e),c===!0||n.isNumeric(f)?f||0:e):e}}),n.each(["height","width"],function(a,b){n.cssHooks[b]={get:function(a,c,d){return c?zb.test(n.css(a,"display"))&&0===a.offsetWidth?n.swap(a,Cb,function(){return Ib(a,b,d)}):Ib(a,b,d):void 0},set:function(a,c,d){var e=d&&wb(a);return Gb(a,c,d?Hb(a,b,d,"border-box"===n.css(a,"boxSizing",!1,e),e):0)}}}),n.cssHooks.marginRight=yb(k.reliableMarginRight,function(a,b){return b?n.swap(a,{display:"inline-block"},xb,[a,"marginRight"]):void 0}),n.each({margin:"",padding:"",border:"Width"},function(a,b){n.cssHooks[a+b]={expand:function(c){for(var d=0,e={},f="string"==typeof c?c.split(" "):[c];4>d;d++)e[a+R[d]+b]=f[d]||f[d-2]||f[0];return e}},ub.test(a)||(n.cssHooks[a+b].set=Gb)}),n.fn.extend({css:function(a,b){return J(this,function(a,b,c){var d,e,f={},g=0;if(n.isArray(b)){for(d=wb(a),e=b.length;e>g;g++)f[b[g]]=n.css(a,b[g],!1,d);return f}return void 0!==c?n.style(a,b,c):n.css(a,b)},a,b,arguments.length>1)},show:function(){return Jb(this,!0)},hide:function(){return Jb(this)},toggle:function(a){return"boolean"==typeof a?a?this.show():this.hide():this.each(function(){S(this)?n(this).show():n(this).hide()})}});function Kb(a,b,c,d,e){return new Kb.prototype.init(a,b,c,d,e)}n.Tween=Kb,Kb.prototype={constructor:Kb,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(n.cssNumber[c]?"":"px")},cur:function(){var a=Kb.propHooks[this.prop];return a&&a.get?a.get(this):Kb.propHooks._default.get(this)},run:function(a){var b,c=Kb.propHooks[this.prop];return this.pos=b=this.options.duration?n.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Kb.propHooks._default.set(this),this}},Kb.prototype.init.prototype=Kb.prototype,Kb.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=n.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){n.fx.step[a.prop]?n.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[n.cssProps[a.prop]]||n.cssHooks[a.prop])?n.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Kb.propHooks.scrollTop=Kb.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},n.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},n.fx=Kb.prototype.init,n.fx.step={};var Lb,Mb,Nb=/^(?:toggle|show|hide)$/,Ob=new RegExp("^(?:([+-])=|)("+Q+")([a-z%]*)$","i"),Pb=/queueHooks$/,Qb=[Vb],Rb={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=Ob.exec(b),f=e&&e[3]||(n.cssNumber[a]?"":"px"),g=(n.cssNumber[a]||"px"!==f&&+d)&&Ob.exec(n.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,n.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function Sb(){return setTimeout(function(){Lb=void 0}),Lb=n.now()}function Tb(a,b){var c,d=0,e={height:a};for(b=b?1:0;4>d;d+=2-b)c=R[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function Ub(a,b,c){for(var d,e=(Rb[b]||[]).concat(Rb["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function Vb(a,b,c){var d,e,f,g,h,i,j,k,l=this,m={},o=a.style,p=a.nodeType&&S(a),q=L.get(a,"fxshow");c.queue||(h=n._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,l.always(function(){l.always(function(){h.unqueued--,n.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[o.overflow,o.overflowX,o.overflowY],j=n.css(a,"display"),k="none"===j?L.get(a,"olddisplay")||tb(a.nodeName):j,"inline"===k&&"none"===n.css(a,"float")&&(o.display="inline-block")),c.overflow&&(o.overflow="hidden",l.always(function(){o.overflow=c.overflow[0],o.overflowX=c.overflow[1],o.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],Nb.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(p?"hide":"show")){if("show"!==e||!q||void 0===q[d])continue;p=!0}m[d]=q&&q[d]||n.style(a,d)}else j=void 0;if(n.isEmptyObject(m))"inline"===("none"===j?tb(a.nodeName):j)&&(o.display=j);else{q?"hidden"in q&&(p=q.hidden):q=L.access(a,"fxshow",{}),f&&(q.hidden=!p),p?n(a).show():l.done(function(){n(a).hide()}),l.done(function(){var b;L.remove(a,"fxshow");for(b in m)n.style(a,b,m[b])});for(d in m)g=Ub(p?q[d]:0,d,l),d in q||(q[d]=g.start,p&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function Wb(a,b){var c,d,e,f,g;for(c in a)if(d=n.camelCase(c),e=b[d],f=a[c],n.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=n.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function Xb(a,b,c){var d,e,f=0,g=Qb.length,h=n.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=Lb||Sb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:n.extend({},b),opts:n.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:Lb||Sb(),duration:c.duration,tweens:[],createTween:function(b,c){var d=n.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(d),d},stop:function(b){var c=0,d=b?j.tweens.length:0;if(e)return this;for(e=!0;d>c;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;for(Wb(k,j.opts.specialEasing);g>f;f++)if(d=Qb[f].call(j,a,k,j.opts))return d;return n.map(k,Ub,j),n.isFunction(j.opts.start)&&j.opts.start.call(a,j),n.fx.timer(n.extend(i,{elem:a,anim:j,queue:j.opts.queue})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}n.Animation=n.extend(Xb,{tweener:function(a,b){n.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");for(var c,d=0,e=a.length;e>d;d++)c=a[d],Rb[c]=Rb[c]||[],Rb[c].unshift(b)},prefilter:function(a,b){b?Qb.unshift(a):Qb.push(a)}}),n.speed=function(a,b,c){var d=a&&"object"==typeof a?n.extend({},a):{complete:c||!c&&b||n.isFunction(a)&&a,duration:a,easing:c&&b||b&&!n.isFunction(b)&&b};return d.duration=n.fx.off?0:"number"==typeof d.duration?d.duration:d.duration in n.fx.speeds?n.fx.speeds[d.duration]:n.fx.speeds._default,(null==d.queue||d.queue===!0)&&(d.queue="fx"),d.old=d.complete,d.complete=function(){n.isFunction(d.old)&&d.old.call(this),d.queue&&n.dequeue(this,d.queue)},d},n.fn.extend({fadeTo:function(a,b,c,d){return this.filter(S).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=n.isEmptyObject(a),f=n.speed(b,c,d),g=function(){var b=Xb(this,n.extend({},a),f);(e||L.get(this,"finish"))&&b.stop(!0)};return g.finish=g,e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,b,c){var d=function(a){var b=a.stop;delete a.stop,b(c)};return"string"!=typeof a&&(c=b,b=a,a=void 0),b&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,e=null!=a&&a+"queueHooks",f=n.timers,g=L.get(this);if(e)g[e]&&g[e].stop&&d(g[e]);else for(e in g)g[e]&&g[e].stop&&Pb.test(e)&&d(g[e]);for(e=f.length;e--;)f[e].elem!==this||null!=a&&f[e].queue!==a||(f[e].anim.stop(c),b=!1,f.splice(e,1));(b||!c)&&n.dequeue(this,a)})},finish:function(a){return a!==!1&&(a=a||"fx"),this.each(function(){var b,c=L.get(this),d=c[a+"queue"],e=c[a+"queueHooks"],f=n.timers,g=d?d.length:0;for(c.finish=!0,n.queue(this,a,[]),e&&e.stop&&e.stop.call(this,!0),b=f.length;b--;)f[b].elem===this&&f[b].queue===a&&(f[b].anim.stop(!0),f.splice(b,1));for(b=0;g>b;b++)d[b]&&d[b].finish&&d[b].finish.call(this);delete c.finish})}}),n.each(["toggle","show","hide"],function(a,b){var c=n.fn[b];n.fn[b]=function(a,d,e){return null==a||"boolean"==typeof a?c.apply(this,arguments):this.animate(Tb(b,!0),a,d,e)}}),n.each({slideDown:Tb("show"),slideUp:Tb("hide"),slideToggle:Tb("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){n.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),n.timers=[],n.fx.tick=function(){var a,b=0,c=n.timers;for(Lb=n.now();b<c.length;b++)a=c[b],a()||c[b]!==a||c.splice(b--,1);c.length||n.fx.stop(),Lb=void 0},n.fx.timer=function(a){n.timers.push(a),a()?n.fx.start():n.timers.pop()},n.fx.interval=13,n.fx.start=function(){Mb||(Mb=setInterval(n.fx.tick,n.fx.interval))},n.fx.stop=function(){clearInterval(Mb),Mb=null},n.fx.speeds={slow:600,fast:200,_default:400},n.fn.delay=function(a,b){return a=n.fx?n.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},function(){var a=l.createElement("input"),b=l.createElement("select"),c=b.appendChild(l.createElement("option"));a.type="checkbox",k.checkOn=""!==a.value,k.optSelected=c.selected,b.disabled=!0,k.optDisabled=!c.disabled,a=l.createElement("input"),a.value="t",a.type="radio",k.radioValue="t"===a.value}();var Yb,Zb,$b=n.expr.attrHandle;n.fn.extend({attr:function(a,b){return J(this,n.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){n.removeAttr(this,a)})}}),n.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(a&&3!==f&&8!==f&&2!==f)return typeof a.getAttribute===U?n.prop(a,b,c):(1===f&&n.isXMLDoc(a)||(b=b.toLowerCase(),d=n.attrHooks[b]||(n.expr.match.bool.test(b)?Zb:Yb)),void 0===c?d&&"get"in d&&null!==(e=d.get(a,b))?e:(e=n.find.attr(a,b),null==e?void 0:e):null!==c?d&&"set"in d&&void 0!==(e=d.set(a,c,b))?e:(a.setAttribute(b,c+""),c):void n.removeAttr(a,b))
+},removeAttr:function(a,b){var c,d,e=0,f=b&&b.match(E);if(f&&1===a.nodeType)while(c=f[e++])d=n.propFix[c]||c,n.expr.match.bool.test(c)&&(a[d]=!1),a.removeAttribute(c)},attrHooks:{type:{set:function(a,b){if(!k.radioValue&&"radio"===b&&n.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}}}),Zb={set:function(a,b,c){return b===!1?n.removeAttr(a,c):a.setAttribute(c,c),c}},n.each(n.expr.match.bool.source.match(/\w+/g),function(a,b){var c=$b[b]||n.find.attr;$b[b]=function(a,b,d){var e,f;return d||(f=$b[b],$b[b]=e,e=null!=c(a,b,d)?b.toLowerCase():null,$b[b]=f),e}});var _b=/^(?:input|select|textarea|button)$/i;n.fn.extend({prop:function(a,b){return J(this,n.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[n.propFix[a]||a]})}}),n.extend({propFix:{"for":"htmlFor","class":"className"},prop:function(a,b,c){var d,e,f,g=a.nodeType;if(a&&3!==g&&8!==g&&2!==g)return f=1!==g||!n.isXMLDoc(a),f&&(b=n.propFix[b]||b,e=n.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){return a.hasAttribute("tabindex")||_b.test(a.nodeName)||a.href?a.tabIndex:-1}}}}),k.optSelected||(n.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null}}),n.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){n.propFix[this.toLowerCase()]=this});var ac=/[\t\r\n\f]/g;n.fn.extend({addClass:function(a){var b,c,d,e,f,g,h="string"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).addClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ac," "):" ")){f=0;while(e=b[f++])d.indexOf(" "+e+" ")<0&&(d+=e+" ");g=n.trim(d),c.className!==g&&(c.className=g)}return this},removeClass:function(a){var b,c,d,e,f,g,h=0===arguments.length||"string"==typeof a&&a,i=0,j=this.length;if(n.isFunction(a))return this.each(function(b){n(this).removeClass(a.call(this,b,this.className))});if(h)for(b=(a||"").match(E)||[];j>i;i++)if(c=this[i],d=1===c.nodeType&&(c.className?(" "+c.className+" ").replace(ac," "):"")){f=0;while(e=b[f++])while(d.indexOf(" "+e+" ")>=0)d=d.replace(" "+e+" "," ");g=a?n.trim(d):"",c.className!==g&&(c.className=g)}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):this.each(n.isFunction(a)?function(c){n(this).toggleClass(a.call(this,c,this.className,b),b)}:function(){if("string"===c){var b,d=0,e=n(this),f=a.match(E)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else(c===U||"boolean"===c)&&(this.className&&L.set(this,"__className__",this.className),this.className=this.className||a===!1?"":L.get(this,"__className__")||"")})},hasClass:function(a){for(var b=" "+a+" ",c=0,d=this.length;d>c;c++)if(1===this[c].nodeType&&(" "+this[c].className+" ").replace(ac," ").indexOf(b)>=0)return!0;return!1}});var bc=/\r/g;n.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=n.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,n(this).val()):a,null==e?e="":"number"==typeof e?e+="":n.isArray(e)&&(e=n.map(e,function(a){return null==a?"":a+""})),b=n.valHooks[this.type]||n.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=n.valHooks[e.type]||n.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(bc,""):null==c?"":c)}}}),n.extend({valHooks:{option:{get:function(a){var b=n.find.attr(a,"value");return null!=b?b:n.trim(n.text(a))}},select:{get:function(a){for(var b,c,d=a.options,e=a.selectedIndex,f="select-one"===a.type||0>e,g=f?null:[],h=f?e+1:d.length,i=0>e?h:f?e:0;h>i;i++)if(c=d[i],!(!c.selected&&i!==e||(k.optDisabled?c.disabled:null!==c.getAttribute("disabled"))||c.parentNode.disabled&&n.nodeName(c.parentNode,"optgroup"))){if(b=n(c).val(),f)return b;g.push(b)}return g},set:function(a,b){var c,d,e=a.options,f=n.makeArray(b),g=e.length;while(g--)d=e[g],(d.selected=n.inArray(d.value,f)>=0)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),n.each(["radio","checkbox"],function(){n.valHooks[this]={set:function(a,b){return n.isArray(b)?a.checked=n.inArray(n(a).val(),b)>=0:void 0}},k.checkOn||(n.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})}),n.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){n.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),n.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return 1===arguments.length?this.off(a,"**"):this.off(b,a||"**",c)}});var cc=n.now(),dc=/\?/;n.parseJSON=function(a){return JSON.parse(a+"")},n.parseXML=function(a){var b,c;if(!a||"string"!=typeof a)return null;try{c=new DOMParser,b=c.parseFromString(a,"text/xml")}catch(d){b=void 0}return(!b||b.getElementsByTagName("parsererror").length)&&n.error("Invalid XML: "+a),b};var ec,fc,gc=/#.*$/,hc=/([?&])_=[^&]*/,ic=/^(.*?):[ \t]*([^\r\n]*)$/gm,jc=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,kc=/^(?:GET|HEAD)$/,lc=/^\/\//,mc=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,nc={},oc={},pc="*/".concat("*");try{fc=location.href}catch(qc){fc=l.createElement("a"),fc.href="",fc=fc.href}ec=mc.exec(fc.toLowerCase())||[];function rc(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(E)||[];if(n.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function sc(a,b,c,d){var e={},f=a===oc;function g(h){var i;return e[h]=!0,n.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function tc(a,b){var c,d,e=n.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&n.extend(!0,a,d),a}function uc(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}return f?(f!==i[0]&&i.unshift(f),c[f]):void 0}function vc(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}n.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:fc,type:"GET",isLocal:jc.test(ec[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":pc,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":n.parseJSON,"text xml":n.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?tc(tc(a,n.ajaxSettings),b):tc(n.ajaxSettings,a)},ajaxPrefilter:rc(nc),ajaxTransport:rc(oc),ajax:function(a,b){"object"==typeof a&&(b=a,a=void 0),b=b||{};var c,d,e,f,g,h,i,j,k=n.ajaxSetup({},b),l=k.context||k,m=k.context&&(l.nodeType||l.jquery)?n(l):n.event,o=n.Deferred(),p=n.Callbacks("once memory"),q=k.statusCode||{},r={},s={},t=0,u="canceled",v={readyState:0,getResponseHeader:function(a){var b;if(2===t){if(!f){f={};while(b=ic.exec(e))f[b[1].toLowerCase()]=b[2]}b=f[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return 2===t?e:null},setRequestHeader:function(a,b){var c=a.toLowerCase();return t||(a=s[c]=s[c]||a,r[a]=b),this},overrideMimeType:function(a){return t||(k.mimeType=a),this},statusCode:function(a){var b;if(a)if(2>t)for(b in a)q[b]=[q[b],a[b]];else v.always(a[v.status]);return this},abort:function(a){var b=a||u;return c&&c.abort(b),x(0,b),this}};if(o.promise(v).complete=p.add,v.success=v.done,v.error=v.fail,k.url=((a||k.url||fc)+"").replace(gc,"").replace(lc,ec[1]+"//"),k.type=b.method||b.type||k.method||k.type,k.dataTypes=n.trim(k.dataType||"*").toLowerCase().match(E)||[""],null==k.crossDomain&&(h=mc.exec(k.url.toLowerCase()),k.crossDomain=!(!h||h[1]===ec[1]&&h[2]===ec[2]&&(h[3]||("http:"===h[1]?"80":"443"))===(ec[3]||("http:"===ec[1]?"80":"443")))),k.data&&k.processData&&"string"!=typeof k.data&&(k.data=n.param(k.data,k.traditional)),sc(nc,k,b,v),2===t)return v;i=k.global,i&&0===n.active++&&n.event.trigger("ajaxStart"),k.type=k.type.toUpperCase(),k.hasContent=!kc.test(k.type),d=k.url,k.hasContent||(k.data&&(d=k.url+=(dc.test(d)?"&":"?")+k.data,delete k.data),k.cache===!1&&(k.url=hc.test(d)?d.replace(hc,"$1_="+cc++):d+(dc.test(d)?"&":"?")+"_="+cc++)),k.ifModified&&(n.lastModified[d]&&v.setRequestHeader("If-Modified-Since",n.lastModified[d]),n.etag[d]&&v.setRequestHeader("If-None-Match",n.etag[d])),(k.data&&k.hasContent&&k.contentType!==!1||b.contentType)&&v.setRequestHeader("Content-Type",k.contentType),v.setRequestHeader("Accept",k.dataTypes[0]&&k.accepts[k.dataTypes[0]]?k.accepts[k.dataTypes[0]]+("*"!==k.dataTypes[0]?", "+pc+"; q=0.01":""):k.accepts["*"]);for(j in k.headers)v.setRequestHeader(j,k.headers[j]);if(k.beforeSend&&(k.beforeSend.call(l,v,k)===!1||2===t))return v.abort();u="abort";for(j in{success:1,error:1,complete:1})v[j](k[j]);if(c=sc(oc,k,b,v)){v.readyState=1,i&&m.trigger("ajaxSend",[v,k]),k.async&&k.timeout>0&&(g=setTimeout(function(){v.abort("timeout")},k.timeout));try{t=1,c.send(r,x)}catch(w){if(!(2>t))throw w;x(-1,w)}}else x(-1,"No Transport");function x(a,b,f,h){var j,r,s,u,w,x=b;2!==t&&(t=2,g&&clearTimeout(g),c=void 0,e=h||"",v.readyState=a>0?4:0,j=a>=200&&300>a||304===a,f&&(u=uc(k,v,f)),u=vc(k,u,v,j),j?(k.ifModified&&(w=v.getResponseHeader("Last-Modified"),w&&(n.lastModified[d]=w),w=v.getResponseHeader("etag"),w&&(n.etag[d]=w)),204===a||"HEAD"===k.type?x="nocontent":304===a?x="notmodified":(x=u.state,r=u.data,s=u.error,j=!s)):(s=x,(a||!x)&&(x="error",0>a&&(a=0))),v.status=a,v.statusText=(b||x)+"",j?o.resolveWith(l,[r,x,v]):o.rejectWith(l,[v,x,s]),v.statusCode(q),q=void 0,i&&m.trigger(j?"ajaxSuccess":"ajaxError",[v,k,j?r:s]),p.fireWith(l,[v,x]),i&&(m.trigger("ajaxComplete",[v,k]),--n.active||n.event.trigger("ajaxStop")))}return v},getJSON:function(a,b,c){return n.get(a,b,c,"json")},getScript:function(a,b){return n.get(a,void 0,b,"script")}}),n.each(["get","post"],function(a,b){n[b]=function(a,c,d,e){return n.isFunction(c)&&(e=e||d,d=c,c=void 0),n.ajax({url:a,type:b,dataType:e,data:c,success:d})}}),n.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(a,b){n.fn[b]=function(a){return this.on(b,a)}}),n._evalUrl=function(a){return n.ajax({url:a,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})},n.fn.extend({wrapAll:function(a){var b;return n.isFunction(a)?this.each(function(b){n(this).wrapAll(a.call(this,b))}):(this[0]&&(b=n(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this)},wrapInner:function(a){return this.each(n.isFunction(a)?function(b){n(this).wrapInner(a.call(this,b))}:function(){var b=n(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=n.isFunction(a);return this.each(function(c){n(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){n.nodeName(this,"body")||n(this).replaceWith(this.childNodes)}).end()}}),n.expr.filters.hidden=function(a){return a.offsetWidth<=0&&a.offsetHeight<=0},n.expr.filters.visible=function(a){return!n.expr.filters.hidden(a)};var wc=/%20/g,xc=/\[\]$/,yc=/\r?\n/g,zc=/^(?:submit|button|image|reset|file)$/i,Ac=/^(?:input|select|textarea|keygen)/i;function Bc(a,b,c,d){var e;if(n.isArray(b))n.each(b,function(b,e){c||xc.test(a)?d(a,e):Bc(a+"["+("object"==typeof e?b:"")+"]",e,c,d)});else if(c||"object"!==n.type(b))d(a,b);else for(e in b)Bc(a+"["+e+"]",b[e],c,d)}n.param=function(a,b){var c,d=[],e=function(a,b){b=n.isFunction(b)?b():null==b?"":b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};if(void 0===b&&(b=n.ajaxSettings&&n.ajaxSettings.traditional),n.isArray(a)||a.jquery&&!n.isPlainObject(a))n.each(a,function(){e(this.name,this.value)});else for(c in a)Bc(c,a[c],b,e);return d.join("&").replace(wc,"+")},n.fn.extend({serialize:function(){return n.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=n.prop(this,"elements");return a?n.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!n(this).is(":disabled")&&Ac.test(this.nodeName)&&!zc.test(a)&&(this.checked||!T.test(a))}).map(function(a,b){var c=n(this).val();return null==c?null:n.isArray(c)?n.map(c,function(a){return{name:b.name,value:a.replace(yc,"\r\n")}}):{name:b.name,value:c.replace(yc,"\r\n")}}).get()}}),n.ajaxSettings.xhr=function(){try{return new XMLHttpRequest}catch(a){}};var Cc=0,Dc={},Ec={0:200,1223:204},Fc=n.ajaxSettings.xhr();a.ActiveXObject&&n(a).on("unload",function(){for(var a in Dc)Dc[a]()}),k.cors=!!Fc&&"withCredentials"in Fc,k.ajax=Fc=!!Fc,n.ajaxTransport(function(a){var b;return k.cors||Fc&&!a.crossDomain?{send:function(c,d){var e,f=a.xhr(),g=++Cc;if(f.open(a.type,a.url,a.async,a.username,a.password),a.xhrFields)for(e in a.xhrFields)f[e]=a.xhrFields[e];a.mimeType&&f.overrideMimeType&&f.overrideMimeType(a.mimeType),a.crossDomain||c["X-Requested-With"]||(c["X-Requested-With"]="XMLHttpRequest");for(e in c)f.setRequestHeader(e,c[e]);b=function(a){return function(){b&&(delete Dc[g],b=f.onload=f.onerror=null,"abort"===a?f.abort():"error"===a?d(f.status,f.statusText):d(Ec[f.status]||f.status,f.statusText,"string"==typeof f.responseText?{text:f.responseText}:void 0,f.getAllResponseHeaders()))}},f.onload=b(),f.onerror=b("error"),b=Dc[g]=b("abort");try{f.send(a.hasContent&&a.data||null)}catch(h){if(b)throw h}},abort:function(){b&&b()}}:void 0}),n.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(a){return n.globalEval(a),a}}}),n.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),n.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(d,e){b=n("<script>").prop({async:!0,charset:a.scriptCharset,src:a.url}).on("load error",c=function(a){b.remove(),c=null,a&&e("error"===a.type?404:200,a.type)}),l.head.appendChild(b[0])},abort:function(){c&&c()}}}});var Gc=[],Hc=/(=)\?(?=&|$)|\?\?/;n.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=Gc.pop()||n.expando+"_"+cc++;return this[a]=!0,a}}),n.ajaxPrefilter("json jsonp",function(b,c,d){var e,f,g,h=b.jsonp!==!1&&(Hc.test(b.url)?"url":"string"==typeof b.data&&!(b.contentType||"").indexOf("application/x-www-form-urlencoded")&&Hc.test(b.data)&&"data");return h||"jsonp"===b.dataTypes[0]?(e=b.jsonpCallback=n.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,h?b[h]=b[h].replace(Hc,"$1"+e):b.jsonp!==!1&&(b.url+=(dc.test(b.url)?"&":"?")+b.jsonp+"="+e),b.converters["script json"]=function(){return g||n.error(e+" was not called"),g[0]},b.dataTypes[0]="json",f=a[e],a[e]=function(){g=arguments},d.always(function(){a[e]=f,b[e]&&(b.jsonpCallback=c.jsonpCallback,Gc.push(e)),g&&n.isFunction(f)&&f(g[0]),g=f=void 0}),"script"):void 0}),n.parseHTML=function(a,b,c){if(!a||"string"!=typeof a)return null;"boolean"==typeof b&&(c=b,b=!1),b=b||l;var d=v.exec(a),e=!c&&[];return d?[b.createElement(d[1])]:(d=n.buildFragment([a],b,e),e&&e.length&&n(e).remove(),n.merge([],d.childNodes))};var Ic=n.fn.load;n.fn.load=function(a,b,c){if("string"!=typeof a&&Ic)return Ic.apply(this,arguments);var d,e,f,g=this,h=a.indexOf(" ");return h>=0&&(d=n.trim(a.slice(h)),a=a.slice(0,h)),n.isFunction(b)?(c=b,b=void 0):b&&"object"==typeof b&&(e="POST"),g.length>0&&n.ajax({url:a,type:e,dataType:"html",data:b}).done(function(a){f=arguments,g.html(d?n("<div>").append(n.parseHTML(a)).find(d):a)}).complete(c&&function(a,b){g.each(c,f||[a.responseText,b,a])}),this},n.expr.filters.animated=function(a){return n.grep(n.timers,function(b){return a===b.elem}).length};var Jc=a.document.documentElement;function Kc(a){return n.isWindow(a)?a:9===a.nodeType&&a.defaultView}n.offset={setOffset:function(a,b,c){var d,e,f,g,h,i,j,k=n.css(a,"position"),l=n(a),m={};"static"===k&&(a.style.position="relative"),h=l.offset(),f=n.css(a,"top"),i=n.css(a,"left"),j=("absolute"===k||"fixed"===k)&&(f+i).indexOf("auto")>-1,j?(d=l.position(),g=d.top,e=d.left):(g=parseFloat(f)||0,e=parseFloat(i)||0),n.isFunction(b)&&(b=b.call(a,c,h)),null!=b.top&&(m.top=b.top-h.top+g),null!=b.left&&(m.left=b.left-h.left+e),"using"in b?b.using.call(a,m):l.css(m)}},n.fn.extend({offset:function(a){if(arguments.length)return void 0===a?this:this.each(function(b){n.offset.setOffset(this,a,b)});var b,c,d=this[0],e={top:0,left:0},f=d&&d.ownerDocument;if(f)return b=f.documentElement,n.contains(b,d)?(typeof d.getBoundingClientRect!==U&&(e=d.getBoundingClientRect()),c=Kc(f),{top:e.top+c.pageYOffset-b.clientTop,left:e.left+c.pageXOffset-b.clientLeft}):e},position:function(){if(this[0]){var a,b,c=this[0],d={top:0,left:0};return"fixed"===n.css(c,"position")?b=c.getBoundingClientRect():(a=this.offsetParent(),b=this.offset(),n.nodeName(a[0],"html")||(d=a.offset()),d.top+=n.css(a[0],"borderTopWidth",!0),d.left+=n.css(a[0],"borderLeftWidth",!0)),{top:b.top-d.top-n.css(c,"marginTop",!0),left:b.left-d.left-n.css(c,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||Jc;while(a&&!n.nodeName(a,"html")&&"static"===n.css(a,"position"))a=a.offsetParent;return a||Jc})}}),n.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(b,c){var d="pageYOffset"===c;n.fn[b]=function(e){return J(this,function(b,e,f){var g=Kc(b);return void 0===f?g?g[c]:b[e]:void(g?g.scrollTo(d?a.pageXOffset:f,d?f:a.pageYOffset):b[e]=f)},b,e,arguments.length,null)}}),n.each(["top","left"],function(a,b){n.cssHooks[b]=yb(k.pixelPosition,function(a,c){return c?(c=xb(a,b),vb.test(c)?n(a).position()[b]+"px":c):void 0})}),n.each({Height:"height",Width:"width"},function(a,b){n.each({padding:"inner"+a,content:b,"":"outer"+a},function(c,d){n.fn[d]=function(d,e){var f=arguments.length&&(c||"boolean"!=typeof d),g=c||(d===!0||e===!0?"margin":"border");return J(this,function(b,c,d){var e;return n.isWindow(b)?b.document.documentElement["client"+a]:9===b.nodeType?(e=b.documentElement,Math.max(b.body["scroll"+a],e["scroll"+a],b.body["offset"+a],e["offset"+a],e["client"+a])):void 0===d?n.css(b,c,g):n.style(b,c,d,g)},b,f?d:void 0,f,null)}})}),n.fn.size=function(){return this.length},n.fn.andSelf=n.fn.addBack,"function"==typeof define&&define.amd&&define("jquery",[],function(){return n});var Lc=a.jQuery,Mc=a.$;return n.noConflict=function(b){return a.$===n&&(a.$=Mc),b&&a.jQuery===n&&(a.jQuery=Lc),n},typeof b===U&&(a.jQuery=a.$=n),n});
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