summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Maps
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/Maps')
-rw-r--r--www/wiki/extensions/Maps/.travis.yml12
-rw-r--r--www/wiki/extensions/Maps/DefaultSettings.php9
-rw-r--r--www/wiki/extensions/Maps/INSTALL.md36
-rw-r--r--www/wiki/extensions/Maps/README.md11
-rw-r--r--www/wiki/extensions/Maps/RELEASE-NOTES.md130
-rw-r--r--www/wiki/extensions/Maps/composer.json5
-rw-r--r--www/wiki/extensions/Maps/extension.json220
-rw-r--r--www/wiki/extensions/Maps/i18n/ar.json44
-rw-r--r--www/wiki/extensions/Maps/i18n/ast.json41
-rw-r--r--www/wiki/extensions/Maps/i18n/be-tarask.json2
-rw-r--r--www/wiki/extensions/Maps/i18n/bn.json7
-rw-r--r--www/wiki/extensions/Maps/i18n/de.json3
-rw-r--r--www/wiki/extensions/Maps/i18n/el.json3
-rw-r--r--www/wiki/extensions/Maps/i18n/en.json54
-rw-r--r--www/wiki/extensions/Maps/i18n/es.json29
-rw-r--r--www/wiki/extensions/Maps/i18n/fi.json9
-rw-r--r--www/wiki/extensions/Maps/i18n/fr.json250
-rw-r--r--www/wiki/extensions/Maps/i18n/gl.json6
-rw-r--r--www/wiki/extensions/Maps/i18n/he.json7
-rw-r--r--www/wiki/extensions/Maps/i18n/hr.json3
-rw-r--r--www/wiki/extensions/Maps/i18n/ia.json2
-rw-r--r--www/wiki/extensions/Maps/i18n/it.json23
-rw-r--r--www/wiki/extensions/Maps/i18n/ja.json3
-rw-r--r--www/wiki/extensions/Maps/i18n/ko.json11
-rw-r--r--www/wiki/extensions/Maps/i18n/lb.json8
-rw-r--r--www/wiki/extensions/Maps/i18n/lt.json41
-rw-r--r--www/wiki/extensions/Maps/i18n/mk.json58
-rw-r--r--www/wiki/extensions/Maps/i18n/nb.json48
-rw-r--r--www/wiki/extensions/Maps/i18n/nl.json3
-rw-r--r--www/wiki/extensions/Maps/i18n/pl.json45
-rw-r--r--www/wiki/extensions/Maps/i18n/pt-br.json39
-rw-r--r--www/wiki/extensions/Maps/i18n/pt.json7
-rw-r--r--www/wiki/extensions/Maps/i18n/qqq.json29
-rw-r--r--www/wiki/extensions/Maps/i18n/roa-tara.json40
-rw-r--r--www/wiki/extensions/Maps/i18n/ru.json41
-rw-r--r--www/wiki/extensions/Maps/i18n/sh.json69
-rw-r--r--www/wiki/extensions/Maps/i18n/sv.json45
-rw-r--r--www/wiki/extensions/Maps/i18n/tt-cyrl.json73
-rw-r--r--www/wiki/extensions/Maps/i18n/uk.json3
-rw-r--r--www/wiki/extensions/Maps/i18n/vi.json3
-rw-r--r--www/wiki/extensions/Maps/i18n/zh-hans.json10
-rw-r--r--www/wiki/extensions/Maps/i18n/zh-hant.json46
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/ext.maps.googlemaps3.js31
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ProjectedOverlay.js186
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/README2
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ZipFile.complete.js3939
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js3398
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/googlemaps3ajax.js (renamed from www/wiki/extensions/Maps/resources/GoogleMaps/ext.sm.googlemaps3ajax.js)2
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/jquery.googlemap.js10
-rw-r--r--www/wiki/extensions/Maps/resources/MapSaver.js57
-rw-r--r--www/wiki/extensions/Maps/resources/WikitextEditor/css/jquery.miniColors.css (renamed from www/wiki/extensions/Maps/resources/editor/css/jquery.miniColors.css)0
-rw-r--r--www/wiki/extensions/Maps/resources/WikitextEditor/css/mapeditor.css (renamed from www/wiki/extensions/Maps/resources/editor/css/mapeditor.css)0
-rw-r--r--www/wiki/extensions/Maps/resources/WikitextEditor/images/circle.gif (renamed from www/wiki/extensions/Maps/resources/editor/images/circle.gif)bin78 -> 78 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/WikitextEditor/images/gradient.png (renamed from www/wiki/extensions/Maps/resources/editor/images/gradient.png)bin6548 -> 6548 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/WikitextEditor/images/line.gif (renamed from www/wiki/extensions/Maps/resources/editor/images/line.gif)bin1104 -> 1104 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/WikitextEditor/images/rainbow.png (renamed from www/wiki/extensions/Maps/resources/editor/images/rainbow.png)bin1665 -> 1665 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/WikitextEditor/images/trigger.png (renamed from www/wiki/extensions/Maps/resources/editor/images/trigger.png)bin538 -> 538 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/WikitextEditor/js/README (renamed from www/wiki/extensions/Maps/resources/editor/js/README)0
-rw-r--r--www/wiki/extensions/Maps/resources/WikitextEditor/js/jquery.miniColors.js (renamed from www/wiki/extensions/Maps/resources/editor/js/jquery.miniColors.js)0
-rw-r--r--www/wiki/extensions/Maps/resources/WikitextEditor/js/mapeditor.iefixes.js (renamed from www/wiki/extensions/Maps/resources/editor/js/mapeditor.iefixes.js)0
-rw-r--r--www/wiki/extensions/Maps/resources/WikitextEditor/js/mapeditor.js (renamed from www/wiki/extensions/Maps/resources/editor/js/mapeditor.js)2
-rw-r--r--www/wiki/extensions/Maps/resources/api.js44
-rw-r--r--www/wiki/extensions/Maps/resources/geoJsonPage.js96
-rw-r--r--www/wiki/extensions/Maps/resources/geojson.new.page.js38
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/FeatureBuilder.js220
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/GeoJson.js74
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/LeafletCluster.js69
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/LeafletEditor.js256
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/LeafletLoader.js15
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/cluster/m1.pngbin3288 -> 3003 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/cluster/m2.pngbin3707 -> 3259 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/cluster/m3.pngbin5605 -> 3956 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/cluster/m4.pngbin6515 -> 5705 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/cluster/m5.pngbin7249 -> 6839 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/ext.maps.leaflet.js4
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/ext.sm.leafletajax.js44
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/images/edit-solid.svg1
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/images/save-solid.svg1
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/jquery.leaflet.js567
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.editable/Leaflet.Editable.js1945
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.editor.js15
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/.jshintrc12
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/Control.FullScreen.css4
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/Control.FullScreen.js164
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/LICENSE22
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/README.md68
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/bower.json30
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/icon-fullscreen-2x.pngbin228 -> 0 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/icon-fullscreen.pngbin153 -> 0 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/index.html48
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.markercluster/leaflet.markercluster.js3
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-icon-2x.pngbin4230 -> 0 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-icon.pngbin1870 -> 0 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet/leaflet.js5
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet-providers/leaflet-providers.js (renamed from www/wiki/extensions/Maps/resources/leaflet/leaflet-providers/leaflet-providers.js)0
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.EasyButton/easy-button.css56
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.EasyButton/easy-button.js377
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.StyleEditor/css/Leaflet.StyleEditor.min.css306
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.StyleEditor/img/control.svg114
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.StyleEditor/img/icon.pngbin0 -> 4546 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.StyleEditor/javascript/Leaflet.StyleEditor.min.js2
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.draw/images/spritesheet-2x.pngbin0 -> 3581 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.draw/images/spritesheet.svg156
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw-src.css325
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw-src.js4774
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw-src.map1
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw.css10
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw.js10
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/Control.FullScreen.css8
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/Control.FullScreen.js228
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/icon-fullscreen-2x.pngbin0 -> 215 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/icon-fullscreen.pngbin0 -> 139 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/package.json (renamed from www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/package.json)6
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.markercluster/MarkerCluster.css (renamed from www/wiki/extensions/Maps/resources/leaflet/leaflet.markercluster/MarkerCluster.css)0
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet.markercluster/leaflet.markercluster.js2689
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet/images/layers-2x.png (renamed from www/wiki/extensions/Maps/resources/leaflet/leaflet/images/layers-2x.png)bin1259 -> 1259 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet/images/layers.png (renamed from www/wiki/extensions/Maps/resources/leaflet/leaflet/images/layers.png)bin696 -> 696 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet/images/marker-icon-2x.pngbin0 -> 2586 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet/images/marker-icon.pngbin0 -> 1466 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet/images/marker-shadow.png (renamed from www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-shadow.png)bin618 -> 618 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet/leaflet.css (renamed from www/wiki/extensions/Maps/resources/leaflet/leaflet/leaflet.css)7
-rw-r--r--www/wiki/extensions/Maps/resources/lib/leaflet/leaflet.js14079
-rw-r--r--www/wiki/extensions/Maps/resources/maps.common.js4
-rw-r--r--www/wiki/extensions/Maps/resources/maps.services.js86
-rw-r--r--www/wiki/extensions/Maps/resources/semanticMaps.js (renamed from www/wiki/extensions/Maps/resources/sm.common.js)34
-rw-r--r--www/wiki/extensions/Maps/src/DataAccess/GeoJsonFetcher.php93
-rw-r--r--www/wiki/extensions/Maps/src/DataAccess/GeoJsonFetcherResult.php31
-rw-r--r--www/wiki/extensions/Maps/src/DataAccess/JsonFileParser.php79
-rw-r--r--www/wiki/extensions/Maps/src/GoogleMapsService.php71
-rw-r--r--www/wiki/extensions/Maps/src/LeafletService.php66
-rw-r--r--www/wiki/extensions/Maps/src/MappingService.php6
-rw-r--r--www/wiki/extensions/Maps/src/MapsFactory.php34
-rw-r--r--www/wiki/extensions/Maps/src/MapsFunctions.php32
-rw-r--r--www/wiki/extensions/Maps/src/MapsSetup.php87
-rw-r--r--www/wiki/extensions/Maps/src/MediaWiki/Content/GeoJsonContent.php57
-rw-r--r--www/wiki/extensions/Maps/src/MediaWiki/Content/GeoJsonContentHandler.php6
-rw-r--r--www/wiki/extensions/Maps/src/MediaWiki/MapsHooks.php61
-rw-r--r--www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/DisplayMapFunction.php117
-rw-r--r--www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/DisplayMapRenderer.php7
-rw-r--r--www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/FindDestinationFunction.php4
-rw-r--r--www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/MapsDocFunction.php19
-rw-r--r--www/wiki/extensions/Maps/src/MediaWiki/Specials/SpecialMapEditor.php3
-rw-r--r--www/wiki/extensions/Maps/src/Presentation/GeoJsonMapPageUi.php83
-rw-r--r--www/wiki/extensions/Maps/src/Presentation/GeoJsonNewPageUi.php27
-rw-r--r--www/wiki/extensions/Maps/src/Presentation/MapHtmlBuilder.php10
-rw-r--r--www/wiki/extensions/Maps/src/Presentation/MapsDistanceParser.php2
-rw-r--r--www/wiki/extensions/Maps/src/Presentation/OutputFacade.php62
-rw-r--r--www/wiki/extensions/Maps/src/SemanticMW/ResultPrinters/MapPrinter.php17
-rw-r--r--www/wiki/extensions/Maps/tests/Integration/DataAccess/GeoJsonFetcherTest.php (renamed from www/wiki/extensions/Maps/tests/Integration/parsers/JsonFileParserTest.php)43
-rw-r--r--www/wiki/extensions/Maps/tests/Integration/MediaWiki/ParserHooks/CoordinatesTest.php8
-rw-r--r--www/wiki/extensions/Maps/tests/Integration/MediaWiki/ParserHooks/ParserHookTest.php13
-rw-r--r--www/wiki/extensions/Maps/tests/Integration/Parser/DisplayMapTest.php39
-rw-r--r--www/wiki/extensions/Maps/tests/Integration/ResourceModulesTest.php56
-rw-r--r--www/wiki/extensions/Maps/tests/Unit/MediaWiki/GeoJsonContentTest.php22
-rw-r--r--www/wiki/extensions/Maps/tests/js/leaflet/GeoJsonTest.js112
155 files changed, 30376 insertions, 7063 deletions
diff --git a/www/wiki/extensions/Maps/.travis.yml b/www/wiki/extensions/Maps/.travis.yml
index 49373ca8..a0e21f15 100644
--- a/www/wiki/extensions/Maps/.travis.yml
+++ b/www/wiki/extensions/Maps/.travis.yml
@@ -8,14 +8,14 @@ sudo: false
matrix:
fast_finish: true
include:
- - env: DBTYPE=mysql; MW=1.31.3
+ - env: DBTYPE=mysql; MW=1.31.3; SMW=3.0.0
php: 7.1
- - env: DBTYPE=mysql; MW=master; SMW=dev-master
- php: 7.3
- - env: DBTYPE=sqlite; MW=1.31.3; SMW=3.0.0
- php: 7.1
- - env: DBTYPE=sqlite; MW=master; TYPE=coverage
+ - env: DBTYPE=sqlite; MW=1.33.0; SMW=3.1.0
php: 7.2
+ - env: DBTYPE=mysql; MW=master; SMW=dev-master; TYPE=coverage
+ php: 7.3
+ - env: DBTYPE=mysql; MW=master
+ php: 7.4snapshot
install:
- travis_retry composer self-update
diff --git a/www/wiki/extensions/Maps/DefaultSettings.php b/www/wiki/extensions/Maps/DefaultSettings.php
index a3834788..146a1730 100644
--- a/www/wiki/extensions/Maps/DefaultSettings.php
+++ b/www/wiki/extensions/Maps/DefaultSettings.php
@@ -1,8 +1,9 @@
<?php
+// DO NOT INCLUDE THIS FILE IN LocalSettings.php! Instead include Maps_Settings.php.
+
// This file lists all settings you can use to configure the Maps extension and their default values.
// Do not modify this file to change settings. See https://www.semantic-mediawiki.org/wiki/Maps/Configuration
-// Do not include this file in LocalSettings. Instead include Maps_Settings.php.
return [
// Mapping services that will be available in the wiki.
@@ -156,6 +157,10 @@ return [
'egMapsLeafletLayer' => 'OpenStreetMap',
'egMapsLeafletLayers' => [ 'OpenStreetMap' ],
+ // Array of strings. The default layers for Leaflet to be used in dark theme mode.
+ // This will override any user parameter set for "layers" or "layer".
+ 'egMapsLeafletLayersDark' => [ 'CartoDB.DarkMatter' ],
+
'egMapsLeafletOverlayLayers' => [],
// The definitions for the layers that should be available for the user.
@@ -362,4 +367,4 @@ return [
'egMapsDebugJS' => false,
'egMapsGlobalJSVars' => [],
-]; \ No newline at end of file
+];
diff --git a/www/wiki/extensions/Maps/INSTALL.md b/www/wiki/extensions/Maps/INSTALL.md
index 20a4b3ad..a0fc3c48 100644
--- a/www/wiki/extensions/Maps/INSTALL.md
+++ b/www/wiki/extensions/Maps/INSTALL.md
@@ -18,27 +18,41 @@ minimum requirements are indicated in bold. For a detailed list of changes, see
<table>
<tr>
- <th>Maps</th>
- <th>PHP</th>
- <th>MediaWiki</th>
- <th>Semantic<br>MediaWiki</th>
- <th>Release status</th>
+ <th>Maps<br>&nbsp;</th>
+ <th>PHP<br>&nbsp;</th>
+ <th>MediaWiki<br>&nbsp;</th>
+ <th>Semantic MediaWiki<br>(optional)</th>
+ <th>Release status<br>&nbsp;</th>
</tr>
<tr>
- <th>7.4.x</th>
+ <th>7.14.x</th>
<td>7.1 - 7.4+</td>
- <td>1.31 - 1.33+</td>
- <td>3.0+</td>
- <td>Planned Q3 2019</td>
+ <td>1.31 - 1.34+</td>
+ <td>3.0 - 3.1</td>
+ <td>Planned Q4 2019</td>
</tr>
<tr>
- <th>7.3.x</th>
+ <th>7.13.x</th>
<td>7.1 - 7.4</td>
- <td>1.31 - 1.33</td>
+ <td>1.31 - 1.34</td>
<td>3.0 - 3.1</td>
<td><strong>Stable release</strong></td>
</tr>
<tr>
+ <th>7.12.x<br>-<br>7.10.x</th>
+ <td>7.1 - 7.4</td>
+ <td>1.31 - 1.34</td>
+ <td>3.0 - 3.1</td>
+ <td>Obsolete release, no support</td>
+ </tr>
+ <tr>
+ <th>7.9.x<br>-<br>7.3.x</th>
+ <td>7.1 - 7.4</td>
+ <td>1.31 - 1.33</td>
+ <td>3.0 - 3.1</td>
+ <td>Obsolete release, no support</td>
+ </tr>
+ <tr>
<th>7.2.x</th>
<td>7.1 - 7.3</td>
<td>1.31 - 1.32</td>
diff --git a/www/wiki/extensions/Maps/README.md b/www/wiki/extensions/Maps/README.md
index 230f3183..9baf56a3 100644
--- a/www/wiki/extensions/Maps/README.md
+++ b/www/wiki/extensions/Maps/README.md
@@ -1,7 +1,7 @@
# Maps
-Maps is a [MediaWiki](https://www.mediawiki.org) extension to work with and visualize geographical
-information.
+Maps is the [MediaWiki](https://www.mediawiki.org) extension to visualize and work with geographical
+information. It has been maintained since 2009 and is installed on 1000+ wikis.
Features:
@@ -12,14 +12,15 @@ Features:
* Export your coordinates as KML or RDF
* Combine coordinates with other structured data stored in your wiki
* Geocoding via several supported services with the [`#geocode`](https://www.semantic-mediawiki.org/wiki/Maps/Geocoding) parser function.
+* [GeoJson support](https://www.semantic-mediawiki.org/wiki/Extension:Maps/GeoJSON) including a basic visual editor
* Coordinate formatting and format conversion via the [`#coordinates`](https://www.semantic-mediawiki.org/wiki/Maps/Coordinates) parser function.
* Geospatial operations
* Calculating the distance between two points with [`#geodistance`](https://www.semantic-mediawiki.org/wiki/Maps/Geodistance)
* Finding a destination given a starting point, bearing and distance with [`#finddestination`](https://www.semantic-mediawiki.org/wiki/Maps/Finddestination)
* Distance formatting and format conversion via the [`#distance`](https://www.semantic-mediawiki.org/wiki/Maps/Distance) parser function.
-* Visual map editor ([Special:MapEditor](https://www.semantic-mediawiki.org/wiki/Special:MapEditor)) to edit [`#display_map`](https://www.semantic-mediawiki.org/wiki/Extension:Maps/Displaying_maps) wikitext (requires Google Maps).
+* Visual map editor (Special:MapEditor) to edit [`#display_map`](https://www.semantic-mediawiki.org/wiki/Extension:Maps/Displaying_maps) wikitext (requires Google Maps).
-Maps has been maintained since 2009 and is installed on 1000+ wikis. [Professional.Wiki](https://professional.wiki/) provides professional support.
+Missing a feature? [Professional.Wiki](https://professional.wiki/) does custom development at a discount if it is open sourced.
## User manual
@@ -49,7 +50,7 @@ Maps has been maintained since 2009 and is installed on 1000+ wikis. [Profession
* TravisCI &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [![Build Status](https://secure.travis-ci.org/JeroenDeDauw/Maps.png?branch=master)](http://travis-ci.org/JeroenDeDauw/Maps)
* Code quality &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/JeroenDeDauw/Maps/badges/quality-score.png?s=3881a27e63cb64e7511d766bfec2e2db5d39bec3)](https://scrutinizer-ci.com/g/JeroenDeDauw/Maps/)
* [Open bugs and feature requests](https://github.com/JeroenDeDauw/Maps/issues)
-* [Maps on OpenHub](https://www.ohloh.net/p/maps/)
+* [Maps on OpenHub](https://www.openhub.net/p/maps/)
* [Blog posts about Maps](https://www.entropywins.wtf/blog/tag/maps/)
## Contributing
diff --git a/www/wiki/extensions/Maps/RELEASE-NOTES.md b/www/wiki/extensions/Maps/RELEASE-NOTES.md
index 118272e2..48a08e49 100644
--- a/www/wiki/extensions/Maps/RELEASE-NOTES.md
+++ b/www/wiki/extensions/Maps/RELEASE-NOTES.md
@@ -3,6 +3,136 @@ different releases and which versions of PHP and MediaWiki they support, see the
[platform compatibility tables](INSTALL.md#platform-compatibility-and-release-status).
+## Mps 7.13.0
+
+Released on December 14th, 2019.
+
+* The GeoJSON editor now shows in #display_maps and #ask for Leaflet maps using the geojson parameter.
+* Removed the need to manually include `Maps_Settings.php` in `LocalSettings.php` when modifying maps settings.
+* Improved compatibility with MediaWiki 1.35
+
+## Maps 7.12.2
+
+Released on December 9th, 2019.
+
+* Invalid KML file names are no longer passed to Google Maps
+
+## Maps 7.12.1
+
+Released on December 9th, 2019.
+
+* Map query output is no longer incorrectly handled by the MediaWiki parser
+* Added logging of debug information when KML parsing fails
+* Upgraded Google Maps GeoXML parsing library for KML
+
+## Maps 7.12.0
+
+Released on December 9th, 2019.
+
+* Enhanced GeoJSON editor
+ * Added editing of titles and descriptions (by clicking markers/shapes)
+ * Added save button and removed auto-save
+ * Added ability to specify an edit summary
+ * Polygon intersections are now allowed
+* Added [simplestyle](https://github.com/mapbox/simplestyle-spec/tree/master/1.1.0) support for GeoJSON
+ * Popup text (property key `text`) (Only plaintext, HTML and wikitext are not supported)
+ * Popup description (property key `description`) (Only plaintext, HTML and wikitext are not supported)
+ * Fill color (property key `fill`)
+ * Fill opacity (property key `fill-opacity`)
+ * Border color (property key `stroke`)
+ * Border width (property key `stroke-width`)
+ * Border opacity (property key `stroke-opacity`)
+ * `marker-size`, `marker-symbol` and `marker-color` are not yet supported and will be ignored
+ * Display only, editing in the visual editor is not yet supported
+* Marker clustering now also cluster markers from the GeoJSON layer
+* Marker clustering now also cluster markers dynamically loaded via the `ajaxquery` feature
+* The Leaflet layer control is now always shown when there are overlays
+* Added `cluster` alias to the `markercluster` parameter for both Leaflet and Google Maps
+* Added `overlays` alias to the `overlaylayers` parameter for Leaflet
+* Leaflet maps with no markers or shapes are now zoomed out by default
+* Upgraded Leaflet from 1.3.4 to 1.6.0
+* Upgraded Leaflet marker cluster plugin from 1.3.0 to 1.4.1
+* Added missing "KML parsing failed" message to Google Maps
+
+## Maps 7.11.0
+
+Released on November 7th, 2019.
+
+* Fixed maps not loading without reloading the page after edit with Visual Editor
+* Fixed Leaflet Ajax functionality
+
+## Maps 7.10.0
+
+Released on October 24th, 2019.
+
+* Added dark mode support for Leaflet. Configurable via the new `egMapsLeafletLayersDark` setting (by @vedmaka)
+* Fixed PHP notice on some MediaWiki versions when running maintenance scripts
+
+## Maps 7.9.0
+
+Released on October 4th, 2019.
+
+* Added `clicktarget` parameter for Leaflet. `clicktarget=http://your.url?latitude=%lat%&longitude=%long%`
+* The `#mapsdoc` parser function now shows all parameters, not just those specific to a mapping service
+* The `visitedicon` parameter is no longer incorrectly shown as supported for Leaflet
+* The `wmsoverlay` parameter is no longer incorrectly shown as supported for Leaflet
+
+## Maps 7.8.3
+
+Released on October 3rd, 2019.
+
+* The "create page" button on GeoJson pages is now only shown to users with `createpage` permission
+
+## Maps 7.8.2
+
+Released on October 2nd, 2019.
+
+* Fixed recent Google Maps regression
+
+## Maps 7.8.1
+
+Released on October 2nd, 2019.
+
+* Fixed double display of marker icons in the GeoJson namespace
+
+## Maps 7.8.0
+
+Released on October 2nd, 2019.
+
+* Loading messages for Leaflet maps are no longer visible when zooming out far or loading new tiles
+* Added entirely visual creation flow for pages in the GeoJson namespace
+* Enhanced validation of content in the GeoJson namespace
+* Improved text on the creation and edit tabs in the GeoJson namespace
+* Added "Visual map edit" tag to revisions created by the GeoJson visual editor
+
+## Maps 7.7.0
+
+Released on September 29th, 2019.
+
+* Fixed GeoJson visual editor on MediaWiki 1.31.x (7.6.0 regression)
+* Internationalized most of the GeoJson visual editor
+* Added fullscreen control to the GeoJson visual editor
+* Added `fullscreen` alias for the `enablefullscreen` parameter
+
+## Maps 7.6.0
+
+Released on September 27th, 2019.
+
+* Fixed GeoJson map preview on MediaWiki 1.33+ (7.5.0 regression)
+* Added `scrollzoom` alias for the `scrollwheelzoom` parameter
+
+## Maps 7.5.0
+
+Released on September 24th, 2019.
+
+* Added visual editing UI to maps in the GeoJson namespace
+
+## Maps 7.4.1
+
+Released on August 31st, 2019.
+
+* Fixed critical map loading bug that caused many maps to not load without a page refresh
+
## Maps 7.4.0
Released on August 9th, 2019.
diff --git a/www/wiki/extensions/Maps/composer.json b/www/wiki/extensions/Maps/composer.json
index ae9b63ed..c8532790 100644
--- a/www/wiki/extensions/Maps/composer.json
+++ b/www/wiki/extensions/Maps/composer.json
@@ -35,7 +35,7 @@
"composer/installers": "^1.0.1",
"mediawiki/validator": "~2.2",
"mediawiki/parser-hooks": "~1.5",
- "param-processor/param-processor": "^1.4.2",
+ "param-processor/param-processor": "^1.10",
"data-values/geo": "~4.0|~3.0",
"jeroen/file-fetcher": "~6.0|~5.0",
"jeroen/file-fetcher-cache": "~1.0",
@@ -52,6 +52,9 @@
},
"classmap": [
"MapsRegistration.php"
+ ],
+ "files": [
+ "Maps_Settings.php"
]
},
"extra": {
diff --git a/www/wiki/extensions/Maps/extension.json b/www/wiki/extensions/Maps/extension.json
index f0bcddc8..6ddee628 100644
--- a/www/wiki/extensions/Maps/extension.json
+++ b/www/wiki/extensions/Maps/extension.json
@@ -1,6 +1,6 @@
{
"name": "Maps",
- "version": "7.4.0",
+ "version": "7.13.0",
"author": [
"[https://www.entropywins.wtf/mediawiki Jeroen De Dauw]",
@@ -54,62 +54,31 @@
},
"ResourceModules": {
- "ext.maps.common": {
+ "ext.sm.common": {
"scripts": [
- "maps.common.js"
- ],
- "messages": [
- "maps-load-failed"
+ "semanticMaps.js"
]
},
- "ext.maps.services": {
+ "ext.maps.geojson.new.page": {
"dependencies": [
- "ext.maps.common"
+ "mediawiki.api.edit"
],
"scripts": [
- "maps.services.js"
- ]
- },
-
- "ext.sm.common": {
- "dependencies": [
- "ext.maps.common",
- "ext.maps.services"
+ "geojson.new.page.js"
],
- "scripts": [
- "sm.common.js"
+ "messages": [
+ "maps-geo-json-create-page-creating",
+ "maps-geo-json-create-page-summary"
]
},
- "mapeditor": {
+ "ext.maps.geojson.page": {
"dependencies": [
- "ext.maps.common",
- "jquery.ui.autocomplete",
- "jquery.ui.slider",
- "jquery.ui.dialog"
+ "ext.maps.leaflet.editor"
],
"scripts": [
- "editor/js/jquery.miniColors.js",
- "editor/js/mapeditor.iefixes.js",
- "editor/js/mapeditor.js"
- ],
- "styles": [
- "editor/css/jquery.miniColors.css",
- "editor/css/mapeditor.css"
- ],
- "messages": [
- "mapeditor-parser-error",
- "mapeditor-none-text",
- "mapeditor-done-button",
- "mapeditor-remove-button",
- "mapeditor-import-button",
- "mapeditor-export-button",
- "mapeditor-import-button2",
- "mapeditor-select-button",
- "mapeditor-mapparam-button",
- "mapeditor-clear-button",
- "mapeditor-imageoverlay-button"
+ "geoJsonPage.js"
]
},
@@ -119,97 +88,129 @@
]
},
- "ext.maps.leaflet.base": {
+ "ext.maps.leaflet.library": {
"scripts": [
- "leaflet/leaflet/leaflet.js"
+ "lib/leaflet/leaflet.js",
+ "lib/leaflet-providers/leaflet-providers.js"
+
],
"styles": [
- "leaflet/leaflet/leaflet.css"
+ "lib/leaflet/leaflet.css"
+ ]
+ },
+
+ "ext.maps.leaflet.geojson": {
+ "scripts": [
+ "leaflet/GeoJson.js"
]
},
- "ext.maps.leaflet": {
+ "ext.maps.leaflet.loader": {
"dependencies": [
- "ext.maps.common",
- "ext.maps.services",
- "ext.maps.leaflet.base"
+ "ext.maps.leaflet.library",
+ "ext.maps.leaflet.geojson",
+ "ext.sm.common"
],
"scripts": [
+ "leaflet/FeatureBuilder.js",
"leaflet/jquery.leaflet.js",
- "leaflet/ext.maps.leaflet.js"
+ "leaflet/LeafletLoader.js"
],
"messages": [
- "maps-markers",
- "maps-copycoords-prompt",
- "maps-searchmarkers-text"
+ "maps-copycoords-prompt"
]
},
"ext.maps.leaflet.fullscreen": {
"dependencies": [
- "ext.maps.leaflet.base"
+ "ext.maps.leaflet.library"
],
"scripts": [
- "leaflet/leaflet.fullscreen/Control.FullScreen.js"
+ "lib/leaflet.fullscreen/Control.FullScreen.js"
],
"styles": [
- "leaflet/leaflet.fullscreen/Control.FullScreen.css"
+ "lib/leaflet.fullscreen/Control.FullScreen.css"
]
},
"ext.maps.leaflet.markercluster": {
"dependencies": [
- "ext.maps.leaflet.base"
+ "ext.maps.leaflet.library"
],
"scripts": [
- "leaflet/leaflet.markercluster/leaflet.markercluster.js"
+ "lib/leaflet.markercluster/leaflet.markercluster.js",
+ "leaflet/LeafletCluster.js"
],
"styles": [
- "leaflet/leaflet.markercluster/MarkerCluster.css"
+ "lib/leaflet.markercluster/MarkerCluster.css"
]
},
- "ext.maps.leaflet.providers": {
+ "ext.maps.leaflet.editor": {
"dependencies": [
- "ext.maps.leaflet.base"
+ "ext.maps.leaflet.geojson",
+ "ext.maps.leaflet.library",
+ "mediawiki.api.edit",
+ "mediawiki.jqueryMsg",
+ "ext.maps.leaflet.fullscreen"
],
"scripts": [
- "leaflet/leaflet-providers/leaflet-providers.js"
- ]
- },
-
- "ext.maps.leaflet.editable": {
- "dependencies": [
- "ext.maps.leaflet.base"
+ "lib/leaflet.StyleEditor/javascript/Leaflet.StyleEditor.min.js",
+ "lib/leaflet.draw/leaflet.draw-src.js",
+ "lib/leaflet.EasyButton/easy-button.js",
+ "api.js",
+ "MapSaver.js",
+ "leaflet/LeafletEditor.js"
],
- "scripts": [
- "leaflet/leaflet.editable/Leaflet.Editable.js"
- ]
- },
-
- "ext.maps.leaflet.editor": {
- "dependencies": [
- "ext.maps.leaflet"
+ "styles": [
+ "lib/leaflet.StyleEditor/css/Leaflet.StyleEditor.min.css",
+ "lib/leaflet.draw/leaflet.draw.css",
+ "lib/leaflet.EasyButton/easy-button.css"
],
- "scripts": [
- "leaflet/leaflet.editor.js"
- ]
- },
+ "messages": [
+ "maps-json-editor-button-marker",
+ "maps-json-editor-button-line",
+ "maps-json-editor-button-polygon",
+ "maps-json-editor-button-rectangle",
+ "maps-json-editor-button-circle",
- "ext.maps.leaflet.leafletajax": {
- "dependencies": [
- "ext.maps.leaflet",
- "ext.sm.common"
- ],
- "scripts": [
- "leaflet/ext.sm.leafletajax.js"
+ "maps-json-editor-tooltip-marker",
+ "maps-json-editor-tooltip-line",
+ "maps-json-editor-tooltip-polygon",
+ "maps-json-editor-tooltip-rectangle",
+ "maps-json-editor-tooltip-circle",
+
+ "maps-json-editor-added-marker",
+ "maps-json-editor-added-line",
+ "maps-json-editor-added-polygon",
+ "maps-json-editor-added-rectangle",
+ "maps-json-editor-added-circle",
+
+ "maps-json-editor-edit-removed-shapes",
+ "maps-json-editor-edit-modified",
+ "maps-json-editor-edit-other",
+ "maps-json-editor-edit-failed",
+
+ "maps-json-editor-toolbar-save-title",
+ "maps-json-editor-toolbar-save-text",
+ "maps-json-editor-toolbar-cancel-title",
+ "maps-json-editor-toolbar-cancel-text",
+ "maps-json-editor-toolbar-clear-title",
+ "maps-json-editor-toolbar-clear-text",
+
+ "maps-json-editor-toolbar-button-save",
+ "maps-json-editor-changes-saved",
+
+ "maps-json-editor-toolbar-button-edit",
+ "maps-json-editor-toolbar-button-edit-disabled",
+ "maps-json-editor-toolbar-button-remove",
+ "maps-json-editor-toolbar-button-remove-disabled",
+
+ "maps-editor-edit-geojson"
]
},
"ext.maps.googlemaps3": {
- "dependencies": [
- "ext.maps.common"
- ],
"scripts": [
"GoogleMaps/jquery.googlemap.js",
"GoogleMaps/ext.maps.googlemaps3.js"
@@ -219,7 +220,8 @@
"maps-copycoords-prompt",
"maps-searchmarkers-text",
"maps-fullscreen-button",
- "maps-fullscreen-button-tooltip"
+ "maps-fullscreen-button-tooltip",
+ "maps-kml-parsing-failed"
]
},
@@ -264,13 +266,43 @@
]
},
- "ext.sm.googlemaps3ajax": {
+ "ext.maps.googlemaps3ajax": {
"dependencies": [
"ext.maps.googlemaps3",
"ext.sm.common"
],
"scripts": [
- "GoogleMaps/ext.sm.googlemaps3ajax.js"
+ "GoogleMaps/googlemaps3ajax.js"
+ ]
+ },
+
+ "ext.maps.wikitext.editor": {
+ "dependencies": [
+ "jquery.ui.autocomplete",
+ "jquery.ui.slider",
+ "jquery.ui.dialog"
+ ],
+ "scripts": [
+ "WikitextEditor/js/jquery.miniColors.js",
+ "WikitextEditor/js/mapeditor.iefixes.js",
+ "WikitextEditor/js/mapeditor.js"
+ ],
+ "styles": [
+ "WikitextEditor/css/jquery.miniColors.css",
+ "WikitextEditor/css/mapeditor.css"
+ ],
+ "messages": [
+ "mapeditor-parser-error",
+ "mapeditor-none-text",
+ "mapeditor-done-button",
+ "mapeditor-remove-button",
+ "mapeditor-import-button",
+ "mapeditor-export-button",
+ "mapeditor-import-button2",
+ "mapeditor-select-button",
+ "mapeditor-mapparam-button",
+ "mapeditor-clear-button",
+ "mapeditor-imageoverlay-button"
]
}
},
diff --git a/www/wiki/extensions/Maps/i18n/ar.json b/www/wiki/extensions/Maps/i18n/ar.json
index 3dcd2223..8909c222 100644
--- a/www/wiki/extensions/Maps/i18n/ar.json
+++ b/www/wiki/extensions/Maps/i18n/ar.json
@@ -7,7 +7,7 @@
"ديفيد"
]
},
- "maps-desc": "يسمح بتضمين خرائط ديناميكية إلى صفحات الويكي، كود العناوين وعمليات جغرافية أخرى",
+ "maps-desc": "يسمح بتضمين الخرائط الديناميكية في صفحات الويكي باستخدام خرائط جوجل أو Leaflet، لديه محرر مرئي، يتكامل اختياريا مع ميدياويكي الدلالي، ويدعم GeoJSON ويضيف إمكانيات الترميز الجغرافي.",
"right-geocode": "الترميز الجغرافي",
"action-geocode": "تفعيل الترميز الجغرافي في هذا الويكي",
"maps_map": "خريطة",
@@ -116,9 +116,11 @@
"maps_leaflet": "الطبقة",
"maps-leaflet-par-defzoom": "يسمح بتعيين مستوى التكبير الافتراضي للخريطة.",
"maps-leaflet-par-layers": "الطبقات التي ستكون متاحة في محدد الطبقة، سيتم عرض الطبقة الأولى عند تحميل الخريطة.",
+ "maps-leaflet-par-layers-dark": "الطبقات التي ستكون متاحة في محدد الطبقة عند تنشيط الوضع الغامق، سيتم عرض الطبقة الأولى عند تحميل الخريطة.",
"maps-leaflet-par-overlaylayers": "طبقات التراكب التي سيتم عرضها عند تحميل الخريطة.",
"maps-leaflet-par-maxclusterradius": "الحد الأقصى لنصف القطر الذي سيغطيه العنقود من العلامة المركزية (بالبكسل).",
"maps-leaflet-par-clusterspiderfy": "عند النقر فوق عنقود في مستوى التكبير السفلي سنكبره حتى تتمكن من رؤية كل علاماته.",
+ "maps-leaflet-par-clicktarget": "عند النقر فوق الخريطة، سيتم إرسالك إلى هذا المسار، يتم استبدال %lat% بخط العرض و% بخط الطول",
"maps_click_to_activate": "اضغط لتنشيط الخريطة",
"maps_centred_on": "الخريطة مركزها في $1، $2.",
"maps-par-mappingservice": "يسمح بتعيين خدمة رسم الخرائط التي سيتم استخدامها لإنشاء الخريطة.",
@@ -223,5 +225,43 @@
"validator-type-mapspolygon": "المضلع الجغرافي",
"validator-type-mapspolygon-list": "قائمة المضلعات الجغرافية",
"validator-type-wmsoverlay": "تراكب خدمة خريطة الويب",
- "validator-type-jsonfile": "نص"
+ "maps-json-editor-button-marker": "ضع علامة",
+ "maps-json-editor-button-line": "ارسم خطا",
+ "maps-json-editor-button-polygon": "ارسم مضلعا",
+ "maps-json-editor-button-rectangle": "ضع مستطيلا",
+ "maps-json-editor-button-circle": "ضع دائرة",
+ "maps-json-editor-tooltip-marker": "انقر على الخريطة لوضع العلامة.",
+ "maps-json-editor-tooltip-line": "انقر على الخريطة لرسم خط.",
+ "maps-json-editor-tooltip-polygon": "انقر على الخريطة لرسم مضلع.",
+ "maps-json-editor-tooltip-rectangle": "انقر فوق الخريطة لوضع مستطيل.",
+ "maps-json-editor-tooltip-circle": "انقر على الخريطة لوضع دائرة.",
+ "maps-json-editor-added-marker": "إضافة علامة",
+ "maps-json-editor-added-line": "إضافة خط",
+ "maps-json-editor-added-polygon": "إضافة مضلع",
+ "maps-json-editor-added-rectangle": "إضافة مستطيل",
+ "maps-json-editor-added-circle": "إضافة دائرة",
+ "maps-json-editor-edit-removed-shapes": "إزالة $1 {{PLURAL:$1|شكل|أشكال}}",
+ "maps-json-editor-edit-modified": "تعديل أشكال الخريطة الحالية",
+ "maps-json-editor-edit-other": "تعديل خريطة مرئي",
+ "maps-json-editor-edit-failed": "فشل في حفظ الخريطة",
+ "maps-json-editor-toolbar-save-title": "الخروج من وضع التحرير مع الحفاظ على جميع التغييرات",
+ "maps-json-editor-toolbar-save-text": "تم",
+ "maps-json-editor-toolbar-cancel-title": "الخروج من وضع التحرير، يتجاهل كل التغييرات",
+ "maps-json-editor-toolbar-cancel-text": "إلغاء",
+ "maps-json-editor-toolbar-clear-title": "مسح كل الطبقات",
+ "maps-json-editor-toolbar-clear-text": "مسح الكل",
+ "maps-json-editor-toolbar-button-edit": "عدل الطبقات",
+ "maps-json-editor-toolbar-button-edit-disabled": "لا طبقات للتعديل",
+ "maps-json-editor-toolbar-button-remove": "حذف الطبقات",
+ "maps-json-editor-toolbar-button-remove-disabled": "لا طبقات للحذف",
+ "maps-json-editor-toolbar-button-save": "حفظ التغييرات",
+ "maps-json-editor-changes-saved": "تم حفظ تعديلاتك.",
+ "maps-geo-json-edit-source": "عدل المصدر",
+ "maps-geo-json-create-source": "إنشاء من مصدر GeoJSON",
+ "maps-geo-json-create-page-button": "إنشاء هذه الصفحة",
+ "maps-geo-json-create-page-creating": "جارٍ إنشاء الصفحة...",
+ "maps-geo-json-create-page-summary": "إنشاء صفحة GeoJSON",
+ "maps-editor-edit-geojson": "تحرير طبقة GeoJSON",
+ "tag-maps-visual-edit": "تعديل خريطة مرئي",
+ "tag-maps-visual-edit-description": "تم التعديل باستخدام المحرر المرئي الذي يوفره [https://www.mediawiki.org/wiki/Extension:Maps امتداد الخرائط]"
}
diff --git a/www/wiki/extensions/Maps/i18n/ast.json b/www/wiki/extensions/Maps/i18n/ast.json
index 7f11fc02..afdffa1d 100644
--- a/www/wiki/extensions/Maps/i18n/ast.json
+++ b/www/wiki/extensions/Maps/i18n/ast.json
@@ -113,9 +113,11 @@
"maps_leaflet": "Leaflet",
"maps-leaflet-par-defzoom": "Permite configurar el nivel predetermináu d'ampliación del mapa.",
"maps-leaflet-par-layers": "La capa que va apaecer cuando se cargue'l mapa.",
+ "maps-leaflet-par-layers-dark": "Les capes que tarán disponibles nel selector de capes cuando ta activáu el mou escuru. La primera capa s'amosará al cargar el mapa.",
"maps-leaflet-par-overlaylayers": "Les capas sobrepuestes qu'apaecerán cuando se cargue'l mapa.",
"maps-leaflet-par-maxclusterradius": "El radiu máximu que cubrirá un grupu dende'l marcador central (en pixels).",
"maps-leaflet-par-clusterspiderfy": "Cuando faes click nun grupu al mínimu d'ampliación, espardémoslu pa que puedan vese los marcadores.",
+ "maps-leaflet-par-clicktarget": "Al pulsiar nel mapa, unviaráte a esta URL. %lat% trócase pola llatitú y %long% pola llonxitú",
"maps_click_to_activate": "Calca p'activar el mapa",
"maps_centred_on": "Mapa centráu en $1, $2.",
"maps-par-mappingservice": "Permite configurar el serviciu de mapes que s'usará pa xenerar el mapa.",
@@ -123,7 +125,7 @@
"maps-par-searchmarkers": "Permite buscar marcadores específicos con un campu incrustáu nel mapa.",
"maps-par-zoom": "Nivel de zoom del mapa. Para los mapes con marcadores el valor predetermináu sedrá el nivel máximu d'ampliación qu'amuese tolos marcadores.",
"maps-par-width": "Permite configurar l'anchor del mapa. De mou predetermináu s'asume el pixel como unidá, pero se pue conseñar esplícitamente una d'estes unidaes: px, ex, em, %.",
- "maps-par-height": "Permite configurar l'altor del mapa. De mou predetermináu s'asume el pixel como unidá, pero se pue conseñar esplícitamente una d'estes unidaes: px, ex, em, %.",
+ "maps-par-height": "Permite configurar l'altor del mapa. De mou predetermináu s'asumen los pixeles como unidá, pero se pue conseñar esplícitamente una d'estes unidaes: px, ex, em.",
"maps-par-centre": "El llugar nel que se tien de centrar el mapa",
"maps-par-enable-fullscreen": "Activar el botón de pantalla completa",
"maps-par-kml": "Ficheros KML a cargar nel mapa.",
@@ -219,5 +221,40 @@
"validator-type-mapspolygon": "Polígonu xeográficu",
"validator-type-mapspolygon-list": "Llista de polígonos xeográficos",
"validator-type-wmsoverlay": "Superposición de Serviciu de Mapes web",
- "validator-type-jsonfile": "testu"
+ "maps-json-editor-button-marker": "Asitiar un marcador",
+ "maps-json-editor-button-line": "Trazar una llinia",
+ "maps-json-editor-button-polygon": "Dibuxar un polígonu",
+ "maps-json-editor-button-rectangle": "Asitiar un rectángulu",
+ "maps-json-editor-button-circle": "Asitiar un círculu",
+ "maps-json-editor-tooltip-marker": "Pulsia nel mapa p'asitiar un marcador.",
+ "maps-json-editor-tooltip-line": "Pulsia nel mapa pa trazar una llinia.",
+ "maps-json-editor-tooltip-polygon": "Pulsia nel mapa pa dibuxar un polígonu.",
+ "maps-json-editor-tooltip-rectangle": "Pulsia nel mapa p'asitiar un rectángulu.",
+ "maps-json-editor-tooltip-circle": "Pulsia nel mapa p'asitiar un círculu.",
+ "maps-json-editor-added-marker": "Añadíu marcador",
+ "maps-json-editor-added-line": "Añadida llinia",
+ "maps-json-editor-added-polygon": "Polígonu añadíu",
+ "maps-json-editor-added-rectangle": "Rectángulu añadíu",
+ "maps-json-editor-added-circle": "Círculu añadíu",
+ "maps-json-editor-edit-removed-shapes": "{{PLURAL:$1|Desanicióse $1 forma|Desaniciáronse $1 formes}}",
+ "maps-json-editor-edit-modified": "Camudaron formes esistentes nel mapa",
+ "maps-json-editor-edit-other": "Edición cartográfica visual",
+ "maps-json-editor-edit-failed": "Nun pudo guardase'l mapa",
+ "maps-json-editor-toolbar-save-title": "Guardar los cambios",
+ "maps-json-editor-toolbar-save-text": "Guardar",
+ "maps-json-editor-toolbar-cancel-title": "Encaboxar la edición, descartar tolos cambeos",
+ "maps-json-editor-toolbar-cancel-text": "Encaboxar",
+ "maps-json-editor-toolbar-clear-title": "Llimpiar toles capes",
+ "maps-json-editor-toolbar-clear-text": "Llimpiar too",
+ "maps-json-editor-toolbar-button-edit": "Editar capes",
+ "maps-json-editor-toolbar-button-edit-disabled": "Nun hai capes qu'editar",
+ "maps-json-editor-toolbar-button-remove": "Desaniciar capes",
+ "maps-json-editor-toolbar-button-remove-disabled": "Nun hai capes que desaniciar",
+ "maps-geo-json-edit-source": "Editar la fonte",
+ "maps-geo-json-create-source": "Crear dende códigu GeoJSON",
+ "maps-geo-json-create-page-button": "Crear esta páxina",
+ "maps-geo-json-create-page-creating": "Creando páxina...",
+ "maps-geo-json-create-page-summary": "Creada una páxina en GeoJSON",
+ "tag-maps-visual-edit": "Edición cartográfica visual",
+ "tag-maps-visual-edit-description": "Edición fecha col editor visual proporcionáu pola [https://www.mediawiki.org/wiki/Extension:Maps estensión Maps]"
}
diff --git a/www/wiki/extensions/Maps/i18n/be-tarask.json b/www/wiki/extensions/Maps/i18n/be-tarask.json
index 58b98b66..76db69e0 100644
--- a/www/wiki/extensions/Maps/i18n/be-tarask.json
+++ b/www/wiki/extensions/Maps/i18n/be-tarask.json
@@ -92,7 +92,7 @@
"maps-par-resizable": "Дазваляе зьмяняць памеры мапы, перацягваючы яе ніжні правы кут.",
"maps-par-zoom": "Маштаб мапы. Для мапаў з пазначэньнямі маштаб будзе такім, пры якім яшчэ будуць паказвацца ўсе пазначэньні.",
"maps-par-width": "Дазваляе наладжваць шырыню мапы. Па змоўчваньні піксэлі выкарыстоўваюцца як адзінкі вымярэньня, але Вы можаце непасрэдна вызначыць адну з гэтых адзінак вымярэньня: px, ex, em, %.",
- "maps-par-height": "Дазваляе наладжваць вышыню мапы. Па змоўчваньні піксэлі выкарыстоўваюцца як адзінкі вымярэньня, але Вы можаце непасрэдна вызначыць адну з гэтых адзінак вымярэньня: px, ex, em, %.",
+ "maps-par-height": "Дазваляе наладжваць вышыню мапы. Па змоўчваньні піксэлі выкарыстоўваюцца як адзінкі вымярэньня, але Вы можаце непасрэдна вызначыць адну з гэтых адзінак вымярэньня: px, ex, em.",
"maps-par-kml": "KML-файлы для загрузкі ў мапу.",
"maps-googlemaps3-incompatbrowser": "Ваш браўзэр не сумяшчальны з Google Maps v3.",
"maps-googlemaps3-par-type": "Тып мапы, які будзе паказвацца ў пачатку.",
diff --git a/www/wiki/extensions/Maps/i18n/bn.json b/www/wiki/extensions/Maps/i18n/bn.json
index 891ea3e4..105052c6 100644
--- a/www/wiki/extensions/Maps/i18n/bn.json
+++ b/www/wiki/extensions/Maps/i18n/bn.json
@@ -5,7 +5,8 @@
"Wikitanvir",
"Aftabuzzaman",
"আজিজ",
- "আফতাবুজ্জামান"
+ "আফতাবুজ্জামান",
+ "Kupulak"
]
},
"right-geocode": "জিওকোড",
@@ -110,5 +111,7 @@
"validator-type-mapsrectangle": "ভৌগলিক আয়তক্ষেত্র",
"validator-type-mapsrectangle-list": "আয়তক্ষেত্রের তালিকা",
"validator-type-mapspolygon": "ভৌগলিক বহুভুজ",
- "validator-type-mapspolygon-list": "ভৌগলিক বহুভুজের তালিকা"
+ "validator-type-mapspolygon-list": "ভৌগলিক বহুভুজের তালিকা",
+ "maps-json-editor-toolbar-save-text": "সম্পন্ন",
+ "maps-json-editor-toolbar-button-save": "পরিবর্তন সংরক্ষণ করুন"
}
diff --git a/www/wiki/extensions/Maps/i18n/de.json b/www/wiki/extensions/Maps/i18n/de.json
index 71905a10..a7962575 100644
--- a/www/wiki/extensions/Maps/i18n/de.json
+++ b/www/wiki/extensions/Maps/i18n/de.json
@@ -226,6 +226,5 @@
"validator-type-mapsrectangle-list": "Liste der Rechtecke",
"validator-type-mapspolygon": "Geografisches Vieleck",
"validator-type-mapspolygon-list": "Liste geografischer Vielecke",
- "validator-type-wmsoverlay": "Web-Map-Service-Überlagerung",
- "validator-type-jsonfile": "text"
+ "validator-type-wmsoverlay": "Web-Map-Service-Überlagerung"
}
diff --git a/www/wiki/extensions/Maps/i18n/el.json b/www/wiki/extensions/Maps/i18n/el.json
index 755e14b0..1c0c2216 100644
--- a/www/wiki/extensions/Maps/i18n/el.json
+++ b/www/wiki/extensions/Maps/i18n/el.json
@@ -160,6 +160,5 @@
"semanticmaps-kml-linkabsolute": "Να είναι οι σύνδεσμοι απόλυτοι (ως αντιπαράθεση με τους σχετικούς)",
"semanticmaps-kml-pagelinktext": "Το κείμενο που θα χρησιμοποιείται για τους συνδέσμους προς τη σελίδα, στο οποίο το $1 θα αντικαθίσταται από τον τίτλο της σελίδας",
"semanticmaps-shapes-improperformat": "Εσφαλμένη μορφοποίηση του $1, ανατρέξτε στην τεκμηρίωση περί μορφοποίησης",
- "semanticmaps-shapes-missingshape": "Δεν βρέθηκαν σχήματα για το $1, ανατρέξτε στην τεκμηρίωση για διαθέσιμα σχήματα",
- "validator-type-jsonfile": "κείμενο"
+ "semanticmaps-shapes-missingshape": "Δεν βρέθηκαν σχήματα για το $1, ανατρέξτε στην τεκμηρίωση για διαθέσιμα σχήματα"
}
diff --git a/www/wiki/extensions/Maps/i18n/en.json b/www/wiki/extensions/Maps/i18n/en.json
index 1b5fa3b2..7eb770f6 100644
--- a/www/wiki/extensions/Maps/i18n/en.json
+++ b/www/wiki/extensions/Maps/i18n/en.json
@@ -5,7 +5,7 @@
"Karsten Hoffmeyer (kghbln)"
]
},
- "maps-desc": "Enables embedding of dynamic maps into wiki pages, geocoding of addresses and other geographical operations",
+ "maps-desc": "Allows embedding of dynamic maps into wiki pages using Google Maps or Leaflet. Has a visual editor, optionally integrates with Semantic MediaWiki, supports GeoJSON and adds geocoding capabilities.",
"right-geocode": "Geocode",
"action-geocode": "do geocoding on this wiki",
"maps_map": "Map",
@@ -114,9 +114,11 @@
"maps_leaflet": "Leaflet",
"maps-leaflet-par-defzoom": "Allows setting the default zoom level of the map.",
"maps-leaflet-par-layers": "The layers that will be available in the layer selector. The first layer will be shown when the map loads.",
+ "maps-leaflet-par-layers-dark": "The layers that will be available in the layer selector when a dark mode is activated. The first layer will be shown when the map loads.",
"maps-leaflet-par-overlaylayers": "The overlay layers that will be shown when the map loads.",
"maps-leaflet-par-maxclusterradius": "The maximum radius that a cluster will cover from the central marker (in pixels).",
"maps-leaflet-par-clusterspiderfy": "When you click a cluster at the bottom zoom level we spiderfy it so you can see all of its markers.",
+ "maps-leaflet-par-clicktarget": "When clicking on the map you will be send to this URL. %lat% is replaced by the latitude and %long% by the longitude",
"maps_click_to_activate": "Click to activate map",
"maps_centred_on": "Map centered on $1, $2.",
"maps-par-mappingservice": "Allows setting the mapping service that will be used to generate the map.",
@@ -221,5 +223,53 @@
"validator-type-mapspolygon": "Geographical polygon",
"validator-type-mapspolygon-list": "List of geographical polygons",
"validator-type-wmsoverlay": "Web Map Service overlay",
- "validator-type-jsonfile": "text"
+
+ "maps-json-editor-button-marker": "Place a marker",
+ "maps-json-editor-button-line": "Draw a line",
+ "maps-json-editor-button-polygon": "Draw a polygon",
+ "maps-json-editor-button-rectangle": "Place a rectangle",
+ "maps-json-editor-button-circle": "Place a circle",
+
+ "maps-json-editor-tooltip-marker": "Click map to place marker.",
+ "maps-json-editor-tooltip-line": "Click map to draw line.",
+ "maps-json-editor-tooltip-polygon": "Click map to draw polygon.",
+ "maps-json-editor-tooltip-rectangle": "Click map to place rectangle.",
+ "maps-json-editor-tooltip-circle": "Click map to place circle.",
+
+ "maps-json-editor-added-marker": "Added marker",
+ "maps-json-editor-added-line": "Added line",
+ "maps-json-editor-added-polygon": "Added polygon",
+ "maps-json-editor-added-rectangle": "Added rectangle",
+ "maps-json-editor-added-circle": "Added circle",
+
+ "maps-json-editor-edit-removed-shapes": "Removed $1 {{PLURAL:$1|shape|shapes}}",
+ "maps-json-editor-edit-modified": "Modified existing map shapes",
+ "maps-json-editor-edit-other": "Visual map edit",
+ "maps-json-editor-edit-failed": "Failed to save the map",
+
+ "maps-json-editor-toolbar-save-title": "Exit edit mode while keeping all changes",
+ "maps-json-editor-toolbar-save-text": "Done",
+ "maps-json-editor-toolbar-cancel-title": "Exit edit mode, discards all changes",
+ "maps-json-editor-toolbar-cancel-text": "Cancel",
+ "maps-json-editor-toolbar-clear-title": "Clear all layers",
+ "maps-json-editor-toolbar-clear-text": "Clear All",
+
+ "maps-json-editor-toolbar-button-edit": "Edit layers",
+ "maps-json-editor-toolbar-button-edit-disabled": "No layers to edit",
+ "maps-json-editor-toolbar-button-remove": "Delete layers",
+ "maps-json-editor-toolbar-button-remove-disabled": "No layers to delete",
+
+ "maps-json-editor-toolbar-button-save": "Save changes",
+ "maps-json-editor-changes-saved": "Your changes have been saved",
+
+ "maps-geo-json-edit-source": "Edit source",
+ "maps-geo-json-create-source": "Create from GeoJSON source",
+ "maps-geo-json-create-page-button": "Create this page",
+ "maps-geo-json-create-page-creating": "Creating page...",
+ "maps-geo-json-create-page-summary": "Created GeoJSON page",
+
+ "maps-editor-edit-geojson": "Edit GeoJSON layer",
+
+ "tag-maps-visual-edit": "Visual map edit",
+ "tag-maps-visual-edit-description": "Edit made with the visual editor provided by the [https://www.mediawiki.org/wiki/Extension:Maps Maps extension]"
}
diff --git a/www/wiki/extensions/Maps/i18n/es.json b/www/wiki/extensions/Maps/i18n/es.json
index e9eaa1ed..fa61b971 100644
--- a/www/wiki/extensions/Maps/i18n/es.json
+++ b/www/wiki/extensions/Maps/i18n/es.json
@@ -25,7 +25,10 @@
"Rubentl134",
"AlvaroMolina",
"Dgstranz",
- "Carlosmg.dg"
+ "Carlosmg.dg",
+ "Madamebiblio",
+ "Hasley",
+ "Ihojose"
]
},
"maps-desc": "Habilita la inserción de mapas dinámicos en páginas wikis, la geocodificación de direcciones y otras operaciones geográficas",
@@ -229,5 +232,27 @@
"validator-type-mapsrectangle-list": "Lista de rectángulos",
"validator-type-mapspolygon": "Polígono geográfico",
"validator-type-mapspolygon-list": "Lista de polígonos geográficos",
- "validator-type-jsonfile": "texto"
+ "maps-json-editor-button-line": "Trazar una línea",
+ "maps-json-editor-button-polygon": "Trazar un polígono",
+ "maps-json-editor-added-polygon": "Polígono añadido",
+ "maps-json-editor-added-rectangle": "Rectángulo añadido",
+ "maps-json-editor-added-circle": "Círculo añadido",
+ "maps-json-editor-edit-other": "Edición cartográfica visual",
+ "maps-json-editor-toolbar-save-title": "Salir del modo edición mientras se mantienen todos los cambios",
+ "maps-json-editor-toolbar-save-text": "Hecho",
+ "maps-json-editor-toolbar-cancel-title": "Salir del modo edición, descartar todos los cambios",
+ "maps-json-editor-toolbar-cancel-text": "Cancelar",
+ "maps-json-editor-toolbar-clear-title": "Borrar todas las capas",
+ "maps-json-editor-toolbar-button-edit": "Editar capas",
+ "maps-json-editor-toolbar-button-remove": "Eliminar capas",
+ "maps-json-editor-toolbar-button-remove-disabled": "No hay capas que eliminar",
+ "maps-json-editor-toolbar-button-save": "Guardar cambios",
+ "maps-json-editor-changes-saved": "Tus cambios han sido guardados",
+ "maps-geo-json-edit-source": "Editar código",
+ "maps-geo-json-create-source": "Crear a partir de código GeoJSON",
+ "maps-geo-json-create-page-button": "Crear esta página",
+ "maps-geo-json-create-page-creating": "Creando página…",
+ "maps-geo-json-create-page-summary": "Se creó una página en GeoJSON",
+ "maps-editor-edit-geojson": "Editar capa GeoJSON",
+ "tag-maps-visual-edit": "Edición cartográfica visual"
}
diff --git a/www/wiki/extensions/Maps/i18n/fi.json b/www/wiki/extensions/Maps/i18n/fi.json
index 516c2703..3b864dbf 100644
--- a/www/wiki/extensions/Maps/i18n/fi.json
+++ b/www/wiki/extensions/Maps/i18n/fi.json
@@ -10,7 +10,8 @@
"ZeiP",
"Macofe",
"Pahkiqaz",
- "Pyscowicz"
+ "Pyscowicz",
+ "Silvonen"
]
},
"maps-desc": "Mahdollistaa dynaamisten karttojen upottamisen wikisivuille, osoitteiden geokoodauksen ja muita karttoihin liittyviä toimintoja",
@@ -154,5 +155,9 @@
"semanticmaps-kml-pagelinktext": "Sivulinkeissä käytettävä teksti, jossa $1 korvataan sivun otsikolla",
"semanticmaps-shapes-improperformat": "$1 on muotoiltu väärin. Katso muotoilun dokumentaatiota.",
"semanticmaps-shapes-missingshape": "$1: muotoja ei löytynyt. Dokumentaatiossa kerrotaan sallituista muodoista.",
- "validator-type-mapslocation-list": "Luettelo sijainneista"
+ "validator-type-mapslocation-list": "Luettelo sijainneista",
+ "maps-json-editor-button-line": "Piirrä viiva",
+ "maps-json-editor-toolbar-save-title": "Tallenna muutokset",
+ "maps-json-editor-toolbar-save-text": "Tallenna",
+ "maps-json-editor-toolbar-clear-text": "Tyhjennä kaikki"
}
diff --git a/www/wiki/extensions/Maps/i18n/fr.json b/www/wiki/extensions/Maps/i18n/fr.json
index b1984cc9..b89b7f59 100644
--- a/www/wiki/extensions/Maps/i18n/fr.json
+++ b/www/wiki/extensions/Maps/i18n/fr.json
@@ -22,156 +22,160 @@
"Trial",
"Wladek92",
"Urhixidur",
- "Grondin"
+ "Grondin",
+ "Thibaut120094",
+ "Pols12"
]
},
- "maps-desc": "Permet d’inclure des cartes dynamiques dans les pages du wiki, des adresses géo-codées et d'autres opérations géographiques.",
- "right-geocode": "Géocode",
+ "maps-desc": "Permet d’inclure des cartes dynamiques dans les pages du wiki, des adresses géocodées et d'autres opérations géographiques.",
+ "right-geocode": "Géocoder",
"action-geocode": "géocoder sur ce wiki",
"maps_map": "Carte",
- "maps-tracking-category": "Pages avec une carte provenant de l’extension Maps",
+ "maps-tracking-category": "Pages avec une carte rendue par l’extension Maps",
"maps-loading-map": "Chargement de la carte...",
- "maps-load-failed": "Impossible de charger la carte !",
+ "maps-load-failed": "Impossible de charger la carte !",
"maps-markers": "Marqueurs",
"maps-copycoords-prompt": "CTRL+C, ENTRÉE",
"maps-searchmarkers-text": "Marqueurs de filtre",
"maps-others": "autres",
- "maps-kml-parsing-failed": "Échec de l’analyse d’un ou plusieurs fichiers KML, le plus souvent à cause d’échecs de récupération ou de XML mal formé.",
+ "maps-kml-parsing-failed": "Échec de l’analyse d’un ou plusieurs fichiers KML. Survient habituellement à cause d’un échec de récupération ou d’un codage XML mal formé.",
"maps-ns-layer": "Couche",
"maps-ns-layer-talk": "Page de discussion des couches",
"maps-layer-property": "Propriété",
"maps-layer-value": "Valeur",
"maps-layer-errors": "Erreurs",
- "maps-layerdef-invalid": "{{PLURAL:$1|Définition non valide|Définitions non valides}}",
+ "maps-layerdef-invalid": "Définition{{PLURAL:$1||s}} non valide{{PLURAL:$1||s}}",
"maps-layerdef-invalid-fatal": "Définition non valide fatale",
"maps-layerdef-wrong-namespace": "Les définitions de couche ne sont valides que sur les pages de l’espace de noms « $1 »",
"maps-layerdef-equal-layer-name": "Les noms de couche doivent être uniques dans la même page de couche. « $1 » est déjà utilisé par une autre couche.",
"maps-layerpage-usage": "Pages avec des cartes utilisant la couche « $1 »",
"maps-layerpage-nousage": "Aucune page n’utilise cette couche pour l’instant.",
- "maps-error-invalid-layertype": "Il n’y a pas de couche de type « $1 ». Seul {{PLURAL:$3|ce type est|ces types sont}} pris en charge : $2",
- "maps-error-no-layertype": "Vous devez spécifier le type de couche. {{PLURAL:$2|Seul ce type est|Ces types sont}} pris en charge : $1",
- "validation-error-invalid-layer": "Le paramètre $1 doit être une couche valide.",
- "validation-error-invalid-layers": "Le paramètre $1 doit être une ou plusieurs couche(s) valide(s).",
- "validation-error-no-non-numeric": "Le paramètre « $1 » doit être une chaîne non numérique.",
- "validation-error-no-non-numerics": "Le paramètre « $1 » doit être une ou plusieurs chaînes non numériques.",
- "maps-layer-of-type": "Couche de type « $1 »",
+ "maps-error-invalid-layertype": "Il n’y a aucune couche de type « $1 ». Seul {{PLURAL:$3|ce type est|ces types sont}} pris en charge : $2",
+ "maps-error-no-layertype": "Vous devez spécifier le type de couche. {{PLURAL:$2|Seul ce type est|Les types suivants sont}} pris en charge : $1",
+ "validation-error-invalid-layer": "Le paramètre « $1 » doit indiquer une couche valide.",
+ "validation-error-invalid-layers": "Le paramètre « $1 » doit indiquer une ou plusieurs couches valides.",
+ "validation-error-no-non-numeric": "Le paramètre « $1 » doit indiquer une chaîne non numérique.",
+ "validation-error-no-non-numerics": "Le paramètre « $1 » doit indiquer une ou plusieurs chaînes non numériques.",
+ "maps-layer-of-type": "Couche de type « $1 »",
"maps-layer-of-type-and-name": "Couche « $2 » de type « $1 »",
- "maps-layer-type-supported-by": "Ce type de couche peut {{PLURAL:$2|être utilisé uniquement avec le service de cartographie $1|être utilisé avec les services de cartographie suivants : $1}}.",
+ "maps-layer-type-supported-by": "Ce type de couche peut être utilisé avec {{PLURAL:$2|le service|les services}} de cartographie {{PLURAL:$2| $1 uniquement|suivants : $1}}.",
"maps-coordinates-description": "Crochet de l’analyseur syntaxique pour formater les coordonnées, depuis et vers n’importe quel format pris en charge.",
"maps-displaymap-description": "Affiche les cartes géographiques sans aucun marqueur wiki associé.",
- "maps-distance-description": "Convertit une distance d'une certaine unité prise en charge à son équivalent utilisant une autre unité.",
- "maps-finddestination-description": "Trouver une destination à partir d'un point de départ donné (qui peut être dans n’importe lequel des formats pris en charge), d'une orientation initiale et d'une distance.",
- "maps-geocode-description": "Permet le géocodage d'adresses, en d'autres termes, la transformation des positions humainement lisible en ensembles de coordonnées. Plusieurs services de géocodage sont pris en charge, qui ne doivent pas être confondus avec les services de cartographie.",
- "maps-geodistance-description": "Calculer la distance géographique entre deux points, depuis et vers n'importe quel format pris en charge.",
+ "maps-distance-description": "Convertit une distance exprimée dans une certaine unité prise en charge à son équivalent exprimée dans une autre unité.",
+ "maps-finddestination-description": "Trouver une destination à partir d’un point de départ donné (qui peut être dans n’importe lequel des formats pris en charge), d’une orientation initiale et d’une distance.",
+ "maps-geocode-description": "Permet le géocodage d’adresses, en d’autres termes, la transformation de positions humainement lisibles en ensembles de coordonnées. Plusieurs services de géocodage sont pris en charge, qui ne doivent pas être confondus avec les services de cartographie.",
+ "maps-geodistance-description": "Calculer la distance géographique entre deux points, depuis et vers n’importe quel format pris en charge.",
"maps-mapsdoc-description": "Affiche une table avec les paramètres pour un service de cartographie spécifié, avec leurs valeurs par défaut et leur description.",
"maps-layerdefinition-description": "Décrit une couche personnalisée qui peut être affichée avec d’autres fonctions de carte.",
"maps-mapsdoc-par-service": "Le service de cartographie pour afficher la documentation concernant les paramètres.",
- "maps-mapsdoc-par-language": "La langue dans laquelle afficher la documentation. Si aucune traduction n'est disponible, l'anglais sera utilisé à la place.",
+ "maps-mapsdoc-par-language": "La langue dans laquelle afficher la documentation. Si aucune traduction n’est disponible, la documentation en anglais sera utilisée en repli.",
"maps-coordinates-par-location": "Les coordonnées que vous souhaitez formater.",
"maps-coordinates-par-format": "Le format cible pour les coordonnées.",
- "maps-coordinates-par-directional": "Indique si les coordonnées doivent être imprimées directionnellement ou non.",
+ "maps-coordinates-par-directional": "Indique si les coordonnées doivent être affichées de façon directionnelle ou non.",
"maps-par-scrollwheelzoom": "Indique si le défilement à la souris doit être ou non activé.",
- "maps-distance-par-distance": "La distance à convertir dans son équivalent avec une unité spécifiée.",
+ "maps-distance-par-distance": "La distance à convertir dans son équivalent exprimé dans une unité spécifiée.",
"maps-distance-par-decimals": "Le nombre maximal de chiffres après la virgule à utiliser dans le résultat.",
- "maps-distance-par-unit": "L'unité utilisée pour afficher la distance.",
- "maps-finddestination-par-location": "L'emplacement initial.",
+ "maps-distance-par-unit": "L’unité utilisée pour afficher la distance.",
+ "maps-finddestination-par-location": "L’emplacement initial.",
"maps-finddestination-par-bearing": "La direction initiale.",
"maps-finddestination-par-distance": "La distance à parcourir.",
"maps-finddestination-par-format": "Le format dans lequel afficher la destination.",
"maps-finddestination-par-directional": "Indique si le format de destination doit être directionnel ou non.",
- "maps-geocode-par-location": "L'adresse que vous souhaitez géocoder.",
+ "maps-geocode-par-location": "L’adresse que vous souhaitez géocoder.",
"maps-geocode-par-format": "Le format des coordonnées qui en résultent.",
- "maps-geocode-par-directional": "Indique si les coordonnées doivent être imprimées directionnellement ou non.",
+ "maps-geocode-par-directional": "Indique si les coordonnées doivent être affichées de façon directionnelle ou non.",
"maps-geodistance-par-location1": "Le premier point dans le jeu pour à utiliser pour calculer la distance.",
"maps-geodistance-par-location2": "Le deuxième point dans le jeu pour à utiliser pour calculer la distance.",
- "maps-geodistance-par-unit": "L'unité utilisée pour afficher la distance.",
- "maps-geodistance-par-decimals": "Le nombre maximal de chiffres après la virgule à utiliser dans le résultat.",
+ "maps-geodistance-par-unit": "L’unité utilisée pour afficher la distance.",
+ "maps-geodistance-par-decimals": "Le nombre maximal de chiffres après la virgule à utiliser dans la valeur en résultat.",
"maps-displaymap-par-mappingservice": "Permet de définir le service de cartographie qui sera utilisé pour générer la carte.",
- "maps-displaymap-par-coordinates": "Un ou plusieurs emplacements à afficher sur la carte. Ils seront représentés par un indicateur.",
- "maps-displaymap-par-visitedicon": "Le nom de fichier d’une image à utiliser comme icône de marquage une fois que les marqueurs d’origine auront été cliqués",
- "maps-displaymap-par-zoom": "Permet de définir le niveau de zoom de la carte.\nLorsqu'il n'est pas fourni et que plusieurs marqueurs sont présents sur la carte, le meilleur zoom sera pris, mais non pas la valeur par défaut configurable.",
- "maps-displaymap-par-centre": "Permet de définir les coordonnées du centre de la carte pour display_point(s).\nAccepte les adresses et les coordonnées.\nLorsque cette propriété n'est pas fournie, la carte se recentre sur le marqueur fourni, ou entre les marqueurs fournis.",
- "maps-displaymap-par-title": "Permet de définir le texte qui sera affiché dans les pop-ups de tous les marqueurs qui ne disposent pas d'un titre spécifique.\nLorsqu'il est utilisé avec le label, le titre sera en gras et souligné.",
- "maps-displaymap-par-label": "Permet de définir le texte qui sera affiché dans les fenêtres surgissantes de tous les marqueurs qui n'ont pas d'étiquette spécifique.",
- "maps-displaymap-par-icon": "Permet de définir l'icône utilisée pour tous les marqueurs.",
+ "maps-displaymap-par-coordinates": "Un ou plusieurs emplacements à afficher sur la carte. Ils seront représentés par un marqueur.",
+ "maps-displaymap-par-visitedicon": "Le nom de fichier d’une image à utiliser comme icônes de marqueur après avoir cliqué les marqueurs d’origine",
+ "maps-displaymap-par-zoom": "Permet de définir le niveau de zoom de la carte.\nLorsqu’il n'est pas fourni et que plusieurs marqueurs sont présents sur la carte, le meilleur zoom sera pris, mais non pas la valeur par défaut configurable.",
+ "maps-displaymap-par-centre": "Permet de définir les coordonnées du centre de la carte pour display_point(s).\nAccepte aussi bien des adresses que des coordonnées.\nLorsque cette propriété n’est pas fournie, la carte se recentrera sur le marqueur fourni ou entre les marqueurs fournis.",
+ "maps-displaymap-par-title": "Permet de définir le texte qui sera affiché dans les bulles contextuelles de tous les marqueurs qui ne disposent pas d’un titre spécifique.\nLorsqu’il est utilisé avec le libellé, le titre sera en gras et souligné.",
+ "maps-displaymap-par-label": "Permet de définir le texte qui sera affiché dans les bulles contextuelles de tous les marqueurs qui n’ont pas de libellé spécifique.",
+ "maps-displaymap-par-icon": "Permet de définir l’icône utilisée pour tous les marqueurs.",
"maps-displaymap-par-circles": "Cercles à afficher",
- "maps-displaymap-par-copycoords": "Afficher une boîte de dialogue lors du clic sur un emplacement depuis lequel ses coordonnées peuvent être copiées.",
+ "maps-displaymap-par-copycoords": "Afficher une boîte de dialogue lors du clic sur un emplacement, depuis laquelle ses coordonnées peuvent être copiées.",
"maps-displaymap-par-lines": "Lignes à afficher",
- "maps-displaymap-par-maxzoom": "Le niveau maximal de grossissement",
- "maps-displaymap-par-minzoom": "Le niveau minimal de grossissement",
+ "maps-displaymap-par-maxzoom": "Le niveau maximal d’agrandissement",
+ "maps-displaymap-par-minzoom": "Le niveau minimal d’agrandissement",
"maps-displaymap-par-polygons": "Polygones à afficher",
"maps-displaymap-par-rectangles": "Rectangles à afficher",
"maps-displaymap-par-static": "Rendre la carte statique",
- "maps-displaymap-par-wmsoverlay": "Utiliser une superposition WMS",
+ "maps-displaymap-par-wmsoverlay": "Utiliser une surcouche WMS",
"maps-displaymap-par-geojson": "URL de fichier ou nom de page contenant des données GeoJSON",
"maps-fullscreen-button": "Basculer en plein écran",
"maps-fullscreen-button-tooltip": "Visualiser la carte en plein écran ou incorporé.",
- "validation-error-invalid-location": "Le paramètre « $1 » doit être un emplacement valide.",
- "validation-error-invalid-locations": "Le paramètre « $1 » doit être un ou plusieurs emplacement(s) valide(s).",
- "validation-error-invalid-width": "Le paramètre « $1 » doit être une largeur valide.",
- "validation-error-invalid-height": "Le paramètre « $1 » doit être une hauteur valide.",
- "validation-error-invalid-distance": "Le paramètre « $1 » doit être une distance valide.",
- "validation-error-invalid-distances": "Le paramètre « $1 » doit être une ou plusieurs distance(s) valide(s).",
- "validation-error-invalid-image": "Le paramètre « $1 » doit être une image valide.",
- "validation-error-invalid-images": "Le paramètre « $1 » doit être une ou plusieurs image(s) valide(s).",
- "validation-error-invalid-goverlay": "Le paramètre « $1 » doit être un recouvrement valide.",
- "validation-error-invalid-goverlays": "Le paramètre « $1 » doit être un ou plusieurs recouvrement(s) valide(s).",
+ "validation-error-invalid-location": "Le paramètre « $1 » doit indiquer un emplacement valide.",
+ "validation-error-invalid-locations": "Le paramètre « $1 » doit indiquer un ou plusieurs emplacements valides.",
+ "validation-error-invalid-width": "Le paramètre « $1 » doit indiquer une largeur valide.",
+ "validation-error-invalid-height": "Le paramètre « $1 » doit indiquer une hauteur valide.",
+ "validation-error-invalid-distance": "Le paramètre « $1 » doit indiquer une distance valide.",
+ "validation-error-invalid-distances": "Le paramètre « $1 » doit indiquer une ou plusieurs distances valides.",
+ "validation-error-invalid-image": "Le paramètre « $1 » doit indiquer une image valide.",
+ "validation-error-invalid-images": "Le paramètre « $1 » doit indiquer une ou plusieurs images valides.",
+ "validation-error-invalid-goverlay": "Le paramètre « $1 » doit indiquer une surcouche valide.",
+ "validation-error-invalid-goverlays": "Le paramètre « $1 » doit indiquer une ou plusieurs surcouches valides.",
"maps-abb-north": "N",
"maps-abb-east": "E",
"maps-abb-south": "S",
"maps-abb-west": "O",
- "maps-latitude": "Latitude :",
- "maps-longitude": "Longitude :",
- "maps-invalid-coordinates": "La valeur $1 n'a pas été reconnue comme un ensemble valide de coordonnées.",
- "maps_coordinates_missing": "Aucune coordonnée n'a été fournie pour le plan.",
- "maps_geocoding_failed": "{{PLURAL:$2|L′adresse suivante n'as pu être géocodée|Les adresses suivantes n'ont pas pu être géocodées}} : $1.",
- "maps_geocoding_failed_for": "{{PLURAL:$2|L′adresse suivante n’as pu être géocodée|Les adresses suivantes n’ont pas pu être géocodées}} et {{PLURAL:$2|n’est pas affichée|ne sont pas affichées}} sur le plan : \n$1",
- "maps_unrecognized_coords": "{{PLURAL:$2|La coordonnée suivante n'a pas été reconnue|Les coordonnées suivantes n'ont pas été reconnues}} : $1.",
- "maps_unrecognized_coords_for": "{{PLURAL:$2|La coordonnée suivante n'a pas été reconnue|Les coordonnées suivantes n'ont pas été reconnues}} et {{PLURAL:$2|a été omise|ont été omises}} sur la carte :\n$1",
+ "maps-latitude": "Latitude :",
+ "maps-longitude": "Longitude :",
+ "maps-invalid-coordinates": "La valeur $1 n’a pas été reconnue comme un ensemble valide de coordonnées.",
+ "maps_coordinates_missing": "Aucune coordonnée n’a été fournie pour la carte.",
+ "maps_geocoding_failed": "{{PLURAL:$2|L’adresse suivante n’a pas pu être géocodée|Les adresses suivantes n’ont pas pu être géocodées}} : $1.",
+ "maps_geocoding_failed_for": "{{PLURAL:$2|L′adresse suivante n’a pas pu être géocodée|Les adresses suivantes n’ont pas pu être géocodées}} et {{PLURAL:$2|n’est pas affichée|ne sont pas affichées}} sur la carte :\n$1",
+ "maps_unrecognized_coords": "{{PLURAL:$2|La coordonnée suivante n’a pas été reconnue|Les coordonnées suivantes n’ont pas été reconnues}} : $1.",
+ "maps_unrecognized_coords_for": "{{PLURAL:$2|La coordonnée suivante n’a pas été reconnue|Les coordonnées suivantes n’ont pas été reconnues}} et {{PLURAL:$2|a été omise|ont été omises}} sur la carte :\n$1",
"maps_map_cannot_be_displayed": "La carte ne peut pas être affichée.",
- "maps-geocoder-not-available": "La fonctionnalité géocodage des cartes n'est pas disponible. Votre emplacement ne peut être géocodé.",
+ "maps-geocoder-not-available": "La fonctionnalité des Cartes pour le géocodage n’est pas disponible. Votre emplacement ne peut être géocodé.",
"maps_leaflet": "Dépliant",
"maps-leaflet-par-defzoom": "Permet de définir le niveau de zoom par défaut de la carte.",
"maps-leaflet-par-layers": "Les couches qui seront disponibles dans le sélecteur de couche. La première est celle qui sera affichée pendant le chargement de la carte.",
+ "maps-leaflet-par-layers-dark": "Les couches qui seront disponibles dans le sélecteur de couche lorsqu’un mode sombre sera activé. La première couche sera affichée lors du chargement de la carte.",
"maps-leaflet-par-overlaylayers": "Les surcouches qui seront affichées pendant que la carte se charge.",
- "maps-leaflet-par-maxclusterradius": "Rayon maximal qu'un agrégat peut couvrir en partant du marqueur central (en pixels).",
- "maps-leaflet-par-clusterspiderfy": "Lorsque vous cliquez sur un cluster à bas niveau de zoom nous l'explicitons afin que vous puissiez voir l'ensemble de ses marqueurs.",
+ "maps-leaflet-par-maxclusterradius": "Rayon maximal qu’un agrégat peut couvrir en partant du marqueur central (en pixels).",
+ "maps-leaflet-par-clusterspiderfy": "Lorsque vous cliquez sur un agrégat à bas niveau de zoom nous l’explicitons afin que vous puissiez voir l’ensemble de ses marqueurs.",
+ "maps-leaflet-par-clicktarget": "En cliquant sur la carte, vous serez redirigé vers cet URL. %lat% est remplacé par la latitude et %long% par la longitude.",
"maps_click_to_activate": "Cliquer pour activer la carte",
"maps_centred_on": "Carte centrée sur $1, $2.",
"maps-par-mappingservice": "Permet de régler le service de cartographie qui sera utilisé pour générer la carte.",
"maps-par-resizable": "Rendre la carte redimensionnable en faisant glisser son coin inférieur droit.",
"maps-par-searchmarkers": "Permet de rechercher des marqueurs spécifiques via un champ inclus dans la carte.",
- "maps-par-zoom": "Le niveau de zoom de la carte. Pour les cartes avec des marqueurs, ceci positionne la valeur par défaut du plus grand zoom qui montre encore tous les marqueurs.",
- "maps-par-width": "Permet de définir la largeur de la carte. Par défaut les pixels seront considérés comme unité, mais vous pouvez spécifier explicitement une de ces unités : px, ex, em, %.",
- "maps-par-height": "Permet de définir la hauteur de la carte. Par défaut les pixels seront considérés comme unité, mais vous pouvez spécifier explicitement une de ces unités : px, ex, em.",
+ "maps-par-zoom": "Le niveau de zoom de la carte. Pour les cartes avec marqueurs, ceci positionne la valeur par défaut du plus grand zoom qui permet encore de montrer tous les marqueurs.",
+ "maps-par-width": "Permet de définir la largeur de la carte. Par défaut les pixels seront considérés comme unité, mais vous pouvez spécifier explicitement une de ces unités : px, ex, em, %.",
+ "maps-par-height": "Permet de définir la hauteur de la carte. Par défaut les pixels seront considérés comme unité, mais vous pouvez spécifier explicitement une de ces unités : px, ex, em.",
"maps-par-centre": "Le lieu sur lequel la carte devra être centrée",
- "maps-par-enable-fullscreen": "Activer le bouton plein écran",
+ "maps-par-enable-fullscreen": "Activer le bouton du mode plein écran",
"maps-par-kml": "Fichiers KML à charger sur la carte.",
"maps-par-markercluster": "Autoriser la fusion de plusieurs repères à proximité en un seul repère",
- "maps-googlemaps3-incompatbrowser": "Votre navigateur n'est pas compatible avec Google Maps v3.",
- "maps-googlemaps3-par-imageoverlays": "Permet d'ajouter une image à l'emplacement indiqué sur la carte.",
+ "maps-googlemaps3-incompatbrowser": "Votre navigateur n’est pas compatible avec Google Maps v3.",
+ "maps-googlemaps3-par-imageoverlays": "Permet d’ajouter une image à l’emplacement indiqué sur la carte.",
"maps-googlemaps3-par-type": "Le type de carte à afficher initialement.",
"maps-googlemaps3-par-types": "Les types de carte qui seront disponibles via le contrôle de type.",
"maps-googlemaps3-par-layers": "Couches spéciales à charger sur la carte.",
"maps-googlemaps3-par-controls": "Les contrôles à placer sur la carte.",
"maps-googlemaps3-par-zoomstyle": "Style du contrôle de zoom.",
"maps-googlemaps3-par-typestyle": "Style du contrôle de type.",
- "maps-googlemaps3-par-autoinfowindows": "Ouvrir automatiquement toutes les fenêtres d'information après le chargement de la page.",
+ "maps-googlemaps3-par-autoinfowindows": "Ouvrir automatiquement toutes les fenêtres d’information après le chargement de la page.",
"maps-googlemaps3-par-gkml": "Les fichiers KML hébergés par Google à charger sur la carte.",
"maps-googlemaps3-par-kmlrezoom": "Zoomer de nouveau la carte une fois que les couches KML ont été chargées.",
"maps-googlemaps3-par-poi": "Afficher les points d’intérêt.",
- "maps-googlemaps3-par-clustergridsize": "La taille de la grille (en pixels) d'un agrégat.",
+ "maps-googlemaps3-par-clustergridsize": "La taille de la grille (en pixels) d’un agrégat.",
"maps-par-clustermaxzoom": "Niveau maximal de zoom pour lequel des agrégats peuvent exister.",
- "maps-par-clusterzoomonclick": "Si le comportement par défaut de cliquer sur un agrégat est de zoomer.",
- "maps-par-maxclusterradius": "Le rayon maximal que ce groupe couvrira.",
- "maps-googlemaps3-par-clusteraveragecenter": "Si le centre de chaque groupe est le barycentre des marqueurs de l'agrégat.",
- "maps-googlemaps3-par-clusterminsize": "Le nombre minimum de marqueurs dans un agrégat avant que les marqueurs ne soient cachés et qu'un compteur ne les remplace.",
+ "maps-par-clusterzoomonclick": "Si le comportement lorsqu’on clique sur un agrégat est par défaut de zoomer dessus.",
+ "maps-par-maxclusterradius": "Le rayon maximal qu’un agrégat couvrira.",
+ "maps-googlemaps3-par-clusteraveragecenter": "Si le centre de chaque agrégat est le barycentre des marqueurs dans l’agrégat.",
+ "maps-googlemaps3-par-clusterminsize": "Le nombre minimum de marqueurs dans un agrégat avant que les marqueurs ne soient cachés et qu’un compteur ne les remplace.",
"mapeditor": "Éditeur de carte",
"specialpages-group-maps": "Cartes",
- "mapeditor-parser-error": "Une erreur s'est produite lors de l'analyse des métadonnées. Entrées de l'utilisateur ignorées.",
+ "mapeditor-parser-error": "Une erreur s’est produite lors de l’analyse des métadonnées. Entrées de l’utilisateur ignorées.",
"mapeditor-none-text": "Aucun",
- "mapeditor-done-button": "Fait",
- "mapeditor-remove-button": "Supprimer",
+ "mapeditor-done-button": "Terminé",
+ "mapeditor-remove-button": "Enlever",
"mapeditor-import-button2": "Importer",
"mapeditor-export-button": "Exporter en code wiki",
"mapeditor-import-button": "Importer à partir de code wiki",
@@ -180,16 +184,16 @@
"mapeditor-clear-button": "Effacer la carte",
"mapeditor-code-title": "Code wiki",
"mapeditor-import-title": "Importer le code wiki",
- "mapeditor-import-note": "Veuillez noter que l'analyseur syntaxique s'attend à un format très strict sur le code wiki. Le code saisi ici doit correspondre au code généré par la fonctionnalité d'exportation.",
+ "mapeditor-import-note": "Veuillez noter que l’analyseur syntaxique n’accepte qu’un format très strict sur le code wiki. Le code saisi ici doit correspondre au code généré par la fonctionnalité d’exportation.",
"mapeditor-form-title": "Modifier les détails",
- "mapeditor-link-title-switcher-popup-text": "Popup avec texte",
+ "mapeditor-link-title-switcher-popup-text": "Bulle contextuelle avec texte",
"mapeditor-link-title-switcher-link-text": "Lien",
"mapeditor-form-field-title": "Titre",
"mapeditor-form-field-text": "Texte",
"mapeditor-form-field-link": "Lien",
"mapeditor-form-field-icon": "Icône",
"mapeditor-form-field-group": "Groupe",
- "mapeditor-form-field-inlinelabel": "Étiquette en ligne",
+ "mapeditor-form-field-inlinelabel": "Libellé en ligne",
"mapeditor-form-field-strokecolor": "Couleur de trait",
"mapeditor-form-field-strokeopacity": "Opacité du trait",
"mapeditor-form-field-strokeweight": "Poids du trait",
@@ -197,47 +201,85 @@
"mapeditor-form-field-fillopcaity": "Opacité de remplissage",
"mapeditor-form-field-showonhover": "Montrer seulement au survol",
"mapeditor-mapparam-title": "Modifier les paramètres de la carte",
- "mapeditor-mapparam-defoption": "-Sélectionnez un paramètre-",
- "mapeditor-imageoverlay-button": "Ajouter la couverture d'image",
+ "mapeditor-mapparam-defoption": "–Sélectionner le paramètre–",
+ "mapeditor-imageoverlay-button": "Ajouter une surcouche d’image",
"mapeditor-form-field-image": "Image",
- "mapeditor-imageoverlay-title": "Détails de la couverture d'image",
+ "mapeditor-imageoverlay-title": "Détails de la surcouche d’image",
"mapeditor-form-field-visitedicon": "Icône visitée",
- "semanticmaps-unrecognizeddistance": "La valeur $1 n'est pas une distance valide.",
+ "semanticmaps-unrecognizeddistance": "La valeur $1 n’est pas une distance valide.",
"semanticmaps-kml-link": "Voir le fichier KML",
"semanticmaps-default-kml-pagelink": "Voir l’article $1",
- "semanticmaps-latitude": "Latitude : $1",
- "semanticmaps-longitude": "Longitude : $1",
- "semanticmaps-altitude": "Altitude : $1",
+ "semanticmaps-latitude": "Latitude : $1",
+ "semanticmaps-longitude": "Longitude : $1",
+ "semanticmaps-altitude": "Altitude : $1",
"semanticmaps-forminput-locations": "Emplacements",
- "semanticmaps-par-staticlocations": "Une liste des endroits à ajouter à la carte avec les données demandées. Comme avec display_points, vous pouvez ajouter un titre, une description et une icône par emplacement en utilisant le tilde « ~ » comme séparateur.",
- "semanticmaps-par-showtitle": "Afficher un titre dans la fenêtre d'informations des marqueurs ou non. La désactivation de ceci est souvent utile lorsque vous utilisez un modèle pour formater le contenu de la fenêtre d'informations.",
+ "semanticmaps-par-staticlocations": "Une liste de lieux à ajouter à la carte avec les données demandées. Comme avec « display_points », vous pouvez ajouter un titre, une description et une icône par emplacement en utilisant le tilde « ~ » comme séparateur.",
+ "semanticmaps-par-showtitle": "Afficher un titre dans la fenêtre d’information des marqueurs ou non. La désactivation de ceci est souvent utile lorsque vous utilisez un modèle pour formater le contenu de la fenêtre d’informations.",
"semanticmaps-par-hidenamespace": "Afficher le titre de l’espace de noms dans la fenêtre d’information du marqueur",
- "semanticmaps-par-centre": "Le centre de la carte. Lorsqu'il n'est pas fourni, la carte va choisir automatiquement le centre optimal pour afficher tous les marqueurs sur la carte.",
- "semanticmaps-par-template": "Un modèle à utiliser pour mettre en forme le contenu de la fenêtre d'informations.",
+ "semanticmaps-par-centre": "Le centre de la carte. Lorsqu’il n’est pas fourni, la carte va choisir automatiquement le centre optimal qui permet d’afficher tous les marqueurs sur la carte.",
+ "semanticmaps-par-template": "Un modèle à utiliser pour mettre en forme le contenu de la fenêtre d’informations.",
"semanticmaps-par-geocodecontrol": "Afficher le contrôle de géocodage.",
"semanticmaps-par-activeicon": "Icône à afficher à la place du marqueur par défaut, quand la page active est égale au résultat de la recherche",
- "semanticmaps-par-pagelabel": "Quand il vaut « oui », tous les marqueurs auront un « inlineLabel » avec un lien vers la page contenant les coordonnées du marqueur",
+ "semanticmaps-par-pagelabel": "Quand il vaut « yes » (oui), tous les marqueurs auront un « inlineLabel » avec un lien vers la page contenant les coordonnées du marqueur",
"semanticmaps-par-ajaxcoordproperty": "Nom de la propriété de coordonnées utilisée pour construire la requête Ajax.",
- "semanticmaps-par-ajaxquery": "Une seconde requête qui est envoyée via Ajax pour récupérer les coordonnées supplémentaires.",
+ "semanticmaps-par-ajaxquery": "Une seconde requête qui est envoyée via Ajax pour récupérer des coordonnées supplémentaires.",
"semanticmaps-par-userparam": "Une valeur passée dans chaque appel de modèle, si un modèle est utilisé",
- "semanticmaps-kml-text": "Le texte associé avec chaque page. Remplacé par des propriétés récupérées supplémentaires s'il y en a.",
+ "semanticmaps-kml-text": "Le texte associé avec chaque page. Remplacé par les éventuelles propriétés supplémentaires récupérées.",
"semanticmaps-kml-title": "Le titre par défaut pour les résultats",
- "semanticmaps-kml-linkabsolute": "Si les titres doivent être absolus (au contraire de relatifs)",
- "semanticmaps-kml-pagelinktext": "Le texte à utiliser pour les liens vers la page, dans lesquels $1 sera remplacé par le titre de la page",
- "semanticmaps-shapes-improperformat": "Format de $1 incorrect, veuillez vous reporter à la documentation pour le format attendu",
- "semanticmaps-shapes-missingshape": "Aucune forme trouvée pour $1; veuillez voir dans la documentation les formes disponibles",
+ "semanticmaps-kml-linkabsolute": "Si les titres doivent être absolus (et non relatifs)",
+ "semanticmaps-kml-pagelinktext": "Le texte à utiliser pour les liens vers la page, dans lequel $1 sera remplacé par le titre de la page cible",
+ "semanticmaps-shapes-improperformat": "Format incorrect pour $1. Veuillez consulter la documentation pour les formats pris en charge.",
+ "semanticmaps-shapes-missingshape": "Aucune forme trouvée pour $1 ; veuillez consulter la documentation pour les formes disponibles",
"validator-type-mapscircle": "Cercle géographique",
- "validator-type-mapscircle-list": "Liste des cercles",
- "validator-type-mapsimageoverlay": "Superposition d’image",
- "validator-type-mapsimageoverlay-list": "Liste des superpositions d’image",
+ "validator-type-mapscircle-list": "Liste de cercles",
+ "validator-type-mapsimageoverlay": "Surcouche d’image",
+ "validator-type-mapsimageoverlay-list": "Liste de surcouches d’images",
"validator-type-mapsline": "Ligne géographique",
- "validator-type-mapsline-list": "Liste des lignes",
+ "validator-type-mapsline-list": "Liste de lignes",
"validator-type-mapslocation": "Emplacement géographique",
- "validator-type-mapslocation-list": "Liste des emplacements",
+ "validator-type-mapslocation-list": "Liste d’emplacements",
"validator-type-mapsrectangle": "Rectangle géographique",
- "validator-type-mapsrectangle-list": "Liste des rectangles",
+ "validator-type-mapsrectangle-list": "Liste de rectangles",
"validator-type-mapspolygon": "Polygone géographique",
"validator-type-mapspolygon-list": "Liste des polygones géographiques",
- "validator-type-wmsoverlay": "Surcouche de Service de Carte web",
- "validator-type-jsonfile": "texte"
+ "validator-type-wmsoverlay": "Surcouche du Service de carte web",
+ "maps-json-editor-button-marker": "Placer un marqueur",
+ "maps-json-editor-button-line": "Tracer une ligne",
+ "maps-json-editor-button-polygon": "Tracer un polygone",
+ "maps-json-editor-button-rectangle": "Placer un rectangle",
+ "maps-json-editor-button-circle": "Placer un cercle",
+ "maps-json-editor-tooltip-marker": "Cliquer la carte pour placer un marqueur.",
+ "maps-json-editor-tooltip-line": "Cliquer la carte pour tracer une ligne.",
+ "maps-json-editor-tooltip-polygon": "Cliquer la carte pour tracer un polygone.",
+ "maps-json-editor-tooltip-rectangle": "Cliquer une carte pour placer un rectangle.",
+ "maps-json-editor-tooltip-circle": "Cliquer la carte pour placer un cercle.",
+ "maps-json-editor-added-marker": "Marqueur ajouté",
+ "maps-json-editor-added-line": "Ligne ajoutée",
+ "maps-json-editor-added-polygon": "Polygone ajouté",
+ "maps-json-editor-added-rectangle": "Rectangle ajouté",
+ "maps-json-editor-added-circle": "Cercle ajouté",
+ "maps-json-editor-edit-removed-shapes": "$1 forme{{PLURAL:$1||s}} supprimée{{PLURAL:$1||s}}",
+ "maps-json-editor-edit-modified": "Formes existantes de la carte modifiées",
+ "maps-json-editor-edit-other": "Modifier visuellement la carte",
+ "maps-json-editor-edit-failed": "Échec lors de l’enregistrement de la carte",
+ "maps-json-editor-toolbar-save-title": "Quitte le mode modification en conservant tous les changements",
+ "maps-json-editor-toolbar-save-text": "Terminé",
+ "maps-json-editor-toolbar-cancel-title": "Quitte le mode modification en abandonnant tous les changements",
+ "maps-json-editor-toolbar-cancel-text": "Annuler",
+ "maps-json-editor-toolbar-clear-title": "Effacer toutes les couches",
+ "maps-json-editor-toolbar-clear-text": "Effacer tout",
+ "maps-json-editor-toolbar-button-edit": "Modifier les couches",
+ "maps-json-editor-toolbar-button-edit-disabled": "Aucune couche à modifier",
+ "maps-json-editor-toolbar-button-remove": "Supprimer des couches",
+ "maps-json-editor-toolbar-button-remove-disabled": "Aucune couche à supprimer",
+ "maps-json-editor-toolbar-button-save": "Enregistrer les modifications",
+ "maps-json-editor-changes-saved": "Vos modifications ont été enregistrées",
+ "maps-geo-json-edit-source": "Modifier la source",
+ "maps-geo-json-create-source": "Créer depuis une source GeoJSON",
+ "maps-geo-json-create-page-button": "Créer cette page",
+ "maps-geo-json-create-page-creating": "Création de la page...",
+ "maps-geo-json-create-page-summary": "Page GeoJSON créée",
+ "maps-editor-edit-geojson": "Modifier la couche GeoJSON",
+ "tag-maps-visual-edit": "Modification de carte visuelle",
+ "tag-maps-visual-edit-description": "Modification faite avec l’éditeur visuel fourni par l’[https://www.mediawiki.org/wiki/Extension:Maps extension Maps]"
}
diff --git a/www/wiki/extensions/Maps/i18n/gl.json b/www/wiki/extensions/Maps/i18n/gl.json
index a69ecd90..9bdc727f 100644
--- a/www/wiki/extensions/Maps/i18n/gl.json
+++ b/www/wiki/extensions/Maps/i18n/gl.json
@@ -5,7 +5,8 @@
"Toliño",
"Banjo",
"Elisardojm",
- "Navhy"
+ "Navhy",
+ "Iváns"
]
},
"maps-desc": "Permite incorporar mapas dinámicos, enderezos xeocodificados e outras operacións xeográficas nas páxinas do wiki",
@@ -215,6 +216,5 @@
"validator-type-mapsrectangle-list": "Lista de rectángulos",
"validator-type-mapspolygon": "Polígono xeográfico",
"validator-type-mapspolygon-list": "Lista dos polígonos xeográficos",
- "validator-type-wmsoverlay": "Transparencia de Servizo de Mapa web",
- "validator-type-jsonfile": "texto"
+ "validator-type-wmsoverlay": "Transparencia de Servizo de Mapa web"
}
diff --git a/www/wiki/extensions/Maps/i18n/he.json b/www/wiki/extensions/Maps/i18n/he.json
index 5f99e108..88942e91 100644
--- a/www/wiki/extensions/Maps/i18n/he.json
+++ b/www/wiki/extensions/Maps/i18n/he.json
@@ -147,7 +147,7 @@
"maps-googlemaps3-par-kmlrezoom": "לקרב מחדש את המפה אחרי ששכבות KML נטענו.",
"maps-googlemaps3-par-poi": "הצגת נקודות עניין.",
"maps-googlemaps3-par-clustergridsize": "גודל הרשת של האשכול בפיקסלים.",
- "maps-par-clusterzoomonclick": "האם ההתנהגות הראשונית ללחיצה על אשכולית היא להתקרב לתוכו.",
+ "maps-par-clusterzoomonclick": "האם ההתנהגות הראשונית ללחיצה על אשכול היא להתקרב לתוכו.",
"maps-googlemaps3-par-clusteraveragecenter": "האם המרכז של כל אשכול אמור להיות הממוצע של כל הסמנים באשכול.",
"maps-googlemaps3-par-clusterminsize": "המספר המזערי של הסמנים שאמורים להיות באשכול לפני שהסמנים מוסתרים ומוצג מונה.",
"mapeditor": "עורך מפות",
@@ -205,13 +205,12 @@
"semanticmaps-par-ajaxquery": "שאילתה שנייה שנשלחת ב־ajax כדי לאחזר נקודות ציון נוספות.",
"semanticmaps-par-userparam": "ערך שמועבר לכל קריאה לתבנית, אם משמשת תבנית",
"semanticmaps-kml-text": "הטקסט משויך לכל עמוד ועמוד. נדרס במאפיינים אחרים שנעשית עליהם שאילתה, אם יש כאלה.",
- "semanticmaps-kml-title": "כותרת לתוצאות לפי בררת המחדל.",
+ "semanticmaps-kml-title": "כותרת לתוצאות לפי ברירת המחדל.",
"semanticmaps-kml-linkabsolute": "האם הקישורים צריכים להיות מוחלטים (או יחסיים)",
"semanticmaps-kml-pagelinktext": "הטקסט שישמש לקישורים לדף, כאשר $1 יוחלף בכותרת הדף",
"semanticmaps-shapes-improperformat": "עיצוב לא מתאים עבור $1. נא לראות את התיעוד על עיצוב",
"semanticmaps-shapes-missingshape": "לא נמצאו צורות עבור $1. נא לראות את התיעוד עבור צורות",
"validator-type-mapscircle-list": "רשימת מעגלים",
"validator-type-mapsline-list": "רשימת קווים",
- "validator-type-mapsrectangle-list": "רשימה של משולשים",
- "validator-type-jsonfile": "טקסט"
+ "validator-type-mapsrectangle-list": "רשימה של משולשים"
}
diff --git a/www/wiki/extensions/Maps/i18n/hr.json b/www/wiki/extensions/Maps/i18n/hr.json
index 7b10792c..0605c4eb 100644
--- a/www/wiki/extensions/Maps/i18n/hr.json
+++ b/www/wiki/extensions/Maps/i18n/hr.json
@@ -87,6 +87,5 @@
"validator-type-mapsrectangle": "Zemljopisni pravokutnik",
"validator-type-mapsrectangle-list": "Popis pravokutnika",
"validator-type-mapspolygon": "Zemljopisni mnogokut",
- "validator-type-mapspolygon-list": "Popis zemljopisnih mnogokuta",
- "validator-type-jsonfile": "tekst"
+ "validator-type-mapspolygon-list": "Popis zemljopisnih mnogokuta"
}
diff --git a/www/wiki/extensions/Maps/i18n/ia.json b/www/wiki/extensions/Maps/i18n/ia.json
index 8f57ca02..99c3cb08 100644
--- a/www/wiki/extensions/Maps/i18n/ia.json
+++ b/www/wiki/extensions/Maps/i18n/ia.json
@@ -93,7 +93,7 @@
"maps-par-resizable": "Rende le carta redimensionabile per traher lo per su angulo dextre inferior.",
"maps-par-zoom": "Le nivello de zoom pro le carta. Pro cartas con marcatores isto es predefinite como le nivello de zoom le plus alte que ancora monstra tote le marcatores.",
"maps-par-width": "Permitte fixar le latitude del carta. Le unitate assumite es pixels, ma tu pote specificar un de iste unitates: px, ex, em, %.",
- "maps-par-height": "Permitte fixar le altitude del carta. Le unitate assumite es pixels, ma tu pote specificar un de iste unitates: px, ex, em, %.",
+ "maps-par-height": "Permitte fixar le altitude del carta. Le unitate assumite es pixels, ma tu pote specificar un de iste unitates: px, ex, em.",
"maps-par-kml": "Files KML pro cargar in le carta.",
"maps-googlemaps3-incompatbrowser": "Tu navigator de web non es compatibile con Google Maps version 3.",
"maps-googlemaps3-par-type": "Le typo de carta a monstrar initialmente.",
diff --git a/www/wiki/extensions/Maps/i18n/it.json b/www/wiki/extensions/Maps/i18n/it.json
index 9eecfe15..ec016b3f 100644
--- a/www/wiki/extensions/Maps/i18n/it.json
+++ b/www/wiki/extensions/Maps/i18n/it.json
@@ -11,10 +11,11 @@
"Viscontino",
"Matteocng",
"Kaspo",
- "S4b1nuz E.656"
+ "S4b1nuz E.656",
+ "JackLantern"
]
},
- "maps-desc": "Consente di includere mappe dinamiche nelle pagine wiki, la geocodifica degli indirizzi ed altre operazioni geografiche",
+ "maps-desc": "Consente di includere mappe dinamiche nelle pagine wiki utilizzando Google Maps o Leaflet. Ha un editor visuale, eventualmente integrato con Semantic MediaWiki, supporta GeoJSON e aggiunge funzionalità di geocodifica.",
"right-geocode": "Effettua la geocodifica",
"maps_map": "Mappa",
"maps-loading-map": "Caricamento mappa ...",
@@ -159,5 +160,21 @@
"validator-type-mapsrectangle-list": "Elenco di rettangoli",
"validator-type-mapspolygon": "Poligono geografico",
"validator-type-mapspolygon-list": "Elenco dei poligoni geografici",
- "validator-type-jsonfile": "testo"
+ "maps-json-editor-tooltip-marker": "Clicca sulla mappa per posizionare un marcatore.",
+ "maps-json-editor-tooltip-line": "Clicca sulla mappa per disegnare una linea.",
+ "maps-json-editor-tooltip-polygon": "Clicca sulla mappa per disegnare un poligono.",
+ "maps-json-editor-added-marker": "Marcatore aggiunto",
+ "maps-json-editor-added-line": "Linea aggiunta",
+ "maps-json-editor-toolbar-save-text": "Fatto",
+ "maps-json-editor-toolbar-cancel-title": "Uscendo dalla modalità modifica, scarti tutti i cambiamenti.",
+ "maps-json-editor-toolbar-cancel-text": "Annulla",
+ "maps-json-editor-toolbar-clear-title": "Pulisci tutti i livelli",
+ "maps-json-editor-toolbar-button-edit-disabled": "Nessun livello da modificare",
+ "maps-json-editor-toolbar-button-remove": "Cancella livelli",
+ "maps-json-editor-toolbar-button-remove-disabled": "Nessun livello da cancellare",
+ "maps-json-editor-toolbar-button-save": "Salva modifiche",
+ "maps-json-editor-changes-saved": "Le tue modifiche sono state salvate",
+ "maps-geo-json-edit-source": "Modifica sorgente",
+ "maps-geo-json-create-page-button": "Crea questa pagina",
+ "maps-geo-json-create-page-summary": "Pagina GeoJSON creata"
}
diff --git a/www/wiki/extensions/Maps/i18n/ja.json b/www/wiki/extensions/Maps/i18n/ja.json
index 7fa54348..27320207 100644
--- a/www/wiki/extensions/Maps/i18n/ja.json
+++ b/www/wiki/extensions/Maps/i18n/ja.json
@@ -171,6 +171,5 @@
"validator-type-mapsimageoverlay-list": "画像オーバーレイの一覧",
"validator-type-mapslocation-list": "位置の一覧",
"validator-type-mapspolygon": "地図ポリゴン",
- "validator-type-mapspolygon-list": "地図ポリゴンの一覧",
- "validator-type-jsonfile": "テキスト"
+ "validator-type-mapspolygon-list": "地図ポリゴンの一覧"
}
diff --git a/www/wiki/extensions/Maps/i18n/ko.json b/www/wiki/extensions/Maps/i18n/ko.json
index b687427b..e458853f 100644
--- a/www/wiki/extensions/Maps/i18n/ko.json
+++ b/www/wiki/extensions/Maps/i18n/ko.json
@@ -204,5 +204,14 @@
"validator-type-mapslocation-list": "위치 목록",
"validator-type-mapsrectangle-list": "직사각형 목록",
"validator-type-wmsoverlay": "웹 지도 서비스 오버레이",
- "validator-type-jsonfile": "텍스트"
+ "maps-json-editor-toolbar-save-title": "모든 변경사항을 유지한 채 편집 모드를 빠져나옵니다",
+ "maps-json-editor-toolbar-save-text": "완료",
+ "maps-json-editor-toolbar-cancel-title": "편집 모드를 빠져나오고 모든 변경사항을 버립니다",
+ "maps-json-editor-toolbar-cancel-text": "취소",
+ "maps-json-editor-toolbar-clear-text": "모두 지우기",
+ "maps-json-editor-toolbar-button-save": "변경사항 저장",
+ "maps-json-editor-changes-saved": "변경사항이 저장되었습니다",
+ "maps-geo-json-edit-source": "원본 편집",
+ "maps-geo-json-create-page-button": "이 문서 만들기",
+ "maps-editor-edit-geojson": "GeoJSON 레이어 편집"
}
diff --git a/www/wiki/extensions/Maps/i18n/lb.json b/www/wiki/extensions/Maps/i18n/lb.json
index 387ab13a..1579d3d7 100644
--- a/www/wiki/extensions/Maps/i18n/lb.json
+++ b/www/wiki/extensions/Maps/i18n/lb.json
@@ -119,5 +119,11 @@
"validator-type-mapsrectangle-list": "Lëscht vun de Rechtecken",
"validator-type-mapspolygon": "Geographesche Polygon",
"validator-type-mapspolygon-list": "Lëscht vun de geographesche Polygonen",
- "validator-type-jsonfile": "Text"
+ "maps-json-editor-button-line": "Eng Linn zeechnen",
+ "maps-json-editor-toolbar-save-title": "Ännerunge späicheren",
+ "maps-json-editor-toolbar-save-text": "Fäerdeg",
+ "maps-json-editor-toolbar-cancel-text": "Ofbriechen",
+ "maps-json-editor-toolbar-clear-text": "Alles ewechemaachen",
+ "maps-json-editor-toolbar-button-save": "Ännerunge späicheren",
+ "maps-json-editor-changes-saved": "Är Ännerunge goufe gespäichert"
}
diff --git a/www/wiki/extensions/Maps/i18n/lt.json b/www/wiki/extensions/Maps/i18n/lt.json
index a54d50ee..2a30424c 100644
--- a/www/wiki/extensions/Maps/i18n/lt.json
+++ b/www/wiki/extensions/Maps/i18n/lt.json
@@ -5,7 +5,8 @@
"Hugo.arg",
"Aswanas",
"Zygimantus",
- "Agne1992"
+ "Agne1992",
+ "Tomasdd"
]
},
"maps-desc": "Suteikia galimybę atvaizduoti koordinačių duomenis žemėlapiuose ir geografinio kodavimo adresus ([http://mapping.referata.com/wiki/Maps_examples demo]).\nKatrografavimo paslaugos pasiekiamos: $1",
@@ -115,5 +116,41 @@
"validator-type-mapslocation": "Geografinė vieta",
"validator-type-mapslocation-list": "Vietų sąrašas",
"validator-type-mapsrectangle": "Geografinis stačiakampis",
- "validator-type-mapsrectangle-list": "Stačiakampių sąrašas"
+ "validator-type-mapsrectangle-list": "Stačiakampių sąrašas",
+ "maps-json-editor-button-marker": "Pridėti žymeklį",
+ "maps-json-editor-button-line": "Piešti liniją",
+ "maps-json-editor-button-polygon": "Piešti daugiakampį",
+ "maps-json-editor-button-rectangle": "Piešti stačiakampį",
+ "maps-json-editor-button-circle": "Pridėti apskritimą",
+ "maps-json-editor-tooltip-marker": "Spustelėkite žemėlapį, kad padėtumėte žymeklį.",
+ "maps-json-editor-tooltip-line": "Paspauskite žemėlapį, kad nupieštumėte liniją.",
+ "maps-json-editor-tooltip-polygon": "Paspauskite žemėlapį, kad nupieštumėte daugiakampį.",
+ "maps-json-editor-tooltip-rectangle": "Paspauskite žemėlapį, kad pridėtumėte keturkampį.",
+ "maps-json-editor-tooltip-circle": "Spustelėkite žemėlapį, kad pridėtumėte apskritimą.",
+ "maps-json-editor-added-marker": "Pridėtas žymeklis",
+ "maps-json-editor-added-line": "Pridėta linija",
+ "maps-json-editor-added-polygon": "Pridėtas daugiakampis",
+ "maps-json-editor-added-rectangle": "Pridėtas keturkampis",
+ "maps-json-editor-added-circle": "Pridėtas apskritimas",
+ "maps-json-editor-edit-removed-shapes": "Pašalinti $1 {{PLURAL:$1|formą|formas}}",
+ "maps-json-editor-edit-other": "Vizualus žemėlapio redaktorius",
+ "maps-json-editor-edit-failed": "Nepavyko išsaugoti žemėlapio",
+ "maps-json-editor-toolbar-save-title": "Išeiti iš redagavimo ir išsaugoti pakeitimus",
+ "maps-json-editor-toolbar-save-text": "Atlikta",
+ "maps-json-editor-toolbar-cancel-title": "Išeiti iš redagavimo, atmesti visus pakeitimus",
+ "maps-json-editor-toolbar-cancel-text": "Atšaukti",
+ "maps-json-editor-toolbar-clear-title": "Valyti visus sluoksnius",
+ "maps-json-editor-toolbar-clear-text": "Valyti Viską",
+ "maps-json-editor-toolbar-button-edit": "Redaguoti sluoksnius",
+ "maps-json-editor-toolbar-button-edit-disabled": "Nėra sluoksnių redagavimui",
+ "maps-json-editor-toolbar-button-remove": "Trinti sluoksnius",
+ "maps-json-editor-toolbar-button-remove-disabled": "Nėra sluoksnių pašalinimui",
+ "maps-json-editor-toolbar-button-save": "Išsaugoti pakeitimus",
+ "maps-json-editor-changes-saved": "Jūsų pakeitimai buvo išsaugoti",
+ "maps-geo-json-edit-source": "Redaguoti šaltinį",
+ "maps-geo-json-create-source": "Sukurti iš GeoJSON šaltinio",
+ "maps-geo-json-create-page-button": "Sukurti šį puslapį",
+ "maps-geo-json-create-page-creating": "Puslapis kuriamas...",
+ "maps-geo-json-create-page-summary": "Sukūrė GeoJSON puslapį",
+ "maps-editor-edit-geojson": "Redaguoti GeoJSON sluoksnį"
}
diff --git a/www/wiki/extensions/Maps/i18n/mk.json b/www/wiki/extensions/Maps/i18n/mk.json
index 78a20ef8..19db3773 100644
--- a/www/wiki/extensions/Maps/i18n/mk.json
+++ b/www/wiki/extensions/Maps/i18n/mk.json
@@ -76,7 +76,7 @@
"maps-displaymap-par-title": "Овозможува задавање на текст што ќе се прикажува во скокачките прозорчиња на сите обележувачи што немаат конкретен наслов.\nАко се користат заедно со ознака, насловот ќе биде задебелен и ќе има линија под него.",
"maps-displaymap-par-label": "Овозможува задавање на текст што ќе се прикажува во скокачките прозорчиња на сите обележувачи што немаат конкретна ознака.",
"maps-displaymap-par-icon": "Овозможува задавање на икона што ќе се користи за сите обележувачи.",
- "maps-displaymap-par-circles": "Кругови за приказ",
+ "maps-displaymap-par-circles": "Кружници за приказ",
"maps-displaymap-par-copycoords": "Прикажувај прозорче со координатите на местото при стискање на место од кајшто можат да се прекопираат.",
"maps-displaymap-par-lines": "Линии за приказ",
"maps-displaymap-par-maxzoom": "Најголема приближеност",
@@ -102,8 +102,8 @@
"maps-abb-east": "И",
"maps-abb-south": "Ј",
"maps-abb-west": "З",
- "maps-latitude": "Геог. ширина",
- "maps-longitude": "Геог. должина:",
+ "maps-latitude": "Гео. ширина",
+ "maps-longitude": "Гео. должина:",
"maps-invalid-coordinates": "Вредноста $1 не беше препознаена како правилен збир координати.",
"maps_coordinates_missing": "Нема координати за картата.",
"maps_geocoding_failed": "{{PLURAL:$2|Следнава адреса не можеше да се геокодира|Следниве адреси не можеа да се геокодираат}}: $1.\nКартата не може да се прикаже.",
@@ -116,9 +116,11 @@
"maps_leaflet": "Леток",
"maps-leaflet-par-defzoom": "Овозможува задавање на основен степен на приближеност на картата.",
"maps-leaflet-par-layers": "Слојот што ќе се покажува кога се вчитува картата.",
+ "maps-leaflet-par-layers-dark": "Слоевите што ќе бидат достапни во бирачот на слоеви кога ќе се вклучи темниот режим. Кога ќе се вчита картата ќе се прикаже првиот слој.",
"maps-leaflet-par-overlaylayers": "Слоеви на облогата што ќе се покажуваат кога се вчитува картата.",
"maps-leaflet-par-maxclusterradius": "Најголемиот полупречник што може да го покрие еден грозд од средишниот обележувач (во пиксели).",
"maps-leaflet-par-clusterspiderfy": "Кога ќе стиснете на грозд при најслаба приближеност, истиот ќе се разграни за да можете да ги видите сите негови обележувачи.",
+ "maps-leaflet-par-clicktarget": "Стискајќи на картата, ќе бидете пренасочени кон следнава URL. %lat% се замеува со геоградската ширина, а %long% со географската должина",
"maps_click_to_activate": "Стиснете за активирање на картата",
"maps_centred_on": "Средиште на картата во $1, $2.",
"maps-par-mappingservice": "Овозможува назначување на картографска служба што ќе се користи за создавање на картата.",
@@ -126,7 +128,7 @@
"maps-par-searchmarkers": "Овозможува пребарување на поединечни бележници од поле вметнато во картата.",
"maps-par-zoom": "Степенот на приближеност на картата. Картите со обележувачи по основно се прикажуваат во најголем степен на приближеност што воедно ги прикажува сите обележувачи.",
"maps-par-width": "Овозможува задавање на ширина на картата во пиксели по основно, но по желба можете да изберете една од следниве единици: px, ex, em, %.",
- "maps-par-height": "Овозможува задавање на висина на картата во пиксели по основно, но по желба можете да изберете една од следниве единици: px, ex, em, %.",
+ "maps-par-height": "Овозможува задавање на висина на картата во пиксели по основно, но по желба можете да изберете една од следниве единици: px, ex, em.",
"maps-par-centre": "На која местоположба да се сосредоточи картата",
"maps-par-enable-fullscreen": "Овозможи копче за цел екран",
"maps-par-kml": "KML-податотеки за вчитување во картата.",
@@ -188,8 +190,8 @@
"semanticmaps-unrecognizeddistance": "Вредноста $1 не претставува важечко растојание.",
"semanticmaps-kml-link": "Преглед на KML-податотеката",
"semanticmaps-default-kml-pagelink": "Преглед на статијата $1",
- "semanticmaps-latitude": "Геог. ширина: $1",
- "semanticmaps-longitude": "Геог. должина: $1",
+ "semanticmaps-latitude": "Гео. ширина: $1",
+ "semanticmaps-longitude": "Гео. должина: $1",
"semanticmaps-altitude": "Надм. вис: $1",
"semanticmaps-forminput-locations": "Места",
"semanticmaps-par-staticlocations": "Список на места за додавање во картатата заедно со побараните податоци. Како и со „display_points“, тука можете да додадете наслов, опис и икона за секое место, користејќи тилда (~) како одделувач.",
@@ -209,8 +211,8 @@
"semanticmaps-kml-pagelinktext": "Текстот на врските на страницата, каде $1 ќе се замени со насловот на страницата",
"semanticmaps-shapes-improperformat": "Несоодветно форматирање на $1. Погледајте во документацијата како треба да се форматира.",
"semanticmaps-shapes-missingshape": "Не пронајдов облици за $1. Погледајте во документацијата кои облици ви се на располагање.",
- "validator-type-mapscircle": "Географски круг",
- "validator-type-mapscircle-list": "Список на кругови",
+ "validator-type-mapscircle": "Географска кружница",
+ "validator-type-mapscircle-list": "Список на кружници",
"validator-type-mapsimageoverlay": "Обложна слика",
"validator-type-mapsimageoverlay-list": "Список на обложни слики",
"validator-type-mapsline": "Географска линија",
@@ -222,5 +224,43 @@
"validator-type-mapspolygon": "Географски многуаголник",
"validator-type-mapspolygon-list": "Список на географски многуаголници",
"validator-type-wmsoverlay": "Облога Web Map Service",
- "validator-type-jsonfile": "текст"
+ "maps-json-editor-button-marker": "Стави обележувач",
+ "maps-json-editor-button-line": "Нацртај линија",
+ "maps-json-editor-button-polygon": "Нацртај многуаголник",
+ "maps-json-editor-button-rectangle": "Стави правоаголник",
+ "maps-json-editor-button-circle": "Стави кружница",
+ "maps-json-editor-tooltip-marker": "Стиснете на картата за да ставите обележувач.",
+ "maps-json-editor-tooltip-line": "Стиснете на картата за да нацртате линија.",
+ "maps-json-editor-tooltip-polygon": "Стиснете на картата за да нацртате многусголник.",
+ "maps-json-editor-tooltip-rectangle": "Стиснете на картата за да ставите правоаголник.",
+ "maps-json-editor-tooltip-circle": "Стиснете на картата за да ставите кружница.",
+ "maps-json-editor-added-marker": "Додаден обележувач",
+ "maps-json-editor-added-line": "Додадена линија",
+ "maps-json-editor-added-polygon": "Додаден многуаголник",
+ "maps-json-editor-added-rectangle": "Додаден правоаголник",
+ "maps-json-editor-added-circle": "Додадена кружница",
+ "maps-json-editor-edit-removed-shapes": "{{PLURAL:$1|Отстранет $1 облик|Отстранети $1 облици}}",
+ "maps-json-editor-edit-modified": "Изменети постоечки облици",
+ "maps-json-editor-edit-other": "Нагледно уредување на карта",
+ "maps-json-editor-edit-failed": "Не успеав да ја зачувам картата.",
+ "maps-json-editor-toolbar-save-title": "Излези од уредувањето, задржувајќи ги сите промени",
+ "maps-json-editor-toolbar-save-text": "Готово",
+ "maps-json-editor-toolbar-cancel-title": "Излези од уредувањето, отфрлајќи ги сите промени",
+ "maps-json-editor-toolbar-cancel-text": "Откажи",
+ "maps-json-editor-toolbar-clear-title": "Исчисти ги сите слоеви",
+ "maps-json-editor-toolbar-clear-text": "Исчисти сè",
+ "maps-json-editor-toolbar-button-edit": "Уреди слоеви",
+ "maps-json-editor-toolbar-button-edit-disabled": "Нема слоеви за уредување",
+ "maps-json-editor-toolbar-button-remove": "Избиши слоеви",
+ "maps-json-editor-toolbar-button-remove-disabled": "Нема слоеви за бришење",
+ "maps-json-editor-toolbar-button-save": "Зачувај промени",
+ "maps-json-editor-changes-saved": "Вашите промени се зачувани",
+ "maps-geo-json-edit-source": "Измени извор",
+ "maps-geo-json-create-source": "Создај од извор на GeoJSON",
+ "maps-geo-json-create-page-button": "Создај ја страницава",
+ "maps-geo-json-create-page-creating": "Ја создавам страницата...",
+ "maps-geo-json-create-page-summary": "Создадена GeoJSON-страница",
+ "maps-editor-edit-geojson": "Уреди GeoJSON-слој",
+ "tag-maps-visual-edit": "Нагледно уредување на картата",
+ "tag-maps-visual-edit-description": "Уредување направено со нагледниот уредник овозможен од додатокот „[https://www.mediawiki.org/wiki/Extension:Maps Карти]“"
}
diff --git a/www/wiki/extensions/Maps/i18n/nb.json b/www/wiki/extensions/Maps/i18n/nb.json
index 7da896db..9d169971 100644
--- a/www/wiki/extensions/Maps/i18n/nb.json
+++ b/www/wiki/extensions/Maps/i18n/nb.json
@@ -10,7 +10,7 @@
"Jon Harald Søby"
]
},
- "maps-desc": "Gir mulighet for å bygge inn dynamiske kart i wikisider, geokoding av adresser og andre geografiske operasjoner",
+ "maps-desc": "Gjør det mulig å bygge inn dynamiske kart i wikisider med Google Maps eller Leaflet. Har visuell redigering, kan integreres med Semantic MediaWiki, støtter GeoJSON og legger til geokodingsmuligheter.",
"right-geocode": "Geokode",
"action-geocode": "utføre geokoding på denne wikien",
"maps_map": "Kart",
@@ -88,7 +88,7 @@
"maps-displaymap-par-rectangles": "Rektangler å vise",
"maps-displaymap-par-static": "Gjør kartet statisk",
"maps-displaymap-par-wmsoverlay": "Legger på et WMS-lag",
- "maps-displaymap-par-geojson": "URL-en til ei GeoJSON-fil",
+ "maps-displaymap-par-geojson": "URL-en til en side eller fil som inneholder GeoJSON-data",
"maps-fullscreen-button": "Slå av/på fullskjerm",
"maps-fullscreen-button-tooltip": "Vis kartet som fullskjerm eller innbygd.",
"validation-error-invalid-location": "Parameter $1 må være en gyldig lokasjon.",
@@ -118,9 +118,11 @@
"maps_leaflet": "Leaflet",
"maps-leaflet-par-defzoom": "Tillater å sette standard zoom-nivå for kartet.",
"maps-leaflet-par-layers": "Laget som vil vises mens kartet laster.",
+ "maps-leaflet-par-layers-dark": "Lagene som vil være tilgjengelige i lagvelgeren når mørk modus er aktivert. Det første laget vises når kartet lastes.",
"maps-leaflet-par-overlaylayers": "Overleggslagene som vises mens kartet lastes.",
"maps-leaflet-par-maxclusterradius": "Minimum radius som et kluster dekker fra den sentrale markøren (angitt i piksler)",
"maps-leaflet-par-clusterspiderfy": "Når du klikker et kluster ved laveste zoom-nivå, så spres den ut slik at du kan se alle dens markører.",
+ "maps-leaflet-par-clicktarget": "Når du klikker på kartet blir du sendt til denne URL-en. %lat% erstattes av breddegraden og %long% av lengdegraden",
"maps_click_to_activate": "Klikk for å aktivere kartet",
"maps_centred_on": "Kart sentrert om $1, $2.",
"maps-par-mappingservice": "Tillater å sette karttjenesten som vil brukes for å skape kartet.",
@@ -128,7 +130,7 @@
"maps-par-searchmarkers": "Tillater å søke etter spesifikke markører via et felt som er innfestet i kartet.",
"maps-par-zoom": "Zoomnivået for kartet. For kart med markører vil standardverdien være den med mest høyest zoom som viser alle markørene.",
"maps-par-width": "Tillater at kartbredden angis. Standard enhet er pixler /(px), men du kan alternativt angi ex, em, %.",
- "maps-par-height": "Tillater at karthøyden angis. Standard enhet er pixler /(px), men du kan alternativt angi ex, em, %.",
+ "maps-par-height": "Tillater at karthøyden angis. Standard enhet er piksler (px), men du kan alternativt angi enhetene ex eller em.",
"maps-par-centre": "Stedet som kartet sentreres rundt",
"maps-par-enable-fullscreen": "Aktiver fullskjermknappen",
"maps-par-kml": "KML-filer som lastes inn på kortet.",
@@ -224,5 +226,43 @@
"validator-type-mapspolygon": "Geografisk polygon",
"validator-type-mapspolygon-list": "Liste over geografiske polygoner",
"validator-type-wmsoverlay": "Webmappetjensteoverlegg",
- "validator-type-jsonfile": "URL"
+ "maps-json-editor-button-marker": "Plasser en markør",
+ "maps-json-editor-button-line": "Tegn ei linje",
+ "maps-json-editor-button-polygon": "Tegn et polygon",
+ "maps-json-editor-button-rectangle": "Plasser et rektangel",
+ "maps-json-editor-button-circle": "Plasser en sirkel",
+ "maps-json-editor-tooltip-marker": "Klikk på kartet for å plassere markør.",
+ "maps-json-editor-tooltip-line": "Klikk på kartet for å tegne linje.",
+ "maps-json-editor-tooltip-polygon": "Klikk på kartet for å tegne polygon.",
+ "maps-json-editor-tooltip-rectangle": "Klikk på kartet for å plassere rektangel.",
+ "maps-json-editor-tooltip-circle": "Klikk på kartet for å plassere sirkel.",
+ "maps-json-editor-added-marker": "La til markør",
+ "maps-json-editor-added-line": "La til linje",
+ "maps-json-editor-added-polygon": "La til polygon",
+ "maps-json-editor-added-rectangle": "La til rektangel",
+ "maps-json-editor-added-circle": "La til sirkel",
+ "maps-json-editor-edit-removed-shapes": "Fjernet {{PLURAL:$1|én form|$1 former}}",
+ "maps-json-editor-edit-modified": "Modifiserte eksisterende kartformer",
+ "maps-json-editor-edit-other": "Visuell kartredigering",
+ "maps-json-editor-edit-failed": "Kunne ikke lagre kartet",
+ "maps-json-editor-toolbar-save-title": "Gå ut av redigeringsmodus uten å forkaste endringer",
+ "maps-json-editor-toolbar-save-text": "Ferdig",
+ "maps-json-editor-toolbar-cancel-title": "Avbryt redigering og forkast alle endringer",
+ "maps-json-editor-toolbar-cancel-text": "Avbryt",
+ "maps-json-editor-toolbar-clear-title": "Tøm alle lag",
+ "maps-json-editor-toolbar-clear-text": "Tøm alt",
+ "maps-json-editor-toolbar-button-edit": "Rediger lag",
+ "maps-json-editor-toolbar-button-edit-disabled": "Ingen lag å redigere",
+ "maps-json-editor-toolbar-button-remove": "Slett lag",
+ "maps-json-editor-toolbar-button-remove-disabled": "Ingen lag å slette",
+ "maps-json-editor-toolbar-button-save": "Lagre endringer",
+ "maps-json-editor-changes-saved": "Endringene dine har blitt lagret",
+ "maps-geo-json-edit-source": "Rediger kilde",
+ "maps-geo-json-create-source": "Opprett fra GeoJSON-kilde",
+ "maps-geo-json-create-page-button": "Opprett denne siden",
+ "maps-geo-json-create-page-creating": "Oppretter side …",
+ "maps-geo-json-create-page-summary": "Opprettet GeoJSON-side",
+ "maps-editor-edit-geojson": "Rediger GeoJSON-lag",
+ "tag-maps-visual-edit": "Visuell kartredigering",
+ "tag-maps-visual-edit-description": "Redigering gjort med visuell redigering fra [https://www.mediawiki.org/wiki/Extension:Maps kartutvidelsen]"
}
diff --git a/www/wiki/extensions/Maps/i18n/nl.json b/www/wiki/extensions/Maps/i18n/nl.json
index 53b3c869..3dd7eef1 100644
--- a/www/wiki/extensions/Maps/i18n/nl.json
+++ b/www/wiki/extensions/Maps/i18n/nl.json
@@ -201,6 +201,5 @@
"validator-type-mapscircle-list": "Lijst van circels",
"validator-type-mapsline-list": "Lijst met lijnen",
"validator-type-mapslocation-list": "Lijst met locaties",
- "validator-type-mapsrectangle-list": "Lijst met rechthoeken",
- "validator-type-jsonfile": "tekst"
+ "validator-type-mapsrectangle-list": "Lijst met rechthoeken"
}
diff --git a/www/wiki/extensions/Maps/i18n/pl.json b/www/wiki/extensions/Maps/i18n/pl.json
index bb71af4d..1097913b 100644
--- a/www/wiki/extensions/Maps/i18n/pl.json
+++ b/www/wiki/extensions/Maps/i18n/pl.json
@@ -9,7 +9,9 @@
"Alan ffm",
"Macofe",
"Deejay1",
- "Railfail536"
+ "Railfail536",
+ "Rail",
+ "FunPL"
]
},
"maps-desc": "Umożliwia zamieszczanie na stronach wiki map dynamicznych, geokodowanych adresów i innych danych geograficznych",
@@ -21,6 +23,7 @@
"maps-load-failed": "Nie można załadować mapy!",
"maps-markers": "Zaznaczenia",
"maps-copycoords-prompt": "CTRL+C, ENTER",
+ "maps-searchmarkers-text": "Filtr markerów",
"maps-others": "inne",
"maps-kml-parsing-failed": "Błąd podczas parsowania jednego lub więcej plików KML, najczęściej jest to wynik błędnego XML",
"maps-ns-layer": "Warstwa",
@@ -166,5 +169,43 @@
"validator-type-mapsrectangle-list": "Lista prostokątów",
"validator-type-mapspolygon": "Wielokąt geograficzny",
"validator-type-mapspolygon-list": "Lista wielokątów geograficznych",
- "validator-type-jsonfile": "tekst"
+ "maps-json-editor-button-marker": "Umieść znacznik",
+ "maps-json-editor-button-line": "Narysuj linię",
+ "maps-json-editor-button-polygon": "Narysuj wielokąt",
+ "maps-json-editor-button-rectangle": "Umieść wielokąt",
+ "maps-json-editor-button-circle": "Umieść okrąg",
+ "maps-json-editor-tooltip-marker": "Kliknij na mapie, aby umieścić znacznik.",
+ "maps-json-editor-tooltip-line": "Kliknij na mapę, aby narysować linię.",
+ "maps-json-editor-tooltip-polygon": "Kliknij na mapę, aby narysować wielokąt.",
+ "maps-json-editor-tooltip-rectangle": "Kliknij na mapie, aby umieścić prostokąt.",
+ "maps-json-editor-tooltip-circle": "Kliknij na mapie, aby umieścić okrągły znacznik.",
+ "maps-json-editor-added-marker": "Dodano znacznik",
+ "maps-json-editor-added-line": "Dodano linię",
+ "maps-json-editor-added-polygon": "Dodano wielokąt",
+ "maps-json-editor-added-rectangle": "Dodano prostokąt",
+ "maps-json-editor-added-circle": "Dodano okrąg",
+ "maps-json-editor-edit-removed-shapes": "Usunięto $1 {{PLURAL:$1|kształt|kształty|kształtów}}",
+ "maps-json-editor-edit-modified": "Zmodyfikowano kształty istniejące na mapie",
+ "maps-json-editor-edit-other": "Wizualna edycja mapy",
+ "maps-json-editor-edit-failed": "Nie udało się zapisać mapy",
+ "maps-json-editor-toolbar-save-title": "Opuść tryb edycji zachowując wszystkie zmiany",
+ "maps-json-editor-toolbar-save-text": "Gotowe",
+ "maps-json-editor-toolbar-cancel-title": "Opuść tryb edycji, odrzuca wszystkie zmiany",
+ "maps-json-editor-toolbar-cancel-text": "Anuluj",
+ "maps-json-editor-toolbar-clear-title": "Wyczyść wszystkie warstwy",
+ "maps-json-editor-toolbar-clear-text": "Wyczyść wszystko",
+ "maps-json-editor-toolbar-button-edit": "Edytuj warstwy",
+ "maps-json-editor-toolbar-button-edit-disabled": "Brak warstw do edycji",
+ "maps-json-editor-toolbar-button-remove": "Usuń warstwy",
+ "maps-json-editor-toolbar-button-remove-disabled": "Brak warstw, które można usunąć",
+ "maps-json-editor-toolbar-button-save": "Zapisz zmiany",
+ "maps-json-editor-changes-saved": "Twoje zmiany zostały zapisane",
+ "maps-geo-json-edit-source": "Edytuj kod źródłowy",
+ "maps-geo-json-create-source": "Utwórz ze źródła GeoJSON",
+ "maps-geo-json-create-page-button": "Utwórz tę stronę",
+ "maps-geo-json-create-page-creating": "Tworzenie strony…",
+ "maps-geo-json-create-page-summary": "Utworzono stronę GeoJSON",
+ "maps-editor-edit-geojson": "Edytuj warstwę GeoJSON",
+ "tag-maps-visual-edit": "Wizualna edycja mapy",
+ "tag-maps-visual-edit-description": "Edycja wykonana za pośrednictwem wizualnego edytora wprowadzonego przez [https://www.mediawiki.org/wiki/Extension:Maps rozszerzenie map]."
}
diff --git a/www/wiki/extensions/Maps/i18n/pt-br.json b/www/wiki/extensions/Maps/i18n/pt-br.json
index e1b7df59..2dd99219 100644
--- a/www/wiki/extensions/Maps/i18n/pt-br.json
+++ b/www/wiki/extensions/Maps/i18n/pt-br.json
@@ -121,9 +121,11 @@
"maps_leaflet": "Folheto",
"maps-leaflet-par-defzoom": "Permite definir o nível de zoom predefinido do mapa.",
"maps-leaflet-par-layers": "A camada que será mostrada quando o mapa é carregado.",
+ "maps-leaflet-par-layers-dark": "As camadas que estarão disponíveis no seletor de camadas quando um modo escuro estiver ativado. A primeira camada será mostrada quando o mapa carregar",
"maps-leaflet-par-overlaylayers": "As camadas de sobreposição que serão mostradas quando o mapa é carregado.",
"maps-leaflet-par-maxclusterradius": "O raio máximo que um agregado irá cobrir a partir do marcador central (em píxeis).",
"maps-leaflet-par-clusterspiderfy": "Quando clica um agregado no nível de zoom mais baixo os marcadores são enteados para que possa vê-los todos.",
+ "maps-leaflet-par-clicktarget": "Ao clicar no mapa, você será enviado para este URL. %lat% é substituído pela latitude e %long% pela longitude",
"maps_click_to_activate": "Clique para ativar o mapa",
"maps_centred_on": "Mapa centrado nas coordenadas $1, $2.",
"maps-par-mappingservice": "Permite definir o serviço de cartografia que será usado para gerar o mapa.",
@@ -227,5 +229,40 @@
"validator-type-mapspolygon": "Polígono geográfico",
"validator-type-mapspolygon-list": "Lista de polígonos geográficos",
"validator-type-wmsoverlay": "Sobreposição Web Map Service",
- "validator-type-jsonfile": "texto"
+ "maps-json-editor-button-marker": "Colocar um marcador",
+ "maps-json-editor-button-line": "Desenhar uma linha",
+ "maps-json-editor-button-polygon": "Desenhar um polígono",
+ "maps-json-editor-button-rectangle": "Colocar um retângulo",
+ "maps-json-editor-button-circle": "Colocar um círculo",
+ "maps-json-editor-tooltip-marker": "Clique no mapa para colocar o marcador.",
+ "maps-json-editor-tooltip-line": "Clique no mapa para desenhar uma linha.",
+ "maps-json-editor-tooltip-polygon": "Clique no mapa para desenhar polígono.",
+ "maps-json-editor-tooltip-rectangle": "Clique no mapa para colocar o retângulo.",
+ "maps-json-editor-tooltip-circle": "Clique no mapa para colocar o círculo.",
+ "maps-json-editor-added-marker": "Marcador adicionado",
+ "maps-json-editor-added-line": "Linha adicionada",
+ "maps-json-editor-added-polygon": "Polígono adicionado",
+ "maps-json-editor-added-rectangle": "Retângulo adicionado",
+ "maps-json-editor-added-circle": "Círculo adicionado",
+ "maps-json-editor-edit-removed-shapes": "$1 removido {{PLURAL:$1|forma|formas}}",
+ "maps-json-editor-edit-modified": "Formas de mapa existentes modificadas",
+ "maps-json-editor-edit-other": "Edição visual do mapa",
+ "maps-json-editor-edit-failed": "Falha ao salvar o mapa",
+ "maps-json-editor-toolbar-save-title": "Salvar alterações",
+ "maps-json-editor-toolbar-save-text": "Salvar",
+ "maps-json-editor-toolbar-cancel-title": "Cancelar edição, descartar todas as alterações",
+ "maps-json-editor-toolbar-cancel-text": "Cancelar",
+ "maps-json-editor-toolbar-clear-title": "Limpar todas as camadas",
+ "maps-json-editor-toolbar-clear-text": "Limpar tudo",
+ "maps-json-editor-toolbar-button-edit": "Editar camadas",
+ "maps-json-editor-toolbar-button-edit-disabled": "Não há camadas para editar",
+ "maps-json-editor-toolbar-button-remove": "Excluir camadas",
+ "maps-json-editor-toolbar-button-remove-disabled": "Não há camadas para excluir",
+ "maps-geo-json-edit-source": "Editar código-fonte",
+ "maps-geo-json-create-source": "Criar a partir da fonte GeoJSON",
+ "maps-geo-json-create-page-button": "Criar esta página",
+ "maps-geo-json-create-page-creating": "Criando página...",
+ "maps-geo-json-create-page-summary": "Página GeoJSON criada",
+ "tag-maps-visual-edit": "Edição visual do mapa",
+ "tag-maps-visual-edit-description": "Edição feita com o editor visual fornecido pelo [https://www.mediawiki.org/wiki/Extension:Maps Maps extension]"
}
diff --git a/www/wiki/extensions/Maps/i18n/pt.json b/www/wiki/extensions/Maps/i18n/pt.json
index 7bf9b166..f4a6a331 100644
--- a/www/wiki/extensions/Maps/i18n/pt.json
+++ b/www/wiki/extensions/Maps/i18n/pt.json
@@ -12,7 +12,8 @@
"Macofe",
"Malafaya",
"Fúlvio",
- "Athena in Wonderland"
+ "Athena in Wonderland",
+ "Mansil alfalb"
]
},
"maps-desc": "Permite incorporar mapas dinâmicos nas páginas da wiki, converter endereços em geocódigos e outras operações geográficas",
@@ -133,7 +134,7 @@
"maps-par-searchmarkers": "Permite pesquisar marcadores específicos usando um campo incorporado no mapa.",
"maps-par-zoom": "O nível de zoom do mapa. Nos mapas com marcadores será usado o maior zoom que, mesmo assim, mostre todos os marcadores.",
"maps-par-width": "Permite definir a largura do mapa. A unidade por omissão é o píxel, mas pode defini-la explicitamente como: px, ex, em, %.",
- "maps-par-height": "Permite definir a altura do mapa. A unidade por omissão é o píxel, mas pode defini-la explicitamente como: px, ex, em, %.",
+ "maps-par-height": "Permite definir a altura do mapa. A unidade por omissão é o píxel, mas pode especificar explicitamente uma destas unidades: px, ex, em.",
"maps-par-centre": "A localização na qual o mapa deve estar centrado",
"maps-par-enable-fullscreen": "Ativar o botão de ecrã completo",
"maps-par-kml": "Ficheiros KML que serão carregados no mapa.",
@@ -229,5 +230,5 @@
"validator-type-mapspolygon": "Polígono geográfico",
"validator-type-mapspolygon-list": "Lista de polígonos geográficos",
"validator-type-wmsoverlay": "Sobreposição Web Map Service",
- "validator-type-jsonfile": "texto"
+ "maps-json-editor-toolbar-save-text": "Gravar"
}
diff --git a/www/wiki/extensions/Maps/i18n/qqq.json b/www/wiki/extensions/Maps/i18n/qqq.json
index 89298677..bd3f62b5 100644
--- a/www/wiki/extensions/Maps/i18n/qqq.json
+++ b/www/wiki/extensions/Maps/i18n/qqq.json
@@ -126,7 +126,7 @@
"maps_leaflet": "This is a field label.\n\n{{optional}}",
"maps-leaflet-par-defzoom": "{{maps-par|leaflet|defzoom}}",
"maps-leaflet-par-layers": "{{maps-par|leaflet|layers}}",
- "maps-leaflet-par-overlaylayers": "{{maps-par|leaflet|overlaylayers}}",
+ "maps-leaflet-par-overlaylayers": "{{maps-par|leaflet|overlays}}",
"maps-leaflet-par-maxclusterradius": "{{maps-par|leaflet|maxclusterradius}}",
"maps-leaflet-par-clusterspiderfy": "{{maps-par|leaflet|clusterspiderfy}}",
"maps_click_to_activate": "This is an informatory message.",
@@ -138,9 +138,9 @@
"maps-par-width": "{{maps-par-all|width}}",
"maps-par-height": "{{maps-par-all|height}}",
"maps-par-centre": "{{maps-par-all|centre}}",
- "maps-par-enable-fullscreen": "{{maps-par-all|enablefullscreen}}",
+ "maps-par-enable-fullscreen": "{{maps-par-all|fullscreen}}",
"maps-par-kml": "System message used by several maps services:\n\n{{maps-par|googlemaps3|kml}}",
- "maps-par-markercluster": "{{maps-par-all|markercluster}}",
+ "maps-par-markercluster": "{{maps-par-all|cluster}}",
"maps-googlemaps3-incompatbrowser": "This is an error message shown to the user instead of a map.",
"maps-googlemaps3-par-imageoverlays": "{{maps-par|googlemaps3|imageoverlays}}",
"maps-googlemaps3-par-type": "{{maps-par|googlemaps3|type}}",
@@ -231,5 +231,26 @@
"validator-type-mapsrectangle": "This is the name of a type of values that may be assigned to a parameter.",
"validator-type-mapsrectangle-list": "This is the name of a type of values that may be assigned to a parameter.",
"validator-type-wmsoverlay": "This is the name of a type of values that may be assigned to a parameter. WMS stands for [[wikipedia:en:Web_Map_Service|Web Map Service]].",
- "validator-type-jsonfile": "{{Identical|Text}}"
+ "maps-json-editor-button-marker": "Text shown when hovering over the 'add marker' button in the visual map editor",
+ "maps-json-editor-button-line": "Text shown when hovering over the 'add line' button in the visual map editor",
+ "maps-json-editor-button-polygon": "Text shown when hovering over the 'add polygon' button in the visual map editor",
+ "maps-json-editor-button-rectangle": "Text shown when hovering over the 'add rectangle' button in the visual map editor",
+ "maps-json-editor-button-circle": "Text shown when hovering over the 'add circle' button in the visual map editor",
+ "maps-json-editor-tooltip-marker": "Text show after clicking the 'add marker' button in the visual map editor",
+ "maps-json-editor-tooltip-line": "Text show after clicking the 'add line' button in the visual map editor",
+ "maps-json-editor-tooltip-polygon": "Text show after clicking the 'add polygon' button in the visual map editor",
+ "maps-json-editor-tooltip-rectangle": "Text show after clicking the 'add rectangle' button in the visual map editor",
+ "maps-json-editor-tooltip-circle": "Text show after clicking the 'add circle' button in the visual map editor",
+ "maps-json-editor-added-marker": "Edit summary created by the visual map editor when adding a marker",
+ "maps-json-editor-added-line": "Edit summary created by the visual map editor when adding a line",
+ "maps-json-editor-added-polygon": "Edit summary created by the visual map editor when adding a polygon",
+ "maps-json-editor-added-rectangle": "Edit summary created by the visual map editor when adding a rectangle",
+ "maps-json-editor-added-circle": "Edit summary created by the visual map editor when adding a circle",
+ "maps-json-editor-edit-removed-shapes": "Edit summary created by the visual map editor when removing one or more shapes",
+ "maps-json-editor-edit-modified": "Edit summary created by the visual map editor when modifying existing shapes",
+ "maps-json-editor-edit-other": "Edit summary created by the visual map editor",
+ "maps-json-editor-edit-failed": "Error message shown when saving a change in the visual map editor fails",
+ "maps-json-editor-toolbar-cancel-text": "{{Identical|Cancel}}",
+ "maps-geo-json-edit-source": "Edit tab text in the GeoJson namespace. This tab leads to editing of raw GeoJson without visual aids",
+ "maps-geo-json-create-source": "Page creation tab text in the GeoJson namespace. This tab leads to editing of raw GeoJson without visual aids"
}
diff --git a/www/wiki/extensions/Maps/i18n/roa-tara.json b/www/wiki/extensions/Maps/i18n/roa-tara.json
index 5fa0db84..2622bb6b 100644
--- a/www/wiki/extensions/Maps/i18n/roa-tara.json
+++ b/www/wiki/extensions/Maps/i18n/roa-tara.json
@@ -36,6 +36,7 @@
"maps-abb-west": "O",
"maps-latitude": "Latitudine:",
"maps-longitude": "Longitudine:",
+ "maps-leaflet-par-layers-dark": "Le strate ca sò disponibbele jndr'à 'u scacchiatore de strate quanne 'a modalità gnure jè abbiltiate. 'U prime strate avène 'ndrucate quanne ste careche 'a mappe.",
"specialpages-group-maps": "Mappe",
"mapeditor-none-text": "Ninde",
"mapeditor-done-button": "Fatte",
@@ -61,5 +62,42 @@
"validator-type-mapsrectangle": "Rettangole sciugrafeche",
"validator-type-mapsrectangle-list": "Elenghe de le rettangole",
"validator-type-mapspolygon": "Poligone sciugrafeche",
- "validator-type-mapspolygon-list": "Elenghe de le poligone sciugrafece"
+ "validator-type-mapspolygon-list": "Elenghe de le poligone sciugrafece",
+ "maps-json-editor-button-marker": "Sckaffe 'nu marcatore",
+ "maps-json-editor-button-line": "Disegne 'na linèe",
+ "maps-json-editor-button-polygon": "Disegne 'nu poligone",
+ "maps-json-editor-button-rectangle": "Sckaffe 'nu rettangole",
+ "maps-json-editor-button-circle": "Sckaffe 'nu cerchie",
+ "maps-json-editor-tooltip-marker": "Cazze sus 'a mappe pe sckaffà 'nu marcatore.",
+ "maps-json-editor-tooltip-line": "Cazze sus 'a mappe pe disegnà 'na linèe.",
+ "maps-json-editor-tooltip-polygon": "Cazze sus 'a mappe pe disegnà 'nu poligone.",
+ "maps-json-editor-tooltip-rectangle": "Cazze sus 'a mappe pe sckaffà 'nu rettangole.",
+ "maps-json-editor-tooltip-circle": "Cazze sus 'a mappe pe sckaffà 'nu cerchie.",
+ "maps-json-editor-added-marker": "Aggiunde marcatore",
+ "maps-json-editor-added-line": "Aggiunde 'na linèe",
+ "maps-json-editor-added-polygon": "Aggiunde 'nu poligone",
+ "maps-json-editor-added-rectangle": "Aggiunde 'nu rettangole",
+ "maps-json-editor-added-circle": "Aggiudne circhie",
+ "maps-json-editor-edit-removed-shapes": "Luate $1 {{PLURAL:$1|figure}}",
+ "maps-json-editor-edit-modified": "Cangiate le figure d'a mappe esistende",
+ "maps-json-editor-edit-other": "Cangiamende d'a mappe visuale",
+ "maps-json-editor-edit-failed": "Reggistrazzione d'a mappe fallite",
+ "maps-json-editor-toolbar-save-title": "Isse da 'u cangiamende e mandine tutte le cangiaminde",
+ "maps-json-editor-toolbar-save-text": "Fatte",
+ "maps-json-editor-toolbar-cancel-title": "Isse da 'u mode d'e cangiamende, scitte tutte le cangiaminde",
+ "maps-json-editor-toolbar-cancel-text": "Annulle",
+ "maps-json-editor-toolbar-clear-title": "Pulizze tutte le strate",
+ "maps-json-editor-toolbar-clear-text": "Pulizze tutte",
+ "maps-json-editor-toolbar-button-edit": "Cange le layer",
+ "maps-json-editor-toolbar-button-edit-disabled": "Nisciune layer da cangià",
+ "maps-json-editor-toolbar-button-remove": "Scangìlle le layer",
+ "maps-json-editor-toolbar-button-remove-disabled": "Nisciune layer da scangellà",
+ "maps-json-editor-toolbar-button-save": "Reggistre le cangiaminde",
+ "maps-json-editor-changes-saved": "Le cangiaminde tune onne state salvate",
+ "maps-geo-json-edit-source": "Cange 'a sorgende",
+ "maps-geo-json-create-source": "Ccreje da 'a sorgende GeoJSON",
+ "maps-geo-json-create-page-button": "Ccreje sta pàgene",
+ "maps-geo-json-create-page-creating": "Stoche a ccreje 'a pàgene...",
+ "maps-geo-json-create-page-summary": "Ccrejate 'na pàgene GeoJSON",
+ "maps-editor-edit-geojson": "Cange 'u strate GeoJSON"
}
diff --git a/www/wiki/extensions/Maps/i18n/ru.json b/www/wiki/extensions/Maps/i18n/ru.json
index ac387d8e..3446cc78 100644
--- a/www/wiki/extensions/Maps/i18n/ru.json
+++ b/www/wiki/extensions/Maps/i18n/ru.json
@@ -30,7 +30,8 @@
"Vlad5250",
"Shaleych",
"NBS",
- "Diralik"
+ "Diralik",
+ "Katunchik"
]
},
"maps-desc": "Позволяет встраивать динамические карты в вики-страницы, геокодировать адреса и выполнять другие географические действия",
@@ -144,6 +145,7 @@
"maps-leaflet-par-overlaylayers": "Слои подложки, которые будут показаны при загрузке карты.",
"maps-leaflet-par-maxclusterradius": "Максимальный радиус, который кластер будет покрывать от центрального маркера (в пикселях).",
"maps-leaflet-par-clusterspiderfy": "Когда вы нажимаете на кластер на нижнем уровне масштаба, то он разворачивается таким образом, чтобы вы могли увидеть все его маркеры.",
+ "maps-leaflet-par-clicktarget": "При нажатии на карту вы будете перенаправлены на этот URL. %lat% заменяется на широту, а %long% на долготу",
"maps_click_to_activate": "Нажмите для активации карты",
"maps_centred_on": "Центр карты — $1, $2.",
"maps-par-mappingservice": "Позволяет выбрать сервис карт, который будет использоваться.",
@@ -247,5 +249,40 @@
"validator-type-mapspolygon": "Географический полигон",
"validator-type-mapspolygon-list": "Перечень географических полигонов",
"validator-type-wmsoverlay": "Оверлей веб-карты",
- "validator-type-jsonfile": "текст"
+ "maps-json-editor-button-marker": "Разместить маркер",
+ "maps-json-editor-button-line": "Нарисовать линию",
+ "maps-json-editor-button-polygon": "Нарисовать многоугольник",
+ "maps-json-editor-button-rectangle": "Разместить прямоугольник",
+ "maps-json-editor-button-circle": "Разместить круг",
+ "maps-json-editor-tooltip-marker": "Кликните на карте, чтобы разместить маркер.",
+ "maps-json-editor-tooltip-line": "Кликните на карте, чтобы нарисовать линию.",
+ "maps-json-editor-tooltip-polygon": "Кликните на карте, чтобы нарисовать многоугольник.",
+ "maps-json-editor-tooltip-rectangle": "Кликните на карте, чтобы разместить прямоугольник.",
+ "maps-json-editor-tooltip-circle": "Кликните на карте, чтобы разместить круг.",
+ "maps-json-editor-added-marker": "Добавлен маркер",
+ "maps-json-editor-added-line": "Добавлена линия",
+ "maps-json-editor-added-polygon": "Добавлен многоугольник",
+ "maps-json-editor-added-rectangle": "Добавлен прямоугольник",
+ "maps-json-editor-added-circle": "Добавлен круг",
+ "maps-json-editor-edit-removed-shapes": "Удалены $1 {{PLURAL:$1|фигура|фигур}}",
+ "maps-json-editor-edit-modified": "Модифицированы существующие фигуры карты",
+ "maps-json-editor-edit-other": "Редактирование визуальной карты",
+ "maps-json-editor-edit-failed": "Не удалось сохранить карту.",
+ "maps-json-editor-toolbar-save-title": "Сохранить изменения",
+ "maps-json-editor-toolbar-save-text": "Сохранить",
+ "maps-json-editor-toolbar-cancel-title": "Отменить редактирование, сбросить все изменения",
+ "maps-json-editor-toolbar-cancel-text": "Отменить",
+ "maps-json-editor-toolbar-clear-title": "Очистить все слои",
+ "maps-json-editor-toolbar-clear-text": "Очистить всё",
+ "maps-json-editor-toolbar-button-edit": "Редактировать слои",
+ "maps-json-editor-toolbar-button-edit-disabled": "Нет слоёв для редактирования",
+ "maps-json-editor-toolbar-button-remove": "Удалить слои",
+ "maps-json-editor-toolbar-button-remove-disabled": "Нет слоёв для удаления",
+ "maps-geo-json-edit-source": "Править код",
+ "maps-geo-json-create-source": "Создать исходный код GeoJSON",
+ "maps-geo-json-create-page-button": "Создать эту страницу",
+ "maps-geo-json-create-page-creating": "Создаётся страница...",
+ "maps-geo-json-create-page-summary": "Создана страница GeoJSON",
+ "tag-maps-visual-edit": "Редактирование визуальной карты",
+ "tag-maps-visual-edit-description": "Редактирование производится с помощью визуального редактора, предоставленного [https://www.mediawiki.org/wiki/Extension:Maps Maps extension]"
}
diff --git a/www/wiki/extensions/Maps/i18n/sh.json b/www/wiki/extensions/Maps/i18n/sh.json
new file mode 100644
index 00000000..395e05c4
--- /dev/null
+++ b/www/wiki/extensions/Maps/i18n/sh.json
@@ -0,0 +1,69 @@
+{
+ "@metadata": {
+ "authors": [
+ "Vlad5250"
+ ]
+ },
+ "right-geocode": "Geokodiranje",
+ "action-geocode": "izvršavanje geokodiranja na ovom wikiju",
+ "maps-tracking-category": "Stranica sa zemljovidom iscrtanim proširenjem \"Zemljovidi\"",
+ "maps-markers": "Označivači",
+ "maps-searchmarkers-text": "Filtriranje označivača",
+ "maps-others": "ostali",
+ "maps-ns-layer": "Sloj",
+ "maps-ns-layer-talk": "Razgovor o sloju",
+ "maps-layer-property": "Svojstvo",
+ "maps-layer-value": "Vrijednost",
+ "maps-layer-errors": "Greške",
+ "maps-layerdef-invalid": "{{PLURAL:$1|Nevrijedeća odredba|Nevrijedeće odreabe}}",
+ "maps-layerdef-invalid-fatal": "Kobna nevrijedeća odredba",
+ "maps-layerdef-wrong-namespace": "Odredbe slojeva su vrijedeće samo na stranicama imenskog prostora »$1«",
+ "maps-layerpage-nousage": "Zasad nema stranica što koriste ovaj sloj.",
+ "maps-error-invalid-layertype": "Nema slojeva tipa \"$1\". {{PLURAL:$3|Podržan je samo ovaj tip|Podržani su samo sljedeći tipovi}}: $2",
+ "maps-error-no-layertype": "Će treba da navedete tip sloja. {{PLURAL:$2|Podržan je samo ovaj tip|Podržani su samo sljedeći tipovi}}: $1",
+ "validation-error-invalid-layer": "Parametar $1 mora da bude valjani sloj.",
+ "validation-error-invalid-layers": "Parametar $1 mora da bude jedan ili više valjanih slojeva.",
+ "maps-displaymap-par-polygons": "Mnogokuti za prikaz",
+ "maps-displaymap-par-rectangles": "Pravokutnici za prikaz",
+ "maps-displaymap-par-static": "Naparvi zemljovid nedvižnim",
+ "validation-error-invalid-location": "Parametar $1 mora da predstavlja validna lokacija.",
+ "maps_click_to_activate": "Škljocnite za aktiviranje zemljovida",
+ "maps_centred_on": "Zemljovid je centriran na $1, $2.",
+ "mapeditor": "Uređivač zemljovida",
+ "specialpages-group-maps": "Zemljovidi",
+ "mapeditor-none-text": "Nema",
+ "mapeditor-done-button": "Gotovo",
+ "mapeditor-remove-button": "Ukloni",
+ "mapeditor-import-button2": "Uvezi - Увези",
+ "mapeditor-export-button": "Izvezi u vikikôdu",
+ "mapeditor-import-button": "Uvezi iz wikikôda",
+ "mapeditor-mapparam-button": "Uredi parametre zemljovida",
+ "mapeditor-clear-button": "Očisti zemljovid",
+ "mapeditor-form-title": "Uredi detalje",
+ "mapeditor-link-title-switcher-popup-text": "Otskok s tekstom",
+ "mapeditor-link-title-switcher-link-text": "Poveznica (link)",
+ "mapeditor-form-field-title": "Naslov",
+ "mapeditor-form-field-text": "Tekst",
+ "mapeditor-form-field-link": "Poveznica (link)",
+ "mapeditor-form-field-icon": "Ikona",
+ "mapeditor-form-field-group": "Grupa",
+ "mapeditor-mapparam-title": "Uredi parametre zemljovida",
+ "mapeditor-imageoverlay-button": "Dodaj oblogu od slike",
+ "mapeditor-form-field-image": "Slika",
+ "maps-json-editor-added-marker": "Dodan označivač",
+ "maps-json-editor-added-line": "Dodana linija",
+ "maps-json-editor-added-polygon": "Dodan mnogokutnik",
+ "maps-json-editor-added-rectangle": "Dodan pravokutnik",
+ "maps-json-editor-added-circle": "Dodan krug",
+ "maps-json-editor-edit-removed-shapes": "Uklonjenih $1 oblika",
+ "maps-json-editor-toolbar-save-title": "Spremi promjene",
+ "maps-json-editor-toolbar-save-text": "Snimi",
+ "maps-json-editor-toolbar-cancel-title": "Odustani uređivanje (odbacivanje sve promjene)",
+ "maps-json-editor-toolbar-cancel-text": "Otkaži",
+ "maps-json-editor-toolbar-clear-title": "Očisti sve slojeve",
+ "maps-json-editor-toolbar-clear-text": "‎Očisti sve",
+ "maps-json-editor-toolbar-button-edit": "Uredi slojeve",
+ "maps-json-editor-toolbar-button-edit-disabled": "Nema slojeva da urediti",
+ "maps-json-editor-toolbar-button-remove": "Izbiši slojeve",
+ "maps-json-editor-toolbar-button-remove-disabled": "Nema slojeva za obrisati"
+}
diff --git a/www/wiki/extensions/Maps/i18n/sv.json b/www/wiki/extensions/Maps/i18n/sv.json
index 0e40304e..58a95034 100644
--- a/www/wiki/extensions/Maps/i18n/sv.json
+++ b/www/wiki/extensions/Maps/i18n/sv.json
@@ -125,14 +125,15 @@
"maps-leaflet-par-overlaylayers": "Överläggslagret som kommer att visas när kartan läser in.",
"maps-leaflet-par-maxclusterradius": "Den maximala radien som ett kluster kommer att täcka från centrummarkören (i bildpunkter)",
"maps-leaflet-par-clusterspiderfy": "När du klickar på ett kluster på den lägsta zoomnivån sprider vi ut den så du kan se alla dess markörer.",
+ "maps-leaflet-par-clicktarget": "När du klickar på kartan kommer du skickas till denna webbadress. %lat% ersätts med latituden och %long% med longituden",
"maps_click_to_activate": "Klicka för att aktivera karta",
"maps_centred_on": "Karta centrerad på $1, $2.",
"maps-par-mappingservice": "Tillåter att karttjänsten som kommer att användas för att skapa kartan anges.",
"maps-par-resizable": "Gör att kartans storlek kan ändras genom att dra i dess nedre högra hörnet.",
"maps-par-searchmarkers": "Gör det möjligt att söka efter specifika markörer via ett fält som är inbäddat i kartan.",
"maps-par-zoom": "Zoomnivån för kartan. För kartor med markörer kommer som standard den mest inzoomade nivå som visar alla markörer att användas.",
- "maps-par-width": "Tillåter att kartans bredd ställs in. Som standard kommer pixlar att antas som enhet, men du kan uttryckligen ange en av dessa enheter: px, ex, em, %.",
- "maps-par-height": "Tillåter att kartans höjd ställs in. Som standard kommer pixlar att antas som enhet, men du kan uttryckligen ange en av dessa enheter: px, ex, em, %.",
+ "maps-par-width": "Gör det möjligt att ändra kartans bredd. Som standard kommer den antagna enheten vara bildpunkter, men du kan uttryckligen ange en av dessa enheter: px, ex, em.",
+ "maps-par-height": "Gör det möjligt att ändra kartans höjd. Som standard kommer den antagna enheten vara bildpunkter, men du kan uttryckligen ange en av dessa enheter: px, ex, em.",
"maps-par-centre": "Platsen där kartan ska vara centrerad",
"maps-par-enable-fullscreen": "Aktivera helskärmsknappen",
"maps-par-kml": "KML-filer att ladda upp på kartan.",
@@ -228,5 +229,43 @@
"validator-type-mapspolygon": "Geografisk polygon",
"validator-type-mapspolygon-list": "Lista över geografiska polygoner",
"validator-type-wmsoverlay": "Webbmappstjänst-överlägg",
- "validator-type-jsonfile": "text"
+ "maps-json-editor-button-marker": "Placera en markör",
+ "maps-json-editor-button-line": "Rita en linje",
+ "maps-json-editor-button-polygon": "Rita en polygon",
+ "maps-json-editor-button-rectangle": "Placera en rektangel",
+ "maps-json-editor-button-circle": "Placera en cirkel",
+ "maps-json-editor-tooltip-marker": "Klicka på kartan för att placera markören.",
+ "maps-json-editor-tooltip-line": "Klicka på kartan för att rita en linje.",
+ "maps-json-editor-tooltip-polygon": "Klicka på kartan för att rita en polygon.",
+ "maps-json-editor-tooltip-rectangle": "Klicka på kartan för att placera rektangeln.",
+ "maps-json-editor-tooltip-circle": "Klicka på kartan för att placera cirkeln.",
+ "maps-json-editor-added-marker": "Lade till markör",
+ "maps-json-editor-added-line": "Lade till linje",
+ "maps-json-editor-added-polygon": "Lade till polygon",
+ "maps-json-editor-added-rectangle": "Lade till rektangel",
+ "maps-json-editor-added-circle": "Lade till cirkel",
+ "maps-json-editor-edit-removed-shapes": "Tog bort $1 {{PLURAL:$1|form|former}}",
+ "maps-json-editor-edit-modified": "Modifierade befintliga kartformer",
+ "maps-json-editor-edit-other": "Visuell kartredigering",
+ "maps-json-editor-edit-failed": "Misslyckades med att spara kartan",
+ "maps-json-editor-toolbar-save-title": "Avsluta redigeringsläget och behåll alla ändringar",
+ "maps-json-editor-toolbar-save-text": "Klar",
+ "maps-json-editor-toolbar-cancel-title": "Avslutar redigeringen, kasserar alla ändringar",
+ "maps-json-editor-toolbar-cancel-text": "Avbryt",
+ "maps-json-editor-toolbar-clear-title": "Rensa alla lager",
+ "maps-json-editor-toolbar-clear-text": "Rensa alla",
+ "maps-json-editor-toolbar-button-edit": "Redigera lager",
+ "maps-json-editor-toolbar-button-edit-disabled": "Ingen lager att redigera",
+ "maps-json-editor-toolbar-button-remove": "Radera lager",
+ "maps-json-editor-toolbar-button-remove-disabled": "Inga lager att radera",
+ "maps-json-editor-toolbar-button-save": "Spara ändringar",
+ "maps-json-editor-changes-saved": "Dina ändringar har sparats",
+ "maps-geo-json-edit-source": "Redigera källa",
+ "maps-geo-json-create-source": "Skapa från GeoJSON-källa",
+ "maps-geo-json-create-page-button": "Skapa denna sida",
+ "maps-geo-json-create-page-creating": "Skapar sida...",
+ "maps-geo-json-create-page-summary": "Skapade GeoJSON-sida",
+ "maps-editor-edit-geojson": "Redigera GeoJSON-lager",
+ "tag-maps-visual-edit": "Visuell kartredigering",
+ "tag-maps-visual-edit-description": "Redigeringen gjordes med den visuella redigeraren som tillhandahålls av tillägget [https://www.mediawiki.org/wiki/Extension:Maps Maps]"
}
diff --git a/www/wiki/extensions/Maps/i18n/tt-cyrl.json b/www/wiki/extensions/Maps/i18n/tt-cyrl.json
new file mode 100644
index 00000000..8ddffba4
--- /dev/null
+++ b/www/wiki/extensions/Maps/i18n/tt-cyrl.json
@@ -0,0 +1,73 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ерней",
+ "Ильнар"
+ ]
+ },
+ "right-geocode": "Геокодлау",
+ "maps_map": "Харита",
+ "maps-copycoords-prompt": "CTRL+C, ENTER",
+ "maps-others": "башка",
+ "maps-ns-layer": "Катлам",
+ "maps-ns-layer-talk": "Катлам бәхәсе",
+ "maps-layer-errors": "Хаталар",
+ "maps-layerdef-invalid": "Ярамаган {{PLURAL:$1|билгеләмә|билгеләмәләр}}",
+ "maps-layerdef-invalid-fatal": "Котылгысыз ярамаган билгеләмә",
+ "maps-layer-of-type": "$1 төрендәге катлам",
+ "maps-layer-of-type-and-name": "$1 төрендәге $2 катламы",
+ "maps-abb-north": "Тя",
+ "maps-abb-east": "Кч",
+ "maps-abb-south": "Кя",
+ "maps-abb-west": "Кб",
+ "maps-latitude": "Киңлек:",
+ "maps-longitude": "Озынлык:",
+ "mapeditor": "Харита төзәткече",
+ "specialpages-group-maps": "Хариталар",
+ "mapeditor-none-text": "Һични",
+ "mapeditor-done-button": "Тәмам",
+ "mapeditor-remove-button": "Бетерү",
+ "mapeditor-import-button2": "Кертү",
+ "mapeditor-link-title-switcher-link-text": "Сылтама",
+ "mapeditor-form-field-text": "Текст",
+ "mapeditor-form-field-link": "Сылтама",
+ "mapeditor-form-field-strokecolor": "Кайма төсе",
+ "mapeditor-form-field-strokeopacity": "Кайма тоныклыгы",
+ "mapeditor-form-field-strokeweight": "Кайма калынлыгы",
+ "mapeditor-form-field-image": "Сурәт",
+ "semanticmaps-latitude": "Киңлек: $1",
+ "semanticmaps-longitude": "Озынлык: $1",
+ "semanticmaps-altitude": "Югарылык: $1",
+ "semanticmaps-forminput-locations": "Урыннар",
+ "validator-type-mapscircle-list": "Түгәрәкләр исемлеге",
+ "validator-type-mapsline-list": "Сызыклар исемлеге",
+ "validator-type-mapsrectangle-list": "Турыпочмаклыклар исемлеге",
+ "maps-json-editor-button-line": "Сызык сызу",
+ "maps-json-editor-button-polygon": "Күппочмаклык ясау",
+ "maps-json-editor-button-rectangle": "Турыпочмаклыкны урнаштыру",
+ "maps-json-editor-button-circle": "Түгәрәкне урнаштыру",
+ "maps-json-editor-tooltip-line": "Сызык сызу өчен харитага басыгыз.",
+ "maps-json-editor-tooltip-polygon": "Күппочмаклык ясау өчен харитага басыгыз.",
+ "maps-json-editor-tooltip-rectangle": "Турыпочмаклыкны урнаштыру өчен харитага басыгыз",
+ "maps-json-editor-tooltip-circle": "Түгәрәкне урнаштыру өчен харитага басыгыз",
+ "maps-json-editor-added-line": "Сызык өстәлде",
+ "maps-json-editor-added-polygon": "Күппочмаклык өстәлде",
+ "maps-json-editor-added-rectangle": "Турыпочмаклык өстәлде",
+ "maps-json-editor-added-circle": "Түгәрәк өстәлде",
+ "maps-json-editor-edit-removed-shapes": "$1 {{PLURAL:$1|шәкел}} бетерелде",
+ "maps-json-editor-edit-other": "Күрсәтмәле хаританы үзгәртү",
+ "maps-json-editor-edit-failed": "Хаританы саклап булмады",
+ "maps-json-editor-toolbar-save-title": "Үзгәрешләрне саклау",
+ "maps-json-editor-toolbar-save-text": "Саклау",
+ "maps-json-editor-toolbar-cancel-text": "Кире алу",
+ "maps-json-editor-toolbar-clear-title": "Барлык катламнарны бушату",
+ "maps-json-editor-toolbar-clear-text": "Барысын да бушату",
+ "maps-json-editor-toolbar-button-edit": "Катламнарны үзгәртү",
+ "maps-json-editor-toolbar-button-edit-disabled": "Үзгәртергә катламнар юк",
+ "maps-json-editor-toolbar-button-remove": "Катламнарны бетерү",
+ "maps-json-editor-toolbar-button-remove-disabled": "Бетерергә катламнар юк",
+ "maps-geo-json-edit-source": "Вики-текстны үзгәртү",
+ "maps-geo-json-create-page-button": "Бу битне төзү",
+ "maps-geo-json-create-page-creating": "Бит төзелә…",
+ "tag-maps-visual-edit": "Күрсәтмәле хаританы үзгәртү"
+}
diff --git a/www/wiki/extensions/Maps/i18n/uk.json b/www/wiki/extensions/Maps/i18n/uk.json
index 8cadcaec..341da513 100644
--- a/www/wiki/extensions/Maps/i18n/uk.json
+++ b/www/wiki/extensions/Maps/i18n/uk.json
@@ -227,6 +227,5 @@
"validator-type-mapsrectangle-list": "Список прямокутників",
"validator-type-mapspolygon": "Географічний багатокутник",
"validator-type-mapspolygon-list": "Список географічних багатокутників",
- "validator-type-wmsoverlay": "Накладення Web Map Service",
- "validator-type-jsonfile": "текст"
+ "validator-type-wmsoverlay": "Накладення Web Map Service"
}
diff --git a/www/wiki/extensions/Maps/i18n/vi.json b/www/wiki/extensions/Maps/i18n/vi.json
index fa1873d0..b8717a5f 100644
--- a/www/wiki/extensions/Maps/i18n/vi.json
+++ b/www/wiki/extensions/Maps/i18n/vi.json
@@ -102,6 +102,5 @@
"semanticmaps-par-showtitle": "Tên tùy chọn của cửa sổ thông tin đánh dấu. Có thể để trống để định dạng nội dung cửa sổ thông tin dùng bản mẫu.",
"semanticmaps-par-centre": "Trung tâm của bản đồ. Nếu không có, bản đồ sẽ tự động chọn trung tâm tối ưu bao gồm tất cả các dấu trên bản đồ.",
"semanticmaps-par-template": "Bản đồ dùng để định dạng nội dung của cửa sổ thông tin.",
- "semanticmaps-par-geocodecontrol": "Hiện điều khiển mã hóa địa lý.",
- "validator-type-jsonfile": "chữ"
+ "semanticmaps-par-geocodecontrol": "Hiện điều khiển mã hóa địa lý."
}
diff --git a/www/wiki/extensions/Maps/i18n/zh-hans.json b/www/wiki/extensions/Maps/i18n/zh-hans.json
index 13fc0e63..21af4ef2 100644
--- a/www/wiki/extensions/Maps/i18n/zh-hans.json
+++ b/www/wiki/extensions/Maps/i18n/zh-hans.json
@@ -14,7 +14,8 @@
"阿pp",
"Hanyanbo98",
"夢蝶葬花",
- "Angrydog001"
+ "Angrydog001",
+ "Josephine W."
]
},
"maps-desc": "允许嵌入动态地图到wiki页面,将地址转换为地理编码,及进行其他地理学操作",
@@ -231,5 +232,10 @@
"validator-type-mapspolygon": "地理多边形",
"validator-type-mapspolygon-list": "地理多边形列表",
"validator-type-wmsoverlay": "网络地图服务覆盖",
- "validator-type-jsonfile": "文本"
+ "maps-json-editor-toolbar-save-title": "保存修改",
+ "maps-json-editor-toolbar-save-text": "保存",
+ "maps-json-editor-toolbar-cancel-text": "取消",
+ "maps-json-editor-toolbar-clear-text": "清空",
+ "maps-geo-json-create-page-button": "创建本页面",
+ "maps-geo-json-create-page-creating": "正在创建页面……"
}
diff --git a/www/wiki/extensions/Maps/i18n/zh-hant.json b/www/wiki/extensions/Maps/i18n/zh-hant.json
index e96f3010..39711ed9 100644
--- a/www/wiki/extensions/Maps/i18n/zh-hant.json
+++ b/www/wiki/extensions/Maps/i18n/zh-hant.json
@@ -17,7 +17,7 @@
"Kly"
]
},
- "maps-desc": "可讓 Wiki 頁面嵌入動態地圖,支援使用地理編碼標記位置,以及地理相關操作",
+ "maps-desc": "允許使用 Google 地圖或 Leaflet 讓 Wiki 頁面嵌入動態地圖。具備視覺化編輯器,且可選擇與 Semantic MediaWiki 整合,並支援 GeoJSON 地理編碼功能。",
"right-geocode": "地理編碼",
"action-geocode": "在此 wiki 上進行地理編碼",
"maps_map": "地圖",
@@ -125,9 +125,11 @@
"maps_leaflet": "Leaflet",
"maps-leaflet-par-defzoom": "允許設定地圖的預設縮放層級。",
"maps-leaflet-par-layers": "此圖層將於地圖加載時顯示",
+ "maps-leaflet-par-layers-dark": "當暗黑模式啟動時,圖層會在圖層選擇器裡可用。當地圖載入會先秀出第一個圖層。",
"maps-leaflet-par-overlaylayers": "此覆蓋圖層將於地圖加載時顯示。",
"maps-leaflet-par-maxclusterradius": "叢集自中央標記起會涵蓋的最大半徑(像素)。",
"maps-leaflet-par-clusterspiderfy": "當您在下方縮放層級點擊叢集時,我們會將其放射狀化來讓您可以查看所有的標記。",
+ "maps-leaflet-par-clicktarget": "當您在地圖上點擊時,您將會被發送到此 URL。緯度會替換成 %lat%,經度會替換成 %long%",
"maps_click_to_activate": "按一下以啟動地圖",
"maps_centred_on": "地圖以 $1, $2 為中心。",
"maps-par-mappingservice": "允許設定用於產生地圖的地圖服務。",
@@ -135,7 +137,7 @@
"maps-par-searchmarkers": "允許透過內嵌在地圖的欄位來搜尋特定標記。",
"maps-par-zoom": "用於地圖的縮放層級。可用在帶標記的地圖,預設會放大到最高層級並顯示所有標記。",
"maps-par-width": "允許設定地圖寬度。預設會以像素為單位,而您可明確指定以下單位之一:px、ex、em、%。",
- "maps-par-height": "允許設定地圖寬度。預設會以像素為單位,而您可明確指定以下單位之一:px、ex、em、%。",
+ "maps-par-height": "允許設定地圖寬度。預設會以像素為單位,而您可明確指定以下單位之一:px、ex、em。",
"maps-par-centre": "在地圖上的位置應置中",
"maps-par-enable-fullscreen": "開啟全螢幕的按鈕",
"maps-par-kml": "載入到地圖的 KML 檔案。",
@@ -231,5 +233,43 @@
"validator-type-mapspolygon": "地理多邊形",
"validator-type-mapspolygon-list": "地理多邊形清單",
"validator-type-wmsoverlay": "網路地圖服務疊加",
- "validator-type-jsonfile": "文字"
+ "maps-json-editor-button-marker": "放置一個標記",
+ "maps-json-editor-button-line": "畫一條直線",
+ "maps-json-editor-button-polygon": "畫一個多邊形",
+ "maps-json-editor-button-rectangle": "放置一個矩形",
+ "maps-json-editor-button-circle": "放置一個圓形",
+ "maps-json-editor-tooltip-marker": "點擊地圖來放置標記。",
+ "maps-json-editor-tooltip-line": "點擊地圖來畫出直線。",
+ "maps-json-editor-tooltip-polygon": "點擊地圖來畫出多邊形。",
+ "maps-json-editor-tooltip-rectangle": "點擊地圖來放置矩形。",
+ "maps-json-editor-tooltip-circle": "點擊地圖來放置圓形。",
+ "maps-json-editor-added-marker": "已新增標記",
+ "maps-json-editor-added-line": "已新增直線",
+ "maps-json-editor-added-polygon": "已新增多邊形",
+ "maps-json-editor-added-rectangle": "已新增矩形",
+ "maps-json-editor-added-circle": "已新增圓形",
+ "maps-json-editor-edit-removed-shapes": "已移除 $1 個{{PLURAL:$1|形狀}}",
+ "maps-json-editor-edit-modified": "已修改現有地圖形狀",
+ "maps-json-editor-edit-other": "視覺化地圖編輯",
+ "maps-json-editor-edit-failed": "儲存地圖失敗。",
+ "maps-json-editor-toolbar-save-title": "離開編輯模式時保持所有變更",
+ "maps-json-editor-toolbar-save-text": "完成",
+ "maps-json-editor-toolbar-cancel-title": "離開編輯模式,放棄所有變更",
+ "maps-json-editor-toolbar-cancel-text": "取消",
+ "maps-json-editor-toolbar-clear-title": "清除全部圖層",
+ "maps-json-editor-toolbar-clear-text": "清除全部",
+ "maps-json-editor-toolbar-button-edit": "編輯圖層",
+ "maps-json-editor-toolbar-button-edit-disabled": "沒有可編輯的圖層",
+ "maps-json-editor-toolbar-button-remove": "刪除圖層",
+ "maps-json-editor-toolbar-button-remove-disabled": "沒有可刪除的圖層",
+ "maps-json-editor-toolbar-button-save": "儲存變更",
+ "maps-json-editor-changes-saved": "已儲存您的變更。",
+ "maps-geo-json-edit-source": "編輯來源",
+ "maps-geo-json-create-source": "從 GeoJSON 來源建立",
+ "maps-geo-json-create-page-button": "建立此頁面",
+ "maps-geo-json-create-page-creating": "正在建立頁面…",
+ "maps-geo-json-create-page-summary": "已建立 GeoJSON 頁面",
+ "maps-editor-edit-geojson": "編輯 GeoJSON 圖層",
+ "tag-maps-visual-edit": "視覺化地圖編輯",
+ "tag-maps-visual-edit-description": "以[https://www.mediawiki.org/wiki/Extension:Maps 地圖擴充套件]所提供的視覺化編輯器做出編輯"
}
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/ext.maps.googlemaps3.js b/www/wiki/extensions/Maps/resources/GoogleMaps/ext.maps.googlemaps3.js
index ce5880c2..8a7b9b49 100644
--- a/www/wiki/extensions/Maps/resources/GoogleMaps/ext.maps.googlemaps3.js
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/ext.maps.googlemaps3.js
@@ -1,25 +1,16 @@
-/**
- * JavaScript for Google Maps v3 maps in the Maps extension.
- * @see https://www.mediawiki.org/wiki/Extension:Maps
- *
- * @licence GNU GPL v2+
- * @author Jeroen De Dauw <jeroendedauw at gmail dot com>
- */
-(function( $, mw ) {
-
- $( document ).ready( function() {
+window.mapsGoogleList = [];
- if ( typeof google === 'undefined' ) {
- $( '.maps-googlemaps3' ).text( mw.msg( 'maps-googlemaps3-incompatbrowser' ) );
- }
- else {
- $( '.maps-googlemaps3' ).each( function() {
+(function( $, mw ) {
+ mw.hook( 'wikipage.content' ).add( function ( $content ) {
+ if( typeof google === 'undefined' ) {
+ $content.find( '.maps-googlemaps3' ).text( mw.msg( 'maps-googlemaps3-incompatbrowser' ) );
+ } else {
+ $content.find( '.maps-googlemaps3' ).each( function() {
var $this = $( this );
- var map = $this.googlemaps( $.parseJSON( $this.find( 'div').text() ) );
- window.maps.googlemapsList.push(map);
+ window.mapsGoogleList.push(
+ $this.googlemaps( JSON.parse( $this.find( 'div.mapdata' ).text() ) )
+ );
} );
}
-
} );
-
-})( window.jQuery, mediaWiki );
+})( window.jQuery, window.mediaWiki );
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ProjectedOverlay.js b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ProjectedOverlay.js
index 4056c2c3..d655ed9e 100644
--- a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ProjectedOverlay.js
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ProjectedOverlay.js
@@ -13,127 +13,139 @@
// addZoom: Added Zoom factor as a parameter to the imageUrl (include complete parameter, including separater like '?zoom='
// percentOpacity: Default 50, percent opacity to use when the image is loaded 0-100.
// id: Default imageUrl, ID of the div
+// rotation: default 0, degrees clockwise to rotate the image
//
-
+
function ProjectedOverlay(map, imageUrl, bounds, opts)
{
- google.maps.OverlayView.call(this);
-
- this.map_ = map;
- this.url_ = imageUrl ;
- this.bounds_ = bounds ;
- this.addZ_ = opts.addZoom || '' ; // Add the zoom to the image as a parameter
- this.id_ = opts.id || this.url_ ; // Added to allow for multiple images
- this.percentOpacity_ = opts.percentOpacity || 50 ;
-
- this.setMap(map);
+ google.maps.OverlayView.call(this);
+
+ this.map_ = map;
+ this.url_ = imageUrl ;
+ this.bounds_ = bounds ;
+ this.addZ_ = opts.addZoom || '' ; // Add the zoom to the image as a parameter
+ this.id_ = opts.id || this.url_ ; // Added to allow for multiple images
+ this.percentOpacity_ = opts.percentOpacity || 50 ;
+ this.rotation_ = opts.rotation || 0;
+ this.setMap(map);
}
ProjectedOverlay.prototype = new google.maps.OverlayView();
ProjectedOverlay.prototype.createElement = function()
{
- var panes = this.getPanes() ;
- var div = this.div_ ;
-
- if (!div)
- {
- div = this.div_ = document.createElement("div");
- div.style.position = "absolute" ;
- div.setAttribute('id',this.id_) ;
- this.div_ = div ;
- this.lastZoom_ = -1 ;
- if( this.percentOpacity_ )
- {
- this.setOpacity(this.percentOpacity_) ;
- }
- panes.overlayLayer.appendChild(div);
- }
+ var panes = this.getPanes() ;
+ var div = this.div_ ;
+
+ if (!div)
+ {
+ div = this.div_ = document.createElement("div");
+ div.style.position = "absolute" ;
+ div.setAttribute('id',this.id_) ;
+ this.div_ = div ;
+ this.lastZoom_ = -1 ;
+ if( this.percentOpacity_ )
+ {
+ this.setOpacity(this.percentOpacity_) ;
+ }
+ if ( this.rotation_ )
+ {
+ this.setRotation(this.rotation_) ;
+ }
+ panes.overlayLayer.appendChild(div);
+ }
}
// Remove the main DIV from the map pane
ProjectedOverlay.prototype.remove = function()
{
- if (this.div_)
- {
- this.div_.parentNode.removeChild(this.div_);
- this.div_ = null;
- this.setMap(null);
- }
+ if (this.div_)
+ {
+ this.div_.parentNode.removeChild(this.div_);
+ this.div_ = null;
+ this.setMap(null);
+ }
}
// Redraw based on the current projection and zoom level...
ProjectedOverlay.prototype.draw = function(firstTime)
{
- // Creates the element if it doesn't exist already.
+ // Creates the element if it doesn't exist already.
+
+ this.createElement();
- this.createElement();
+ if (!this.div_)
+ {
+ return ;
+ }
- if (!this.div_)
- {
- return ;
- }
+ var c1 = this.get('projection').fromLatLngToDivPixel(this.bounds_.getSouthWest());
+ var c2 = this.get('projection').fromLatLngToDivPixel(this.bounds_.getNorthEast());
- var c1 = this.get('projection').fromLatLngToDivPixel(this.bounds_.getSouthWest());
- var c2 = this.get('projection').fromLatLngToDivPixel(this.bounds_.getNorthEast());
+ if (!c1 || !c2) return;
- if (!c1 || !c2) return;
+ // Now position our DIV based on the DIV coordinates of our bounds
- // Now position our DIV based on the DIV coordinates of our bounds
+ this.div_.style.width = Math.abs(c2.x - c1.x) + "px";
+ this.div_.style.height = Math.abs(c2.y - c1.y) + "px";
+ this.div_.style.left = Math.min(c2.x, c1.x) + "px";
+ this.div_.style.top = Math.min(c2.y, c1.y) + "px";
- this.div_.style.width = Math.abs(c2.x - c1.x) + "px";
- this.div_.style.height = Math.abs(c2.y - c1.y) + "px";
- this.div_.style.left = Math.min(c2.x, c1.x) + "px";
- this.div_.style.top = Math.min(c2.y, c1.y) + "px";
+ var url = this.url_ ;
- // Do the rest only if the zoom has changed...
-
- if ( this.lastZoom_ == this.map_.getZoom() )
- {
- return ;
- }
+ if ( this.addZ_ )
+ {
+ url += this.addZ_ + this.map_.getZoom() ;
+ }
- this.lastZoom_ = this.map_.getZoom() ;
+ this.div_.innerHTML = '<img src="' + url + '" width=' + this.div_.style.width + ' height=' + this.div_.style.height + ' >' ;
- var url = this.url_ ;
+ // Do the rest only if the zoom has changed...
- if ( this.addZ_ )
- {
- url += this.addZ_ + this.map_.getZoom() ;
- }
+ if ( this.lastZoom_ == this.map_.getZoom() )
+ {
+ return ;
+ }
- this.div_.innerHTML = '<img src="' + url + '" width=' + this.div_.style.width + ' height=' + this.div_.style.height + ' >' ;
+ this.lastZoom_ = this.map_.getZoom() ;
}
ProjectedOverlay.prototype.setOpacity=function(opacity)
{
- if (opacity < 0)
- {
- opacity = 0 ;
- }
- if(opacity > 100)
- {
- opacity = 100 ;
- }
- var c = opacity/100 ;
-
- if (typeof(this.div_.style.filter) =='string')
- {
- this.div_.style.filter = 'alpha(opacity:' + opacity + ')' ;
- }
- if (typeof(this.div_.style.KHTMLOpacity) == 'string' )
- {
- this.div_.style.KHTMLOpacity = c ;
- }
- if (typeof(this.div_.style.MozOpacity) == 'string')
- {
- this.div_.style.MozOpacity = c ;
- }
- if (typeof(this.div_.style.opacity) == 'string')
- {
- this.div_.style.opacity = c ;
- }
+ if (opacity < 0)
+ {
+ opacity = 0 ;
+ }
+ if(opacity > 100)
+ {
+ opacity = 100 ;
+ }
+ var c = opacity/100 ;
+
+ if (typeof(this.div_.style.filter) =='string')
+ {
+ this.div_.style.filter = 'alpha(opacity:' + opacity + ')' ;
+ }
+ if (typeof(this.div_.style.KHTMLOpacity) == 'string' )
+ {
+ this.div_.style.KHTMLOpacity = c ;
+ }
+ if (typeof(this.div_.style.MozOpacity) == 'string')
+ {
+ this.div_.style.MozOpacity = c ;
+ }
+ if (typeof(this.div_.style.opacity) == 'string')
+ {
+ this.div_.style.opacity = c ;
+ }
+}
+ProjectedOverlay.prototype.setRotation=function(deg)
+{
+ this.div_.style.webkitTransform = 'rotate('+deg+'deg)';
+ this.div_.style.mozTransform = 'rotate('+deg+'deg)';
+ this.div_.style.msTransform = 'rotate('+deg+'deg)';
+ this.div_.style.oTransform = 'rotate('+deg+'deg)';
+ this.div_.style.transform = 'rotate('+deg+'deg)';
}
-
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/README b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/README
index a82f556e..bcc2ca97 100644
--- a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/README
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/README
@@ -2,4 +2,4 @@ geoxml3.js and ZipFile.complete.js has been fetched from the googlecode project
from the branch kmz (which seems to be the branch containing the most features)
http://code.google.com/p/geoxml3/source/browse/#svn%2Fbranches%2Fkmz
-The latest version fetched when writing this was rev 79. \ No newline at end of file
+The latest version fetched when writing this was https://github.com/geocodezip/geoxml3/tree/a07796fa205d9d40a3c6f898a44324245935984f.
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ZipFile.complete.js b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ZipFile.complete.js
index d49bb056..f2f374cf 100644
--- a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ZipFile.complete.js
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ZipFile.complete.js
@@ -19,143 +19,143 @@
(function(){
- if (typeof JSIO == "object"){
- var e1 = new Error("JSIO is already defined");
- e1.source = "JSIO.core.js";
- throw e1;
- }
-
- JSIO = {};
-
- JSIO.version = "2.0 2012Feb";
-
- JSIO.throwError = function(msg, source, sub) {
- var error = new Error("Error: " + msg);
- error.source = (source || this._typename || 'JSIO') + (sub ? '.'+sub : '');
- throw error;
- }
-
- // Format a number as hex. Quantities over 7ffffff will be displayed properly.
- JSIO.decimalToHexString = function(number, digits) {
- if (number < 0) {
- number = 0xFFFFFFFF + number + 1;
- }
- var r1 = number.toString(16).toUpperCase();
- if (digits) {
- r1 = "00000000" + r1;
- r1 = r1.substring(r1.length - digits);
- }
- return r1;
- };
-
- JSIO.FileType = {
- Text : 0,
- Binary : 1,
- XML : 2,
- Unknown : 3
- };
-
-
- JSIO.guessFileType = function(name) {
- if (name == "makefile") { return JSIO.FileType.Text; }
-
- var lastDot = name.lastIndexOf(".");
- if (lastDot <= 0) { return JSIO.FileType.Unknown; }
-
- var ext= name.substring(lastDot);
- if (ext == ".zip") { return JSIO.FileType.Binary; }
- if (ext == ".xlsx") { return JSIO.FileType.Binary; }
- if (ext == ".docx") { return JSIO.FileType.Binary; }
- if (ext == ".dll") { return JSIO.FileType.Binary; }
- if (ext == ".obj") { return JSIO.FileType.Binary; }
- if (ext == ".pdb") { return JSIO.FileType.Binary; }
- if (ext == ".exe") { return JSIO.FileType.Binary; }
- if (ext == ".kmz") { return JSIO.FileType.Binary; }
-
- if (ext == ".xml") { return JSIO.FileType.XML; }
- if (ext == ".xsl") { return JSIO.FileType.XML; }
- if (ext == ".kml") { return JSIO.FileType.XML; }
- if (ext == ".csproj") { return JSIO.FileType.XML; }
- if (ext == ".vbproj") { return JSIO.FileType.XML; }
- if (ext == ".shfbproj") { return JSIO.FileType.XML; }
- if (ext == ".resx") { return JSIO.FileType.XML; }
- if (ext == ".xslt") { return JSIO.FileType.XML; }
-
- if (ext == ".sln") { return JSIO.FileType.Text; }
- if (ext == ".htm") { return JSIO.FileType.Text; }
- if (ext == ".html") { return JSIO.FileType.Text; }
- if (ext == ".js") { return JSIO.FileType.Text; }
- if (ext == ".vb") { return JSIO.FileType.Text; }
- if (ext == ".txt") { return JSIO.FileType.Text; }
- if (ext == ".rels") { return JSIO.FileType.Text; }
- if (ext == ".css") { return JSIO.FileType.Text; }
- if (ext == ".cs") { return JSIO.FileType.Text; }
- if (ext == ".asp") { return JSIO.FileType.Text; }
-
- return JSIO.FileType.Unknown;
- };
-
- JSIO.stringOfLength = function (charCode, length) {
- var s3 = "";
- for (var i = 0; i < length; i++) {
- s3 += String.fromCharCode(charCode);
- }
- return s3;
- };
-
- JSIO.formatByteArray = function(b) {
- var s1 = "0000 ";
- var s2 = "";
- for (var i = 0; i < b.length; i++) {
- if (i !== 0 && i % 16 === 0) {
- s1 += " " + s2 +"\n" + JSIO.decimalToHexString(i, 4) + " ";
- s2 = "";
- }
- s1 += JSIO.decimalToHexString(b[i], 2) + " ";
- if (b[i] >=32 && b[i] <= 126) {
- s2 += String.fromCharCode(b[i]);
- } else {
- s2 += ".";
- }
- }
- if (s2.length > 0) {
- s1 += JSIO.stringOfLength(32, ((i%16>0)? ((16 - i%16) * 3) : 0) + 4) + s2;
- }
- return s1;
- };
-
- JSIO.htmlEscape = function(str) {
- return str
- .replace(new RegExp( "&", "g" ), "&amp;")
- .replace(new RegExp( "<", "g" ), "&lt;")
- .replace(new RegExp( ">", "g" ), "&gt;")
- .replace(new RegExp( "\x13", "g" ), "<br/>")
- .replace(new RegExp( "\x10", "g" ), "<br/>");
- };
-
- JSIO.massApply = function(func, funcThis, arr, needReturn) {
- var arrayLimit = 99999; // Chrome has an apply/array limit of 99999; Firefox = 491519
- if (arr.length < arrayLimit) return func.apply(funcThis, arr);
- else {
- var newThis = funcThis;
- var offset = 0;
- var end = 99999;
-
- while (offset < arr.length) {
- var arrSlice;
- if (arr.subarray) arrSlice = arr.subarray(offset, end);
- else if (arr.slice) arrSlice = arr.slice(offset, end);
-
- if (needReturn) newThis += func.apply(newThis, arrSlice);
- else func.apply(funcThis, arrSlice);
-
- offset += arrayLimit;
- end += arrayLimit;
- end = Math.min(arr.length, end);
- }
- return newThis;
- }
- }
+ if (typeof JSIO == "object"){
+ var e1 = new Error("JSIO is already defined");
+ e1.source = "JSIO.core.js";
+ throw e1;
+ }
+
+ JSIO = {};
+
+ JSIO.version = "2.0 2012Feb";
+
+ JSIO.throwError = function(msg, source, sub) {
+ var error = new Error("Error: " + msg);
+ error.source = (source || this._typename || 'JSIO') + (sub ? '.'+sub : '');
+ throw error;
+ }
+
+ // Format a number as hex. Quantities over 7ffffff will be displayed properly.
+ JSIO.decimalToHexString = function(number, digits) {
+ if (number < 0) {
+ number = 0xFFFFFFFF + number + 1;
+ }
+ var r1 = number.toString(16).toUpperCase();
+ if (digits) {
+ r1 = "00000000" + r1;
+ r1 = r1.substring(r1.length - digits);
+ }
+ return r1;
+ };
+
+ JSIO.FileType = {
+ Text : 0,
+ Binary : 1,
+ XML : 2,
+ Unknown : 3
+ };
+
+
+ JSIO.guessFileType = function(name) {
+ if (name == "makefile") { return JSIO.FileType.Text; }
+
+ var lastDot = name.lastIndexOf(".");
+ if (lastDot <= 0) { return JSIO.FileType.Unknown; }
+
+ var ext= name.substring(lastDot);
+ if (ext == ".zip") { return JSIO.FileType.Binary; }
+ if (ext == ".xlsx") { return JSIO.FileType.Binary; }
+ if (ext == ".docx") { return JSIO.FileType.Binary; }
+ if (ext == ".dll") { return JSIO.FileType.Binary; }
+ if (ext == ".obj") { return JSIO.FileType.Binary; }
+ if (ext == ".pdb") { return JSIO.FileType.Binary; }
+ if (ext == ".exe") { return JSIO.FileType.Binary; }
+ if (ext == ".kmz") { return JSIO.FileType.Binary; }
+
+ if (ext == ".xml") { return JSIO.FileType.XML; }
+ if (ext == ".xsl") { return JSIO.FileType.XML; }
+ if (ext == ".kml") { return JSIO.FileType.XML; }
+ if (ext == ".csproj") { return JSIO.FileType.XML; }
+ if (ext == ".vbproj") { return JSIO.FileType.XML; }
+ if (ext == ".shfbproj") { return JSIO.FileType.XML; }
+ if (ext == ".resx") { return JSIO.FileType.XML; }
+ if (ext == ".xslt") { return JSIO.FileType.XML; }
+
+ if (ext == ".sln") { return JSIO.FileType.Text; }
+ if (ext == ".htm") { return JSIO.FileType.Text; }
+ if (ext == ".html") { return JSIO.FileType.Text; }
+ if (ext == ".js") { return JSIO.FileType.Text; }
+ if (ext == ".vb") { return JSIO.FileType.Text; }
+ if (ext == ".txt") { return JSIO.FileType.Text; }
+ if (ext == ".rels") { return JSIO.FileType.Text; }
+ if (ext == ".css") { return JSIO.FileType.Text; }
+ if (ext == ".cs") { return JSIO.FileType.Text; }
+ if (ext == ".asp") { return JSIO.FileType.Text; }
+
+ return JSIO.FileType.Unknown;
+ };
+
+ JSIO.stringOfLength = function (charCode, length) {
+ var s3 = "";
+ for (var i = 0; i < length; i++) {
+ s3 += String.fromCharCode(charCode);
+ }
+ return s3;
+ };
+
+ JSIO.formatByteArray = function(b) {
+ var s1 = "0000 ";
+ var s2 = "";
+ for (var i = 0; i < b.length; i++) {
+ if (i !== 0 && i % 16 === 0) {
+ s1 += " " + s2 +"\n" + JSIO.decimalToHexString(i, 4) + " ";
+ s2 = "";
+ }
+ s1 += JSIO.decimalToHexString(b[i], 2) + " ";
+ if (b[i] >=32 && b[i] <= 126) {
+ s2 += String.fromCharCode(b[i]);
+ } else {
+ s2 += ".";
+ }
+ }
+ if (s2.length > 0) {
+ s1 += JSIO.stringOfLength(32, ((i%16>0)? ((16 - i%16) * 3) : 0) + 4) + s2;
+ }
+ return s1;
+ };
+
+ JSIO.htmlEscape = function(str) {
+ return str
+ .replace(new RegExp( "&", "g" ), "&amp;")
+ .replace(new RegExp( "<", "g" ), "&lt;")
+ .replace(new RegExp( ">", "g" ), "&gt;")
+ .replace(new RegExp( "\x13", "g" ), "<br/>")
+ .replace(new RegExp( "\x10", "g" ), "<br/>");
+ };
+
+ JSIO.massApply = function(func, funcThis, arr, needReturn) {
+ var arrayLimit = 65536; // Chrome has an apply/array limit of 99999; Firefox = 491519, Safari = 65536
+ if (arr.length < arrayLimit) return func.apply(funcThis, arr);
+ else {
+ var newThis = funcThis;
+ var offset = 0;
+ var end = 65536;
+
+ while (offset < arr.length) {
+ var arrSlice;
+ if (arr.subarray) arrSlice = arr.subarray(offset, end);
+ else if (arr.slice) arrSlice = arr.slice(offset, end);
+
+ if (needReturn) newThis += func.apply(newThis, arrSlice);
+ else func.apply(funcThis, arrSlice);
+
+ offset += arrayLimit;
+ end += arrayLimit;
+ end = Math.min(arr.length, end);
+ }
+ return newThis;
+ }
+ }
})();
@@ -197,364 +197,364 @@
(function(){
- var version = "2.0 2012Feb";
-
- if (typeof JSIO !== "object") { JSIO = {}; }
- if ((typeof JSIO.version !== "string")) {
- JSIO.version = version;
- }
- else if ((JSIO.version.length < 3) ||
- (JSIO.version.substring(0,3) !== "2.0")) {
- JSIO.version += " " + version;
- }
-
- // =======================================================
- // the base object, used as the prototype of all ByteReader objects.
- var _byteReaderBase = function () {
- this.position = 0;
- // position must be incremented in .readByte() for all derived classes
- };
-
- _byteReaderBase.prototype._throwError = JSIO.throwError;
-
- _byteReaderBase.prototype._limitCheck = function(len, startPos) {
- var LOE = {
- len: len,
- pos: startPos,
- end: startPos+len
- };
-
- if (len === 0) return {len:0, pos:0, end:0};
- if (len < 0) this._throwError("Invalid read length");
- if (!this.length) return {len:len, pos:this.position, end:len+this.position};
- if (!startPos >= 0) LOE.pos = this.position;
- if (this.length <= LOE.pos) this._throwError("EOF reached");
-
- LOE.end = LOE.pos+len;
- if (this.length < LOE.end) LOE.end = LOE.pos+(LOE.len = this.length-this.position);
- return LOE;
- }
-
- JSIO.SeekOrigin = {
- Begin : 0,
- Current : 1,
- End : 2,
- SEEK_SET : 0,
- SEEK_CUR : 1,
- SEEK_END : 2
- };
-
- _byteReaderBase.prototype.seek = function(offset, origin) {
- switch (origin) {
- case JSIO.SeekOrigin.Begin:
- if (offset == this.position) return this.position;
- if (!this.length) {
- if (offset < this.position) this._throwError('Uni-directional stream cannot seek backwards', null, 'seek');
- else if (offset > this.position) return this.read(offset - this.position); // read will limit check
- }
- else {
- if (this.length < offset) this._throwError('Cannot seek past reader length', null, 'seek');
- this.position = offset;
- }
- break;
- case JSIO.SeekOrigin.Current:
- return this.seek(this.position + offset, JSIO.SeekOrigin.Begin);
- break;
- case JSIO.SeekOrigin.End:
- if (!this.length) this._throwError('Uni-directional stream has no known end length for seek', null, 'seek');
- return this.seek(this.length - 1 + offset, JSIO.SeekOrigin.Begin);
- break;
- default:
- this._throwError('Invalid seek method', null, 'seek');
- break;
- }
-
- return this.position;
- };
-
- _byteReaderBase.prototype.read = function(len, startPos) {
- var LOE = this._limitCheck(len, startPos);
- if (LOE.len === 0) return [];
- if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin);
-
- var bytesRead = [];
-
- // Faster methods with an array or stream
- if (this.array && this.array.subarray) bytesRead = this.array.subarray(LOE.pos, LOE.end);
- else if (this.array && this.array.slice) bytesRead = this.array.slice(LOE.pos, LOE.end);
- else if (this.stream) bytesRead = this.stream.read(LOE.len, LOE.pos);
- else if (this.length) { // Random-access stream
- for(var i=LOE.pos; i<LOE.end; i++) { bytesRead.push(this.readByteAt(i)); }
- }
- else { // Uni-directional stream
- for(var i=LOE.pos; i<LOE.end; i++) {
- var b = this.readByte();
- if (b === null || b === undefined) break;
- bytesRead.push(b);
- }
- }
- this.position = LOE.end;
- return bytesRead;
- };
-
- _byteReaderBase.prototype.beginRead = function(len, startPos, callback) {
- var LOE = this._limitCheck(len, startPos);
- if (LOE.len === 0) return setTimeout(function() { callback([]); }, 1);
- if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin);
-
- var bytesRead = [];
- var thisReader = this;
- var leftToRead = LOE.len;
-
- var readBatchAsync = function() {
- var c = 0;
- var pos = thisReader.position;
-
- // read a 32k batch
- var l = (leftToRead >= 32768) ? 32768 : leftToRead;
- var newBytes = thisReader.read(l);
- JSIO.massApply(bytesRead.push, bytesRead, newBytes);
- c += l;
- leftToRead -= l;
- if (newBytes.length < l) leftToRead = 0;
-
- if (leftToRead>0) setTimeout(readBatchAsync, 1);
- else callback(bytesRead);
- };
-
- // kickoff
- setTimeout(readBatchAsync, 1); // always async, in ALL situations
- return null;
- };
-
- _byteReaderBase.prototype.readToEnd = function() {
- if (this.array && this.array.subarray) return this.array.subarray(this.position);
- else if (this.array && this.array.slice) return this.array.slice(this.position);
- else if (this.length) return this.read(this.length - this.position);
- else return this.read(9000 * 9000); // over 9000
- };
-
- _byteReaderBase.prototype.beginReadToEnd = function(callback) {
- if (this.array && this.array.subarray) setTimeout(function() { callback( this.array.subarray(this.position) ); }, 1);
- else if (this.array && this.array.slice) setTimeout(function() { callback( this.array.slice(this.position) ); }, 1);
- else if (this.length) return this.beginRead(this.length - this.position, this.position, callback);
- else return this.beginRead(9000 * 9000, this.position, callback);
- };
-
- // Generic routines; one of these two MUST be overloaded (preferrably both)
- _byteReaderBase.prototype.readByte = function(){
- if (this.length && this.position >= this.length) return null; // EOF
-
- var byte;
- if (this.array) byte = this.array[this.position++];
- else if (this.length) byte = this.readByteAt(this.position++);
- else if (this.stream) byte = this.stream.read(1)[0];
- else byte = this.read(1)[0];
- return (byte === null || byte === undefined) ? null : byte;
- };
- _byteReaderBase.prototype.readByteAt = function(i) {
- var pos = this.position; // no position changes on this one
- if (i === null || i === undefined) i = this.position;
-
- var byte;
- if (this.array) byte = this.array[i];
- else if (i === pos) byte = this.readByte();
- else if (this.stream) byte = this.stream.read(1, i)[0];
- else byte = this.read(1, i)[0];
-
- this.position = pos;
- return (byte === null || byte === undefined) ? null : byte;
- }
-
- _byteReaderBase.prototype.readBytes = _byteReaderBase.prototype.read;
- _byteReaderBase.prototype.beginReadBytes = function(len, callback) { return this.beginRead(len, this.position, callback); };
-
- _byteReaderBase.prototype.readNumber = function(len, startPos){
- var LOE = this._limitCheck(len, startPos);
- if (LOE.len === 0) LOE.len = 1;
- if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin);
-
- var result = 0;
- var bytes = this.read(LOE.len, LOE.pos);
- for (var i=bytes.length-1; i>=0; i--) {
- // IE only supports 32-bit integer shifting
- //result = result << 8 | bytes[i];
- result = result*256 + bytes[i];
- }
- return result;
- };
-
- _byteReaderBase.prototype.readString = function(len, startPos){
- var LOE = this._limitCheck(len, startPos);
- if (LOE.len === 0) return '';
- if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin);
-
- var result = '';
- var bytes = this.read(LOE.len, LOE.pos);
- for(var i=0; i<bytes.length; i++){
- result += String.fromCharCode(bytes[i]);
- }
- return result;
- };
-
- _byteReaderBase.prototype.readNullTerminatedString = function(startPos){
- var pos = startPos || this.position;
- if (this.length && this.length < pos) this._throwError('EOF reached', null, 'readNullTerminatedString');
- if (pos != this.position) this.seek(pos, JSIO.SeekOrigin.Begin);
-
- var slarge = "";
- var s = "";
- var c = 0;
-
- // Faster method with an array
- if (this.array && this.array.indexOf) {
- var len = pos - this.array.indexOf(0, pos);
- if (len > 0) return this.readString(len, pos);
- }
-
- var ch;
- while(1) {
- ch = String.fromCharCode(this.readByteAt(pos+c));
- if (ch === null) break;
-
- s += ch;
- c++;
- if(c >= 32768) {
- slarge += s;
- s = "";
- pos += c;
- this.position += c;
- c = 0;
- }
- };
- this.position = pos + c;
- return slarge + s;
- };
-
- _byteReaderBase.prototype.beginReadNullTerminatedString = function(callback, startPos){
- var pos = startPos || this.position;
- if (this.length && this.length < pos) this._throwError('EOF reached', null, 'beginReadNullTerminatedString');
-
- var slarge = "";
- var s = "";
- var thisBinStream = this;
-
- var readBatchAsync = function() {
- var c = 0;
-
- var ch;
- while(1) {
- ch = String.fromCharCode(this.readByteAt(pos+c));
- if (ch === null) break;
-
- s += ch;
- c++;
- if(c >= 32768) {
- slarge += s;
- s = "";
- pos += c;
- this.position += c;
- c = 0;
- }
- };
-
- thisBinStream.position = pos + c;
- if (ch!==null) setTimeout(readBatchAsync, 1);
- else callback(slarge+s);
- };
-
- // Faster method with an array
- if (this.array && this.array.indexOf) {
- var len = pos - this.array.indexOf(0, pos);
- if (len > 0) readBatchASync = function() { callback(thisBinStream.readString(len, pos)); };
- }
-
- // kickoff
- setTimeout(readBatchAsync, 1); // always async, in ALL situations
- return null;
- };
-
-
-
- JSIO._ByteReaderBase = _byteReaderBase;
- // =======================================================
-
-
-
-
- // =======================================================
- // reads from an array of bytes.
- // This basically wraps a readByte() fn onto array access.
- var _arrayReader = function(array) {
- if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.ArrayReader', 'ctor');
- this.position = 0;
- this.array = array;
- this.length = array.length;
- this._typename = "JSIO.ArrayReader";
- this._version = version;
- return this;
- };
-
- _arrayReader.prototype = new JSIO._ByteReaderBase();
-
- _arrayReader.prototype.readByte = function() {
- if (this.position >= this.array.length) return null; // EOF
- return this.array[this.position++];
- };
- _arrayReader.prototype.readByteAt = function(i) {
- return this.array[i];
- };
-
- // =======================================================
-
-
- // =======================================================
- // reads bytes at a time from a defined segment of a stream.
- var _streamSegmentReader = function(stream, offset, len) {
- if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.StreamSegmentReader', 'ctor');
- if (!stream) this._throwError('You must pass a non-null stream', 'JSIO.StreamSegmentReader', 'ctor');
-
- if (!(offset >= 1)) offset = 0;
- if (!(len >= 1)) len = 0;
-
- this.stream = stream;
- this.array = null;
- if (stream.array) {
- var end = len ? offset + len : null;
- if (stream.array.subarray) this.array = stream.array.subarray(offset, end);
- else if (stream.array.slice) this.array = stream.array.slice(offset, end);
- }
- this.length = this.array ? this.array.length : (stream.length ? stream.length - offset : null);
- this.offset = offset;
- this.limit = len;
- this.position = 0;
- this._typeName = 'JSIO.StreamSegmentReader';
- this._version = version;
-
- if (this.array) {
- this.readByte = _arrayReader.prototype.readByte;
- this.readByteAt = _arrayReader.prototype.readByteAt;
- }
- return this;
- };
-
- _streamSegmentReader.prototype = new JSIO._ByteReaderBase();
-
- _streamSegmentReader.prototype.readByte = function() {
- if (this.limit && this.position >= this.limit) return null; // EOF
- this.position++;
- return this.stream.readByteAt(this.offset + this.position - 1);
- };
- _streamSegmentReader.prototype.readByteAt = function(i) {
- if (this.limit && i >= this.limit) return null; // EOF
- return this.stream.readByteAt(this.offset + i);
- };
-
- // =======================================================
-
- JSIO.ArrayReader = _arrayReader;
- JSIO.StreamReader = _streamSegmentReader;
- JSIO.StreamSegmentReader = _streamSegmentReader;
+ var version = "2.0 2012Feb";
+
+ if (typeof JSIO !== "object") { JSIO = {}; }
+ if ((typeof JSIO.version !== "string")) {
+ JSIO.version = version;
+ }
+ else if ((JSIO.version.length < 3) ||
+ (JSIO.version.substring(0,3) !== "2.0")) {
+ JSIO.version += " " + version;
+ }
+
+ // =======================================================
+ // the base object, used as the prototype of all ByteReader objects.
+ var _byteReaderBase = function () {
+ this.position = 0;
+ // position must be incremented in .readByte() for all derived classes
+ };
+
+ _byteReaderBase.prototype._throwError = JSIO.throwError;
+
+ _byteReaderBase.prototype._limitCheck = function(len, startPos) {
+ var LOE = {
+ len: len,
+ pos: startPos,
+ end: startPos+len
+ };
+
+ if (len === 0) return {len:0, pos:0, end:0};
+ if (len < 0) this._throwError("Invalid read length");
+ if (!this.length) return {len:len, pos:this.position, end:len+this.position};
+ if (!startPos >= 0) LOE.pos = this.position;
+ if (this.length <= LOE.pos) this._throwError("EOF reached");
+
+ LOE.end = LOE.pos+len;
+ if (this.length < LOE.end) LOE.end = LOE.pos+(LOE.len = this.length-this.position);
+ return LOE;
+ }
+
+ JSIO.SeekOrigin = {
+ Begin : 0,
+ Current : 1,
+ End : 2,
+ SEEK_SET : 0,
+ SEEK_CUR : 1,
+ SEEK_END : 2
+ };
+
+ _byteReaderBase.prototype.seek = function(offset, origin) {
+ switch (origin) {
+ case JSIO.SeekOrigin.Begin:
+ if (offset == this.position) return this.position;
+ if (!this.length) {
+ if (offset < this.position) this._throwError('Uni-directional stream cannot seek backwards', null, 'seek');
+ else if (offset > this.position) return this.read(offset - this.position); // read will limit check
+ }
+ else {
+ if (this.length < offset) this._throwError('Cannot seek past reader length', null, 'seek');
+ this.position = offset;
+ }
+ break;
+ case JSIO.SeekOrigin.Current:
+ return this.seek(this.position + offset, JSIO.SeekOrigin.Begin);
+ break;
+ case JSIO.SeekOrigin.End:
+ if (!this.length) this._throwError('Uni-directional stream has no known end length for seek', null, 'seek');
+ return this.seek(this.length - 1 + offset, JSIO.SeekOrigin.Begin);
+ break;
+ default:
+ this._throwError('Invalid seek method', null, 'seek');
+ break;
+ }
+
+ return this.position;
+ };
+
+ _byteReaderBase.prototype.read = function(len, startPos) {
+ var LOE = this._limitCheck(len, startPos);
+ if (LOE.len === 0) return [];
+ if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin);
+
+ var bytesRead = [];
+
+ // Faster methods with an array or stream
+ if (this.array && this.array.subarray) bytesRead = this.array.subarray(LOE.pos, LOE.end);
+ else if (this.array && this.array.slice) bytesRead = this.array.slice(LOE.pos, LOE.end);
+ else if (this.stream) bytesRead = this.stream.read(LOE.len, LOE.pos);
+ else if (this.length) { // Random-access stream
+ for(var i=LOE.pos; i<LOE.end; i++) { bytesRead.push(this.readByteAt(i)); }
+ }
+ else { // Uni-directional stream
+ for(var i=LOE.pos; i<LOE.end; i++) {
+ var b = this.readByte();
+ if (b === null || b === undefined) break;
+ bytesRead.push(b);
+ }
+ }
+ this.position = LOE.end;
+ return bytesRead;
+ };
+
+ _byteReaderBase.prototype.beginRead = function(len, startPos, callback) {
+ var LOE = this._limitCheck(len, startPos);
+ if (LOE.len === 0) return setTimeout(function() { callback([]); }, 1);
+ if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin);
+
+ var bytesRead = [];
+ var thisReader = this;
+ var leftToRead = LOE.len;
+
+ var readBatchAsync = function() {
+ var c = 0;
+ var pos = thisReader.position;
+
+ // read a 32k batch
+ var l = (leftToRead >= 32768) ? 32768 : leftToRead;
+ var newBytes = thisReader.read(l);
+ JSIO.massApply(bytesRead.push, bytesRead, newBytes);
+ c += l;
+ leftToRead -= l;
+ if (newBytes.length < l) leftToRead = 0;
+
+ if (leftToRead>0) setTimeout(readBatchAsync, 1);
+ else callback(bytesRead);
+ };
+
+ // kickoff
+ setTimeout(readBatchAsync, 1); // always async, in ALL situations
+ return null;
+ };
+
+ _byteReaderBase.prototype.readToEnd = function() {
+ if (this.array && this.array.subarray) return this.array.subarray(this.position);
+ else if (this.array && this.array.slice) return this.array.slice(this.position);
+ else if (this.length) return this.read(this.length - this.position);
+ else return this.read(9000 * 9000); // over 9000
+ };
+
+ _byteReaderBase.prototype.beginReadToEnd = function(callback) {
+ if (this.array && this.array.subarray) setTimeout(function() { callback( this.array.subarray(this.position) ); }, 1);
+ else if (this.array && this.array.slice) setTimeout(function() { callback( this.array.slice(this.position) ); }, 1);
+ else if (this.length) return this.beginRead(this.length - this.position, this.position, callback);
+ else return this.beginRead(9000 * 9000, this.position, callback);
+ };
+
+ // Generic routines; one of these two MUST be overloaded (preferrably both)
+ _byteReaderBase.prototype.readByte = function(){
+ if (this.length && this.position >= this.length) return null; // EOF
+
+ var oneByte;
+ if (this.array) oneByte = this.array[this.position++];
+ else if (this.length) oneByte = this.readByteAt(this.position++);
+ else if (this.stream) oneByte = this.stream.read(1)[0];
+ else oneByte = this.read(1)[0];
+ return (oneByte === null || oneByte === undefined) ? null : oneByte;
+ };
+ _byteReaderBase.prototype.readByteAt = function(i) {
+ var pos = this.position; // no position changes on this one
+ if (i === null || i === undefined) i = this.position;
+
+ var oneByte;
+ if (this.array) oneByte = this.array[i];
+ else if (i === pos) oneByte = this.readByte();
+ else if (this.stream) oneByte = this.stream.read(1, i)[0];
+ else oneByte = this.read(1, i)[0];
+
+ this.position = pos;
+ return (oneByte === null || oneByte === undefined) ? null : oneByte;
+ }
+
+ _byteReaderBase.prototype.readBytes = _byteReaderBase.prototype.read;
+ _byteReaderBase.prototype.beginReadBytes = function(len, callback) { return this.beginRead(len, this.position, callback); };
+
+ _byteReaderBase.prototype.readNumber = function(len, startPos){
+ var LOE = this._limitCheck(len, startPos);
+ if (LOE.len === 0) LOE.len = 1;
+ if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin);
+
+ var result = 0;
+ var bytes = this.read(LOE.len, LOE.pos);
+ for (var i=bytes.length-1; i>=0; i--) {
+ // IE only supports 32-bit integer shifting
+ //result = result << 8 | bytes[i];
+ result = result*256 + bytes[i];
+ }
+ return result;
+ };
+
+ _byteReaderBase.prototype.readString = function(len, startPos){
+ var LOE = this._limitCheck(len, startPos);
+ if (LOE.len === 0) return '';
+ if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin);
+
+ var result = '';
+ var bytes = this.read(LOE.len, LOE.pos);
+ for(var i=0; i<bytes.length; i++){
+ result += String.fromCharCode(bytes[i]);
+ }
+ return result;
+ };
+
+ _byteReaderBase.prototype.readNullTerminatedString = function(startPos){
+ var pos = startPos || this.position;
+ if (this.length && this.length < pos) this._throwError('EOF reached', null, 'readNullTerminatedString');
+ if (pos != this.position) this.seek(pos, JSIO.SeekOrigin.Begin);
+
+ var slarge = "";
+ var s = "";
+ var c = 0;
+
+ // Faster method with an array
+ if (this.array && this.array.indexOf) {
+ var len = pos - this.array.indexOf(0, pos);
+ if (len > 0) return this.readString(len, pos);
+ }
+
+ var ch;
+ while(1) {
+ ch = String.fromCharCode(this.readByteAt(pos+c));
+ if (ch === null) break;
+
+ s += ch;
+ c++;
+ if(c >= 32768) {
+ slarge += s;
+ s = "";
+ pos += c;
+ this.position += c;
+ c = 0;
+ }
+ };
+ this.position = pos + c;
+ return slarge + s;
+ };
+
+ _byteReaderBase.prototype.beginReadNullTerminatedString = function(callback, startPos){
+ var pos = startPos || this.position;
+ if (this.length && this.length < pos) this._throwError('EOF reached', null, 'beginReadNullTerminatedString');
+
+ var slarge = "";
+ var s = "";
+ var thisBinStream = this;
+
+ var readBatchAsync = function() {
+ var c = 0;
+
+ var ch;
+ while(1) {
+ ch = String.fromCharCode(this.readByteAt(pos+c));
+ if (ch === null) break;
+
+ s += ch;
+ c++;
+ if(c >= 32768) {
+ slarge += s;
+ s = "";
+ pos += c;
+ this.position += c;
+ c = 0;
+ }
+ };
+
+ thisBinStream.position = pos + c;
+ if (ch!==null) setTimeout(readBatchAsync, 1);
+ else callback(slarge+s);
+ };
+
+ // Faster method with an array
+ if (this.array && this.array.indexOf) {
+ var len = pos - this.array.indexOf(0, pos);
+ if (len > 0) readBatchASync = function() { callback(thisBinStream.readString(len, pos)); };
+ }
+
+ // kickoff
+ setTimeout(readBatchAsync, 1); // always async, in ALL situations
+ return null;
+ };
+
+
+
+ JSIO._ByteReaderBase = _byteReaderBase;
+ // =======================================================
+
+
+
+
+ // =======================================================
+ // reads from an array of bytes.
+ // This basically wraps a readByte() fn onto array access.
+ var _arrayReader = function(array) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.ArrayReader', 'ctor');
+ this.position = 0;
+ this.array = array;
+ this.length = array.length;
+ this._typename = "JSIO.ArrayReader";
+ this._version = version;
+ return this;
+ };
+
+ _arrayReader.prototype = new JSIO._ByteReaderBase();
+
+ _arrayReader.prototype.readByte = function() {
+ if (this.position >= this.array.length) return null; // EOF
+ return this.array[this.position++];
+ };
+ _arrayReader.prototype.readByteAt = function(i) {
+ return this.array[i];
+ };
+
+ // =======================================================
+
+
+ // =======================================================
+ // reads bytes at a time from a defined segment of a stream.
+ var _streamSegmentReader = function(stream, offset, len) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.StreamSegmentReader', 'ctor');
+ if (!stream) this._throwError('You must pass a non-null stream', 'JSIO.StreamSegmentReader', 'ctor');
+
+ if (!(offset >= 1)) offset = 0;
+ if (!(len >= 1)) len = 0;
+
+ this.stream = stream;
+ this.array = null;
+ if (stream.array) {
+ var end = len ? offset + len : null;
+ if (stream.array.subarray) this.array = stream.array.subarray(offset, end);
+ else if (stream.array.slice) this.array = stream.array.slice(offset, end);
+ }
+ this.length = this.array ? this.array.length : (stream.length ? stream.length - offset : null);
+ this.offset = offset;
+ this.limit = len;
+ this.position = 0;
+ this._typeName = 'JSIO.StreamSegmentReader';
+ this._version = version;
+
+ if (this.array) {
+ this.readByte = _arrayReader.prototype.readByte;
+ this.readByteAt = _arrayReader.prototype.readByteAt;
+ }
+ return this;
+ };
+
+ _streamSegmentReader.prototype = new JSIO._ByteReaderBase();
+
+ _streamSegmentReader.prototype.readByte = function() {
+ if (this.limit && this.position >= this.limit) return null; // EOF
+ this.position++;
+ return this.stream.readByteAt(this.offset + this.position - 1);
+ };
+ _streamSegmentReader.prototype.readByteAt = function(i) {
+ if (this.limit && i >= this.limit) return null; // EOF
+ return this.stream.readByteAt(this.offset + i);
+ };
+
+ // =======================================================
+
+ JSIO.ArrayReader = _arrayReader;
+ JSIO.StreamReader = _streamSegmentReader;
+ JSIO.StreamSegmentReader = _streamSegmentReader;
})();
@@ -574,195 +574,195 @@
// This work is licensed under the GPLv3.
(function(){
- var version = "2.0 2012Feb";
- var typename = "JSIO.BinaryUrlStream";
-
- if ((typeof JSIO !== "object") ||
- (typeof JSIO.version !== "string") ||
- (JSIO.version.length < 3) ||
- (JSIO.version.substring(0,3) !== "2.0"))
- JSIO.throwError('This extension requires JSIO.core.js v2.0', typename);
-
- if (typeof JSIO._ByteReaderBase !== "function")
- JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
-
- if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
- var IEBinaryToArray_ByteStr_Script =
- "<!-- IEBinaryToArray_ByteStr -->\r\n"+
- "<script type='text/vbscript'>\r\n"+
- "Function IEBinaryToArray_ByteStr(Binary)\r\n"+
- " IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+
- "End Function\r\n"+
- "Function IEBinaryToArray_ByteAsc_Last(Binary)\r\n"+
- " Dim lastIndex\r\n"+
- " lastIndex = LenB(Binary)\r\n"+
- " if lastIndex mod 2 Then\r\n"+
- " IEBinaryToArray_ByteAsc_Last = AscB( MidB( Binary, lastIndex, 1 ) )\r\n"+
- " Else\r\n"+
- " IEBinaryToArray_ByteAsc_Last = -1\r\n"+
- " End If\r\n"+
- "End Function\r\n"+
- "</script>\r\n";
-
- // inject VBScript
- document.write(IEBinaryToArray_ByteStr_Script);
- }
-
- JSIO.IEByteMapping = null;
-
- var bus = function(url, callback) {
- if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.BinaryUrlStream', 'ctor');
-
- this.callback = callback;
- this.position = 0;
- this.length = null;
- this.readByte = JSIO.ArrayReader.prototype.readByte;
- this.readByteAt = JSIO.ArrayReader.prototype.readByteAt;
- this.req = null;
- this._typename = typename;
- this._version = version;
- this.status = "-none-";
-
- var _IeGetBinResource = function(fileURL){
- var binStream = this;
- // http://stackoverflow.com/questions/1919972/how-do-i-access-xhr-responsebody-for-binary-data-from-javascript-in-ie
-
- // my helper to convert from responseBody to a byte array
- var convertResponseBodyToArray = function (binary) {
- var byteArray = new Array;
-
- try {
- // very fast; very little work involved
- byteArray = new VBArray(binary).toArray();
- }
- catch(err) {
- // use the BinaryToArray VBScript
- if (!JSIO.IEByteMapping) {
- JSIO.IEByteMapping = {};
- for ( var i = 0; i < 256; i++ ) {
- for ( var j = 0; j < 256; j++ ) {
- JSIO.IEByteMapping[ String.fromCharCode( i + j * 256 ) ] = [ i, j ];
- }
- }
- }
- var rawBytes = IEBinaryToArray_ByteStr(binary);
- var lastAsc = IEBinaryToArray_ByteAsc_Last(binary);
-
- for ( var i = 0; i < rawBytes.length; i++ ) {
- byteArray.push.apply(byteArray, JSIO.IEByteMapping[ rawBytes.substr(i,1) ]);
- }
- if (lastAsc >= 0) byteArray.push(lastAsc);
- }
-
- return byteArray;
- };
-
- this.req = (function() {
- if (window.XMLHttpRequest) return new window.XMLHttpRequest();
- else if (window.ActiveXObject) {
- // the many versions of IE's XML fetchers
- var AXOs = [
- 'MSXML2.XMLHTTP.6.0',
- 'MSXML2.XMLHTTP.5.0',
- 'MSXML2.XMLHTTP.4.0',
- 'MSXML2.XMLHTTP.3.0',
- 'MSXML2.XMLHTTP',
- 'Microsoft.XMLHTTP',
- 'MSXML.XMLHTTP'
- ];
- for (var i = 0; i < AXOs.length; i++) {
- try { return new ActiveXObject(AXOs[i]); }
- catch(e) { continue; }
- }
- }
- return null;
- })();
- this.req.open("GET", fileURL, true);
- this.req.setRequestHeader("Accept-Charset", "x-user-defined");
- this.req.onreadystatechange = function(event){
- if (binStream.req.readyState == 4) {
- binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText;
- if (binStream.req.status == 200) {
- binStream.array = convertResponseBodyToArray(binStream.req.responseBody);
- binStream.length = binStream.array.length;
- if (binStream.length < 0) this._throwError('Failed to load "'+ fileURL + '" after converting');
-
- if (typeof binStream.callback == "function") binStream.callback(binStream);
- }
- else {
- binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status);
- }
- }
- };
- this.req.send();
- };
-
- var _NormalGetBinResource = function(fileURL){
- var binStream= this;
- this.req = new XMLHttpRequest();
- this.req.open('GET', fileURL, true);
- this.req.onreadystatechange = function(aEvt) {
- if (binStream.req.readyState == 4) {
- binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText;
- if(binStream.req.status == 200){
- var fileContents = binStream.req.responseText;
- binStream.length = fileContents.byteLength;
- binStream.array = fileContents.split('');
- for ( var i = 0; i < binStream.array.length; i++ ) {
- binStream.array[i] = binStream.array[i].charCodeAt(0) & 0xff;
- }
-
- if (typeof binStream.callback == "function") binStream.callback(binStream);
- }
- else {
- binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status);
- }
- }
- };
- //XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
- this.req.overrideMimeType('text/plain; charset=x-user-defined');
- this.req.send(null);
- };
-
- // http://stackoverflow.com/questions/327685/is-there-a-way-to-read-binary-data-into-javascript
- var _ArrayBufferGetBinResource = function(fileURL){
- var binStream= this;
- this.req = new XMLHttpRequest();
- this.req.open('GET', fileURL, true);
- this.req.onreadystatechange = function(aEvt) {
- if (binStream.req.readyState == 4) {
- binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText;
- if(binStream.req.status == 200){
- var fileContents = binStream.req.response;
- binStream.length = fileContents.byteLength;
- binStream.array = new Uint8Array(fileContents);
-
- if (typeof binStream.callback == "function") binStream.callback(binStream);
- }
- else {
- binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status);
- }
- }
- };
- this.req.responseType = 'arraybuffer';
- this.req.overrideMimeType('text/plain; charset=x-user-defined');
- this.req.send(null);
- };
-
-
- if (typeof ArrayBuffer !== 'undefined') _ArrayBufferGetBinResource.apply(this, [url]);
- else if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) _IeGetBinResource.apply(this, [url]);
- else _NormalGetBinResource.apply(this, [url]);
- };
-
- bus.prototype = new JSIO._ByteReaderBase();
-
- bus.prototype.readByte = function(){
- var byte = this.readByteAt(this.position++);
- return (byte === null || byte === undefined) ? null : byte;
- };
-
- JSIO.BinaryUrlStream = bus;
+ var version = "2.0 2012Feb";
+ var typename = "JSIO.BinaryUrlStream";
+
+ if ((typeof JSIO !== "object") ||
+ (typeof JSIO.version !== "string") ||
+ (JSIO.version.length < 3) ||
+ (JSIO.version.substring(0,3) !== "2.0"))
+ JSIO.throwError('This extension requires JSIO.core.js v2.0', typename);
+
+ if (typeof JSIO._ByteReaderBase !== "function")
+ JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
+
+ if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
+ var IEBinaryToArray_ByteStr_Script =
+ "<!-- IEBinaryToArray_ByteStr -->\r\n"+
+ "<script type='text/vbscript'>\r\n"+
+ "Function IEBinaryToArray_ByteStr(Binary)\r\n"+
+ " IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+
+ "End Function\r\n"+
+ "Function IEBinaryToArray_ByteAsc_Last(Binary)\r\n"+
+ " Dim lastIndex\r\n"+
+ " lastIndex = LenB(Binary)\r\n"+
+ " if lastIndex mod 2 Then\r\n"+
+ " IEBinaryToArray_ByteAsc_Last = AscB( MidB( Binary, lastIndex, 1 ) )\r\n"+
+ " Else\r\n"+
+ " IEBinaryToArray_ByteAsc_Last = -1\r\n"+
+ " End If\r\n"+
+ "End Function\r\n"+
+ "</script>\r\n";
+
+ // inject VBScript
+ document.write(IEBinaryToArray_ByteStr_Script);
+ }
+
+ JSIO.IEByteMapping = null;
+
+ var bus = function(url, callback) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.BinaryUrlStream', 'ctor');
+
+ this.callback = callback;
+ this.position = 0;
+ this.length = null;
+ this.readByte = JSIO.ArrayReader.prototype.readByte;
+ this.readByteAt = JSIO.ArrayReader.prototype.readByteAt;
+ this.req = null;
+ this._typename = typename;
+ this._version = version;
+ this.status = "-none-";
+
+ var _IeGetBinResource = function(fileURL){
+ var binStream = this;
+ // http://stackoverflow.com/questions/1919972/how-do-i-access-xhr-responsebody-for-binary-data-from-javascript-in-ie
+
+ // my helper to convert from responseBody to a byte array
+ var convertResponseBodyToArray = function (binary) {
+ var byteArray = new Array;
+
+ try {
+ // very fast; very little work involved
+ byteArray = new VBArray(binary).toArray();
+ }
+ catch(err) {
+ // use the BinaryToArray VBScript
+ if (!JSIO.IEByteMapping) {
+ JSIO.IEByteMapping = {};
+ for ( var i = 0; i < 256; i++ ) {
+ for ( var j = 0; j < 256; j++ ) {
+ JSIO.IEByteMapping[ String.fromCharCode( i + j * 256 ) ] = [ i, j ];
+ }
+ }
+ }
+ var rawBytes = IEBinaryToArray_ByteStr(binary);
+ var lastAsc = IEBinaryToArray_ByteAsc_Last(binary);
+
+ for ( var i = 0; i < rawBytes.length; i++ ) {
+ byteArray.push.apply(byteArray, JSIO.IEByteMapping[ rawBytes.substr(i,1) ]);
+ }
+ if (lastAsc >= 0) byteArray.push(lastAsc);
+ }
+
+ return byteArray;
+ };
+
+ this.req = (function() {
+ if (window.XMLHttpRequest) return new window.XMLHttpRequest();
+ else if (window.ActiveXObject) {
+ // the many versions of IE's XML fetchers
+ var AXOs = [
+ 'MSXML2.XMLHTTP.6.0',
+ 'MSXML2.XMLHTTP.5.0',
+ 'MSXML2.XMLHTTP.4.0',
+ 'MSXML2.XMLHTTP.3.0',
+ 'MSXML2.XMLHTTP',
+ 'Microsoft.XMLHTTP',
+ 'MSXML.XMLHTTP'
+ ];
+ for (var i = 0; i < AXOs.length; i++) {
+ try { return new ActiveXObject(AXOs[i]); }
+ catch(e) { continue; }
+ }
+ }
+ return null;
+ })();
+ this.req.open("GET", fileURL, true);
+ this.req.setRequestHeader("Accept-Charset", "x-user-defined");
+ this.req.onreadystatechange = function(event){
+ if (binStream.req.readyState == 4) {
+ binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText;
+ if (binStream.req.status == 200) {
+ binStream.array = convertResponseBodyToArray(binStream.req.responseBody);
+ binStream.length = binStream.array.length;
+ if (binStream.length < 0) this._throwError('Failed to load "'+ fileURL + '" after converting');
+
+ if (typeof binStream.callback == "function") binStream.callback(binStream);
+ }
+ else {
+ binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status);
+ }
+ }
+ };
+ this.req.send();
+ };
+
+ var _NormalGetBinResource = function(fileURL){
+ var binStream= this;
+ this.req = new XMLHttpRequest();
+ this.req.open('GET', fileURL, true);
+ this.req.onreadystatechange = function(aEvt) {
+ if (binStream.req.readyState == 4) {
+ binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText;
+ if(binStream.req.status == 200){
+ var fileContents = binStream.req.responseText;
+ binStream.length = fileContents.byteLength;
+ binStream.array = fileContents.split('');
+ for ( var i = 0; i < binStream.array.length; i++ ) {
+ binStream.array[i] = binStream.array[i].charCodeAt(0) & 0xff;
+ }
+
+ if (typeof binStream.callback == "function") binStream.callback(binStream);
+ }
+ else {
+ binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status);
+ }
+ }
+ };
+ //XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
+ if (!!this.req.overrideMimeType) this.req.overrideMimeType('text/plain; charset=x-user-defined');
+ this.req.send(null);
+ };
+
+ // http://stackoverflow.com/questions/327685/is-there-a-way-to-read-binary-data-into-javascript
+ var _ArrayBufferGetBinResource = function(fileURL){
+ var binStream= this;
+ this.req = new XMLHttpRequest();
+ this.req.open('GET', fileURL, true);
+ this.req.onreadystatechange = function(aEvt) {
+ if (binStream.req.readyState == 4) {
+ binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText;
+ if(binStream.req.status == 200){
+ var fileContents = binStream.req.response;
+ binStream.length = fileContents.byteLength;
+ binStream.array = new Uint8Array(fileContents);
+ if (typeof binStream.callback == "function") binStream.callback(binStream);
+ }
+ else {
+ binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status);
+ }
+ }
+ };
+ this.req.responseType = 'arraybuffer';
+ // http://stackoverflow.com/questions/11284728/how-do-i-access-8-bit-binary-data-from-javascript-in-opera
+ if (!!this.req.overrideMimeType) this.req.overrideMimeType('application/octet-stream; charset=x-user-defined');
+ this.req.send(null);
+ };
+
+
+ if (typeof ArrayBuffer !== 'undefined') _ArrayBufferGetBinResource.apply(this, [url]);
+ else if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) _IeGetBinResource.apply(this, [url]);
+ else _NormalGetBinResource.apply(this, [url]);
+ };
+
+ bus.prototype = new JSIO._ByteReaderBase();
+
+ bus.prototype.readByte = function(){
+ var oneByte = this.readByteAt(this.position++);
+ return (oneByte === null || oneByte === undefined) ? null : oneByte;
+ };
+
+ JSIO.BinaryUrlStream = bus;
})();
@@ -786,147 +786,147 @@
// This work is licensed under the GPLv3.
(function(){
- var version = "2.0 2012Feb";
- var typename = "JSIO.TextDecoder";
-
- if ((typeof JSIO !== "object") ||
- (typeof JSIO.version !== "string") ||
- (JSIO.version.length < 3) ||
- (JSIO.version.substring(0,3) !== "2.0"))
- JSIO.throwError('This extension requires JSIO.core.js v2.0', typename);
-
- if (typeof JSIO._ByteReaderBase !== "function")
- JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
-
- var _ansi = function(reader) {
- if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.ANSI', 'ctor');
- this.byteReader = reader;
- this.charWidth = 1;
- this._version = version;
- this._typename = typename + ".ANSI";
- return this;
- };
-
- _ansi.prototype.readChar = function() {
- var code = this.byteReader.readByte();
- return (code < 0) ? null : String.fromCharCode(code);
- };
-
- _ansi.prototype.parseChar = function(code) {
- return (code < 0) ? null : String.fromCharCode(code);
- };
-
- var _utf16 = function(reader) {
- if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.UTF16', 'ctor');
- this.byteReader = reader;
- this.charWidth = 2;
- this.bomState = 0;
- this._version = version;
- this._typename = typename + ".UTF16";
- return this;
- };
-
- _utf16.prototype.readChar = function() {
- var b1 = this.byteReader.readByte();
- if (b1 < 0) return null;
- var b2 = this.byteReader.readByte();
- if (b2 < 0) this._throwError('Incomplete UTF16 character', null, 'readChar');
-
- if ((this.bomState === 0) && ((b1 + b2) == 509)) {
- this.bomState = (b2 == 254) ? 1 : 2;
-
- b1 = this.byteReader.readByte();
- if (b1 < 0) return null;
- b2 = this.byteReader.readByte();
- if (b2 < 0) this._throwError('Incomplete UTF16 character', null, 'readChar');
- }
- else {
- this.bomState = 1;
- }
- return this.parseChar(b1, b2);
- };
-
- _utf16.prototype.parseChar = function(b1, b2) {
- return String.fromCharCode( this.bomState == 1 ? (b2 << 8 | b1) : (b1 << 8 | b2) );
- }
-
- /* RFC 3629 */
- var _utf8 = function(reader) {
- if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.UTF8', 'ctor');
- this.byteReader = reader;
- this.charWidth = null;
- this.waitBom = true;
- this.strict = false;
- this.pendingChar = null;
- this._version = version;
- this._typename = typename + ".UTF8";
- return this;
- };
-
- _utf8.prototype.readChar = function() {
- var ch = null;
- do {
- if (this.pendingChar !== null) {
- ch = this.pendingChar;
- this.pendingChar = null;
- }
- else {
- var b1 = this.byteReader.readByte();
- if (b1 === null) return null;
-
- if ((b1 & 0x80) === 0) ch = String.fromCharCode(b1);
- else {
- var currentPrefix = 0xC0;
- var ttlBytes = 0;
- do {
- var mask = currentPrefix >> 1 | 0x80;
- if((b1 & mask) == currentPrefix) break;
- currentPrefix = currentPrefix >> 1 | 0x80;
- } while(++ttlBytes < 5);
-
- if (ttlBytes > 0) {
- var code;
- if (ttlBytes === 1) code = (b1 & 0x1F) << 6 | (this.byteReader.readByte() & 0x3F);
- else {
- code = code << 6*ttlBytes
- var bytes = this.byteReader.read(ttlBytes);
- for (var i = 0; i > ttlBytes; i++) {
- var bi = bytes[i];
- if ((bi & 0xC0) != 0x80) this._throwError('Invalid sequence character', null, 'readChar');
- code = (code << 6) | (bi & 0x3F);
- }
- }
-
- if (code <= 0xFFFF) {
- ch = (code == 0xFEFF && this.waitBom) ? null : String.fromCharCode(code);
- }
- else {
- var v = code - 0x10000;
- var w1 = 0xD800 | ((v >> 10) & 0x3FF);
- var w2 = 0xDC00 | (v & 0x3FF);
- this.pendingChar = String.fromCharCode(w2);
- ch = String.fromCharCode(w1);
- }
- }
- else {
- // a byte higher than 0x80.
- if (this.strict) this._throwError('Invalid character', null, 'readChar');
- // fall back to "super ascii" (eg IBM-437)
- else ch = String.fromCharCode(b1);
- }
- }
- }
- this.waitBom = false;
- } while(ch === null);
- return ch;
- };
-
- JSIO.TextDecoder = {
- Default : _ansi,
- ANSI : _ansi,
- UTF16 : _utf16,
- UTF8 : _utf8
- };
+ var version = "2.0 2012Feb";
+ var typename = "JSIO.TextDecoder";
+
+ if ((typeof JSIO !== "object") ||
+ (typeof JSIO.version !== "string") ||
+ (JSIO.version.length < 3) ||
+ (JSIO.version.substring(0,3) !== "2.0"))
+ JSIO.throwError('This extension requires JSIO.core.js v2.0', typename);
+
+ if (typeof JSIO._ByteReaderBase !== "function")
+ JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
+
+ var _ansi = function(reader) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.ANSI', 'ctor');
+ this.byteReader = reader;
+ this.charWidth = 1;
+ this._version = version;
+ this._typename = typename + ".ANSI";
+ return this;
+ };
+
+ _ansi.prototype.readChar = function() {
+ var code = this.byteReader.readByte();
+ return (code < 0) ? null : String.fromCharCode(code);
+ };
+
+ _ansi.prototype.parseChar = function(code) {
+ return (code < 0) ? null : String.fromCharCode(code);
+ };
+
+ var _utf16 = function(reader) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.UTF16', 'ctor');
+ this.byteReader = reader;
+ this.charWidth = 2;
+ this.bomState = 0;
+ this._version = version;
+ this._typename = typename + ".UTF16";
+ return this;
+ };
+
+ _utf16.prototype.readChar = function() {
+ var b1 = this.byteReader.readByte();
+ if (b1 < 0) return null;
+ var b2 = this.byteReader.readByte();
+ if (b2 < 0) this._throwError('Incomplete UTF16 character', null, 'readChar');
+
+ if ((this.bomState === 0) && ((b1 + b2) == 509)) {
+ this.bomState = (b2 == 254) ? 1 : 2;
+
+ b1 = this.byteReader.readByte();
+ if (b1 < 0) return null;
+ b2 = this.byteReader.readByte();
+ if (b2 < 0) this._throwError('Incomplete UTF16 character', null, 'readChar');
+ }
+ else {
+ this.bomState = 1;
+ }
+ return this.parseChar(b1, b2);
+ };
+
+ _utf16.prototype.parseChar = function(b1, b2) {
+ return String.fromCharCode( this.bomState == 1 ? (b2 << 8 | b1) : (b1 << 8 | b2) );
+ }
+
+ /* RFC 3629 */
+ var _utf8 = function(reader) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.UTF8', 'ctor');
+ this.byteReader = reader;
+ this.charWidth = null;
+ this.waitBom = true;
+ this.strict = false;
+ this.pendingChar = null;
+ this._version = version;
+ this._typename = typename + ".UTF8";
+ return this;
+ };
+
+ _utf8.prototype.readChar = function() {
+ var ch = null;
+ do {
+ if (this.pendingChar !== null) {
+ ch = this.pendingChar;
+ this.pendingChar = null;
+ }
+ else {
+ var b1 = this.byteReader.readByte();
+ if (b1 === null) return null;
+
+ if ((b1 & 0x80) === 0) ch = String.fromCharCode(b1);
+ else {
+ var currentPrefix = 0xC0;
+ var ttlBytes = 0;
+ do {
+ var mask = currentPrefix >> 1 | 0x80;
+ if((b1 & mask) == currentPrefix) break;
+ currentPrefix = currentPrefix >> 1 | 0x80;
+ } while(++ttlBytes < 5);
+
+ if (ttlBytes > 0) {
+ var code;
+ if (ttlBytes === 1) code = (b1 & 0x1F) << 6 | (this.byteReader.readByte() & 0x3F);
+ else {
+ code = code << 6*ttlBytes
+ var bytes = this.byteReader.read(ttlBytes);
+ for (var i = 0; i > ttlBytes; i++) {
+ var bi = bytes[i];
+ if ((bi & 0xC0) != 0x80) this._throwError('Invalid sequence character', null, 'readChar');
+ code = (code << 6) | (bi & 0x3F);
+ }
+ }
+
+ if (code <= 0xFFFF) {
+ ch = (code == 0xFEFF && this.waitBom) ? null : String.fromCharCode(code);
+ }
+ else {
+ var v = code - 0x10000;
+ var w1 = 0xD800 | ((v >> 10) & 0x3FF);
+ var w2 = 0xDC00 | (v & 0x3FF);
+ this.pendingChar = String.fromCharCode(w2);
+ ch = String.fromCharCode(w1);
+ }
+ }
+ else {
+ // a byte higher than 0x80.
+ if (this.strict) this._throwError('Invalid character', null, 'readChar');
+ // fall back to "super ascii" (eg IBM-437)
+ else ch = String.fromCharCode(b1);
+ }
+ }
+ }
+ this.waitBom = false;
+ } while(ch === null);
+ return ch;
+ };
+
+ JSIO.TextDecoder = {
+ Default : _ansi,
+ ANSI : _ansi,
+ UTF16 : _utf16,
+ UTF8 : _utf8
+ };
})();
@@ -962,122 +962,122 @@
(function(){
- var version = "2.0 2012Feb";
- var typename = "JSIO.TextReader";
-
- if (typeof JSIO._ByteReaderBase !== "function")
- JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
-
- var tr = function(textDecoder) {
- if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor');
- this.decoder = textDecoder;
- this._version = version;
- this._typename = typename;
- this.unreads = [];
- };
-
- // read one char
- tr.prototype.readChar = function() {
- return (this.unreads.length > 0) ? this.unreads.pop() : this.decoder.readChar();
- };
-
- // read a length of data
- tr.prototype.read = function(n) {
- // ANSI makes this easy
- if (this.decoder.charWidth === 1) return JSIO.massApply(String.fromCharCode, new String, this.decoder.byteReader.read(n), true);
-
- var s = "";
- for (vari=0; i<n; i++) {
- var ch = this.readChar();
- if (ch !== null) s+= ch;
- else break;
- }
- return s;
- };
-
- tr.prototype.unreadChar = function(ch) {
- this.unreads.push(ch);
- };
-
- tr.prototype.readToEnd = function() {
- // ANSI makes this easy
- if (this.decoder.charWidth === 1) return JSIO.massApply(String.fromCharCode, new String, this.decoder.byteReader.readToEnd(n), true);
-
- var slarge = "";
- var s = "";
- var c = 0;
- var ch = this.readChar();
- while(ch !== null) {
- s += ch;
- c++;
- if(c >= 32768) {
- slarge += s;
- s = "";
- c = 0;
- }
- ch = this.readChar();
- }
- return slarge + s;
- };
-
- tr.prototype.beginReadToEnd = function(callback) {
- // ANSI makes this easy
- if (this.decoder.charWidth === 1) {
- this.decoder.byteReader.beginReadToEnd(function (bytes) {
- callback( JSIO.massApply(String.fromCharCode, new String, bytes, true) );
- });
- return null;
- }
-
- var slarge = "";
- var s = "";
- var txtrdr = this;
-
- var readBatchAsync = function() {
- var c = 0;
- var ch = txtrdr.readChar();
- while(ch !== null) {
- s += ch;c++;
- if(c >= 32768) {
- slarge += s;
- s = "";
- break;
- }
- ch = txtrdr.readChar();
- }
- if (ch!==null){
- setTimeout(readBatchAsync, 1);
- }
- else {
- callback(slarge+s);
- }
- };
-
- // kickoff
- setTimeout(readBatchAsync, 1); // always async, in ALL situations
- return null;
- };
-
- tr.prototype.readLine = function() {
- var s = "";
- var ch = this.readChar();
- if (ch === null) return null;
-
- while(ch != "\r" && ch != "\n") {
- s += ch;
- ch = this.readChar();
- if (ch === null) return s;
- }
- if(ch == "\r") {
- ch = this.readChar();
- if(ch !== null && ch != "\n"){
- this.unreadChar(ch);
- }
- }
- return s;
- };
-
- JSIO.TextReader = tr;
+ var version = "2.0 2012Feb";
+ var typename = "JSIO.TextReader";
+
+ if (typeof JSIO._ByteReaderBase !== "function")
+ JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
+
+ var tr = function(textDecoder) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor');
+ this.decoder = textDecoder;
+ this._version = version;
+ this._typename = typename;
+ this.unreads = [];
+ };
+
+ // read one char
+ tr.prototype.readChar = function() {
+ return (this.unreads.length > 0) ? this.unreads.pop() : this.decoder.readChar();
+ };
+
+ // read a length of data
+ tr.prototype.read = function(n) {
+ // ANSI makes this easy
+ if (this.decoder.charWidth === 1) return JSIO.massApply(String.fromCharCode, new String, this.decoder.byteReader.read(n), true);
+
+ var s = "";
+ for (vari=0; i<n; i++) {
+ var ch = this.readChar();
+ if (ch !== null) s+= ch;
+ else break;
+ }
+ return s;
+ };
+
+ tr.prototype.unreadChar = function(ch) {
+ this.unreads.push(ch);
+ };
+
+ tr.prototype.readToEnd = function() {
+ // ANSI makes this easy
+ if (this.decoder.charWidth === 1) return JSIO.massApply(String.fromCharCode, new String, this.decoder.byteReader.readToEnd(n), true);
+
+ var slarge = "";
+ var s = "";
+ var c = 0;
+ var ch = this.readChar();
+ while(ch !== null) {
+ s += ch;
+ c++;
+ if(c >= 32768) {
+ slarge += s;
+ s = "";
+ c = 0;
+ }
+ ch = this.readChar();
+ }
+ return slarge + s;
+ };
+
+ tr.prototype.beginReadToEnd = function(callback) {
+ // ANSI makes this easy
+ if (this.decoder.charWidth === 1) {
+ this.decoder.byteReader.beginReadToEnd(function (bytes) {
+ callback( JSIO.massApply(String.fromCharCode, new String, bytes, true) );
+ });
+ return null;
+ }
+
+ var slarge = "";
+ var s = "";
+ var txtrdr = this;
+
+ var readBatchAsync = function() {
+ var c = 0;
+ var ch = txtrdr.readChar();
+ while(ch !== null) {
+ s += ch;c++;
+ if(c >= 32768) {
+ slarge += s;
+ s = "";
+ break;
+ }
+ ch = txtrdr.readChar();
+ }
+ if (ch!==null){
+ setTimeout(readBatchAsync, 1);
+ }
+ else {
+ callback(slarge+s);
+ }
+ };
+
+ // kickoff
+ setTimeout(readBatchAsync, 1); // always async, in ALL situations
+ return null;
+ };
+
+ tr.prototype.readLine = function() {
+ var s = "";
+ var ch = this.readChar();
+ if (ch === null) return null;
+
+ while(ch != "\r" && ch != "\n") {
+ s += ch;
+ ch = this.readChar();
+ if (ch === null) return s;
+ }
+ if(ch == "\r") {
+ ch = this.readChar();
+ if(ch !== null && ch != "\n"){
+ this.unreadChar(ch);
+ }
+ }
+ return s;
+ };
+
+ JSIO.TextReader = tr;
})();
@@ -1116,111 +1116,111 @@
// This work is licensed under the GPLv3.
(function(){
- var version = "2.0 2012Feb";
- var typename = "JSIO.Crc32";
-
- if (typeof JSIO._ByteReaderBase !== "function")
- JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
-
- JSIO.crc32Table = null;
- JSIO.crc32Polynomial = 0xEDB88320;
-
- var crc32TableCalc = function () {
- // do this once only, for all instances
- if (JSIO.crc32Table) return;
- JSIO.crc32Table = new Array(256);
- for (var i = 0; i < 256; i++) {
- var c=i;
- for (var k = 0; k < 8; k++) {
- if ((c & 1) == 1) c = JSIO.crc32Polynomial ^ (c>>>1);
- else c >>>= 1;
- }
- JSIO.crc32Table[i] = c;
- }
- };
-
- JSIO.computeCrc32 = function(str) {
- crc32TableCalc(); // once
- var c = 0xFFFFFFFF;
- var sL = str.length;
- if (typeof str == "object") {
- for (var n1=0; n1<sL; n1++) {
- c = JSIO.crc32Table[(c&0xff) ^ str[n1]] ^ (c>>>8);
- }
- } else {
- for (var n2=0; n2<sL; n2++) {
- c = JSIO.crc32Table[(c&0xff) ^ str.charCodeAt(n2)] ^ (c>>>8);
- }
- }
- c ^= 0xFFFFFFFF;
- if (c < 0) c += 0xFFFFFFFF+1;
- return c;
- };
-
- // =======================================================
- var _crc32 = function() {
- if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor');
- crc32TableCalc(); // once
- this._typename = typename;
- this._version = version;
- this._runningCrc32 = 0xFFFFFFFF;
- };
-
- _crc32.prototype.slurpByte = function(b) {
- var r = this._runningCrc32;
- this._runningCrc32 = r>>>8 ^ JSIO.crc32Table[b ^ (r & 0x000000FF)];
- };
-
- _crc32.prototype.result = function() {
- var c = this._runningCrc32 ^ 0xFFFFFFFF;
- if (c < 0) c += 0xFFFFFFFF+1;
- return c;
- };
- // =======================================================
-
-
-
- var _crc32CalculatingReader = function(reader) {
- if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.Crc32Reader', 'ctor');
- this._byteReader = reader;
- this._typename = "JSIO.Crc32Reader";
- this._version = version;
- this._crc32 = new JSIO.Crc32();
- };
-
- _crc32CalculatingReader.prototype = new JSIO._ByteReaderBase();
-
- _crc32CalculatingReader.prototype.readByte = function() {
- var b = this._byteReader.readByte();
- if (b !== null) this._crc32.slurpByte(b);
- this.position++;
- return b;
- };
-
- _crc32CalculatingReader.prototype.read = function(len) {
- if (len === 0) return [];
- var bytes = this._byteReader.read(len);
- len = bytes.length;
-
- var tbl = JSIO.crc32Table;
- var r = this._crc32._runningCrc32;
- var t;
- for (var i = 0; i < len; i++) {
- t = tbl[ bytes[i] ^ (r & 0x000000FF) ];
- r = (r >>> 8) ^ t;
- }
- this._crc32._runningCrc32 = r;
-
- this.position += len;
- return bytes;
- }
-
- _crc32CalculatingReader.prototype.crc32 = function() {
- return this._crc32.result();
- };
-
- JSIO.Crc32 = _crc32;
- JSIO.Crc32Reader = _crc32CalculatingReader;
+ var version = "2.0 2012Feb";
+ var typename = "JSIO.Crc32";
+
+ if (typeof JSIO._ByteReaderBase !== "function")
+ JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
+
+ JSIO.crc32Table = null;
+ JSIO.crc32Polynomial = 0xEDB88320;
+
+ var crc32TableCalc = function () {
+ // do this once only, for all instances
+ if (JSIO.crc32Table) return;
+ JSIO.crc32Table = new Array(256);
+ for (var i = 0; i < 256; i++) {
+ var c=i;
+ for (var k = 0; k < 8; k++) {
+ if ((c & 1) == 1) c = JSIO.crc32Polynomial ^ (c>>>1);
+ else c >>>= 1;
+ }
+ JSIO.crc32Table[i] = c;
+ }
+ };
+
+ JSIO.computeCrc32 = function(str) {
+ crc32TableCalc(); // once
+ var c = 0xFFFFFFFF;
+ var sL = str.length;
+ if (typeof str == "object") {
+ for (var n1=0; n1<sL; n1++) {
+ c = JSIO.crc32Table[(c&0xff) ^ str[n1]] ^ (c>>>8);
+ }
+ } else {
+ for (var n2=0; n2<sL; n2++) {
+ c = JSIO.crc32Table[(c&0xff) ^ str.charCodeAt(n2)] ^ (c>>>8);
+ }
+ }
+ c ^= 0xFFFFFFFF;
+ if (c < 0) c += 0xFFFFFFFF+1;
+ return c;
+ };
+
+ // =======================================================
+ var _crc32 = function() {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor');
+ crc32TableCalc(); // once
+ this._typename = typename;
+ this._version = version;
+ this._runningCrc32 = 0xFFFFFFFF;
+ };
+
+ _crc32.prototype.slurpByte = function(b) {
+ var r = this._runningCrc32;
+ this._runningCrc32 = r>>>8 ^ JSIO.crc32Table[b ^ (r & 0x000000FF)];
+ };
+
+ _crc32.prototype.result = function() {
+ var c = this._runningCrc32 ^ 0xFFFFFFFF;
+ if (c < 0) c += 0xFFFFFFFF+1;
+ return c;
+ };
+ // =======================================================
+
+
+
+ var _crc32CalculatingReader = function(reader) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.Crc32Reader', 'ctor');
+ this._byteReader = reader;
+ this._typename = "JSIO.Crc32Reader";
+ this._version = version;
+ this._crc32 = new JSIO.Crc32();
+ };
+
+ _crc32CalculatingReader.prototype = new JSIO._ByteReaderBase();
+
+ _crc32CalculatingReader.prototype.readByte = function() {
+ var b = this._byteReader.readByte();
+ if (b !== null) this._crc32.slurpByte(b);
+ this.position++;
+ return b;
+ };
+
+ _crc32CalculatingReader.prototype.read = function(len) {
+ if (len === 0) return [];
+ var bytes = this._byteReader.read(len);
+ len = bytes.length;
+
+ var tbl = JSIO.crc32Table;
+ var r = this._crc32._runningCrc32;
+ var t;
+ for (var i = 0; i < len; i++) {
+ t = tbl[ bytes[i] ^ (r & 0x000000FF) ];
+ r = (r >>> 8) ^ t;
+ }
+ this._crc32._runningCrc32 = r;
+
+ this.position += len;
+ return bytes;
+ }
+
+ _crc32CalculatingReader.prototype.crc32 = function() {
+ return this._crc32.result();
+ };
+
+ JSIO.Crc32 = _crc32;
+ JSIO.Crc32Reader = _crc32CalculatingReader;
})();
@@ -1263,436 +1263,436 @@
(function(){
- var version = "2.0 2012Feb";
- var typename = "JSIO.InflatingReader";
-
- if (typeof JSIO._ByteReaderBase !== "function")
- JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
-
- // =======================================================
- // _InternalBitReader is used internally in the InflatingReader class.
- //
-
- JSIO.bitShiftTable = null;
-
- var bitShiftTableCalc = function () {
- // do this once only, for all instances
- if (JSIO.bitShiftTable) return;
-
- var bits = 8;
- JSIO.bitShiftTable = {
- LSB: new Array(bits),
- MSB: new Array(bits)
- }
- for (var bp = 0; bp < bits; bp++) {
- var lim = bits - bp;
- JSIO.bitShiftTable.LSB[bp] = new Array(lim);
- JSIO.bitShiftTable.MSB[bp] = new Array(lim);
-
- var maskLSB = 1 << bp;
- var maskMSB = 1 << lim-1;
- for (var len = 1; len <= lim; len++) {
- JSIO.bitShiftTable.LSB[bp][len-1] = maskLSB;
- JSIO.bitShiftTable.MSB[bp][len-1] = maskMSB;
- maskLSB |= 1 << bp+len;
- maskMSB |= 1 << lim-len-1;
- }
- }
- };
-
- var _InternalBitReader = function(reader) {
- if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename + '._InternalBitReader.ctor');
- this.bitsLength = 0;
- this.bits = 0;
- this.byteReader = reader;
- this._typeName = typename + "._InternalBitReader";
- this._version = version;
- bitShiftTableCalc();
- };
-
- _InternalBitReader.prototype._throwError = JSIO.throwError;
-
- _InternalBitReader.prototype.readBit = function() {
- if (this.bitsLength === 0) {
- var nextByte = this.byteReader.readByte();
- if (nextByte === null) this._throwError('Unexpected end of stream', null, 'readBit');
- this.bits = nextByte;
- this.bitsLength = 8;
- }
-
- var bit = (this.bits & 1 << 8-this.bitsLength) !== 0;
- this.bitsLength--;
- return bit;
- };
-
- _InternalBitReader.prototype.align = function() { this.bitsLength = 0; };
-
- _InternalBitReader.prototype.readBits = function(len, type) {
- var data = 0;
- type = type || 'LSB';
- var tbl = JSIO.bitShiftTable[type];
- var dl = 0;
- while (len > 0) {
- if (this.bitsLength === 0) {
- var nextByte = this.byteReader.readByte();
- if (nextByte === null) this._throwError('Unexpected end of stream', null, 'read'+type);
- this.bits = nextByte;
- this.bitsLength = 8;
- }
-
- var maskLen = (this.bitsLength >= len) ? len : this.bitsLength;
- var bitsPos = 8-this.bitsLength;
- data |= (this.bits & tbl[bitsPos][maskLen-1]) >>> bitsPos << dl;
-
- dl += maskLen;
- len -= maskLen;
- this.bitsLength -= maskLen;
- };
- return data;
- };
-
- _InternalBitReader.prototype.readLSB = function(len) { return this.readBits(len, 'LSB'); }
- _InternalBitReader.prototype.readMSB = function(len) { return this.readBits(len, 'MSB'); }
-
- //
- // =======================================================
-
-
- /* inflating ByteReader - RFC 1951 */
- var _inflatingReader = function(reader) {
- if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor');
- this._byteReader = reader;
- this._bitReader = new _InternalBitReader(reader);
- this._buffer = [];
- this._bufferPosition = 0;
- this._state = 0;
- this._blockFinal = false;
- this._typeName = typename;
- this._version = version;
- return this;
- };
-
-
- // shared fns and variables
-
- var staticCodes = null;
- var staticDistances = null;
-
- var clenMap = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
-
- var buildCodes = function(lengths){
- var i=0;
- var codes = new Array(lengths.length);
- var maxBits = lengths[0];
- for (i=1; i<lengths.length; i++) {
- if (maxBits < lengths[i]) maxBits = lengths[i];
- }
-
- var bitLengthsCount = new Array(maxBits + 1);
- for (i=0; i<=maxBits; i++) bitLengthsCount[i]=0;
-
- for (i=0; i<lengths.length; i++) {
- ++bitLengthsCount[lengths[i]];
- }
-
- var nextCode = new Array(maxBits + 1);
- var code = 0;
- bitLengthsCount[0] = 0;
- for (var bits=1; bits<=maxBits; bits++) {
- code = (code + bitLengthsCount[bits - 1]) << 1;
- nextCode[bits] = code;
- }
-
- for (i=0; i<codes.length; i++) {
- var len = lengths[i];
- if (len !== 0) {
- codes[i] = nextCode[len];
- nextCode[len]++;
- }
- }
- return codes;
- };
-
- var buildTree = function(codes, lengths){
- var nonEmptyCodes = [];
- for(var i=0; i<codes.length; ++i) {
- if(lengths[i] > 0) {
- var code = {};
- code.bits = codes[i];
- code.length = lengths[i];
- code.index = i;
- nonEmptyCodes.push(code);
- }
- }
- return buildTreeBranch(nonEmptyCodes, 0, 0);
- };
-
-
- var buildTreeBranch = function(codes, prefix, prefixLength){
- if (codes.length === 0) return null;
-
- var zeros = [];
- var ones = [];
- var branch = {};
- branch.isLeaf = false;
- for(var i=0; i<codes.length; ++i) {
- if (codes[i].length == prefixLength && codes[i].bits == prefix) {
- branch.isLeaf = true;
- branch.index = codes[i].index;
- break;
- }
- else {
- var nextBit = ((codes[i].bits >> (codes[i].length - prefixLength - 1)) & 1) > 0;
- if (nextBit) ones.push(codes[i]);
- else zeros.push(codes[i]);
- }
- }
- if(!branch.isLeaf) {
- branch.zero = buildTreeBranch(zeros, (prefix << 1), prefixLength + 1);
- branch.one = buildTreeBranch(ones, (prefix << 1) | 1, prefixLength + 1);
- }
- return branch;
- };
-
-
- var encodedLengthStart = [3,4,5,6,7,8,9,10,
- 11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,
- 115,131,163,195,227,258];
-
- var encodedLengthAdditionalBits = [0,0,0,0,0,0,0,0,
- 1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0];
-
- var encodedDistanceStart = [1,2,3,4, 5,7,9, 13,17,25, 33,49,65,
- 97,129,193,257,385,513,769,1025,1537,2049,
- 3073,4097,6145,8193,12289,16385,24577];
-
- var encodedDistanceAdditionalBits = [0,0,0,0,1,1,2,2,3,3,4,4,
- 5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];
-
-
- var readDynamicTrees = function(bitReader){
- var hlit = bitReader.readLSB(5) + 257;
- var hdist = bitReader.readLSB(5) + 1;
- var hclen = bitReader.readLSB(4) + 4;
- var clen = new Array(19);
- var i;
- for (i=0; i<clen.length; i++) { clen[i] = 0; }
- for (i=0; i<hclen; i++) { clen[clenMap[i]] = bitReader.readLSB(3); }
-
- var clenCodes = buildCodes(clen);
- var clenTree = buildTree(clenCodes, clen);
-
- var lengthsSequence = [];
- while(lengthsSequence.length < hlit + hdist) {
- var p = clenTree;
- while(!p.isLeaf) { p = bitReader.readBit() ? p.one : p.zero; }
-
- var code = p.index;
- if (code <= 15) lengthsSequence.push(code);
- else if (code == 16) {
- // TODO: replace with faster repeat algorythm
- var repeat = bitReader.readLSB(2) + 3;
- for(var q=0; q<repeat; ++q){
- lengthsSequence.push(lengthsSequence[lengthsSequence.length - 1]);
- }
- }
- else if (code == 17) {
- var repeat1 = bitReader.readLSB(3) + 3;
- for(var q1=0; q1<repeat1; ++q1) {
- lengthsSequence.push(0);
- }
- }
- else if (code == 18) {
- var repeat2 = bitReader.readLSB(7) + 11;
- for(var q2=0; q2<repeat2; ++q2){
- lengthsSequence.push(0);
- }
- }
- }
-
- var codesLengths = lengthsSequence.slice(0, hlit);
- var codes = buildCodes(codesLengths);
- var distancesLengths = lengthsSequence.slice(hlit, hlit + hdist);
- var distances = buildCodes(distancesLengths);
-
- return {
- codesTree : buildTree(codes, codesLengths),
- distancesTree : buildTree(distances, distancesLengths)
- };
- };
-
-
- _inflatingReader.prototype = new JSIO._ByteReaderBase();
-
-
- // internal instance fns
- _inflatingReader.prototype._decodeItem = function() {
- if (this._state == 2) return null; // end-of-blocks
-
- var item;
- if(this._state === 0) {
- this._blockFinal = this._bitReader.readBit();
- var blockType = this._bitReader.readLSB(2);
- switch(blockType) {
- case 0:
- this._bitReader.align();
- var len = this._bitReader.readLSB(16); // low-byte first, as opposed to readNumber's HBF
- var nlen = this._bitReader.readLSB(16);
- if ((len & ~nlen) != len) this._throwError('Invalid block type 0 length', null, '_decodeItem');
-
- item = {};
- item.itemType = 0;
- item.array = this._bitReader.byteReader.read(len);
- if (item.array.length < len) this._throwError('Incomplete block', null, '_decodeItem');
- if (this._blockFinal) this._state = 2;
- return item;
- case 1:
- this._codesTree = staticCodes;
- this._distancesTree = staticDistances;
- this._state = 1;
- break;
- case 2:
- var dTrees = readDynamicTrees(this._bitReader);
- this._codesTree = dTrees.codesTree;
- this._distancesTree = dTrees.distancesTree;
- this._state = 1;
- break;
- default:
- this._throwError('Invalid block type ('+ blockType +')', null, '_decodeItem');
- }
- }
-
- item = {};
-
- var p = this._codesTree;
- while (!p.isLeaf) { p = this._bitReader.readBit() ? p.one : p.zero; }
- if(p.index < 256) {
- item.itemType = 2;
- item.symbol = p.index;
- } else if(p.index > 256) {
- var lengthCode = p.index;
- if(lengthCode > 285) this._throwError('Invalid length code', null, '_decodeItem');
-
- var length = encodedLengthStart[lengthCode - 257];
- if(encodedLengthAdditionalBits[lengthCode - 257] > 0) {
- length += this._bitReader.readLSB(encodedLengthAdditionalBits[lengthCode - 257]);
- }
-
- p = this._distancesTree;
- while (!p.isLeaf) { p = this._bitReader.readBit() ? p.one : p.zero; }
-
- var distanceCode = p.index;
- var distance = encodedDistanceStart[distanceCode];
- if (encodedDistanceAdditionalBits[distanceCode] > 0)
- distance += this._bitReader.readLSB(encodedDistanceAdditionalBits[distanceCode]);
-
- item.itemType = 3;
- item.distance = distance;
- item.length = length;
- } else {
- item.itemType = 1;
- this._state = this._blockFinal ? 2 : 0; // EOB
- }
- return item;
- };
-
-
-
- // public instance functions
-
- _inflatingReader.prototype.readByte = function() {
- var byte = this.read(1)[0];
- return (byte === null || byte === undefined) ? null : byte;
- };
-
- _inflatingReader.prototype.read = function(len) {
- var b = this._buffer; // (since we use this so much...)
-
- // Keep reading until we get to the right length
- while (this._bufferPosition+len > b.length) {
- var item = this._decodeItem();
- if (item === null) { // EOF
- len = b.length - this._bufferPosition;
- break;
- }
- switch(item.itemType) {
- case 0:
- JSIO.massApply(b.push, b, item.array);
- break;
- case 2:
- b.push(item.symbol);
- break;
- case 3:
- var j = b.length - item.distance;
- if (item.distance >= item.length)
- JSIO.massApply(b.push, b, b.slice(j, j+item.length));
- // sometimes DEFLATE tries some trickery with "look-ahead" compression
- else {
- // this is basically just a repetition of the same string, plus some possible cutoff
- var count = parseInt(item.length / item.distance);
- var repArr = b.slice(j);
- // http://stackoverflow.com/questions/202605/repeat-string-javascript/5450113#5450113
- while (count > 0) {
- if (count & 1) JSIO.massApply( b.push, b, repArr);
- if (count >>= 1) JSIO.massApply(repArr.push, repArr, repArr);
- }
- // add any remaining cutoff
- var r;
- if (r = item.length % item.distance)
- JSIO.massApply(b.push, b, b.slice(j, j+r));
- }
- break;
- }
- }
- var bytes = b.slice(this._bufferPosition, this._bufferPosition+len);
- this._bufferPosition += len;
- this.position += len;
-
- if (this._bufferPosition > 0xC000) {
- var shift = b.length - 0x8000;
- if (shift > this._bufferPosition) shift = this._bufferPosition;
- b.splice(0, shift);
- this._bufferPosition -= shift;
- }
-
- return bytes;
- };
-
- // initialization routine - once per type
- (function(){
-
- var codes = new Array(288);
- var codesLengths = new Array(288);
- var i=0;
- for ( i = 0; i <= 143; i++) {
- codes[i] = 0x0030 + i;
- codesLengths[i] = 8;
- }
- for ( i = 144; i <= 255; i++) {
- codes[i] = 0x0190 + i - 144;
- codesLengths[i] = 9;
- }
- for ( i = 256; i <= 279; i++) {
- codes[i] = 0x0000 + i - 256;
- codesLengths[i] = 7;
- }
- for ( i = 280; i <= 287; i++) {
- codes[i] = 0x00C0 + i - 280;
- codesLengths[i] = 8;
- }
- staticCodes = buildTree(codes, codesLengths);
-
- var distances = new Array(32);
- var distancesLengths = new Array(32);
- for ( i = 0; i <= 31; i++) {
- distances[i] = i;
- distancesLengths[i] = 5;
- }
- staticDistances = buildTree(distances, distancesLengths);
- })();
-
-
- JSIO.InflatingReader = _inflatingReader;
+ var version = "2.0 2012Feb";
+ var typename = "JSIO.InflatingReader";
+
+ if (typeof JSIO._ByteReaderBase !== "function")
+ JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
+
+ // =======================================================
+ // _InternalBitReader is used internally in the InflatingReader class.
+ //
+
+ JSIO.bitShiftTable = null;
+
+ var bitShiftTableCalc = function () {
+ // do this once only, for all instances
+ if (JSIO.bitShiftTable) return;
+
+ var bits = 8;
+ JSIO.bitShiftTable = {
+ LSB: new Array(bits),
+ MSB: new Array(bits)
+ }
+ for (var bp = 0; bp < bits; bp++) {
+ var lim = bits - bp;
+ JSIO.bitShiftTable.LSB[bp] = new Array(lim);
+ JSIO.bitShiftTable.MSB[bp] = new Array(lim);
+
+ var maskLSB = 1 << bp;
+ var maskMSB = 1 << lim-1;
+ for (var len = 1; len <= lim; len++) {
+ JSIO.bitShiftTable.LSB[bp][len-1] = maskLSB;
+ JSIO.bitShiftTable.MSB[bp][len-1] = maskMSB;
+ maskLSB |= 1 << bp+len;
+ maskMSB |= 1 << lim-len-1;
+ }
+ }
+ };
+
+ var _InternalBitReader = function(reader) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename + '._InternalBitReader.ctor');
+ this.bitsLength = 0;
+ this.bits = 0;
+ this.byteReader = reader;
+ this._typeName = typename + "._InternalBitReader";
+ this._version = version;
+ bitShiftTableCalc();
+ };
+
+ _InternalBitReader.prototype._throwError = JSIO.throwError;
+
+ _InternalBitReader.prototype.readBit = function() {
+ if (this.bitsLength === 0) {
+ var nextByte = this.byteReader.readByte();
+ if (nextByte === null) this._throwError('Unexpected end of stream', null, 'readBit');
+ this.bits = nextByte;
+ this.bitsLength = 8;
+ }
+
+ var bit = (this.bits & 1 << 8-this.bitsLength) !== 0;
+ this.bitsLength--;
+ return bit;
+ };
+
+ _InternalBitReader.prototype.align = function() { this.bitsLength = 0; };
+
+ _InternalBitReader.prototype.readBits = function(len, type) {
+ var data = 0;
+ type = type || 'LSB';
+ var tbl = JSIO.bitShiftTable[type];
+ var dl = 0;
+ while (len > 0) {
+ if (this.bitsLength === 0) {
+ var nextByte = this.byteReader.readByte();
+ if (nextByte === null) this._throwError('Unexpected end of stream', null, 'read'+type);
+ this.bits = nextByte;
+ this.bitsLength = 8;
+ }
+
+ var maskLen = (this.bitsLength >= len) ? len : this.bitsLength;
+ var bitsPos = 8-this.bitsLength;
+ data |= (this.bits & tbl[bitsPos][maskLen-1]) >>> bitsPos << dl;
+
+ dl += maskLen;
+ len -= maskLen;
+ this.bitsLength -= maskLen;
+ };
+ return data;
+ };
+
+ _InternalBitReader.prototype.readLSB = function(len) { return this.readBits(len, 'LSB'); }
+ _InternalBitReader.prototype.readMSB = function(len) { return this.readBits(len, 'MSB'); }
+
+ //
+ // =======================================================
+
+
+ /* inflating ByteReader - RFC 1951 */
+ var _inflatingReader = function(reader) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor');
+ this._byteReader = reader;
+ this._bitReader = new _InternalBitReader(reader);
+ this._buffer = [];
+ this._bufferPosition = 0;
+ this._state = 0;
+ this._blockFinal = false;
+ this._typeName = typename;
+ this._version = version;
+ return this;
+ };
+
+
+ // shared fns and variables
+
+ var staticCodes = null;
+ var staticDistances = null;
+
+ var clenMap = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
+
+ var buildCodes = function(lengths){
+ var i=0;
+ var codes = new Array(lengths.length);
+ var maxBits = lengths[0];
+ for (i=1; i<lengths.length; i++) {
+ if (maxBits < lengths[i]) maxBits = lengths[i];
+ }
+
+ var bitLengthsCount = new Array(maxBits + 1);
+ for (i=0; i<=maxBits; i++) bitLengthsCount[i]=0;
+
+ for (i=0; i<lengths.length; i++) {
+ ++bitLengthsCount[lengths[i]];
+ }
+
+ var nextCode = new Array(maxBits + 1);
+ var code = 0;
+ bitLengthsCount[0] = 0;
+ for (var bits=1; bits<=maxBits; bits++) {
+ code = (code + bitLengthsCount[bits - 1]) << 1;
+ nextCode[bits] = code;
+ }
+
+ for (i=0; i<codes.length; i++) {
+ var len = lengths[i];
+ if (len !== 0) {
+ codes[i] = nextCode[len];
+ nextCode[len]++;
+ }
+ }
+ return codes;
+ };
+
+ var buildTree = function(codes, lengths){
+ var nonEmptyCodes = [];
+ for(var i=0; i<codes.length; ++i) {
+ if(lengths[i] > 0) {
+ var code = {};
+ code.bits = codes[i];
+ code.length = lengths[i];
+ code.index = i;
+ nonEmptyCodes.push(code);
+ }
+ }
+ return buildTreeBranch(nonEmptyCodes, 0, 0);
+ };
+
+
+ var buildTreeBranch = function(codes, prefix, prefixLength){
+ if (codes.length === 0) return null;
+
+ var zeros = [];
+ var ones = [];
+ var branch = {};
+ branch.isLeaf = false;
+ for(var i=0; i<codes.length; ++i) {
+ if (codes[i].length == prefixLength && codes[i].bits == prefix) {
+ branch.isLeaf = true;
+ branch.index = codes[i].index;
+ break;
+ }
+ else {
+ var nextBit = ((codes[i].bits >> (codes[i].length - prefixLength - 1)) & 1) > 0;
+ if (nextBit) ones.push(codes[i]);
+ else zeros.push(codes[i]);
+ }
+ }
+ if(!branch.isLeaf) {
+ branch.zero = buildTreeBranch(zeros, (prefix << 1), prefixLength + 1);
+ branch.one = buildTreeBranch(ones, (prefix << 1) | 1, prefixLength + 1);
+ }
+ return branch;
+ };
+
+
+ var encodedLengthStart = [3,4,5,6,7,8,9,10,
+ 11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,
+ 115,131,163,195,227,258];
+
+ var encodedLengthAdditionalBits = [0,0,0,0,0,0,0,0,
+ 1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0];
+
+ var encodedDistanceStart = [1,2,3,4, 5,7,9, 13,17,25, 33,49,65,
+ 97,129,193,257,385,513,769,1025,1537,2049,
+ 3073,4097,6145,8193,12289,16385,24577];
+
+ var encodedDistanceAdditionalBits = [0,0,0,0,1,1,2,2,3,3,4,4,
+ 5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];
+
+
+ var readDynamicTrees = function(bitReader){
+ var hlit = bitReader.readLSB(5) + 257;
+ var hdist = bitReader.readLSB(5) + 1;
+ var hclen = bitReader.readLSB(4) + 4;
+ var clen = new Array(19);
+ var i;
+ for (i=0; i<clen.length; i++) { clen[i] = 0; }
+ for (i=0; i<hclen; i++) { clen[clenMap[i]] = bitReader.readLSB(3); }
+
+ var clenCodes = buildCodes(clen);
+ var clenTree = buildTree(clenCodes, clen);
+
+ var lengthsSequence = [];
+ while(lengthsSequence.length < hlit + hdist) {
+ var p = clenTree;
+ while(!p.isLeaf) { p = bitReader.readBit() ? p.one : p.zero; }
+
+ var code = p.index;
+ if (code <= 15) lengthsSequence.push(code);
+ else if (code == 16) {
+ // TODO: replace with faster repeat algorythm
+ var repeat = bitReader.readLSB(2) + 3;
+ for(var q=0; q<repeat; ++q){
+ lengthsSequence.push(lengthsSequence[lengthsSequence.length - 1]);
+ }
+ }
+ else if (code == 17) {
+ var repeat1 = bitReader.readLSB(3) + 3;
+ for(var q1=0; q1<repeat1; ++q1) {
+ lengthsSequence.push(0);
+ }
+ }
+ else if (code == 18) {
+ var repeat2 = bitReader.readLSB(7) + 11;
+ for(var q2=0; q2<repeat2; ++q2){
+ lengthsSequence.push(0);
+ }
+ }
+ }
+
+ var codesLengths = lengthsSequence.slice(0, hlit);
+ var codes = buildCodes(codesLengths);
+ var distancesLengths = lengthsSequence.slice(hlit, hlit + hdist);
+ var distances = buildCodes(distancesLengths);
+
+ return {
+ codesTree : buildTree(codes, codesLengths),
+ distancesTree : buildTree(distances, distancesLengths)
+ };
+ };
+
+
+ _inflatingReader.prototype = new JSIO._ByteReaderBase();
+
+
+ // internal instance fns
+ _inflatingReader.prototype._decodeItem = function() {
+ if (this._state == 2) return null; // end-of-blocks
+
+ var item;
+ if(this._state === 0) {
+ this._blockFinal = this._bitReader.readBit();
+ var blockType = this._bitReader.readLSB(2);
+ switch(blockType) {
+ case 0:
+ this._bitReader.align();
+ var len = this._bitReader.readLSB(16); // low-byte first, as opposed to readNumber's HBF
+ var nlen = this._bitReader.readLSB(16);
+ if ((len & ~nlen) != len) this._throwError('Invalid block type 0 length', null, '_decodeItem');
+
+ item = {};
+ item.itemType = 0;
+ item.array = this._bitReader.byteReader.read(len);
+ if (item.array.length < len) this._throwError('Incomplete block', null, '_decodeItem');
+ if (this._blockFinal) this._state = 2;
+ return item;
+ case 1:
+ this._codesTree = staticCodes;
+ this._distancesTree = staticDistances;
+ this._state = 1;
+ break;
+ case 2:
+ var dTrees = readDynamicTrees(this._bitReader);
+ this._codesTree = dTrees.codesTree;
+ this._distancesTree = dTrees.distancesTree;
+ this._state = 1;
+ break;
+ default:
+ this._throwError('Invalid block type ('+ blockType +')', null, '_decodeItem');
+ }
+ }
+
+ item = {};
+
+ var p = this._codesTree;
+ while (!p.isLeaf) { p = this._bitReader.readBit() ? p.one : p.zero; }
+ if(p.index < 256) {
+ item.itemType = 2;
+ item.symbol = p.index;
+ } else if(p.index > 256) {
+ var lengthCode = p.index;
+ if(lengthCode > 285) this._throwError('Invalid length code', null, '_decodeItem');
+
+ var length = encodedLengthStart[lengthCode - 257];
+ if(encodedLengthAdditionalBits[lengthCode - 257] > 0) {
+ length += this._bitReader.readLSB(encodedLengthAdditionalBits[lengthCode - 257]);
+ }
+
+ p = this._distancesTree;
+ while (!p.isLeaf) { p = this._bitReader.readBit() ? p.one : p.zero; }
+
+ var distanceCode = p.index;
+ var distance = encodedDistanceStart[distanceCode];
+ if (encodedDistanceAdditionalBits[distanceCode] > 0)
+ distance += this._bitReader.readLSB(encodedDistanceAdditionalBits[distanceCode]);
+
+ item.itemType = 3;
+ item.distance = distance;
+ item.length = length;
+ } else {
+ item.itemType = 1;
+ this._state = this._blockFinal ? 2 : 0; // EOB
+ }
+ return item;
+ };
+
+
+
+ // public instance functions
+
+ _inflatingReader.prototype.readByte = function() {
+ var oneByte = this.read(1)[0];
+ return (oneByte === null || oneByte === undefined) ? null : oneByte;
+ };
+
+ _inflatingReader.prototype.read = function(len) {
+ var b = this._buffer; // (since we use this so much...)
+
+ // Keep reading until we get to the right length
+ while (this._bufferPosition+len > b.length) {
+ var item = this._decodeItem();
+ if (item === null) { // EOF
+ len = b.length - this._bufferPosition;
+ break;
+ }
+ switch(item.itemType) {
+ case 0:
+ JSIO.massApply(b.push, b, item.array);
+ break;
+ case 2:
+ b.push(item.symbol);
+ break;
+ case 3:
+ var j = b.length - item.distance;
+ if (item.distance >= item.length)
+ JSIO.massApply(b.push, b, b.slice(j, j+item.length));
+ // sometimes DEFLATE tries some trickery with "look-ahead" compression
+ else {
+ // this is basically just a repetition of the same string, plus some possible cutoff
+ var count = parseInt(item.length / item.distance);
+ var repArr = b.slice(j);
+ // http://stackoverflow.com/questions/202605/repeat-string-javascript/5450113#5450113
+ while (count > 0) {
+ if (count & 1) JSIO.massApply( b.push, b, repArr);
+ if (count >>= 1) JSIO.massApply(repArr.push, repArr, repArr);
+ }
+ // add any remaining cutoff
+ var r;
+ if (r = item.length % item.distance)
+ JSIO.massApply(b.push, b, b.slice(j, j+r));
+ }
+ break;
+ }
+ }
+ var bytes = b.slice(this._bufferPosition, this._bufferPosition+len);
+ this._bufferPosition += len;
+ this.position += len;
+
+ if (this._bufferPosition > 0xC000) {
+ var shift = b.length - 0x8000;
+ if (shift > this._bufferPosition) shift = this._bufferPosition;
+ b.splice(0, shift);
+ this._bufferPosition -= shift;
+ }
+
+ return bytes;
+ };
+
+ // initialization routine - once per type
+ (function(){
+
+ var codes = new Array(288);
+ var codesLengths = new Array(288);
+ var i=0;
+ for ( i = 0; i <= 143; i++) {
+ codes[i] = 0x0030 + i;
+ codesLengths[i] = 8;
+ }
+ for ( i = 144; i <= 255; i++) {
+ codes[i] = 0x0190 + i - 144;
+ codesLengths[i] = 9;
+ }
+ for ( i = 256; i <= 279; i++) {
+ codes[i] = 0x0000 + i - 256;
+ codesLengths[i] = 7;
+ }
+ for ( i = 280; i <= 287; i++) {
+ codes[i] = 0x00C0 + i - 280;
+ codesLengths[i] = 8;
+ }
+ staticCodes = buildTree(codes, codesLengths);
+
+ var distances = new Array(32);
+ var distancesLengths = new Array(32);
+ for ( i = 0; i <= 31; i++) {
+ distances[i] = i;
+ distancesLengths[i] = 5;
+ }
+ staticDistances = buildTree(distances, distancesLengths);
+ })();
+
+
+ JSIO.InflatingReader = _inflatingReader;
})();
@@ -1713,460 +1713,537 @@
// This work is licensed under the GPLv3.
(function(){
- var version = "2.0 2012Feb";
- var typename = "Zipfile";
-
- if (typeof JSIO.BinaryUrlStream !== "function") JSIO.throwError('This extension requires JSIO.BinaryUrlStream.js v2.0', typename);
- if (typeof JSIO.TextDecoder !== "object") JSIO.throwError('This extension requires JSIO.TextDecoder.js v2.0', typename);
- if (typeof JSIO.TextReader !== "function") JSIO.throwError('This extension requires JSIO.TextReader.js v2.0', typename);
- if (typeof JSIO.Crc32 !== "function") JSIO.throwError('This extension requires JSIO.Crc32.js v2.0', typename);
- if (typeof JSIO.InflatingReader !== "function") JSIO.throwError('This extension requires JSIO.InflatingReader.js v2.0', typename);
-
- // =======================================================
- function ZipEntry(zip) {
- this.zipfile = zip;
- this._typename = "ZipEntry";
- this._version = version;
- this._crcCalculator = null;
- }
-
- ZipEntry.prototype._throwError = JSIO.throwError;
-
- // return byte array or string
- ZipEntry.prototype.extract = function(callback, asString) {
- this.contentType = JSIO.guessFileType(this.name);
- asString = asString || ( this.contentType == JSIO.FileType.Text ||
- this.contentType == JSIO.FileType.XML);
- var thisEntry = this;
-
- if (this.compressionMethod !== 0 && this.compressionMethod != 8)
- this._throwError('Unsupported compression method: ' + this.compressionMethod, null, 'extract');
-
- var reader = (asString) ? this.openTextReader(thisEntry.utf8 ? JSIO.TextDecoder.UTF8 : JSIO.TextDecoder.ANSI) : this.openBinaryReader();
-
- // diagnostic purpose only; tag the reader with the entry name
- reader.zipEntryName = thisEntry.name;
-
- if (typeof callback != "function") {
- // synchronous
- var result = reader.readToEnd();
- this.verifyCrc32();
- return result;
- }
-
- // asynchronous
- reader.beginReadToEnd(function(result){
- try {
- thisEntry.verifyCrc32();
- callback(thisEntry, result);
- }
- catch (exc1) {
- callback(thisEntry, exc1);
- }
- });
- return null;
- };
-
-
- // open a ByteReader on the entry, which will read binary
- // content from the compressed stream.
- ZipEntry.prototype.openBinaryReader = function() {
- var reader =
- new JSIO.StreamSegmentReader(this.zipfile.binaryStream,
- this.offset + this.lengthOfHeader,
- this.compressedSize);
- if (this.compressionMethod === 0) {
- this._crcCalculator = new JSIO.Crc32Reader(reader);
- }
- else {
- var inflator = new JSIO.InflatingReader(reader);
- this._crcCalculator = new JSIO.Crc32Reader(inflator);
- }
- // Whether compressed or not, the source ByteReader in each case
- // is wrapped in a second ByteReader object that calculates CRC
- // as it reads. That way, after all reading is complete, the
- // caller can check the calcuated CRC against the expected CRC.
- return this._crcCalculator;
- };
-
- // open a TextReader on the entry, to read text from the
- // compressed stream.
- ZipEntry.prototype.openTextReader = function(decoderKind) {
- var reader = this.openBinaryReader();
- decoderKind = decoderKind || JSIO.TextDecoder.UTF8;
- var d = new decoderKind(reader);
- var textReader = new JSIO.TextReader(d);
- d._parent = textReader; // store a reference, for diagnostic purposes only
- return textReader;
- };
-
- // verify the CRC on the entry.
- // call this after all bytes have been read.
- ZipEntry.prototype.verifyCrc32 = function() {
- var computedCrc = this._crcCalculator.crc32();
- var rc = false; // CRC FAIL
- if (this.crc32 != computedCrc) {
- var msg = "WARNING: CRC check failed: " +
- "entry(" + this.name + ") " +
- "computed(" + JSIO.decimalToHexString(computedCrc,8) + ") " +
- "expected(" + JSIO.decimalToHexString(this.crc32,8) + ") ";
- this.zipfile.status.push(msg);
- } else {
- rc = true; // OK
- if (this.zipfile.verbose>2) {
- this.zipfile.status.push("INFO: CRC check ok: 0x" +
- JSIO.decimalToHexString(this.crc32,8));
- }
- }
- return rc;
- };
-
-
- // ctor
- ZipFile = function(fileUrl, callback, verbosity) {
- if (! (this instanceof arguments.callee) ) JSIO.throwError('You must use new to instantiate this class', typename, 'ctor');
-
- this.verbose = verbosity || 0;
- this.entries = [];
- this.entryNames = [];
- this.status = [];
- this._version = version;
- this._typename = "ZipFile";
- this._throwError = JSIO.throwError;
-
- var thisZipFile = this;
-
- // Could use a back-tracking reader for the central directory, but
- // there's no point, since all the zip data is held in memory anyway.
-
- /* function ReadCentralDirectory(){
- var posn = thisZipFile.binaryStream.length - 64;
- var maxSeekback = Math.Max(s.Length - 0x4000, 10);
- var success = false;
- var nTries = 0;
- do
- {
- thisZipFile.binaryStream.Seek(posn, SeekOrigin.Begin);
- var bytesRead = thisZipFile.binaryStream.findSignature(thisZipFile.Signatures.EndOfCentralDirectory);
- if (bytesRead != -1)
- success = true;
- else
- {
- nTries++;
- // increasingly larger
- posn -= (32 * (nTries + 1) * nTries);
- if (posn < 0) posn = 0; // BOF
- }
- }
- while (!success && posn > maxSeekback);
- if (!success) {
- thisZipFile.status.push("cannot find End of Central Directory");
- return;
- }
- } */
-
-
- function DateFromPackedFormat(packed) {
- if (packed == 0xFFFF || packed === 0) return new Date(1995, 0, 1, 0,0,0,0);
-
- var packedTime = packed & 0x0000ffff;
- var packedDate = ((packed & 0xffff0000) >> 16);
-
- var year = 1980 + ((packedDate & 0xFE00) >> 9);
- var month = ((packedDate & 0x01E0) >> 5) -1;
- var day = packedDate & 0x001F;
-
- var hour = (packedTime & 0xF800) >> 11;
- var minute = (packedTime & 0x07E0) >> 5;
- var second = (packedTime & 0x001F) * 2;
-
- // Validation and error checking.
- // This is not foolproof but will catch most errors.
-
- // I can't believe how many different ways applications
- // can mess up a simple date format.
-
- if (second >= 60) { minute++; second = 0; }
- if (minute >= 60) { hour++; minute = 0; }
- if (hour >= 24) { day++; hour = 0; }
- var success = false;
- var d;
- try {
- d = new Date(year, month, day, hour, minute, second, 0);
- success= true;
- }
- catch (exc1) {
- if (year == 1980 && (month === 0 || day === 0)) {
- try {
- d = new Date(1980, 0, 1, hour, minute, second, 0);
- success= true;
- }
- catch (exc2) {
- try {
- d = new Date(1980, 0, 1, 0, 0, 0, 0);
- success= true;
- }
- catch (exc3) { } // how could this fail??
- }
- }
- else {
- try {
- if (year < 1980) year = 1980;
- if (year > 2030) year = 2030;
- if (month < 1) month = 1;
- if (month > 12) month = 12;
- if (day < 1) day = 1;
- if (day > 31) day = 31;
- if (minute < 0) minute = 0;
- if (minute > 59) minute = 59;
- if (second < 0) second = 0;
- if (second > 59) second = 59;
- d = new Date(year, month-1, day, hour, minute, second, 0);
- success= true;
- }
- catch (exc4){}
- }
- }
- if (!success) this._throwError('Bad date/time value in this ZIP file', null, 'DateFromPackedFormat');
- return d;
- }
-
-
- function ReadZipEntries () {
- // read only once
- if (thisZipFile.entryNames.length === 0){
- var e;
- while ((e = ReadZipEntry()) !== null) {
- thisZipFile.entries.push(e);
- thisZipFile.entryNames.push(e.name);
- }
- }
- }
-
-
- function ReadZipEntry () {
- var offset = thisZipFile.binaryStream.position;
- var sig = thisZipFile.binaryStream.readNumber(4);
- if (sig == ZipFile.Signatures.DirEntry) {
- // after all entries, comes the central directory
- if (thisZipFile.verbose > 0) {
- thisZipFile.status.push("INFO: at offset 0x" +
- JSIO.decimalToHexString(offset) +
- ", found start of Zip Directory.");
- }
- // all done reading
- return null;
- }
- if (sig != ZipFile.Signatures.Entry) {
- thisZipFile.status.push("WARNING: at offset 0x" +
- JSIO.decimalToHexString(offset) +
- ", found unexpected signature: 0x" +
- JSIO.decimalToHexString(sig));
- return null;
- }
-
- var entry = new ZipEntry(thisZipFile);
- entry.offset = offset;
- entry.versionNeeded = thisZipFile.binaryStream.readNumber(2);
- entry.bitField = thisZipFile.binaryStream.readNumber(2);
- entry.compressionMethod = thisZipFile.binaryStream.readNumber(2);
- var timeBlob = thisZipFile.binaryStream.readNumber(4);
- entry.lastModified = DateFromPackedFormat(timeBlob);
- entry.crc32 = thisZipFile.binaryStream.readNumber(4);
- entry.compressedSize = thisZipFile.binaryStream.readNumber(4);
- entry.uncompressedSize = thisZipFile.binaryStream.readNumber(4);
-
- if ((entry.bitField & 0x01) == 0x01){
- thisZipFile.status.push("This zipfile uses Encryption, which is not supported by ZipFile.js.");
- return null;
- }
-
- entry.utf8 = ((entry.bitField & 0x0800) == 0x0800);
-
- if ((entry.bitField & 0x0008) == 0x0008){
- thisZipFile.status.push("This zipfile uses a bit 3 trailing data descriptor, which is not supported by ZipFile.js.");
- return null;
- }
-
- if (entry.compressedSize == 0xFFFFFFFF ||
- entry.uncompressedSize == 0xFFFFFFFF) {
- thisZipFile.status.push("This zipfile uses ZIP64, which is not supported by ZipFile.js");
- return null;
- }
-
- var filenameLength = thisZipFile.binaryStream.readNumber(2);
- var extraFieldLength = thisZipFile.binaryStream.readNumber(2);
-
- thisZipFile.status.push("INFO: filename length= " + filenameLength);
-
- // we've read 30 bytes of metadata so far
- var bytesRead = 30 + filenameLength + extraFieldLength;
-
- if (entry.utf8) {
- thisZipFile.status.push("INFO: before filename, position= 0x" +
- JSIO.decimalToHexString( thisZipFile.binaryStream.position ));
- var binReader =
- new JSIO.StreamSegmentReader(thisZipFile.binaryStream,
- thisZipFile.binaryStream.position,
- filenameLength);
- var utf8Decoder = new JSIO.TextDecoder.UTF8(binReader);
- var textReader = new JSIO.TextReader(utf8Decoder);
- entry.name = textReader.readToEnd();
-
- // advance the filepointer:
- thisZipFile.binaryStream.seek(filenameLength,
- JSIO.SeekOrigin.Current,
- thisZipFile);
-
- thisZipFile.status.push("INFO: after filename, position= 0x" +
- JSIO.decimalToHexString( thisZipFile.binaryStream.position ));
- }
- else {
- entry.name = thisZipFile.binaryStream.readString(filenameLength);
- }
-
- // There are a bunch of things in the "extra" header, thisZipFile we
- // could parse, like timestamps and other things. This class
- // only identifies and separates them.
-
- // More info here: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
-
- var extraPos = 0;
- entry.extra = [];
- while (extraPos < extraFieldLength) {
- var extraBlock = {
- type: thisZipFile.binaryStream.readNumber(2),
- size: thisZipFile.binaryStream.readNumber(2)
- };
- extraBlock.typeDescription = ZipFile.ExtraFieldTypes[extraBlock.type];
- extraBlock.data = thisZipFile.binaryStream.read(extraBlock.size);
- entry.extra.push(extraBlock);
- extraPos += 4 + extraBlock.size;
- }
-
- if (thisZipFile.verbose > 1) {
- thisZipFile.status.push("INFO: at offset 0x" +
- JSIO.decimalToHexString(entry.offset) +
- ", found entry '" + entry.name + "' fnl(" +
- filenameLength + ") efl(" +
- extraFieldLength +")");
- }
-
- if (extraFieldLength > 0) {
- if (thisZipFile.verbose > 0) {
- thisZipFile.status.push("INFO: entry " + entry.name + " has " +
- extraFieldLength + " bytes of " +
- "extra metadata (ID'd but ignored)");
- }
- }
-
- entry.lengthOfHeader = bytesRead;
- entry.totalEntrySize = entry.lengthOfHeader + entry.compressedSize;
-
- // seek past the data without reading it. We will read on Extract()
- if (thisZipFile.verbose > 1) {
- thisZipFile.status.push("INFO: seek 0x" +
- JSIO.decimalToHexString(entry.compressedSize) +
- " (" + entry.compressedSize + ") bytes");
- }
-
- thisZipFile.binaryStream.seek(entry.compressedSize,
- JSIO.SeekOrigin.Current,
- thisZipFile);
-
- return entry;
- }
-
-
- var parseZipFile = function(bfr){
- try {
- if (bfr.req.status == 200) {
- var sig = thisZipFile.binaryStream.readNumber(4);
- if (sig != ZipFile.Signatures.Entry){
- thisZipFile.status.push("WARNING: this file does not appear to be a zip file");
- } else {
- thisZipFile.binaryStream.seek(0, JSIO.SeekOrigin.Begin);
- ReadZipEntries();
- if (thisZipFile.verbose > 0) {
- thisZipFile.status.push("INFO: read " + thisZipFile.entries.length + " entries");
- }
- }
- }
- else {
- thisZipFile.status.push("ERROR: the URL could not be read (" +
- bfr.req.status + " " + bfr.req.statusText + ")");
- }
- callback(thisZipFile);
- }
- catch (exc1)
- {
- thisZipFile.status.push("Exception: " + exc1.message);
- callback(thisZipFile);
- }
- };
-
- this.binaryStream = new JSIO.BinaryUrlStream(fileUrl, parseZipFile);
-
- return this;
- };
-
-
- ZipFile.Signatures = {
- Entry : 0x04034b50,
- EndOfCentralDirectory : 0x06054b50,
- DirEntry : 0x02014b50
- };
-
- ZipFile.Version = version;
-
- ZipFile.EncryptionAlgorithm = {
- None : 0,
- PkzipWeak : 1,
- WinZipAes : 2
- };
-
- ZipFile.ExtraFieldTypes = {};
- ZipFile.ExtraFieldTypes[0x0001] = 'Zip64 Extended Info';
- ZipFile.ExtraFieldTypes[0x0007] = 'AV Info';
- ZipFile.ExtraFieldTypes[0x0008] = 'Extended Language Encoding Data (PFS)';
- ZipFile.ExtraFieldTypes[0x0009] = 'OS/2';
- ZipFile.ExtraFieldTypes[0x000a] = 'NTFS ';
- ZipFile.ExtraFieldTypes[0x000c] = 'OpenVMS';
- ZipFile.ExtraFieldTypes[0x000d] = 'UNIX';
- ZipFile.ExtraFieldTypes[0x000e] = 'File Stream and Fork Descriptors';
- ZipFile.ExtraFieldTypes[0x000f] = 'Patch Descriptor';
- ZipFile.ExtraFieldTypes[0x0014] = 'PKCS#7 Store for X.509 Certificates';
- ZipFile.ExtraFieldTypes[0x0015] = 'X.509 Certificate ID and Signature (Individual File)';
- ZipFile.ExtraFieldTypes[0x0016] = 'X.509 Certificate ID (Central Directory)';
- ZipFile.ExtraFieldTypes[0x0017] = 'Strong Encryption Header';
- ZipFile.ExtraFieldTypes[0x0018] = 'Record Management Controls';
- ZipFile.ExtraFieldTypes[0x0019] = 'PKCS#7 Encryption Recipient Certificate List';
- ZipFile.ExtraFieldTypes[0x0065] = 'IBM S/390 (Z390), AS/400 (I400) attributes (uncompressed)';
- ZipFile.ExtraFieldTypes[0x0066] = 'IBM S/390 (Z390), AS/400 (I400) attributes (compressed)';
- ZipFile.ExtraFieldTypes[0x4690] = 'POSZIP 4690 (reserved) ';
- ZipFile.ExtraFieldTypes[0x07c8] = 'Macintosh';
- ZipFile.ExtraFieldTypes[0x2605] = 'ZipIt Macintosh';
- ZipFile.ExtraFieldTypes[0x2705] = 'ZipIt Macintosh 1.3.5+';
- ZipFile.ExtraFieldTypes[0x2805] = 'ZipIt Macintosh 1.3.5+';
- ZipFile.ExtraFieldTypes[0x334d] = 'Info-ZIP Macintosh';
- ZipFile.ExtraFieldTypes[0x4341] = 'Acorn/SparkFS ';
- ZipFile.ExtraFieldTypes[0x4453] = 'Windows NT security descriptor (binary ACL)';
- ZipFile.ExtraFieldTypes[0x4704] = 'VM/CMS';
- ZipFile.ExtraFieldTypes[0x470f] = 'MVS';
- ZipFile.ExtraFieldTypes[0x4b46] = 'FWKCS MD5';
- ZipFile.ExtraFieldTypes[0x4c41] = 'OS/2 access control list (text ACL)';
- ZipFile.ExtraFieldTypes[0x4d49] = 'Info-ZIP OpenVMS';
- ZipFile.ExtraFieldTypes[0x4f4c] = 'Xceed original location extra field';
- ZipFile.ExtraFieldTypes[0x5356] = 'AOS/VS (ACL)';
- ZipFile.ExtraFieldTypes[0x5455] = 'extended timestamp';
- ZipFile.ExtraFieldTypes[0x554e] = 'Xceed unicode extra field';
- ZipFile.ExtraFieldTypes[0x5855] = 'Info-ZIP UNIX (original, also OS/2, NT, etc)';
- ZipFile.ExtraFieldTypes[0x6375] = 'Info-ZIP Unicode Comment Extra Field';
- ZipFile.ExtraFieldTypes[0x6542] = 'BeOS/BeBox';
- ZipFile.ExtraFieldTypes[0x7075] = 'Info-ZIP Unicode Path Extra Field';
- ZipFile.ExtraFieldTypes[0x756e] = 'ASi UNIX';
- ZipFile.ExtraFieldTypes[0x7855] = 'Info-ZIP UNIX (new)';
- ZipFile.ExtraFieldTypes[0xa220] = 'Microsoft Open Packaging Growth Hint';
- ZipFile.ExtraFieldTypes[0xfd4a] = 'SMS/QDOS';
+ var version = "2.0 2012Feb";
+ var typename = "Zipfile";
+
+ if (typeof JSIO.BinaryUrlStream !== "function") JSIO.throwError('This extension requires JSIO.BinaryUrlStream.js v2.0', typename);
+ if (typeof JSIO.TextDecoder !== "object") JSIO.throwError('This extension requires JSIO.TextDecoder.js v2.0', typename);
+ if (typeof JSIO.TextReader !== "function") JSIO.throwError('This extension requires JSIO.TextReader.js v2.0', typename);
+ if (typeof JSIO.Crc32 !== "function") JSIO.throwError('This extension requires JSIO.Crc32.js v2.0', typename);
+ if (typeof JSIO.InflatingReader !== "function") JSIO.throwError('This extension requires JSIO.InflatingReader.js v2.0', typename);
+
+ // =======================================================
+ function ZipEntry(zip) {
+ this.zipfile = zip;
+ this._typename = "ZipEntry";
+ this._version = version;
+ this._crcCalculator = null;
+ }
+
+ ZipEntry.prototype._throwError = JSIO.throwError;
+
+ // return byte array or string
+ ZipEntry.prototype.extract = function(callback, asString) {
+ this.contentType = JSIO.guessFileType(this.name);
+ asString = asString || ( this.contentType == JSIO.FileType.Text ||
+ this.contentType == JSIO.FileType.XML);
+ var thisEntry = this;
+
+ if (this.compressionMethod !== 0 && this.compressionMethod != 8)
+ this._throwError('Unsupported compression method: ' + this.compressionMethod, null, 'extract');
+
+ var reader = (asString) ? this.openTextReader(thisEntry.utf8 ? JSIO.TextDecoder.UTF8 : JSIO.TextDecoder.ANSI) : this.openBinaryReader();
+
+ // diagnostic purpose only; tag the reader with the entry name
+ reader.zipEntryName = thisEntry.name;
+
+ if (typeof callback != "function") {
+ // synchronous
+ var result = reader.readToEnd();
+ this.verifyCrc32();
+ return result;
+ }
+
+ // asynchronous
+ reader.beginReadToEnd(function(result){
+ try {
+ thisEntry.verifyCrc32();
+ callback(thisEntry, result);
+ }
+ catch (exc1) {
+ this.zipfile.status.push("EXCEPTION: " + exc1.message);
+ callback(thisEntry, exc1);
+ }
+ });
+ return null;
+ };
+
+
+ // open a ByteReader on the entry, which will read binary
+ // content from the compressed stream.
+ ZipEntry.prototype.openBinaryReader = function() {
+ var reader =
+ new JSIO.StreamSegmentReader(this.zipfile.binaryStream,
+ this.offset + this.lengthOfHeader,
+ this.compressedSize);
+ if (this.compressionMethod === 0) {
+ this._crcCalculator = new JSIO.Crc32Reader(reader);
+ }
+ else {
+ var inflator = new JSIO.InflatingReader(reader);
+ this._crcCalculator = new JSIO.Crc32Reader(inflator);
+ }
+ // Whether compressed or not, the source ByteReader in each case
+ // is wrapped in a second ByteReader object that calculates CRC
+ // as it reads. That way, after all reading is complete, the
+ // caller can check the calcuated CRC against the expected CRC.
+ return this._crcCalculator;
+ };
+
+ // open a TextReader on the entry, to read text from the
+ // compressed stream.
+ ZipEntry.prototype.openTextReader = function(decoderKind) {
+ var reader = this.openBinaryReader();
+ decoderKind = decoderKind || JSIO.TextDecoder.UTF8;
+ var d = new decoderKind(reader);
+ var textReader = new JSIO.TextReader(d);
+ d._parent = textReader; // store a reference, for diagnostic purposes only
+ return textReader;
+ };
+
+ // verify the CRC on the entry.
+ // call this after all bytes have been read.
+ ZipEntry.prototype.verifyCrc32 = function() {
+ var computedCrc = this._crcCalculator.crc32();
+ var rc = false; // CRC FAIL
+ if (this.crc32 != computedCrc) {
+ var msg = "WARNING: CRC check failed: " +
+ "entry(" + this.name + ") " +
+ "computed(" + JSIO.decimalToHexString(computedCrc,8) + ") " +
+ "expected(" + JSIO.decimalToHexString(this.crc32,8) + ") ";
+ this.zipfile.status.push(msg);
+ } else {
+ rc = true; // OK
+ if (this.zipfile.verbose>2) {
+ this.zipfile.status.push("INFO: CRC check ok: 0x" +
+ JSIO.decimalToHexString(this.crc32,8));
+ }
+ }
+ return rc;
+ };
+
+
+ // ctor
+ ZipFile = function(fileUrl, callback, verbosity) {
+ if (! (this instanceof arguments.callee) ) JSIO.throwError('You must use new to instantiate this class', typename, 'ctor');
+
+ this.verbose = verbosity || 0;
+ this.entries = [];
+ this.entryNames = [];
+ this.status = [];
+ this._version = version;
+ this._typename = "ZipFile";
+ this._throwError = JSIO.throwError;
+
+ var thisZipFile = this;
+
+ // Could use a back-tracking reader for the central directory, but
+ // there's no point, since all the zip data is held in memory anyway.
+
+ /* function ReadCentralDirectory(){
+ var posn = thisZipFile.binaryStream.length - 64;
+ var maxSeekback = Math.Max(s.Length - 0x4000, 10);
+ var success = false;
+ var nTries = 0;
+ do
+ {
+ thisZipFile.binaryStream.Seek(posn, SeekOrigin.Begin);
+ var bytesRead = thisZipFile.binaryStream.findSignature(thisZipFile.Signatures.EndOfCentralDirectory);
+ if (bytesRead != -1)
+ success = true;
+ else
+ {
+ nTries++;
+ // increasingly larger
+ posn -= (32 * (nTries + 1) * nTries);
+ if (posn < 0) posn = 0; // BOF
+ }
+ }
+ while (!success && posn > maxSeekback);
+ if (!success) {
+ thisZipFile.status.push("cannot find End of Central Directory");
+ return;
+ }
+ } */
+
+
+ function DateFromPackedFormat(packed) {
+ if (packed == 0xFFFF || packed === 0) return new Date(1995, 0, 1, 0,0,0,0);
+
+ var packedTime = packed & 0x0000ffff;
+ var packedDate = ((packed & 0xffff0000) >> 16);
+
+ var year = 1980 + ((packedDate & 0xFE00) >> 9);
+ var month = ((packedDate & 0x01E0) >> 5) -1;
+ var day = packedDate & 0x001F;
+
+ var hour = (packedTime & 0xF800) >> 11;
+ var minute = (packedTime & 0x07E0) >> 5;
+ var second = (packedTime & 0x001F) * 2;
+
+ // Validation and error checking.
+ // This is not foolproof but will catch most errors.
+
+ // I can't believe how many different ways applications
+ // can mess up a simple date format.
+
+ if (second >= 60) { minute++; second = 0; }
+ if (minute >= 60) { hour++; minute = 0; }
+ if (hour >= 24) { day++; hour = 0; }
+ var success = false;
+ var d;
+ try {
+ d = new Date(year, month, day, hour, minute, second, 0);
+ success= true;
+ }
+ catch (exc1) {
+ if (year == 1980 && (month === 0 || day === 0)) {
+ try {
+ d = new Date(1980, 0, 1, hour, minute, second, 0);
+ success= true;
+ }
+ catch (exc2) {
+ try {
+ d = new Date(1980, 0, 1, 0, 0, 0, 0);
+ success= true;
+ }
+ catch (exc3) { } // how could this fail??
+ }
+ }
+ else {
+ try {
+ if (year < 1980) year = 1980;
+ if (year > 2030) year = 2030;
+ if (month < 1) month = 1;
+ if (month > 12) month = 12;
+ if (day < 1) day = 1;
+ if (day > 31) day = 31;
+ if (minute < 0) minute = 0;
+ if (minute > 59) minute = 59;
+ if (second < 0) second = 0;
+ if (second > 59) second = 59;
+ d = new Date(year, month-1, day, hour, minute, second, 0);
+ success= true;
+ }
+ catch (exc4){}
+ }
+ }
+ if (!success) this._throwError('Bad date/time value in this ZIP file', null, 'DateFromPackedFormat');
+ return d;
+ }
+
+
+ function ReadZipEntries () {
+ // read only once
+ if (thisZipFile.entryNames.length === 0){
+ var e;
+ while ((e = ReadZipEntry()) !== null) {
+ thisZipFile.entries.push(e);
+ thisZipFile.entryNames.push(e.name);
+ }
+ }
+ }
+
+
+ function ReadZipEntry () {
+ var offset = thisZipFile.binaryStream.position;
+ var sig = thisZipFile.binaryStream.readNumber(4);
+ if (sig == ZipFile.Signatures.DirEntry) {
+ // after all entries, comes the central directory
+ if (thisZipFile.verbose > 2) {
+ thisZipFile.status.push("INFO: at offset 0x" +
+ JSIO.decimalToHexString(offset) +
+ ", found start of Zip Directory.");
+ }
+ // all done reading
+ return null;
+ }
+ if (sig != ZipFile.Signatures.Entry) {
+ thisZipFile.status.push("WARNING: at offset 0x" +
+ JSIO.decimalToHexString(offset) +
+ ", found unexpected signature: 0x" +
+ JSIO.decimalToHexString(sig));
+ return null;
+ }
+
+ var entry = new ZipEntry(thisZipFile);
+ entry.offset = offset;
+ entry.versionNeeded = thisZipFile.binaryStream.readNumber(2);
+ entry.bitField = thisZipFile.binaryStream.readNumber(2);
+ entry.compressionMethod = thisZipFile.binaryStream.readNumber(2);
+ var timeBlob = thisZipFile.binaryStream.readNumber(4);
+ entry.lastModified = DateFromPackedFormat(timeBlob);
+ entry.crc32 = thisZipFile.binaryStream.readNumber(4);
+ entry.compressedSize = thisZipFile.binaryStream.readNumber(4);
+ entry.uncompressedSize = thisZipFile.binaryStream.readNumber(4);
+
+ if (thisZipFile.verbose>2) {
+ thisZipFile.status.push("INFO: crc32 0x" +
+ JSIO.decimalToHexString(entry.crc32) +
+ " (" + entry.crc32 + ")");
+ thisZipFile.status.push("INFO: compressedSize 0x" +
+ JSIO.decimalToHexString(entry.compressedSize) +
+ " (" + entry.compressedSize + ") bytes");
+ thisZipFile.status.push("INFO: uncompressedSize 0x" +
+ JSIO.decimalToHexString(entry.uncompressedSize) +
+ " (" + entry.uncompressedSize + ") bytes");
+ }
+ if ((entry.bitField & 0x01) == 0x01){
+ thisZipFile.status.push("This zipfile uses Encryption, which is not supported by ZipFile.js.");
+ return null;
+ }
+
+ entry.utf8 = ((entry.bitField & 0x0800) == 0x0800);
+
+ if (entry.compressedSize == 0xFFFFFFFF ||
+ entry.uncompressedSize == 0xFFFFFFFF) {
+ thisZipFile.status.push("This zipfile uses ZIP64, which is not supported by ZipFile.js");
+ return null;
+ }
+
+ var filenameLength = thisZipFile.binaryStream.readNumber(2);
+ var extraFieldLength = thisZipFile.binaryStream.readNumber(2);
+
+ if (thisZipFile.verbose>2) {
+ thisZipFile.status.push("INFO: filename length= " + filenameLength);
+ }
+
+ // we've read 30 bytes of metadata so far
+ var bytesRead = 30 + filenameLength + extraFieldLength;
+
+ if (entry.utf8) {
+ thisZipFile.status.push("INFO: before filename, position= 0x" +
+ JSIO.decimalToHexString( thisZipFile.binaryStream.position ));
+ var binReader =
+ new JSIO.StreamSegmentReader(thisZipFile.binaryStream,
+ thisZipFile.binaryStream.position,
+ filenameLength);
+ var utf8Decoder = new JSIO.TextDecoder.UTF8(binReader);
+ var textReader = new JSIO.TextReader(utf8Decoder);
+ entry.name = textReader.readToEnd();
+
+ // advance the filepointer:
+ thisZipFile.binaryStream.seek(filenameLength,
+ JSIO.SeekOrigin.Current,
+ thisZipFile);
+
+ thisZipFile.status.push("INFO: after filename, position= 0x" +
+ JSIO.decimalToHexString( thisZipFile.binaryStream.position ));
+ }
+ else {
+ entry.name = thisZipFile.binaryStream.readString(filenameLength);
+ }
+
+ // There are a bunch of things in the "extra" header, thisZipFile we
+ // could parse, like timestamps and other things. This class
+ // only identifies and separates them.
+
+ // More info here: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
+
+ var extraPos = 0;
+ entry.extra = [];
+ while (extraPos < extraFieldLength) {
+ var extraBlock = {
+ type: thisZipFile.binaryStream.readNumber(2),
+ size: thisZipFile.binaryStream.readNumber(2)
+ };
+ extraBlock.typeDescription = ZipFile.ExtraFieldTypes[extraBlock.type];
+ extraBlock.data = thisZipFile.binaryStream.read(extraBlock.size);
+ entry.extra.push(extraBlock);
+ extraPos += 4 + extraBlock.size;
+ }
+
+ if (thisZipFile.verbose > 1) {
+ thisZipFile.status.push("INFO: at offset 0x" +
+ JSIO.decimalToHexString(entry.offset) +
+ ", found entry '" + entry.name + "' fnl(" +
+ filenameLength + ") efl(" +
+ extraFieldLength +")");
+ }
+
+ if (extraFieldLength > 0) {
+ if (thisZipFile.verbose > 0) {
+ thisZipFile.status.push("INFO: entry " + entry.name + " has " +
+ extraFieldLength + " bytes of " +
+ "extra metadata (ID'd but ignored)");
+ }
+ }
+
+ entry.lengthOfHeader = bytesRead;
+ if (thisZipFile.verbose>2) {
+ thisZipFile.status.push("INFO: lengthOfHeader 0x" +
+ JSIO.decimalToHexString(entry.lengthOfHeader) +
+ " (" + entry.lengthOfHeader + ")");
+ }
+
+ /*
+ Data descriptor Offset Bytes Description[25]
+ 0 0/4 Optional data descriptor signature = 0x08074b50
+ 0/4 4 CRC-32
+ 4/8 4 Compressed size
+ 8/12 4 Uncompressed size
+ */
+
+ if ((entry.bitField & 0x0008) == 0x0008){
+ thisZipFile.status.push("INFO: This zipfile uses a bit 3 trailing data descriptor");
+ // return null;
+ var currentPosition = thisZipFile.binaryStream.position;
+ var dataDescLength = 0;
+ if (thisZipFile.verbose>2) {
+ thisZipFile.status.push("INFO: currentPosition 0x" +
+ JSIO.decimalToHexString(thisZipFile.binaryStream.position) +
+ " (" + thisZipFile.binaryStream.position + ")");
+ thisZipFile.status.push("INFO: length 0x" +
+ JSIO.decimalToHexString(thisZipFile.binaryStream.length) +
+ " (" + thisZipFile.binaryStream.length + ")");
+ }
+
+ for (var i=0; i<128; i++) { // maximum search backwards from end for signature is 128 bytes
+ thisZipFile.binaryStream.seek(thisZipFile.binaryStream.length-i-4,JSIO.SeekOrigin.Begin);
+ var num = thisZipFile.binaryStream.readNumber(4)
+ if (thisZipFile.verbose>2) {
+ thisZipFile.status.push("INFO: ["+i+"] 0x" +
+ JSIO.decimalToHexString(num) +
+ " (" + num + ")");
+ }
+ if (num == ZipFile.Signatures.DataDescriptor) {
+ sig = num;
+ entry.crc32 = thisZipFile.binaryStream.readNumber(4);
+ entry.compressedSize = thisZipFile.binaryStream.readNumber(4);
+ entry.uncompressedSize = thisZipFile.binaryStream.readNumber(4);
+ dataDescLength = 16;
+ break;
+ }
+ }
+
+ if (thisZipFile.verbose>2) {
+ thisZipFile.status.push("INFO: sig 0x" +
+ JSIO.decimalToHexString(sig) +
+ " (" + sig + ")");
+ thisZipFile.status.push("INFO: crc32 0x" +
+ JSIO.decimalToHexString(entry.crc32) +
+ " (" + entry.crc32 + ")");
+ thisZipFile.status.push("INFO: compressedSize 0x" +
+ JSIO.decimalToHexString(entry.compressedSize) +
+ " (" + entry.compressedSize + ") bytes");
+ thisZipFile.status.push("INFO: uncompressedSize 0x" +
+ JSIO.decimalToHexString(entry.uncompressedSize) +
+ " (" + entry.uncompressedSize + ") bytes");
+ thisZipFile.status.push("INFO: lengthOfHeader 0x" +
+ JSIO.decimalToHexString(entry.lengthOfHeader) +
+ " (" + entry.lengthOfHeader + ")");
+ }
+
+ thisZipFile.binaryStream.position = currentPosition+dataDescLength;
+ }
+
+ entry.totalEntrySize = entry.lengthOfHeader + entry.compressedSize;
+
+ // seek past the data without reading it. We will read on Extract()
+ if (thisZipFile.verbose > 1) {
+ thisZipFile.status.push("INFO: seek 0x" +
+ JSIO.decimalToHexString(entry.compressedSize) +
+ " (" + entry.compressedSize + ") bytes");
+ }
+
+ thisZipFile.binaryStream.seek(entry.compressedSize,
+ JSIO.SeekOrigin.Current,
+ thisZipFile);
+
+ return entry;
+ }
+
+
+ var parseZipFile = function(bfr){
+ try {
+ if (bfr.req.status == 200) {
+ var sig = thisZipFile.binaryStream.readNumber(4);
+ if (sig != ZipFile.Signatures.Entry){
+ thisZipFile.status.push("WARNING: this file does not appear to be a zip file");
+ } else {
+ thisZipFile.binaryStream.seek(0, JSIO.SeekOrigin.Begin);
+ ReadZipEntries();
+ if (thisZipFile.verbose > 0) {
+ thisZipFile.status.push("INFO: read " + thisZipFile.entries.length + " entries");
+ }
+ }
+ }
+ else {
+ thisZipFile.status.push("ERROR: the URL could not be read (" +
+ bfr.req.status + " " + bfr.req.statusText + ")");
+ }
+ callback(thisZipFile);
+ }
+ catch (exc1)
+ {
+ thisZipFile.status.push("EXCEPTION: " + exc1.message);
+ callback(thisZipFile);
+ }
+ };
+
+ this.binaryStream = new JSIO.BinaryUrlStream(fileUrl, parseZipFile);
+
+ return this;
+ };
+
+
+ ZipFile.Signatures = {
+ Entry : 0x04034b50,
+ EndOfCentralDirectory : 0x06054b50,
+ DirEntry : 0x02014b50,
+ DataDescriptor : 0x08074b50
+ };
+
+ ZipFile.Version = version;
+
+ ZipFile.EncryptionAlgorithm = {
+ None : 0,
+ PkzipWeak : 1,
+ WinZipAes : 2
+ };
+
+ ZipFile.ExtraFieldTypes = {};
+ ZipFile.ExtraFieldTypes[0x0001] = 'Zip64 Extended Info';
+ ZipFile.ExtraFieldTypes[0x0007] = 'AV Info';
+ ZipFile.ExtraFieldTypes[0x0008] = 'Extended Language Encoding Data (PFS)';
+ ZipFile.ExtraFieldTypes[0x0009] = 'OS/2';
+ ZipFile.ExtraFieldTypes[0x000a] = 'NTFS ';
+ ZipFile.ExtraFieldTypes[0x000c] = 'OpenVMS';
+ ZipFile.ExtraFieldTypes[0x000d] = 'UNIX';
+ ZipFile.ExtraFieldTypes[0x000e] = 'File Stream and Fork Descriptors';
+ ZipFile.ExtraFieldTypes[0x000f] = 'Patch Descriptor';
+ ZipFile.ExtraFieldTypes[0x0014] = 'PKCS#7 Store for X.509 Certificates';
+ ZipFile.ExtraFieldTypes[0x0015] = 'X.509 Certificate ID and Signature (Individual File)';
+ ZipFile.ExtraFieldTypes[0x0016] = 'X.509 Certificate ID (Central Directory)';
+ ZipFile.ExtraFieldTypes[0x0017] = 'Strong Encryption Header';
+ ZipFile.ExtraFieldTypes[0x0018] = 'Record Management Controls';
+ ZipFile.ExtraFieldTypes[0x0019] = 'PKCS#7 Encryption Recipient Certificate List';
+ ZipFile.ExtraFieldTypes[0x0065] = 'IBM S/390 (Z390), AS/400 (I400) attributes (uncompressed)';
+ ZipFile.ExtraFieldTypes[0x0066] = 'IBM S/390 (Z390), AS/400 (I400) attributes (compressed)';
+ ZipFile.ExtraFieldTypes[0x4690] = 'POSZIP 4690 (reserved) ';
+ ZipFile.ExtraFieldTypes[0x07c8] = 'Macintosh';
+ ZipFile.ExtraFieldTypes[0x2605] = 'ZipIt Macintosh';
+ ZipFile.ExtraFieldTypes[0x2705] = 'ZipIt Macintosh 1.3.5+';
+ ZipFile.ExtraFieldTypes[0x2805] = 'ZipIt Macintosh 1.3.5+';
+ ZipFile.ExtraFieldTypes[0x334d] = 'Info-ZIP Macintosh';
+ ZipFile.ExtraFieldTypes[0x4341] = 'Acorn/SparkFS ';
+ ZipFile.ExtraFieldTypes[0x4453] = 'Windows NT security descriptor (binary ACL)';
+ ZipFile.ExtraFieldTypes[0x4704] = 'VM/CMS';
+ ZipFile.ExtraFieldTypes[0x470f] = 'MVS';
+ ZipFile.ExtraFieldTypes[0x4b46] = 'FWKCS MD5';
+ ZipFile.ExtraFieldTypes[0x4c41] = 'OS/2 access control list (text ACL)';
+ ZipFile.ExtraFieldTypes[0x4d49] = 'Info-ZIP OpenVMS';
+ ZipFile.ExtraFieldTypes[0x4f4c] = 'Xceed original location extra field';
+ ZipFile.ExtraFieldTypes[0x5356] = 'AOS/VS (ACL)';
+ ZipFile.ExtraFieldTypes[0x5455] = 'extended timestamp';
+ ZipFile.ExtraFieldTypes[0x554e] = 'Xceed unicode extra field';
+ ZipFile.ExtraFieldTypes[0x5855] = 'Info-ZIP UNIX (original, also OS/2, NT, etc)';
+ ZipFile.ExtraFieldTypes[0x6375] = 'Info-ZIP Unicode Comment Extra Field';
+ ZipFile.ExtraFieldTypes[0x6542] = 'BeOS/BeBox';
+ ZipFile.ExtraFieldTypes[0x7075] = 'Info-ZIP Unicode Path Extra Field';
+ ZipFile.ExtraFieldTypes[0x756e] = 'ASi UNIX';
+ ZipFile.ExtraFieldTypes[0x7855] = 'Info-ZIP UNIX (new)';
+ ZipFile.ExtraFieldTypes[0xa220] = 'Microsoft Open Packaging Growth Hint';
+ ZipFile.ExtraFieldTypes[0xfd4a] = 'SMS/QDOS';
})();
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js
index 6cc72d79..83d5929c 100644
--- a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js
@@ -24,17 +24,67 @@
* limitations under the License.
*
*/
-
-if (!String.prototype.trim) {
/**
- * Remove leading and trailing whitespace.
+ * A MultiGeometry object that will allow multiple polylines in a MultiGeometry
+ * containing LineStrings to be treated as a single object
*
- * @augments String
- * @return {String}
+ * @param {MutiGeometryOptions} anonymous object. Available properties:
+ * map: The map on which to attach the MultiGeometry
+ * paths: the individual polylines
+ * polylineOptions: options to use when constructing all the polylines
+ *
+ * @constructor
*/
- String.prototype.trim = function () {
- return this.replace(/^\s+|\s+$/g, '');
- };
+// only if Google Maps API included
+if (!!window.google && !! google.maps) {
+ function MultiGeometry(multiGeometryOptions) {
+ function createPolyline(polylineOptions, mg) {
+ var polyline = new google.maps.Polyline(polylineOptions);
+ google.maps.event.addListener(polyline,'click', function(evt) { google.maps.event.trigger(mg,'click',evt);});
+ google.maps.event.addListener(polyline,'dblclick', function(evt) { google.maps.event.trigger(mg, 'dblclick', evt);});
+ google.maps.event.addListener(polyline,'mousedown', function(evt) { google.maps.event.trigger(mg, 'mousedown', evt);});
+ google.maps.event.addListener(polyline,'mousemove', function(evt) { google.maps.event.trigger(mg, 'mousemove', evt);});
+ google.maps.event.addListener(polyline,'mouseout', function(evt) { google.maps.event.trigger(mg, 'mouseout', evt);});
+ google.maps.event.addListener(polyline,'mouseover', function(evt) { google.maps.event.trigger(mg, 'mouseover', evt);});
+ google.maps.event.addListener(polyline,'mouseup', function(evt) { google.maps.event.trigger(mg, 'mouseup', evt);});
+ google.maps.event.addListener(polyline,'rightclick', function(evt) { google.maps.event.trigger(mg, 'rightclick', evt);});
+ return polyline;
+ }
+ this.setValues(multiGeometryOptions);
+ this.polylines = [];
+
+ for (i=0; i<this.paths.length;i++) {
+ var polylineOptions = multiGeometryOptions;
+ polylineOptions.path = this.paths[i];
+ var polyline = createPolyline(polylineOptions,this);
+ // Bind the polyline properties to the MultiGeometry properties
+ this.polylines.push(polyline);
+ }
+ }
+ MultiGeometry.prototype = new google.maps.MVCObject();
+ MultiGeometry.prototype.changed = function(key) {
+ // alert(key+" changed");
+ if (this.polylines) {
+ for (var i=0; i<this.polylines.length; i++) {
+ this.polylines[i].set(key,this.get(key));
+ }
+ }
+ };
+ MultiGeometry.prototype.setMap = function(map) { this.set('map',map); };
+ MultiGeometry.prototype.getMap = function() { return this.get('map'); };
+}
+
+// Extend the global String object with a method to remove leading and trailing whitespace
+if (!String.prototype.trim) {
+ /**
+ * Remove leading and trailing whitespace.
+ *
+ * @augments String
+ * @return {String}
+ */
+ String.prototype.trim = function () {
+ return this.replace(/^\s+|\s+$/g, '');
+ };
}
/**
@@ -53,977 +103,1049 @@ geoXML3 = window.geoXML3 || {instances: []};
* @param {geoXML3.parserOptions} options
*/
geoXML3.parser = function (options) {
- // Private variables
- var parserOptions = new geoXML3.parserOptions(options);
- var docs = []; // Individual KML documents
- var docsByUrl = {}; // Same docs as an hash by cleanURL
- var kmzMetaData = {}; // Extra files from KMZ data
- var styles = {}; // Global list of styles
- var lastPlacemark;
- var parserName;
- if (!parserOptions.infoWindow && parserOptions.singleInfoWindow)
- parserOptions.infoWindow = new google.maps.InfoWindow();
-
- var parseKmlString = function (kmlString, docSet) {
- // Internal values for the set of documents as a whole
- var internals = {
- parser: this,
- docSet: docSet || [],
- remaining: 1,
- parseOnly: !(parserOptions.afterParse || parserOptions.processStyles)
- };
- thisDoc = new Object();
- thisDoc.internals = internals;
- internals.docSet.push(thisDoc);
- render(geoXML3.xmlParse(kmlString),thisDoc);
- }
-
- var parse = function (urls, docSet) {
- // Process one or more KML documents
- if (!parserName) {
- parserName = 'geoXML3.instances[' + (geoXML3.instances.push(this) - 1) + ']';
- }
-
- if (typeof urls === 'string') {
- // Single KML document
- urls = [urls];
- }
-
- // Internal values for the set of documents as a whole
- var internals = {
- parser: this,
- docSet: docSet || [],
- remaining: urls.length,
- parseOnly: !(parserOptions.afterParse || parserOptions.processStyles)
- };
- var thisDoc, j;
- for (var i = 0; i < urls.length; i++) {
- var baseUrl = cleanURL(defileURL(location.pathname), urls[i]);
- if (docsByUrl[baseUrl]) {
- // Reloading an existing document
- thisDoc = docsByUrl[baseUrl];
- thisDoc.reload = true;
- }
- else {
- thisDoc = new Object();
- thisDoc.baseUrl = baseUrl;
- internals.docSet.push(thisDoc);
- }
- thisDoc.url = urls[i];
- thisDoc.internals = internals;
- fetchDoc(thisDoc.url, thisDoc);
- }
- };
-
- function fetchDoc(url, doc, resFunc) {
- resFunc = resFunc || function (responseXML) { render(responseXML, doc); };
-
- if (typeof ZipFile === 'function' && typeof JSIO === 'object' && typeof JSIO.guessFileType === 'function') { // KMZ support requires these modules loaded
- contentType = JSIO.guessFileType(doc.baseUrl);
- if (contentType == JSIO.FileType.Binary || contentType == JSIO.FileType.Unknown) {
- doc.isCompressed = true;
- doc.baseDir = doc.baseUrl + '/';
- geoXML3.fetchZIP(url, resFunc, doc.internals.parser);
- return;
- }
- }
- doc.isCompressed = false;
- doc.baseDir = defileURL(doc.baseUrl);
- geoXML3.fetchXML(url, resFunc);
- }
-
- var hideDocument = function (doc) {
- if (!doc) doc = docs[0];
- // Hide the map objects associated with a document
- var i;
- if (!!doc.markers) {
- for (i = 0; i < doc.markers.length; i++) {
- if(!!doc.markers[i].infoWindow) doc.markers[i].infoWindow.close();
- doc.markers[i].setVisible(false);
- }
- }
- if (!!doc.ggroundoverlays) {
- for (i = 0; i < doc.ggroundoverlays.length; i++) {
- doc.ggroundoverlays[i].setOpacity(0);
- }
- }
- if (!!doc.gpolylines) {
- for (i=0;i<doc.gpolylines.length;i++) {
- if(!!doc.gpolylines[i].infoWindow) doc.gpolylines[i].infoWindow.close();
- doc.gpolylines[i].setMap(null);
- }
- }
- if (!!doc.gpolygons) {
- for (i=0;i<doc.gpolygons.length;i++) {
- if(!!doc.gpolygons[i].infoWindow) doc.gpolygons[i].infoWindow.close();
- doc.gpolygons[i].setMap(null);
- }
- }
- };
-
- var showDocument = function (doc) {
- if (!doc) doc = docs[0];
- // Show the map objects associated with a document
- var i;
- if (!!doc.markers) {
- for (i = 0; i < doc.markers.length; i++) {
- doc.markers[i].setVisible(true);
- }
- }
- if (!!doc.ggroundoverlays) {
- for (i = 0; i < doc.ggroundoverlays.length; i++) {
- doc.ggroundoverlays[i].setOpacity(doc.ggroundoverlays[i].percentOpacity_);
- }
- }
- if (!!doc.gpolylines) {
- for (i=0;i<doc.gpolylines.length;i++) {
- doc.gpolylines[i].setMap(parserOptions.map);
- }
- }
- if (!!doc.gpolygons) {
- for (i=0;i<doc.gpolygons.length;i++) {
- doc.gpolygons[i].setMap(parserOptions.map);
- }
- }
- };
-
- var defaultStyle = {
- balloon: {
- bgColor: 'ffffffff',
- textColor: 'ff000000',
- text: "<h3>$[name]</h3>\n<div>$[description]</div>\n<div>$[geDirections]</div>",
- displayMode: 'default'
- },
- icon: {
- scale: 1.0,
- dim: {
- x: 0,
- y: 0,
- w: -1,
- h: -1
- },
- hotSpot: {
- x: 0.5,
- y: 0.5,
- xunits: 'fraction',
- yunits: 'fraction'
- }
- },
- line: {
- color: 'ffffffff', // white (KML default)
- colorMode: 'normal',
- width: 1.0
- },
- poly: {
- color: 'ffffffff', // white (KML default)
- colorMode: 'normal',
- fill: true,
- outline: true
- }
- };
-
- var kmlNS = 'http://www.opengis.net/kml/2.2';
- var gxNS = 'http://www.google.com/kml/ext/2.2';
- var nodeValue = geoXML3.nodeValue;
- var getBooleanValue = geoXML3.getBooleanValue;
- var getElementsByTagNameNS = geoXML3.getElementsByTagNameNS;
- var getElementsByTagName = geoXML3.getElementsByTagName;
-
-function processStyleUrl(node) {
- var styleUrlStr = nodeValue(getElementsByTagName(node, 'styleUrl')[0]);
- if (!!styleUrlStr && styleUrlStr.indexOf('#') != -1)
- var styleUrl = styleUrlStr.split('#');
- else var styleUrl = ["",""];
- return styleUrl;
-}
-
- function processStyle(thisNode, baseUrl, styleID, baseDir) {
- var style = (baseUrl === '{inline}') ? clone(defaultStyle) : (styles[baseUrl][styleID] = styles[baseUrl][styleID] || clone(defaultStyle));
-
- var styleNodes = getElementsByTagName(thisNode, 'BalloonStyle');
- if (!!styleNodes && styleNodes.length > 0) {
- style.balloon.bgColor = nodeValue(getElementsByTagName(styleNodes[0], 'bgColor')[0], style.balloon.bgColor);
- style.balloon.textColor = nodeValue(getElementsByTagName(styleNodes[0], 'textColor')[0], style.balloon.textColor);
- style.balloon.text = nodeValue(getElementsByTagName(styleNodes[0], 'text')[0], style.balloon.text);
- style.balloon.displayMode = nodeValue(getElementsByTagName(styleNodes[0], 'displayMode')[0], style.balloon.displayMode);
- }
-
- // style.list = (unsupported; doesn't make sense in Google Maps)
-
- var styleNodes = getElementsByTagName(thisNode, 'IconStyle');
- if (!!styleNodes && styleNodes.length > 0) {
- var icon = style.icon;
-
- icon.scale = parseFloat(nodeValue(getElementsByTagName(styleNodes[0], 'scale')[0], icon.scale));
- // style.icon.heading = (unsupported; not supported in API)
- // style.icon.color = (unsupported; not supported in API)
- // style.icon.colorMode = (unsupported; not supported in API)
-
- styleNodes = getElementsByTagName(thisNode, 'Icon');
- if (!!styleNodes && styleNodes.length > 0) {
- icon.href = nodeValue(getElementsByTagName(styleNodes[0], 'href')[0]);
- icon.url = cleanURL(baseDir, icon.href);
- // Detect images buried in KMZ files (and use a base64 encoded URL)
- if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl;
-
- // Support for icon palettes and exact size dimensions
- icon.dim = {
- x: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'x')[0], icon.dim.x)),
- y: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'y')[0], icon.dim.y)),
- w: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'w')[0], icon.dim.w)),
- h: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'h')[0], icon.dim.h))
- };
-
- styleNodes = getElementsByTagName(styleNodes[0], 'hotSpot')[0];
- if (!!styleNodes && styleNodes.length > 0) {
- icon.hotSpot = {
- x: styleNodes[0].getAttribute('x'),
- y: styleNodes[0].getAttribute('y'),
- xunits: styleNodes[0].getAttribute('xunits'),
- yunits: styleNodes[0].getAttribute('yunits')
- };
- }
-
- // certain occasions where we need the pixel size of the image (like the default settings...)
- // (NOTE: Scale is applied to entire image, not just the section of the icon palette. So,
- // if we need scaling, we'll need the img dimensions no matter what.)
- if ( (icon.dim.w < 0 || icon.dim.h < 0) && (icon.xunits != 'pixels' || icon.yunits == 'fraction') || icon.scale != 1.0) {
- // (hopefully, this will load by the time we need it...)
- icon.img = new Image();
- icon.img.onload = function() {
- if (icon.dim.w < 0 || icon.dim.h < 0) {
- icon.dim.w = this.width;
- icon.dim.h = this.height;
- }
- };
- icon.img.src = icon.url;
-
- // sometimes the file is already cached and it never calls onLoad
- if (icon.img.width > 0) {
- icon.dim.w = icon.img.width;
- icon.dim.h = icon.img.height;
- }
- }
- }
- }
-
- // style.label = (unsupported; may be possible but not with API)
-
- styleNodes = getElementsByTagName(thisNode, 'LineStyle');
- if (!!styleNodes && styleNodes.length > 0) {
- style.line.color = nodeValue(getElementsByTagName(styleNodes[0], 'color')[0], style.line.color);
- style.line.colorMode = nodeValue(getElementsByTagName(styleNodes[0], 'colorMode')[0], style.line.colorMode);
- style.line.width = nodeValue(getElementsByTagName(styleNodes[0], 'width')[0], style.line.width);
- // style.line.outerColor = (unsupported; not supported in API)
- // style.line.outerWidth = (unsupported; not supported in API)
- // style.line.physicalWidth = (unsupported; unnecessary in Google Maps)
- // style.line.labelVisibility = (unsupported; possible to implement)
- }
-
- styleNodes = getElementsByTagName(thisNode, 'PolyStyle');
- if (!!styleNodes && styleNodes.length > 0) {
- style.poly.color = nodeValue( getElementsByTagName(styleNodes[0], 'color')[0], style.poly.color);
- style.poly.colorMode = nodeValue( getElementsByTagName(styleNodes[0], 'colorMode')[0], style.poly.colorMode);
- style.poly.outline = getBooleanValue(getElementsByTagName(styleNodes[0], 'outline')[0], style.poly.outline);
- style.poly.fill = getBooleanValue(getElementsByTagName(styleNodes[0], 'fill')[0], style.poly.fill);
- }
- return style;
- }
-
- // from http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-a-javascript-object
- // http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
- function clone(obj){
- if(obj == null || typeof(obj) != 'object') return obj;
- if (obj.cloneNode) return obj.cloneNode(true);
- var temp = new obj.constructor();
- for(var key in obj) temp[key] = clone(obj[key]);
- return temp;
- }
-
- function processStyleMap(thisNode, baseUrl, styleID, baseDir) {
- var pairs = getElementsByTagName(thisNode, 'Pair');
- var map = new Object();
-
- // add each key to the map
- for (var pr=0;pr<pairs.length;pr++) {
- var pairKey = nodeValue(getElementsByTagName(pairs[pr], 'key')[0]);
- var pairStyle = nodeValue(getElementsByTagName(pairs[pr], 'Style')[0]);
- var pairStyleUrl = processStyleUrl(pairs[pr]);
- var pairStyleBaseUrl = pairStyleUrl[0] ? cleanURL(baseDir, pairStyleUrl[0]) : baseUrl;
- var pairStyleID = pairStyleUrl[1];
-
- if (!!pairStyle) {
- map[pairKey] = processStyle(pairStyle, pairStyleBaseUrl, pairStyleID);
- } else if (!!pairStyleID && !!styles[pairStyleBaseUrl][pairStyleID]) {
- map[pairKey] = clone(styles[pairStyleBaseUrl][pairStyleID]);
- }
- }
- if (!!map["normal"]) {
- styles[baseUrl][styleID] = clone(map["normal"]);
- } else {
- styles[baseUrl][styleID] = clone(defaultStyle);
- }
- if (!!map["highlight"]) {
- processStyleID(map["highlight"]);
- }
- styles[baseUrl][styleID].map = clone(map);
- }
-
- function processPlacemarkCoords(node, tag) {
- var parent = getElementsByTagName(node, tag);
- var coordListA = [];
- for (var i=0; i<parent.length; i++) {
- var coordNodes = getElementsByTagName(parent[i], 'coordinates');
- if (!coordNodes) {
- if (coordListA.length > 0) {
- break;
- } else {
- return [{coordinates: []}];
- }
- }
-
- for (var j=0; j<coordNodes.length;j++) {
- var coords = nodeValue(coordNodes[j]).trim();
- coords = coords.replace(/,\s+/g, ',');
- var path = coords.split(/\s+/g);
- var pathLength = path.length;
- var coordList = [];
- for (var k = 0; k < pathLength; k++) {
- coords = path[k].split(',');
- if (!isNaN(coords[0]) && !isNaN(coords[1])) {
- coordList.push({
- lat: parseFloat(coords[1]),
- lng: parseFloat(coords[0]),
- alt: parseFloat(coords[2])
- });
- }
- }
- coordListA.push({coordinates: coordList});
- }
- }
- return coordListA;
- }
-
- var render = function (responseXML, doc) {
- // Callback for retrieving a KML document: parse the KML and display it on the map
- if (!responseXML) {
- // Error retrieving the data
- geoXML3.log('Unable to retrieve ' + doc.url);
- if (parserOptions.failedParse) parserOptions.failedParse(doc);
- doc.failed = true;
- return;
- } else if (responseXML.parseError && responseXML.parseError.errorCode != 0) {
- // IE parse error
- var err = responseXML.parseError;
- var msg = 'Parse error in line ' + err.line + ', col ' + err.linePos + ' (error code: ' + err.errorCode + ")\n" +
- "\nError Reason: " + err.reason +
- 'Error Line: ' + err.srcText;
-
- geoXML3.log('Unable to retrieve ' + doc.url + ': ' + msg);
- if (parserOptions.failedParse) parserOptions.failedParse(doc);
- doc.failed = true;
- return;
- } else if (responseXML.documentElement && responseXML.documentElement.nodeName == 'parsererror') {
- // Firefox parse error
- geoXML3.log('Unable to retrieve ' + doc.url + ': ' + responseXML.documentElement.childNodes[0].nodeValue);
- if (parserOptions.failedParse) parserOptions.failedParse(doc);
- doc.failed = true;
- return;
- } else if (!doc) {
- throw 'geoXML3 internal error: render called with null document';
- } else { //no errors
- var i;
- doc.placemarks = [];
- doc.groundoverlays = [];
- doc.ggroundoverlays = [];
- doc.networkLinks = [];
- doc.gpolygons = [];
- doc.gpolylines = [];
-
- // Check for dependent KML files
- var nodes = getElementsByTagName(responseXML, 'styleUrl');
- var docSet = doc.internals.docSet;
-
- for (var i = 0; i < nodes.length; i++) {
- var url = nodeValue(nodes[i]).split('#')[0];
- if (!url) continue; // #id (inside doc)
- var rUrl = cleanURL( doc.baseDir, url );
- if (rUrl === doc.baseUrl) continue; // self
- if (docsByUrl[rUrl]) continue; // already loaded
-
- var thisDoc;
- var j = docSet.indexOfObjWithItem('baseUrl', rUrl);
- if (j != -1) {
- // Already listed to be loaded, but probably in the wrong order.
- // Load it right away to immediately resolve dependency.
- thisDoc = docSet[j];
- if (thisDoc.failed) continue; // failed to load last time; don't retry it again
- }
- else {
- // Not listed at all; add it in
- thisDoc = new Object();
- thisDoc.url = rUrl; // url can't be trusted inside KMZ files, since it may .. outside of the archive
- thisDoc.baseUrl = rUrl;
- thisDoc.internals = doc.internals;
-
- doc.internals.docSet.push(thisDoc);
- doc.internals.remaining++;
- }
-
- // render dependent KML first then re-run renderer
- fetchDoc(rUrl, thisDoc, function (thisResXML) {
- render(thisResXML, thisDoc);
- render(responseXML, doc);
- });
-
- // to prevent cross-dependency issues, just load the one
- // file first and re-check the rest later
- return;
- }
-
- // Parse styles
- doc.styles = styles[doc.baseUrl] = styles[doc.baseUrl] || {};
- var styleID, styleNodes;
- nodes = getElementsByTagName(responseXML, 'Style');
- nodeCount = nodes.length;
- for (i = 0; i < nodeCount; i++) {
- thisNode = nodes[i];
- var styleID = thisNode.getAttribute('id');
- if (!!styleID) processStyle(thisNode, doc.baseUrl, styleID, doc.baseDir);
- }
- // Parse StyleMap nodes
- nodes = getElementsByTagName(responseXML, 'StyleMap');
- for (i = 0; i < nodes.length; i++) {
- thisNode = nodes[i];
- var styleID = thisNode.getAttribute('id');
- if (!!styleID) processStyleMap(thisNode, doc.baseUrl, styleID, doc.baseDir);
- }
-
- if (!!parserOptions.processStyles || !parserOptions.createMarker) {
- // Convert parsed styles into GMaps equivalents
- processStyles(doc);
- }
-
- // Parse placemarks
- if (!!doc.reload && !!doc.markers) {
- for (i = 0; i < doc.markers.length; i++) {
- doc.markers[i].active = false;
- }
- }
- var placemark, node, coords, path, marker, poly;
- var placemark, coords, path, pathLength, marker, polygonNodes, coordList;
- var placemarkNodes = getElementsByTagName(responseXML, 'Placemark');
- for (pm = 0; pm < placemarkNodes.length; pm++) {
- // Init the placemark object
- node = placemarkNodes[pm];
- var styleUrl = processStyleUrl(node);
- placemark = {
- name: nodeValue(getElementsByTagName(node, 'name')[0]),
- description: nodeValue(getElementsByTagName(node, 'description')[0]),
- styleUrl: styleUrl.join('#'),
- styleBaseUrl: styleUrl[0] ? cleanURL(doc.baseDir, styleUrl[0]) : doc.baseUrl,
- styleID: styleUrl[1],
- visibility: getBooleanValue(getElementsByTagName(node, 'visibility')[0], true),
- balloonVisibility: getBooleanValue(getElementsByTagNameNS(node, gxNS, 'balloonVisibility')[0], !parserOptions.suppressInfoWindows)
- };
- placemark.style = (styles[placemark.styleBaseUrl] && styles[placemark.styleBaseUrl][placemark.styleID]) || clone(defaultStyle);
- // inline style overrides shared style
- var inlineStyles = getElementsByTagName(node, 'Style');
- if (inlineStyles && (inlineStyles.length > 0)) {
- var style = processStyle(node, '{inline}', '{inline}');
- processStyleID(style);
- if (style) placemark.style = style;
- }
-
- if (/^https?:\/\//.test(placemark.description)) {
- placemark.description = ['<a href="', placemark.description, '">', placemark.description, '</a>'].join('');
- }
-
- // record list of variables for substitution
- placemark.vars = {
- display: {
- name: 'Name',
- description: 'Description',
- address: 'Street Address',
- id: 'ID',
- Snippet: 'Snippet',
- geDirections: 'Directions'
- },
- val: {
- name: placemark.name || '',
- description: placemark.description || '',
- address: nodeValue(getElementsByTagName(node, 'address')[0], ''),
- id: node.getAttribute('id') || '',
- Snippet: nodeValue(getElementsByTagName(node, 'Snippet')[0], '')
- },
- directions: [
- 'f=d',
- 'source=GeoXML3'
- ]
- };
-
- // add extended data to variables
- var extDataNodes = getElementsByTagName(node, 'ExtendedData');
- if (!!extDataNodes && extDataNodes.length > 0) {
- var dataNodes = getElementsByTagName(extDataNodes[0], 'Data');
- for (var d = 0; d < dataNodes.length; d++) {
- var dn = dataNodes[d];
- var name = dn.getAttribute('name');
- if (!name) continue;
- var dName = nodeValue(getElementsByTagName(dn, 'displayName')[0], name);
- var val = nodeValue(getElementsByTagName(dn, 'value')[0]);
-
- placemark.vars.val[name] = val;
- placemark.vars.display[name] = dName;
- }
- }
-
- // process MultiGeometry
- var GeometryNodes = getElementsByTagName(node, 'coordinates');
- var Geometry = null;
- if (!!GeometryNodes && (GeometryNodes.length > 0)) {
- for (var gn=0;gn<GeometryNodes.length;gn++) {
- if (GeometryNodes[gn].parentNode &&
- GeometryNodes[gn].parentNode.nodeName) {
- var GeometryPN = GeometryNodes[gn].parentNode;
- Geometry = GeometryPN.nodeName;
-
- // Extract the coordinates
- // What sort of placemark?
- switch(Geometry) {
- case "Point":
- placemark.Point = processPlacemarkCoords(node, "Point")[0];
- placemark.latlng = new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng);
- pathLength = 1;
- break;
- case "LinearRing":
- // Polygon/line
- polygonNodes = getElementsByTagName(node, 'Polygon');
- // Polygon
- if (!placemark.Polygon)
- placemark.Polygon = [{
- outerBoundaryIs: {coordinates: []},
- innerBoundaryIs: [{coordinates: []}]
- }];
- for (var pg=0;pg<polygonNodes.length;pg++) {
- placemark.Polygon[pg] = {
- outerBoundaryIs: {coordinates: []},
- innerBoundaryIs: [{coordinates: []}]
- }
- placemark.Polygon[pg].outerBoundaryIs = processPlacemarkCoords(polygonNodes[pg], "outerBoundaryIs");
- placemark.Polygon[pg].innerBoundaryIs = processPlacemarkCoords(polygonNodes[pg], "innerBoundaryIs");
- }
- coordList = placemark.Polygon[0].outerBoundaryIs;
- break;
-
- case "LineString":
- pathLength = 0;
- placemark.LineString = processPlacemarkCoords(node,"LineString");
- break;
-
- default:
- break;
- }
- }
- }
- }
-
- // call the custom placemark parse function if it is defined
- if (!!parserOptions.pmParseFn) parserOptions.pmParseFn(node, placemark);
- doc.placemarks.push(placemark);
-
- // single marker
- if (placemark.Point) {
- if (!!google.maps) {
- doc.bounds = doc.bounds || new google.maps.LatLngBounds();
- doc.bounds.extend(placemark.latlng);
- }
-
- // Potential user-defined marker handler
- var pointCreateFunc = parserOptions.createMarker || createMarker;
- var found = false;
- if (!parserOptions.createMarker) {
- // Check to see if this marker was created on a previous load of this document
- if (!!doc) {
- doc.markers = doc.markers || [];
- if (doc.reload) {
- for (var j = 0; j < doc.markers.length; j++) {
- if (doc.markers[j].getPosition().equals(placemark.latlng)) {
- found = doc.markers[j].active = true;
- break;
- }
- }
- }
- }
- }
- if (!found) {
- // Call the marker creator
- var marker = pointCreateFunc(placemark, doc);
- if (marker) marker.active = placemark.visibility;
- }
- }
- // polygon/line
- var poly, line;
- if (!!doc) {
- if (placemark.Polygon) doc.gpolygons = doc.gpolygons || [];
- if (placemark.LineString) doc.gpolylines = doc.gpolylines || [];
- }
-
- var polyCreateFunc = parserOptions.createPolygon || createPolygon;
- var lineCreateFunc = parserOptions.createLineString || createPolyline;
- if (placemark.Polygon) {
- poly = polyCreateFunc(placemark,doc);
- if (poly) poly.active = placemark.visibility;
- }
- if (placemark.LineString) {
- line = lineCreateFunc(placemark,doc);
- if (line) line.active = placemark.visibility;
- }
- if (!!google.maps) {
- doc.bounds = doc.bounds || new google.maps.LatLngBounds();
- if (poly) doc.bounds.union(poly.bounds);
- if (line) doc.bounds.union(line.bounds);
- }
-
- } // placemark loop
-
- if (!!doc.reload && !!doc.markers) {
- for (i = doc.markers.length - 1; i >= 0 ; i--) {
- if (!doc.markers[i].active) {
- if (!!doc.markers[i].infoWindow) {
- doc.markers[i].infoWindow.close();
- }
- doc.markers[i].setMap(null);
- doc.markers.splice(i, 1);
- }
- }
- }
-
- // Parse ground overlays
- if (!!doc.reload && !!doc.groundoverlays) {
- for (i = 0; i < doc.groundoverlays.length; i++) {
- doc.groundoverlays[i].active = false;
- }
- }
-
- if (!!doc) {
- doc.groundoverlays = doc.groundoverlays || [];
- }
- // doc.groundoverlays =[];
- var groundOverlay, color, transparency, overlay;
- var groundNodes = getElementsByTagName(responseXML, 'GroundOverlay');
- for (i = 0; i < groundNodes.length; i++) {
- node = groundNodes[i];
-
- // Detect images buried in KMZ files (and use a base64 encoded URL)
- var gnUrl = cleanURL( doc.baseDir, nodeValue(getElementsByTagName(node, 'href')[0]) );
- if (kmzMetaData[gnUrl]) gnUrl = kmzMetaData[gnUrl].dataUrl;
-
- // Init the ground overlay object
- groundOverlay = {
- name: nodeValue(getElementsByTagName(node, 'name')[0]),
- description: nodeValue(getElementsByTagName(node, 'description')[0]),
- icon: { href: gnUrl },
- latLonBox: {
- north: parseFloat(nodeValue(getElementsByTagName(node, 'north')[0])),
- east: parseFloat(nodeValue(getElementsByTagName(node, 'east')[0])),
- south: parseFloat(nodeValue(getElementsByTagName(node, 'south')[0])),
- west: parseFloat(nodeValue(getElementsByTagName(node, 'west')[0]))
- }
- };
- if (!!google.maps) {
- doc.bounds = doc.bounds || new google.maps.LatLngBounds();
- doc.bounds.union(new google.maps.LatLngBounds(
- new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west),
- new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)
- ));
- }
-
- // Opacity is encoded in the color node
- var colorNode = getElementsByTagName(node, 'color');
- if (colorNode && colorNode.length > 0) {
- groundOverlay.opacity = geoXML3.getOpacity(nodeValue(colorNode[0]));
- } else {
- groundOverlay.opacity = 1.0; // KML default
- }
-
- doc.groundoverlays.push(groundOverlay);
- if (!!parserOptions.createOverlay) {
- // User-defined overlay handler
- parserOptions.createOverlay(groundOverlay, doc);
- } else {
- // Check to see if this overlay was created on a previous load of this document
- var found = false;
- if (!!doc) {
- doc.groundoverlays = doc.groundoverlays || [];
- if (doc.reload) {
- overlayBounds = new google.maps.LatLngBounds(
- new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west),
- new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)
- );
- var overlays = doc.groundoverlays;
- for (i = overlays.length; i--;) {
- if ((overlays[i].bounds().equals(overlayBounds)) &&
- (overlays.url_ === groundOverlay.icon.href)) {
- found = overlays[i].active = true;
- break;
- }
- }
- }
- }
-
- if (!found) {
- // Call the built-in overlay creator
- overlay = createOverlay(groundOverlay, doc);
- overlay.active = true;
- }
- }
- if (!!doc.reload && !!doc.groundoverlays && !!doc.groundoverlays.length) {
- var overlays = doc.groundoverlays;
- for (i = overlays.length; i--;) {
- if (!overlays[i].active) {
- overlays[i].remove();
- overlays.splice(i, 1);
- }
- }
- doc.groundoverlays = overlays;
- }
- }
-
- // Parse network links
- var networkLink;
- var docPath = document.location.pathname.split('/');
- docPath = docPath.splice(0, docPath.length - 1).join('/');
- var linkNodes = getElementsByTagName(responseXML, 'NetworkLink');
- for (i = 0; i < linkNodes.length; i++) {
- node = linkNodes[i];
-
- // Init the network link object
- networkLink = {
- name: nodeValue(getElementsByTagName(node, 'name')[0]),
- link: {
- href: nodeValue(getElementsByTagName(node, 'href')[0]),
- refreshMode: nodeValue(getElementsByTagName(node, 'refreshMode')[0])
- }
- };
-
- // Establish the specific refresh mode
- if (!networkLink.link.refreshMode) {
- networkLink.link.refreshMode = 'onChange';
- }
- if (networkLink.link.refreshMode === 'onInterval') {
- networkLink.link.refreshInterval = parseFloat(nodeValue(getElementsByTagName(node, 'refreshInterval')[0]));
- if (isNaN(networkLink.link.refreshInterval)) {
- networkLink.link.refreshInterval = 0;
- }
- } else if (networkLink.link.refreshMode === 'onChange') {
- networkLink.link.viewRefreshMode = nodeValue(getElementsByTagName(node, 'viewRefreshMode')[0]);
- if (!networkLink.link.viewRefreshMode) {
- networkLink.link.viewRefreshMode = 'never';
- }
- if (networkLink.link.viewRefreshMode === 'onStop') {
- networkLink.link.viewRefreshTime = nodeValue(getElementsByTagName(node, 'refreshMode')[0]);
- networkLink.link.viewFormat = nodeValue(getElementsByTagName(node, 'refreshMode')[0]);
- if (!networkLink.link.viewFormat) {
- networkLink.link.viewFormat = 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]';
- }
- }
- }
-
- if (!/^[\/|http]/.test(networkLink.link.href)) {
- // Fully-qualify the HREF
- networkLink.link.href = docPath + '/' + networkLink.link.href;
- }
-
- // Apply the link
- if ((networkLink.link.refreshMode === 'onInterval') &&
- (networkLink.link.refreshInterval > 0)) {
- // Reload at regular intervals
- setInterval(parserName + '.parse("' + networkLink.link.href + '")',
- 1000 * networkLink.link.refreshInterval);
- } else if (networkLink.link.refreshMode === 'onChange') {
- if (networkLink.link.viewRefreshMode === 'never') {
- // Load the link just once
- doc.internals.parser.parse(networkLink.link.href, doc.internals.docSet);
- } else if (networkLink.link.viewRefreshMode === 'onStop') {
- // Reload when the map view changes
-
- }
- }
- }
- }
-
- if (!!doc.bounds) {
- doc.internals.bounds = doc.internals.bounds || new google.maps.LatLngBounds();
- doc.internals.bounds.union(doc.bounds);
- }
- if (!!doc.markers || !!doc.groundoverlays || !!doc.gpolylines || !!doc.gpolygons) {
- doc.internals.parseOnly = false;
- }
-
- if (!doc.internals.parseOnly) {
- // geoXML3 is not being used only as a real-time parser, so keep the processed documents around
- if (!docsByUrl[doc.baseUrl]) {
- docs.push(doc);
- docsByUrl[doc.baseUrl] = doc;
- }
- else {
- // internal replacement, which keeps the same memory ref loc in docs and docsByUrl
- for (var i in docsByUrl[doc.baseUrl]) {
- docsByUrl[doc.baseUrl][i] = doc[i];
- }
- }
- }
-
- doc.internals.remaining--;
- if (doc.internals.remaining === 0) {
- // We're done processing this set of KML documents
- // Options that get invoked after parsing completes
- if (parserOptions.zoom && !!doc.internals.bounds) {
- parserOptions.map.fitBounds(doc.internals.bounds);
- }
- if (parserOptions.afterParse) {
- parserOptions.afterParse(doc.internals.docSet);
- }
- }
- };
-
- var kmlColor = function (kmlIn, colorMode) {
- var kmlColor = {};
- kmlIn = kmlIn || 'ffffffff'; // white (KML 2.2 default)
-
- var aa = kmlIn.substr(0,2);
- var bb = kmlIn.substr(2,2);
- var gg = kmlIn.substr(4,2);
- var rr = kmlIn.substr(6,2);
-
- kmlColor.opacity = parseInt(aa, 16) / 256;
- kmlColor.color = (colorMode === 'random') ? randomColor(rr, gg, bb) : '#' + rr + gg + bb;
- return kmlColor;
- };
-
- // Implemented per KML 2.2 <ColorStyle> specs
- var randomColor = function(rr, gg, bb) {
- var col = { rr: rr, gg: gg, bb: bb };
- for (var k in col) {
- var v = col[k];
- if (v == null) v = 'ff';
-
- // RGB values are limiters for random numbers (ie: 7f would be a random value between 0 and 7f)
- v = Math.round(Math.random() * parseInt(rr, 16)).toString(16);
- if (v.length === 1) v = '0' + v;
- col[k] = v;
- }
-
- return '#' + col.rr + col.gg + col.bb;
- };
-
- var processStyleID = function (style) {
- var icon = style.icon;
- if (!icon.href) return;
-
- if (icon.img && !icon.img.complete) {
- // we're still waiting on the image loading (probably because we've been blocking since the declaration)
- // so, let's queue this function on the onload stack
- icon.markerBacklog = [];
- icon.img.onload = function() {
- if (icon.dim.w < 0 || icon.dim.h < 0) {
- icon.dim.w = this.width;
- icon.dim.h = this.height;
- }
- processStyleID(style);
-
- // we will undoubtedly get some createMarker queuing, so set this up in advance
- for (var i = 0; i < icon.markerBacklog.length; i++) {
- var p = icon.markerBacklog[i][0];
- var d = icon.markerBacklog[i][1];
- createMarker(p, d);
- if (p.marker) p.marker.active = true;
- }
- delete icon.markerBacklog;
- };
- return;
- }
- else if (icon.dim.w < 0 || icon.dim.h < 0) {
- if (icon.img && icon.img.complete) {
- // sometimes the file is already cached and it never calls onLoad
- icon.dim.w = icon.img.width;
- icon.dim.h = icon.img.height;
- }
- else {
- // settle for a default of 32x32
- icon.dim.whGuess = true;
- icon.dim.w = 32;
- icon.dim.h = 32;
- }
- }
-
- // pre-scaled variables
- var rnd = Math.round;
- var scaled = {
- x: icon.dim.x * icon.scale,
- y: icon.dim.y * icon.scale,
- w: icon.dim.w * icon.scale,
- h: icon.dim.h * icon.scale,
- aX: icon.hotSpot.x * icon.scale,
- aY: icon.hotSpot.y * icon.scale,
- iW: (icon.img ? icon.img.width : icon.dim.w) * icon.scale,
- iH: (icon.img ? icon.img.height : icon.dim.h) * icon.scale
- };
-
- // Figure out the anchor spot
- var aX, aY;
- switch (icon.hotSpot.xunits) {
- case 'fraction': aX = rnd(scaled.aX * icon.dim.w); break;
- case 'insetPixels': aX = rnd(icon.dim.w * icon.scale - scaled.aX); break;
- default: aX = rnd(scaled.aX); break; // already pixels
- }
- aY = rnd( ((icon.hotSpot.yunits === 'fraction') ? icon.dim.h : 1) * scaled.aY ); // insetPixels Y = pixels Y
- var iconAnchor = new google.maps.Point(aX, aY);
-
- // Sizes
- // (NOTE: Scale is applied to entire image, not just the section of the icon palette.)
- var iconSize = icon.dim.whGuess ? null : new google.maps.Size(rnd(scaled.w), rnd(scaled.h));
- var iconScale = icon.scale == 1.0 ? null :
- icon.dim.whGuess ? new google.maps.Size(rnd(scaled.w), rnd(scaled.h))
- : new google.maps.Size(rnd(scaled.iW), rnd(scaled.iH));
- var iconOrigin = new google.maps.Point(rnd(scaled.x), rnd(scaled.y));
-
- // Detect images buried in KMZ files (and use a base64 encoded URL)
- if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl;
-
- // Init the style object with the KML icon
- icon.marker = new google.maps.MarkerImage(
- icon.url, // url
- iconSize, // size
- iconOrigin, // origin
- iconAnchor, // anchor
- iconScale // scaledSize
- );
-
- // Look for a predictable shadow
- var stdRegEx = /\/(red|blue|green|yellow|lightblue|purple|pink|orange)(-dot)?\.png/;
- var shadowSize = new google.maps.Size(59, 32);
- var shadowPoint = new google.maps.Point(16, 32);
- if (stdRegEx.test(icon.href)) {
- // A standard GMap-style marker icon
- icon.shadow = new google.maps.MarkerImage(
- 'http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png', // url
- shadowSize, // size
- null, // origin
- shadowPoint, // anchor
- shadowSize // scaledSize
- );
- } else if (icon.href.indexOf('-pushpin.png') > -1) {
- // Pushpin marker icon
- icon.shadow = new google.maps.MarkerImage(
- 'http://maps.google.com/mapfiles/ms/micons/pushpin_shadow.png', // url
- shadowSize, // size
- null, // origin
- shadowPoint, // anchor
- shadowSize // scaledSize
- );
- } /* else {
+ // Inherit from Google MVC Object to include event handling
+ google.maps.MVCObject.call(this);
+
+ // Private variables
+ var parserOptions = new geoXML3.parserOptions(options);
+ var docs = []; // Individual KML documents
+ var docsByUrl = {}; // Same docs as an hash by cleanURL
+ var kmzMetaData = {}; // Extra files from KMZ data
+ var styles = {}; // Global list of styles
+ var lastPlacemark;
+ var parserName;
+ if (!parserOptions.infoWindow && parserOptions.singleInfoWindow)
+ parserOptions.infoWindow = new google.maps.InfoWindow();
+
+ var parseKmlString = function (kmlString, docSet) {
+ // Internal values for the set of documents as a whole
+ var internals = {
+ parser: this,
+ docSet: docSet || [],
+ remaining: 1,
+ parseOnly: !(parserOptions.afterParse || parserOptions.processStyles)
+ };
+ thisDoc = new Object();
+ thisDoc.internals = internals;
+ internals.docSet.push(thisDoc);
+ render(geoXML3.xmlParse(kmlString),thisDoc);
+ }
+
+ var parse = function (urls, docSet) {
+ // Process one or more KML documents
+ if (!parserName) {
+ parserName = 'geoXML3.instances[' + (geoXML3.instances.push(this) - 1) + ']';
+ }
+
+ if (typeof urls === 'string') {
+ // Single KML document
+ urls = [urls];
+ }
+
+ // Internal values for the set of documents as a whole
+ var internals = {
+ parser: this,
+ docSet: docSet || [],
+ remaining: urls.length,
+ parseOnly: !(parserOptions.afterParse || parserOptions.processStyles)
+ };
+ var thisDoc, j;
+ for (var i = 0; i < urls.length; i++) {
+ var baseUrl = cleanURL(defileURL(location.pathname), urls[i]);
+ if (docsByUrl[baseUrl]) {
+ // Reloading an existing document
+ thisDoc = docsByUrl[baseUrl];
+ thisDoc.reload = true;
+ }
+ else {
+ thisDoc = new Object();
+ thisDoc.baseUrl = baseUrl;
+ internals.docSet.push(thisDoc);
+ }
+ thisDoc.url = urls[i];
+ thisDoc.internals = internals;
+ fetchDoc(thisDoc.url, thisDoc);
+ }
+ };
+
+ function fetchDoc(url, doc, resFunc) {
+ resFunc = resFunc || function (responseXML) { render(responseXML, doc); };
+
+ if (typeof ZipFile === 'function' && typeof JSIO === 'object' && typeof JSIO.guessFileType === 'function') { // KMZ support requires these modules loaded
+ // if url is a data URI scheme, do not guess type based on extension.
+ if (/^data:[^,]*(kmz)/.test(doc.baseUrl)) {
+ contentType = JSIO.FileType.Binary;
+ } else if (/^data:[^,]*(kml|xml)/.test(doc.baseUrl)) {
+ contentType = JSIO.FileType.XML;
+ } else if (/^data:/.test(doc.baseUrl)) {
+ contentType = JSIO.FileType.Unknown;
+ } else if (parserOptions.forceZip) {
+ contentType = JSIO.FileType.Binary;
+ } else {
+ contentType = JSIO.guessFileType(doc.baseUrl);
+ }
+ if (contentType == JSIO.FileType.Binary || contentType == JSIO.FileType.Unknown) {
+ doc.isCompressed = true;
+ doc.baseDir = doc.baseUrl + '/';
+ geoXML3.fetchZIP(url, resFunc, doc.internals.parser);
+ return;
+ }
+ }
+ doc.isCompressed = false;
+ doc.baseDir = defileURL(doc.baseUrl);
+ geoXML3.fetchXML(url, resFunc);
+ }
+
+ var hideDocument = function (doc) {
+ if (!doc) doc = docs[0];
+ // Hide the map objects associated with a document
+ var i;
+ if (!!doc.markers) {
+ for (i = 0; i < doc.markers.length; i++) {
+ if(!!doc.markers[i].infoWindow) doc.markers[i].infoWindow.close();
+ doc.markers[i].setVisible(false);
+ }
+ }
+ if (!!doc.ggroundoverlays) {
+ for (i = 0; i < doc.ggroundoverlays.length; i++) {
+ doc.ggroundoverlays[i].setOpacity(0);
+ }
+ }
+ if (!!doc.gpolylines) {
+ for (i=0;i<doc.gpolylines.length;i++) {
+ if(!!doc.gpolylines[i].infoWindow) doc.gpolylines[i].infoWindow.close();
+ doc.gpolylines[i].setMap(null);
+ }
+ }
+ if (!!doc.gpolygons) {
+ for (i=0;i<doc.gpolygons.length;i++) {
+ if(!!doc.gpolygons[i].infoWindow) doc.gpolygons[i].infoWindow.close();
+ doc.gpolygons[i].setMap(null);
+ }
+ }
+ };
+
+ var showDocument = function (doc) {
+ if (!doc) doc = docs[0];
+ // Show the map objects associated with a document
+ var i;
+ if (!!doc.markers) {
+ for (i = 0; i < doc.markers.length; i++) {
+ doc.markers[i].setVisible(true);
+ }
+ }
+ if (!!doc.ggroundoverlays) {
+ for (i = 0; i < doc.ggroundoverlays.length; i++) {
+ doc.ggroundoverlays[i].setOpacity(doc.ggroundoverlays[i].percentOpacity_);
+ }
+ }
+ if (!!doc.gpolylines) {
+ for (i=0;i<doc.gpolylines.length;i++) {
+ doc.gpolylines[i].setMap(parserOptions.map);
+ }
+ }
+ if (!!doc.gpolygons) {
+ for (i=0;i<doc.gpolygons.length;i++) {
+ doc.gpolygons[i].setMap(parserOptions.map);
+ }
+ }
+ };
+
+ var defaultStyle = {
+ balloon: {
+ bgColor: 'ffffffff',
+ textColor: 'ff000000',
+ text: "<h3>$[name]</h3>\n<div>$[description]</div>\n<div>$[geDirections]</div>",
+ displayMode: 'default'
+ },
+ icon: {
+ scale: 1.0,
+ dim: {
+ x: 0,
+ y: 0,
+ w: -1,
+ h: -1
+ },
+ hotSpot: {
+ x: 0.5,
+ y: 0.5,
+ xunits: 'fraction',
+ yunits: 'fraction'
+ }
+ },
+ line: {
+ color: 'ffffffff', // white (KML default)
+ colorMode: 'normal',
+ width: 1.0
+ },
+ poly: {
+ color: 'ffffffff', // white (KML default)
+ colorMode: 'normal',
+ fill: true,
+ outline: true
+ }
+ };
+
+ var kmlNS = 'http://www.opengis.net/kml/2.2';
+ var gxNS = 'http://www.google.com/kml/ext/2.2';
+ var nodeValue = geoXML3.nodeValue;
+ var getBooleanValue = geoXML3.getBooleanValue;
+ var getElementsByTagNameNS = geoXML3.getElementsByTagNameNS;
+ var getElementsByTagName = geoXML3.getElementsByTagName;
+
+ function processStyleUrl(node) {
+ var styleUrlStr = nodeValue(getElementsByTagName(node, 'styleUrl')[0]);
+ if (!!styleUrlStr && styleUrlStr.indexOf('#') != -1)
+ var styleUrl = styleUrlStr.split('#');
+ else var styleUrl = ["",""];
+ return styleUrl;
+ }
+
+ function processStyle(thisNode, baseUrl, styleID, baseDir) {
+ var style = (baseUrl === '{inline}') ? clone(defaultStyle) : (styles[baseUrl][styleID] = styles[baseUrl][styleID] || clone(defaultStyle));
+
+ var styleNodes = getElementsByTagName(thisNode, 'BalloonStyle');
+ if (!!styleNodes && styleNodes.length > 0) {
+ style.balloon.bgColor = nodeValue(getElementsByTagName(styleNodes[0], 'bgColor')[0], style.balloon.bgColor);
+ style.balloon.textColor = nodeValue(getElementsByTagName(styleNodes[0], 'textColor')[0], style.balloon.textColor);
+ style.balloon.text = nodeValue(getElementsByTagName(styleNodes[0], 'text')[0], style.balloon.text);
+ style.balloon.displayMode = nodeValue(getElementsByTagName(styleNodes[0], 'displayMode')[0], style.balloon.displayMode);
+ }
+
+ // style.list = (unsupported; doesn't make sense in Google Maps)
+
+ var styleNodes = getElementsByTagName(thisNode, 'IconStyle');
+ if (!!styleNodes && styleNodes.length > 0) {
+ var icon = style.icon;
+
+ icon.scale = parseFloat(nodeValue(getElementsByTagName(styleNodes[0], 'scale')[0], icon.scale));
+ // style.icon.heading = (unsupported; not supported in API)
+ // style.icon.color = (unsupported; not supported in API)
+ // style.icon.colorMode = (unsupported; not supported in API)
+
+ styleNodes = getElementsByTagName(styleNodes[0], 'hotSpot');
+ if (!!styleNodes && styleNodes.length > 0) {
+ icon.hotSpot = {
+ x: styleNodes[0].getAttribute('x'),
+ y: styleNodes[0].getAttribute('y'),
+ xunits: styleNodes[0].getAttribute('xunits'),
+ yunits: styleNodes[0].getAttribute('yunits')
+ };
+ }
+
+ styleNodes = getElementsByTagName(thisNode, 'Icon');
+ if (!!styleNodes && styleNodes.length > 0) {
+ icon.href = nodeValue(getElementsByTagName(styleNodes[0], 'href')[0]);
+ icon.url = cleanURL(baseDir, icon.href);
+ // Detect images buried in KMZ files (and use a base64 encoded URL)
+ if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl;
+
+ // Support for icon palettes and exact size dimensions
+ icon.dim = {
+ x: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'x')[0], icon.dim.x)),
+ y: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'y')[0], icon.dim.y)),
+ w: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'w')[0], icon.dim.w)),
+ h: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'h')[0], icon.dim.h))
+ };
+
+ // certain occasions where we need the pixel size of the image (like the default settings...)
+ // (NOTE: Scale is applied to entire image, not just the section of the icon palette. So,
+ // if we need scaling, we'll need the img dimensions no matter what.)
+ if (true /* (icon.dim.w < 0 || icon.dim.h < 0) && (icon.xunits != 'pixels' || icon.yunits == 'fraction') || icon.scale != 1.0 */) {
+ // (hopefully, this will load by the time we need it...)
+ icon.img = new Image();
+ icon.img.onload = function() {
+ if (icon.dim.w < 0 || icon.dim.h < 0) {
+ icon.dim.w = this.width;
+ icon.dim.h = this.height;
+ } else {
+ icon.dim.th = this.height;
+ }
+ };
+ icon.img.src = icon.url;
+
+ // sometimes the file is already cached and it never calls onLoad
+ if (icon.img.width > 0) {
+ if (icon.dim.w < 0 || icon.dim.h < 0) {
+ icon.dim.w = icon.img.width;
+ icon.dim.h = icon.img.height;
+ } else {
+ icon.dim.th = icon.img.height;
+ }
+ }
+ }
+ }
+ }
+
+ // style.label = (unsupported; may be possible but not with API)
+
+ styleNodes = getElementsByTagName(thisNode, 'LineStyle');
+ if (!!styleNodes && styleNodes.length > 0) {
+ style.line.color = nodeValue(getElementsByTagName(styleNodes[0], 'color')[0], style.line.color);
+ style.line.colorMode = nodeValue(getElementsByTagName(styleNodes[0], 'colorMode')[0], style.line.colorMode);
+ style.line.width = nodeValue(getElementsByTagName(styleNodes[0], 'width')[0], style.line.width);
+ // style.line.outerColor = (unsupported; not supported in API)
+ // style.line.outerWidth = (unsupported; not supported in API)
+ // style.line.physicalWidth = (unsupported; unneccesary in Google Maps)
+ // style.line.labelVisibility = (unsupported; possible to implement)
+ }
+
+ styleNodes = getElementsByTagName(thisNode, 'PolyStyle');
+ if (!!styleNodes && styleNodes.length > 0) {
+ style.poly.color = nodeValue( getElementsByTagName(styleNodes[0], 'color')[0], style.poly.color);
+ style.poly.colorMode = nodeValue( getElementsByTagName(styleNodes[0], 'colorMode')[0], style.poly.colorMode);
+ style.poly.outline = getBooleanValue(getElementsByTagName(styleNodes[0], 'outline')[0], style.poly.outline);
+ style.poly.fill = getBooleanValue(getElementsByTagName(styleNodes[0], 'fill')[0], style.poly.fill);
+ }
+ return style;
+ }
+
+ // from http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-a-javascript-object
+ // http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
+ function clone(obj){
+ if(obj == null || typeof(obj) != 'object') return obj;
+ if (obj.cloneNode) return obj.cloneNode(true);
+ var temp = new obj.constructor();
+ for(var key in obj) temp[key] = clone(obj[key]);
+ return temp;
+ }
+
+ function processStyleMap(thisNode, baseUrl, styleID, baseDir) {
+ var pairs = getElementsByTagName(thisNode, 'Pair');
+ var map = new Object();
+
+ // add each key to the map
+ for (var pr=0;pr<pairs.length;pr++) {
+ var pairKey = nodeValue(getElementsByTagName(pairs[pr], 'key')[0]);
+ var pairStyle = nodeValue(getElementsByTagName(pairs[pr], 'Style')[0]);
+ var pairStyleUrl = processStyleUrl(pairs[pr]);
+ var pairStyleBaseUrl = pairStyleUrl[0] ? cleanURL(baseDir, pairStyleUrl[0]) : baseUrl;
+ var pairStyleID = pairStyleUrl[1];
+
+ if (!!pairStyle) {
+ map[pairKey] = processStyle(pairStyle, pairStyleBaseUrl, pairStyleID);
+ } else if (!!pairStyleID && !!styles[pairStyleBaseUrl][pairStyleID]) {
+ map[pairKey] = clone(styles[pairStyleBaseUrl][pairStyleID]);
+ }
+ }
+ if (!!map["normal"]) {
+ styles[baseUrl][styleID] = clone(map["normal"]);
+ } else {
+ styles[baseUrl][styleID] = clone(defaultStyle);
+ }
+ if (!!map["highlight"] && !!parserOptions.processStyles) {
+ processStyleID(map["highlight"]);
+ }
+ styles[baseUrl][styleID].map = clone(map);
+ }
+
+ function processPlacemarkCoords(node, tag) {
+ var parent = getElementsByTagName(node, tag);
+ var coordListA = [];
+ for (var i=0; i<parent.length; i++) {
+ var coordNodes = getElementsByTagName(parent[i], 'coordinates');
+ if (!coordNodes) {
+ if (coordListA.length > 0) {
+ break;
+ } else {
+ return [{coordinates: []}];
+ }
+ }
+
+ for (var j=0; j<coordNodes.length;j++) {
+ var coords = nodeValue(coordNodes[j]).trim();
+ coords = coords.replace(/,\s+/g, ',');
+ var path = coords.split(/\s+/g);
+ var pathLength = path.length;
+ var coordList = [];
+ for (var k = 0; k < pathLength; k++) {
+ coords = path[k].split(',');
+ if (!isNaN(coords[0]) && !isNaN(coords[1])) {
+ coordList.push({
+ lat: parseFloat(coords[1]),
+ lng: parseFloat(coords[0]),
+ alt: parseFloat(coords[2])
+ });
+ }
+ }
+ coordListA.push({coordinates: coordList});
+ }
+ }
+ return coordListA;
+ }
+
+ var render = function (responseXML, doc) {
+ // Callback for retrieving a KML document: parse the KML and display it on the map
+ if (!responseXML || responseXML == "failed parse") {
+ // Error retrieving the data
+ geoXML3.log('Unable to retrieve ' + doc.url);
+ if (parserOptions.failedParse) parserOptions.failedParse(doc);
+ doc.failed = true;
+ return;
+ } else if (responseXML.parseError && responseXML.parseError.errorCode != 0) {
+ // IE parse error
+ var err = responseXML.parseError;
+ var msg = 'Parse error in line ' + err.line + ', col ' + err.linePos + ' (error code: ' + err.errorCode + ")\n" +
+ "\nError Reason: " + err.reason +
+ 'Error Line: ' + err.srcText;
+
+ geoXML3.log('Unable to retrieve ' + doc.url + ': ' + msg);
+ if (parserOptions.failedParse) parserOptions.failedParse(doc);
+ doc.failed = true;
+ return;
+ } else if (responseXML.documentElement && responseXML.documentElement.nodeName == 'parsererror') {
+ // Firefox parse error
+ geoXML3.log('Unable to retrieve ' + doc.url + ': ' + responseXML.documentElement.childNodes[0].nodeValue);
+ if (parserOptions.failedParse) parserOptions.failedParse(doc);
+ doc.failed = true;
+ return;
+ } else if (!doc) {
+ throw 'geoXML3 internal error: render called with null document';
+ } else { //no errors
+ var i;
+ doc.placemarks = [];
+ doc.groundoverlays = [];
+ doc.ggroundoverlays = [];
+ doc.networkLinks = [];
+ doc.gpolygons = [];
+ doc.gpolylines = [];
+
+ // Check for dependent KML files
+ var nodes = getElementsByTagName(responseXML, 'styleUrl');
+ var docSet = doc.internals.docSet;
+
+ for (var i = 0; i < nodes.length; i++) {
+ var url = nodeValue(nodes[i]).split('#')[0];
+ if (!url) continue; // #id (inside doc)
+ var rUrl = cleanURL( doc.baseDir, url );
+ if (rUrl === doc.baseUrl) continue; // self
+ if (docsByUrl[rUrl]) continue; // already loaded
+
+ var thisDoc;
+ var j = docSet.indexOfObjWithItem('baseUrl', rUrl);
+ if (j != -1) {
+ // Already listed to be loaded, but probably in the wrong order.
+ // Load it right away to immediately resolve dependency.
+ thisDoc = docSet[j];
+ if (thisDoc.failed) continue; // failed to load last time; don't retry it again
+ }
+ else {
+ // Not listed at all; add it in
+ thisDoc = new Object();
+ thisDoc.url = rUrl; // url can't be trusted inside KMZ files, since it may .. outside of the archive
+ thisDoc.baseUrl = rUrl;
+ thisDoc.internals = doc.internals;
+
+ doc.internals.docSet.push(thisDoc);
+ doc.internals.remaining++;
+ }
+
+ // render dependent KML first then re-run renderer
+ fetchDoc(rUrl, thisDoc, function (thisResXML) {
+ render(thisResXML, thisDoc);
+ render(responseXML, doc);
+ });
+
+ // to prevent cross-dependency issues, just load the one
+ // file first and re-check the rest later
+ return;
+ }
+
+ // Parse styles
+ doc.styles = styles[doc.baseUrl] = styles[doc.baseUrl] || {};
+ var styleID, styleNodes;
+ nodes = getElementsByTagName(responseXML, 'Style');
+ nodeCount = nodes.length;
+ for (i = 0; i < nodeCount; i++) {
+ thisNode = nodes[i];
+ var styleID = thisNode.getAttribute('id');
+ if (!!styleID) processStyle(thisNode, doc.baseUrl, styleID, doc.baseDir);
+ }
+ // Parse StyleMap nodes
+ nodes = getElementsByTagName(responseXML, 'StyleMap');
+ for (i = 0; i < nodes.length; i++) {
+ thisNode = nodes[i];
+ var styleID = thisNode.getAttribute('id');
+ if (!!styleID) processStyleMap(thisNode, doc.baseUrl, styleID, doc.baseDir);
+ }
+
+ if (!!parserOptions.processStyles || !parserOptions.createMarker) {
+ // Convert parsed styles into GMaps equivalents
+ processStyles(doc);
+ }
+
+ // Parse placemarks
+ if (!!doc.reload && !!doc.markers) {
+ for (i = 0; i < doc.markers.length; i++) {
+ doc.markers[i].active = false;
+ }
+ }
+ var placemark, node, coords, path, marker, poly;
+ var pathLength, marker, polygonNodes, coordList;
+ var placemarkNodes = getElementsByTagName(responseXML, 'Placemark');
+ for (pm = 0; pm < placemarkNodes.length; pm++) {
+ // Init the placemark object
+ node = placemarkNodes[pm];
+ var styleUrl = processStyleUrl(node);
+ placemark = {
+ name: nodeValue(getElementsByTagName(node, 'name')[0]),
+ description: nodeValue(getElementsByTagName(node, 'description')[0]),
+ styleUrl: styleUrl.join('#'),
+ styleBaseUrl: styleUrl[0] ? cleanURL(doc.baseDir, styleUrl[0]) : doc.baseUrl,
+ styleID: styleUrl[1],
+ visibility: getBooleanValue(getElementsByTagName(node, 'visibility')[0], true),
+ balloonVisibility: getBooleanValue(getElementsByTagNameNS(node, gxNS, 'balloonVisibility')[0], !parserOptions.suppressInfoWindows),
+ id: node.getAttribute('id')
+ };
+ placemark.style = (styles[placemark.styleBaseUrl] && styles[placemark.styleBaseUrl][placemark.styleID]) || clone(defaultStyle);
+ // inline style overrides shared style
+ var inlineStyles = getElementsByTagName(node, 'Style');
+ if (inlineStyles && (inlineStyles.length > 0)) {
+ var style = processStyle(node, '{inline}', '{inline}');
+ processStyleID(style);
+ if (style) placemark.style = style;
+ }
+
+ if (/^https?:\/\//.test(placemark.description)) {
+ placemark.description = ['<a href="', placemark.description, '">', placemark.description, '</a>'].join('');
+ }
+
+ // record list of variables for substitution
+ placemark.vars = {
+ display: {
+ name: 'Name',
+ description: 'Description',
+ address: 'Street Address',
+ id: 'ID',
+ Snippet: 'Snippet',
+ geDirections: 'Directions'
+ },
+ val: {
+ name: placemark.name || '',
+ description: placemark.description || '',
+ address: nodeValue(getElementsByTagName(node, 'address')[0], ''),
+ id: node.getAttribute('id') || '',
+ Snippet: nodeValue(getElementsByTagName(node, 'Snippet')[0], '')
+ },
+ directions: [
+ 'f=d',
+ 'source=GeoXML3'
+ ]
+ };
+
+ // add extended data to variables
+ var extDataNodes = getElementsByTagName(node, 'ExtendedData');
+ if (!!extDataNodes && extDataNodes.length > 0) {
+ var dataNodes = getElementsByTagName(extDataNodes[0], 'Data');
+ for (var d = 0; d < dataNodes.length; d++) {
+ var dn = dataNodes[d];
+ var name = dn.getAttribute('name');
+ if (!name) continue;
+ var dName = nodeValue(getElementsByTagName(dn, 'displayName')[0], name);
+ var val = nodeValue(getElementsByTagName(dn, 'value')[0]);
+
+ placemark.vars.val[name] = val;
+ placemark.vars.display[name] = dName;
+ }
+ }
+
+ // process MultiGeometry
+ var GeometryNodes = getElementsByTagName(node, 'coordinates');
+ var Geometry = null;
+ if (!!GeometryNodes && (GeometryNodes.length > 0)) {
+ for (var gn=0;gn<GeometryNodes.length;gn++) {
+ if (GeometryNodes[gn].parentNode &&
+ GeometryNodes[gn].parentNode.nodeName) {
+ var GeometryPN = GeometryNodes[gn].parentNode;
+ Geometry = GeometryPN.nodeName;
+
+ // Extract the coordinates
+ // What sort of placemark?
+ switch(Geometry) {
+ case "Point":
+ placemark.Point = processPlacemarkCoords(node, "Point")[0];
+ placemark.latlng = new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng);
+ pathLength = 1;
+ break;
+ case "LinearRing":
+ // Polygon/line
+ polygonNodes = getElementsByTagName(node, 'Polygon');
+ // Polygon
+ if (!placemark.Polygon)
+ placemark.Polygon = [{
+ outerBoundaryIs: {coordinates: []},
+ innerBoundaryIs: [{coordinates: []}]
+ }];
+ for (var pg=0;pg<polygonNodes.length;pg++) {
+ placemark.Polygon[pg] = {
+ outerBoundaryIs: {coordinates: []},
+ innerBoundaryIs: [{coordinates: []}]
+ }
+ placemark.Polygon[pg].outerBoundaryIs = processPlacemarkCoords(polygonNodes[pg], "outerBoundaryIs");
+ placemark.Polygon[pg].innerBoundaryIs = processPlacemarkCoords(polygonNodes[pg], "innerBoundaryIs");
+ }
+ coordList = placemark.Polygon[0].outerBoundaryIs;
+ break;
+
+ case "LineString":
+ pathLength = 0;
+ placemark.LineString = processPlacemarkCoords(node,"LineString");
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ // parse MultiTrack/Track
+ var TrackNodes = getElementsByTagNameNS(node,gxNS,"Track");
+ var coordListA = [];
+ if (TrackNodes.length > 0) {
+ for (var i=0; i<TrackNodes.length; i++) {
+ var coordNodes = getElementsByTagNameNS(TrackNodes[i],gxNS,"coord");
+ var coordList = [];
+ for (var j=0; j<coordNodes.length;j++) {
+ var coords = geoXML3.nodeValue(coordNodes[j]).trim();
+ coords = coords.split(/\s+/g);
+ if (!isNaN(coords[0]) && !isNaN(coords[1])) {
+ coordList.push({
+ lat: parseFloat(coords[1]),
+ lng: parseFloat(coords[0]),
+ alt: parseFloat(coords[2])
+ });
+ }
+ }
+ coordListA.push({coordinates:coordList});
+ }
+ placemark.Track = coordListA;
+ }
+
+ // call the custom placemark parse function if it is defined
+ if (!!parserOptions.pmParseFn) parserOptions.pmParseFn(node, placemark);
+ doc.placemarks.push(placemark);
+
+ // single marker
+ if (placemark.Point) {
+ if (!!google.maps) {
+ doc.bounds = doc.bounds || new google.maps.LatLngBounds();
+ doc.bounds.extend(placemark.latlng);
+ }
+
+ // Potential user-defined marker handler
+ var pointCreateFunc = parserOptions.createMarker || createMarker;
+ var found = false;
+ if (!parserOptions.createMarker) {
+ // Check to see if this marker was created on a previous load of this document
+ if (!!doc) {
+ doc.markers = doc.markers || [];
+ if (doc.reload) {
+ for (var j = 0; j < doc.markers.length; j++) {
+ if ((doc.markers[j].id == placemark.id) ||
+ // if no id, check position
+ (!doc.markers[j].id &&
+ (doc.markers[j].getPosition().equals(placemark.latlng)))) {
+ found = doc.markers[j].active = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (!found) {
+ // Call the marker creator
+ var marker = pointCreateFunc(placemark, doc);
+ if (marker) {
+ marker.active = placemark.visibility;
+ marker.id = placemark.id;
+ }
+ }
+ }
+ // polygon/line
+ var poly, line;
+ if (!!doc) {
+ if (placemark.Polygon) doc.gpolygons = doc.gpolygons || [];
+ if (placemark.LineString) doc.gpolylines = doc.gpolylines || [];
+ if (placemark.Track) doc.gpolylines = doc.gpolylines || [];
+ }
+
+ var polyCreateFunc = parserOptions.createPolygon || createPolygon;
+ var lineCreateFunc = parserOptions.createLineString || createPolyline;
+ if (placemark.Polygon) {
+ poly = polyCreateFunc(placemark,doc);
+ if (poly) poly.active = placemark.visibility;
+ }
+ if (placemark.LineString) {
+ line = lineCreateFunc(placemark,doc);
+ if (line) line.active = placemark.visibility;
+ }
+ if (placemark.Track) { // gx:Track polyline
+ line = lineCreateFunc(placemark,doc);
+ if (line) line.active = placemark.visibility;
+ }
+ if (!!google.maps) {
+ doc.bounds = doc.bounds || new google.maps.LatLngBounds();
+ if (poly) doc.bounds.union(poly.bounds);
+ if (line) doc.bounds.union(line.bounds);
+ }
+
+ } // placemark loop
+
+ if (!!doc.reload && !!doc.markers) {
+ for (i = doc.markers.length - 1; i >= 0 ; i--) {
+ if (!doc.markers[i].active) {
+ if (!!doc.markers[i].infoWindow) {
+ doc.markers[i].infoWindow.close();
+ }
+ doc.markers[i].setMap(null);
+ doc.markers.splice(i, 1);
+ }
+ }
+ }
+
+ var overlayCreateFunc = parserOptions.createOverlay || createOverlay;
+ // Parse ground overlays
+ if (!!doc.reload && !!doc.groundoverlays) {
+ for (i = 0; i < doc.groundoverlays.length; i++) {
+ doc.groundoverlays[i].active = false;
+ }
+ }
+
+ if (!!doc) {
+ doc.groundoverlays = doc.groundoverlays || [];
+ }
+ // doc.groundoverlays =[];
+ var groundOverlay, color, transparency, overlay;
+ var groundNodes = getElementsByTagName(responseXML, 'GroundOverlay');
+ for (i = 0; i < groundNodes.length; i++) {
+ node = groundNodes[i];
+
+ // Detect images buried in KMZ files (and use a base64 encoded URL)
+ var gnUrl = cleanURL( doc.baseDir, nodeValue(getElementsByTagName(node, 'href')[0]) );
+ if (kmzMetaData[gnUrl]) gnUrl = kmzMetaData[gnUrl].dataUrl;
+
+ // Init the ground overlay object
+ groundOverlay = {
+ name: nodeValue(getElementsByTagName(node, 'name')[0]),
+ description: nodeValue(getElementsByTagName(node, 'description')[0]),
+ icon: { href: gnUrl },
+ latLonBox: {
+ north: parseFloat(nodeValue(getElementsByTagName(node, 'north')[0])),
+ east: parseFloat(nodeValue(getElementsByTagName(node, 'east')[0])),
+ south: parseFloat(nodeValue(getElementsByTagName(node, 'south')[0])),
+ west: parseFloat(nodeValue(getElementsByTagName(node, 'west')[0]))
+ },
+ rotation: -1 * parseFloat(nodeValue(getElementsByTagName(node, 'rotation')[0]))
+ };
+ if (!!google.maps) {
+ doc.bounds = doc.bounds || new google.maps.LatLngBounds();
+ doc.bounds.union(new google.maps.LatLngBounds(
+ new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west),
+ new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)
+ ));
+ }
+
+ // Opacity is encoded in the color node
+ var colorNode = getElementsByTagName(node, 'color');
+ if (colorNode && colorNode.length > 0) {
+ groundOverlay.opacity = geoXML3.getOpacity(nodeValue(colorNode[0]));
+ } else {
+ groundOverlay.opacity = 1.0; // KML default
+ }
+
+ doc.groundoverlays.push(groundOverlay);
+ // Check to see if this overlay was created on a previous load of this document
+ var found = false;
+ if (!!doc) {
+ doc.groundoverlays = doc.groundoverlays || [];
+ if (doc.reload) {
+ overlayBounds = new google.maps.LatLngBounds(
+ new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west),
+ new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)
+ );
+ var overlays = doc.groundoverlays;
+ for (i = overlays.length; i--;) {
+ if ((overlays[i].bounds().equals(overlayBounds)) &&
+ (overlays.url_ === groundOverlay.icon.href)) {
+ found = overlays[i].active = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ overlay = overlayCreateFunc(groundOverlay, doc);
+ overlay.active = true;
+ }
+ }
+ if (!!doc.reload && !!doc.groundoverlays && !!doc.groundoverlays.length) {
+ var overlays = doc.groundoverlays;
+ for (i = overlays.length; i--;) {
+ if (!overlays[i].active) {
+ overlays[i].remove();
+ overlays.splice(i, 1);
+ }
+ }
+ doc.groundoverlays = overlays;
+ }
+ }
+
+ // Parse network links
+ var networkLink;
+ var docPath = document.location.pathname.split('/');
+ docPath = docPath.splice(0, docPath.length - 1).join('/');
+ var linkNodes = getElementsByTagName(responseXML, 'NetworkLink');
+ for (i = 0; i < linkNodes.length; i++) {
+ node = linkNodes[i];
+
+ // Init the network link object
+ networkLink = {
+ name: nodeValue(getElementsByTagName(node, 'name')[0]),
+ link: {
+ href: nodeValue(getElementsByTagName(node, 'href')[0]),
+ refreshMode: nodeValue(getElementsByTagName(node, 'refreshMode')[0])
+ }
+ };
+
+ // Establish the specific refresh mode
+ if (!networkLink.link.refreshMode) {
+ networkLink.link.refreshMode = 'onChange';
+ }
+ if (networkLink.link.refreshMode === 'onInterval') {
+ networkLink.link.refreshInterval = parseFloat(nodeValue(getElementsByTagName(node, 'refreshInterval')[0]));
+ if (isNaN(networkLink.link.refreshInterval)) {
+ networkLink.link.refreshInterval = 0;
+ }
+ } else if (networkLink.link.refreshMode === 'onChange') {
+ networkLink.link.viewRefreshMode = nodeValue(getElementsByTagName(node, 'viewRefreshMode')[0]);
+ if (!networkLink.link.viewRefreshMode) {
+ networkLink.link.viewRefreshMode = 'never';
+ }
+ if (networkLink.link.viewRefreshMode === 'onStop') {
+ networkLink.link.viewRefreshTime = nodeValue(getElementsByTagName(node, 'refreshMode')[0]);
+ networkLink.link.viewFormat = nodeValue(getElementsByTagName(node, 'refreshMode')[0]);
+ if (!networkLink.link.viewFormat) {
+ networkLink.link.viewFormat = 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]';
+ }
+ }
+ }
+
+ if (!/^[\/|http]/.test(networkLink.link.href)) {
+ // Fully-qualify the HREF
+ networkLink.link.href = docPath + '/' + networkLink.link.href;
+ }
+
+ // Apply the link
+ if ((networkLink.link.refreshMode === 'onInterval') &&
+ (networkLink.link.refreshInterval > 0)) {
+ // Reload at regular intervals
+ setInterval(parserName + '.parse("' + networkLink.link.href + '")',
+ 1000 * networkLink.link.refreshInterval);
+ } else if (networkLink.link.refreshMode === 'onChange') {
+ if (networkLink.link.viewRefreshMode === 'never') {
+ // Load the link just once
+ doc.internals.parser.parse(networkLink.link.href, doc.internals.docSet);
+ } else if (networkLink.link.viewRefreshMode === 'onStop') {
+ // Reload when the map view changes
+
+ }
+ }
+ }
+ }
+
+ if (!!doc.bounds) {
+ doc.internals.bounds = doc.internals.bounds || new google.maps.LatLngBounds();
+ doc.internals.bounds.union(doc.bounds);
+ }
+ if (!!doc.markers || !!doc.groundoverlays || !!doc.gpolylines || !!doc.gpolygons) {
+ doc.internals.parseOnly = false;
+ }
+
+ if (!doc.internals.parseOnly) {
+ // geoXML3 is not being used only as a real-time parser, so keep the processed documents around
+ if (doc.baseUrl){ // handle case from parseKmlString (no doc.baseUrl)
+ if (!docsByUrl[doc.baseUrl]) {
+ docs.push(doc);
+ docsByUrl[doc.baseUrl] = doc;
+ } else {
+ // internal replacement, which keeps the same memory ref loc in docs and docsByUrl
+ for (var i in docsByUrl[doc.baseUrl]) {
+ docsByUrl[doc.baseUrl][i] = doc[i];
+ }
+ }
+ }
+ }
+
+ doc.internals.remaining--;
+ if (doc.internals.remaining === 0) {
+ // We're done processing this set of KML documents
+ // Options that get invoked after parsing completes
+ if (parserOptions.zoom && !!doc.internals.bounds &&
+ !doc.internals.bounds.isEmpty() && !!parserOptions.map) {
+ parserOptions.map.fitBounds(doc.internals.bounds);
+ }
+ if (parserOptions.afterParse) {
+ parserOptions.afterParse(doc.internals.docSet);
+ }
+ google.maps.event.trigger(doc.internals.parser, 'parsed');
+ }
+ };
+
+ var kmlColor = function (kmlIn, colorMode) {
+ var kmlColor = {};
+ kmlIn = kmlIn || 'ffffffff'; // white (KML 2.2 default)
+
+ var aa = kmlIn.substr(0,2);
+ var bb = kmlIn.substr(2,2);
+ var gg = kmlIn.substr(4,2);
+ var rr = kmlIn.substr(6,2);
+
+ kmlColor.opacity = parseInt(aa, 16) / 256;
+ kmlColor.color = (colorMode === 'random') ? randomColor(rr, gg, bb) : '#' + rr + gg + bb;
+ return kmlColor;
+ };
+
+ // Implemented per KML 2.2 <ColorStyle> specs
+ var randomColor = function(rr, gg, bb) {
+ var col = { rr: rr, gg: gg, bb: bb };
+ for (var k in col) {
+ var v = col[k];
+ if (v == null) v = 'ff';
+
+ // RGB values are limiters for random numbers (ie: 7f would be a random value between 0 and 7f)
+ v = Math.round(Math.random() * parseInt(rr, 16)).toString(16);
+ if (v.length === 1) v = '0' + v;
+ col[k] = v;
+ }
+
+ return '#' + col.rr + col.gg + col.bb;
+ };
+
+ var processStyleID = function (style) {
+ var icon = style.icon;
+ if (!icon || !icon.href) return;
+
+ if (icon.img && !icon.img.complete && (icon.dim.w < 0) && (icon.dim.h < 0) ) {
+ // we're still waiting on the image loading (probably because we've been blocking since the declaration)
+ // so, let's queue this function on the onload stack
+ icon.markerBacklog = [];
+ icon.img.onload = function() {
+ if (icon.dim.w < 0 || icon.dim.h < 0) {
+ icon.dim.w = this.width;
+ icon.dim.h = this.height;
+ } else {
+ icon.dim.th = this.height;
+ }
+ processStyleID(style);
+
+ // we will undoubtedly get some createMarker queuing, so set this up in advance
+ for (var i = 0; i < icon.markerBacklog.length; i++) {
+ var p = icon.markerBacklog[i][0];
+ var d = icon.markerBacklog[i][1];
+ createMarker(p, d);
+ if (p.marker) p.marker.active = true;
+ }
+ delete icon.markerBacklog;
+ };
+ return;
+ }
+ else { //if (icon.dim.w < 0 || icon.dim.h < 0) {
+ if (icon.img && icon.img.complete) {
+ // sometimes the file is already cached and it never calls onLoad
+ if (icon.dim.w < 0 || icon.dim.h < 0) {
+ icon.dim.w = icon.img.width;
+ icon.dim.h = icon.img.height;
+ } else {
+ icon.dim.th = icon.img.height;
+ }
+ }
+ else {
+ // settle for a default of 32x32
+ icon.dim.whGuess = true;
+ icon.dim.w = 32;
+ icon.dim.h = 32;
+ icon.dim.th = 32;
+ }
+ }
+
+ // pre-scaled variables
+ var rnd = Math.round;
+ var y = icon.dim.y;
+ if (typeof icon.dim.th !== 'undefined' && icon.dim.th != icon.dim.h) { // palette - reverse kml y for maps
+ y = Math.abs(y - (icon.dim.th - icon.dim.h));
+ }
+
+ var scaled = {
+ x: icon.dim.x * icon.scale,
+ y: y * icon.scale,
+ w: icon.dim.w * icon.scale,
+ h: icon.dim.h * icon.scale,
+ aX:icon.hotSpot.x * icon.scale,
+ aY:icon.hotSpot.y * icon.scale,
+ iW:(icon.img ? icon.img.width : icon.dim.w) * icon.scale,
+ iH:(icon.img ? icon.img.height : icon.dim.h) * icon.scale
+ };
+
+ // Figure out the anchor spot
+ // Origins, anchor positions and coordinates of the marker increase in the X direction to the right and in
+ // the Y direction down.
+ var aX, aY;
+ switch (icon.hotSpot.xunits) {
+ case 'fraction': aX = rnd(scaled.aX * icon.dim.w); break;
+ case 'insetPixels': aX = rnd(icon.dim.w * icon.scale - scaled.aX); break;
+ default: aX = rnd(scaled.aX); break; // already pixels
+ }
+ switch(icon.hotSpot.yunits) {
+ case 'fraction': aY = scaled.h - rnd(icon.dim.h * scaled.aY); break;
+ case 'insetPixels': aY = rnd(scaled.aY); break;
+ default: aY = rnd(icon.dim.h * icon.scale - scaled.aY); break;
+ }
+ var iconAnchor = new google.maps.Point(aX, aY);
+
+ // Sizes
+ // (NOTE: Scale is applied to entire image, not just the section of the icon palette.)
+ var iconSize = icon.dim.whGuess ? null : new google.maps.Size(rnd(scaled.w), rnd(scaled.h));
+ var iconScale = icon.scale == 1.0 ? null :
+ icon.dim.whGuess ? new google.maps.Size(rnd(scaled.w), rnd(scaled.h))
+ : new google.maps.Size(rnd(scaled.iW), rnd(scaled.iH));
+ var iconOrigin = new google.maps.Point(rnd(scaled.x), rnd(scaled.y));
+
+ // Detect images buried in KMZ files (and use a base64 encoded URL)
+ if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl;
+
+ // Init the style object with the KML icon
+ icon.marker = {
+ url: icon.url, // url
+ size: iconSize, // size
+ origin: iconOrigin, // origin
+ anchor: iconAnchor, // anchor
+ scaledSize: iconScale // scaledSize
+ };
+
+ // Look for a predictable shadow
+ var stdRegEx = /\/(red|blue|green|yellow|lightblue|purple|pink|orange)(-dot)?\.png/;
+ var shadowSize = new google.maps.Size(59, 32);
+ var shadowPoint = new google.maps.Point(16, 32);
+ if (stdRegEx.test(icon.href)) {
+ // A standard GMap-style marker icon
+ icon.shadow = {
+ url: 'http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png', // url
+ size: shadowSize, // size
+ origin: null, // origin
+ anchor: shadowPoint, // anchor
+ scaledSize: shadowSize // scaledSize
+ };
+ } else if (icon.href.indexOf('-pushpin.png') > -1) {
+ // Pushpin marker icon
+ icon.shadow = {
+ url: 'http://maps.google.com/mapfiles/ms/micons/pushpin_shadow.png', // url
+ size: shadowSize, // size
+ origin: null, // origin
+ anchor: shadowPoint, // anchor
+ scaledSize: shadowSize // scaledSize
+ };
+ } /* else {
// Other MyMaps KML standard icon
icon.shadow = new google.maps.MarkerImage(
icon.href.replace('.png', '.shadow.png'), // url
@@ -1033,251 +1155,293 @@ function processStyleUrl(node) {
shadowSize // scaledSize
);
} */
- }
-
- var processStyles = function (doc) {
- for (var styleID in doc.styles) {
- processStyleID(doc.styles[styleID]);
- }
- };
-
- var createMarker = function (placemark, doc) {
- // create a Marker to the map from a placemark KML object
- var icon = placemark.style.icon;
-
- if ( !icon.marker && icon.img ) {
- // yay, single point of failure is holding up multiple markers...
- icon.markerBacklog = icon.markerBacklog || [];
- icon.markerBacklog.push([placemark, doc]);
- return;
- }
-
- // Load basic marker properties
- var markerOptions = geoXML3.combineOptions(parserOptions.markerOptions, {
- map: parserOptions.map,
- position: new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng),
- title: placemark.name,
- zIndex: Math.round(placemark.Point.coordinates[0].lat * -100000)<<5,
- icon: icon.marker,
- shadow: icon.shadow,
- flat: !icon.shadow,
- visible: placemark.visibility
- });
-
- // Create the marker on the map
- var marker = new google.maps.Marker(markerOptions);
- if (!!doc) doc.markers.push(marker);
-
- // Set up and create the infowindow if it is not suppressed
- createInfoWindow(placemark, doc, marker);
- placemark.marker = marker;
- return marker;
- };
-
- var createOverlay = function (groundOverlay, doc) {
- // Add a ProjectedOverlay to the map from a groundOverlay KML object
-
- if (!window.ProjectedOverlay) {
- throw 'geoXML3 error: ProjectedOverlay not found while rendering GroundOverlay from KML';
- }
-
- var bounds = new google.maps.LatLngBounds(
- new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west),
- new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)
- );
- var overlayOptions = geoXML3.combineOptions(parserOptions.overlayOptions, {percentOpacity: groundOverlay.opacity*100});
- var overlay = new ProjectedOverlay(parserOptions.map, groundOverlay.icon.href, bounds, overlayOptions);
-
- if (!!doc) {
- doc.ggroundoverlays = doc.ggroundoverlays || [];
- doc.ggroundoverlays.push(overlay);
- }
-
- return overlay;
- };
-
- // Create Polyline
- var createPolyline = function(placemark, doc) {
- var path = [];
- for (var j=0; j<placemark.LineString.length; j++) {
- var coords = placemark.LineString[j].coordinates;
- var bounds = new google.maps.LatLngBounds();
- for (var i=0;i<coords.length;i++) {
- var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
- path.push(pt);
- bounds.extend(pt);
- }
- }
- // point to open the infowindow if triggered
- var point = path[Math.floor(path.length/2)];
- // Load basic polyline properties
- var kmlStrokeColor = kmlColor(placemark.style.line.color, placemark.style.line.colorMode);
- var polyOptions = geoXML3.combineOptions(parserOptions.polylineOptions, {
- map: parserOptions.map,
- path: path,
- strokeColor: kmlStrokeColor.color,
- strokeWeight: placemark.style.line.width,
- strokeOpacity: kmlStrokeColor.opacity,
- title: placemark.name,
- visible: placemark.visibility
- });
- var p = new google.maps.Polyline(polyOptions);
- p.bounds = bounds;
-
- // setup and create the infoWindow if it is not suppressed
- createInfoWindow(placemark, doc, p);
- if (!!doc) doc.gpolylines.push(p);
- placemark.polyline = p;
- return p;
- }
-
- // Create Polygon
- var createPolygon = function(placemark, doc) {
- var bounds = new google.maps.LatLngBounds();
- var pathsLength = 0;
- var paths = [];
- for (var polygonPart=0;polygonPart<placemark.Polygon.length;polygonPart++) {
- for (var j=0; j<placemark.Polygon[polygonPart].outerBoundaryIs.length; j++) {
- var coords = placemark.Polygon[polygonPart].outerBoundaryIs[j].coordinates;
- var path = [];
- for (var i=0;i<coords.length;i++) {
- var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
- path.push(pt);
- bounds.extend(pt);
- }
- paths.push(path);
- pathsLength += path.length;
- }
- for (var j=0; j<placemark.Polygon[polygonPart].innerBoundaryIs.length; j++) {
- var coords = placemark.Polygon[polygonPart].innerBoundaryIs[j].coordinates;
- var path = [];
- for (var i=0;i<coords.length;i++) {
- var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
- path.push(pt);
- bounds.extend(pt);
- }
- paths.push(path);
- pathsLength += path.length;
- }
- }
-
- // Load basic polygon properties
- var kmlStrokeColor = kmlColor(placemark.style.line.color, placemark.style.line.colorMode);
- var kmlFillColor = kmlColor(placemark.style.poly.color, placemark.style.poly.colorMode);
- if (!placemark.style.poly.fill) kmlFillColor.opacity = 0.0;
- var strokeWeight = placemark.style.line.width;
- if (!placemark.style.poly.outline) {
- strokeWeight = 0;
- kmlStrokeColor.opacity = 0.0;
- }
- var polyOptions = geoXML3.combineOptions(parserOptions.polygonOptions, {
- map: parserOptions.map,
- paths: paths,
- title: placemark.name,
- strokeColor: kmlStrokeColor.color,
- strokeWeight: strokeWeight,
- strokeOpacity: kmlStrokeColor.opacity,
- fillColor: kmlFillColor.color,
- fillOpacity: kmlFillColor.opacity,
- visible: placemark.visibility
- });
- var p = new google.maps.Polygon(polyOptions);
- p.bounds = bounds;
-
- createInfoWindow(placemark, doc, p);
- if (!!doc) doc.gpolygons.push(p);
- placemark.polygon = p;
- return p;
- }
-
- var createInfoWindow = function(placemark, doc, gObj) {
- var bStyle = placemark.style.balloon;
- var vars = placemark.vars;
-
- if (!placemark.balloonVisibility || bStyle.displayMode === 'hide') return;
-
- // define geDirections
- if (placemark.latlng) {
- vars.directions.push('sll=' + placemark.latlng.toUrlValue());
-
- var url = 'http://maps.google.com/maps?' + vars.directions.join('&');
- var address = encodeURIComponent( vars.val.address || placemark.latlng.toUrlValue() ).replace(/\%20/g, '+');
-
- vars.val.geDirections = '<a href="' + url + '&daddr=' + address + '" target=_blank>To Here</a> - <a href="' + url + '&saddr=' + address + '" target=_blank>From Here</a>';
- }
- else vars.val.geDirections = '';
-
- // add in the variables
- var iwText = bStyle.text.replace(/\$\[(\w+(\/displayName)?)\]/g, function(txt, n, dn) { return dn ? vars.display[n] : vars.val[n]; });
- var classTxt = 'geoxml3_infowindow geoxml3_style_' + placemark.styleID;
-
- // color styles
- var styleArr = [];
- if (bStyle.bgColor != 'ffffffff') styleArr.push('background: ' + kmlColor(bStyle.bgColor ).color + ';');
- if (bStyle.textColor != 'ff000000') styleArr.push('color: ' + kmlColor(bStyle.textColor).color + ';');
- var styleProp = styleArr.length ? ' style="' + styleArr.join(' ') + '"' : '';
-
- var infoWindowOptions = geoXML3.combineOptions(parserOptions.infoWindowOptions, {
- content: '<div class="' + classTxt + '"' + styleProp + '>' + iwText + '</div>',
- pixelOffset: new google.maps.Size(0, 2)
- });
-
- gObj.infoWindow = parserOptions.infoWindow || new google.maps.InfoWindow(infoWindowOptions);
- gObj.infoWindowOptions = infoWindowOptions;
-
- // Info Window-opening event handler
- google.maps.event.addListener(gObj, 'click', function(e) {
- var iW = this.infoWindow;
- iW.close();
- iW.setOptions(this.infoWindowOptions);
-
- if (e && e.latLng) iW.setPosition(e.latLng);
- else if (this.bounds) iW.setPosition(this.bounds.getCenter());
-
- iW.open(this.map, this.bounds ? null : this);
- });
-
- }
-
- return {
- // Expose some properties and methods
-
- options: parserOptions,
- docs: docs,
- docsByUrl: docsByUrl,
- kmzMetaData: kmzMetaData,
-
- parse: parse,
- parseKmlString: parseKmlString,
- hideDocument: hideDocument,
- showDocument: showDocument,
- processStyles: processStyles,
- createMarker: createMarker,
- createOverlay: createOverlay,
- createPolyline: createPolyline,
- createPolygon: createPolygon
- };
+ }
+
+ var processStyles = function (doc) {
+ for (var styleID in doc.styles) {
+ processStyleID(doc.styles[styleID]);
+ }
+ };
+
+ var createMarker = function (placemark, doc) {
+ // create a Marker to the map from a placemark KML object
+ var icon = placemark.style.icon;
+
+ if ( !icon.marker && icon.img ) {
+ // yay, single point of failure is holding up multiple markers...
+ icon.markerBacklog = icon.markerBacklog || [];
+ icon.markerBacklog.push([placemark, doc]);
+ return;
+ }
+
+ // Load basic marker properties
+ var markerOptions = geoXML3.combineOptions(parserOptions.markerOptions, {
+ map: parserOptions.map,
+ position: new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng),
+ title: placemark.name,
+ zIndex: Math.round(placemark.Point.coordinates[0].lat * -100000)<<5,
+ icon: icon.marker,
+ shadow: icon.shadow,
+ flat: !icon.shadow,
+ visible: placemark.visibility
+ });
+
+ // Create the marker on the map
+ var marker = new google.maps.Marker(markerOptions);
+ if (!!doc) doc.markers.push(marker);
+
+ // Set up and create the infowindow if it is not suppressed
+ createInfoWindow(placemark, doc, marker);
+ placemark.marker = marker;
+ return marker;
+ };
+
+ var createOverlay = function (groundOverlay, doc) {
+ // Add a ProjectedOverlay to the map from a groundOverlay KML object
+
+ if (!window.ProjectedOverlay) {
+ throw 'geoXML3 error: ProjectedOverlay not found while rendering GroundOverlay from KML';
+ }
+
+ var bounds = new google.maps.LatLngBounds(
+ new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west),
+ new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)
+ );
+ var overlayOptions = geoXML3.combineOptions(parserOptions.overlayOptions, {
+ percentOpacity: groundOverlay.opacity*100,
+ rotation: groundOverlay.rotation
+ });
+ var overlay = new ProjectedOverlay(parserOptions.map, groundOverlay.icon.href, bounds, overlayOptions);
+
+ if (!!doc) {
+ doc.ggroundoverlays = doc.ggroundoverlays || [];
+ doc.ggroundoverlays.push(overlay);
+ }
+
+ return overlay;
+ };
+
+ // Create Polyline
+ var createPolyline = function(placemark, doc) {
+ var paths = [];
+ var bounds = new google.maps.LatLngBounds();
+ if (placemark.LineString) {
+ for (var j=0; j<placemark.LineString.length; j++) {
+ var path = [];
+ var coords = placemark.LineString[j].coordinates;
+ for (var i=0;i<coords.length;i++) {
+ var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
+ path.push(pt);
+ bounds.extend(pt);
+ }
+ paths.push(path);
+ }
+ } else if (placemark.Track) {
+ for (var j=0; j<placemark.Track.length; j++) {
+ var path = [];
+ var coords = placemark.Track[j].coordinates;
+ for (var i=0;i<coords.length;i++) {
+ var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
+ path.push(pt);
+ bounds.extend(pt);
+ }
+ paths.push(path);
+ }
+ }
+ // point to open the infowindow if triggered
+ var point = paths[0][Math.floor(path.length/2)];
+ // Load basic polyline properties
+ var kmlStrokeColor = kmlColor(placemark.style.line.color, placemark.style.line.colorMode);
+ var polyOptions = geoXML3.combineOptions(parserOptions.polylineOptions, {
+ map: parserOptions.map,
+ path: path,
+ strokeColor: kmlStrokeColor.color,
+ strokeWeight: placemark.style.line.width,
+ strokeOpacity: kmlStrokeColor.opacity,
+ title: placemark.name,
+ visible: placemark.visibility
+ });
+ if (paths.length > 1) {
+ polyOptions.paths = paths;
+ var p = new MultiGeometry(polyOptions);
+ } else {
+ polyOptions.path = paths[0];
+ var p = new google.maps.Polyline(polyOptions);
+ }
+ p.bounds = bounds;
+
+ // setup and create the infoWindow if it is not suppressed
+ createInfoWindow(placemark, doc, p);
+ if (!!doc) doc.gpolylines.push(p);
+ placemark.polyline = p;
+ return p;
+ }
+
+ // Create Polygon
+ var createPolygon = function(placemark, doc) {
+ var bounds = new google.maps.LatLngBounds();
+ var pathsLength = 0;
+ var paths = [];
+ for (var polygonPart=0;polygonPart<placemark.Polygon.length;polygonPart++) {
+ for (var j=0; j<placemark.Polygon[polygonPart].outerBoundaryIs.length; j++) {
+ var coords = placemark.Polygon[polygonPart].outerBoundaryIs[j].coordinates;
+ var path = [];
+ for (var i=0;i<coords.length;i++) {
+ var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
+ path.push(pt);
+ bounds.extend(pt);
+ }
+ paths.push(path);
+ pathsLength += path.length;
+ }
+ for (var j=0; j<placemark.Polygon[polygonPart].innerBoundaryIs.length; j++) {
+ var coords = placemark.Polygon[polygonPart].innerBoundaryIs[j].coordinates;
+ var path = [];
+ for (var i=0;i<coords.length;i++) {
+ var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
+ path.push(pt);
+ bounds.extend(pt);
+ }
+ paths.push(path);
+ pathsLength += path.length;
+ }
+ }
+
+ // Load basic polygon properties
+ var kmlStrokeColor = kmlColor(placemark.style.line.color, placemark.style.line.colorMode);
+ var kmlFillColor = kmlColor(placemark.style.poly.color, placemark.style.poly.colorMode);
+ if (!placemark.style.poly.fill) kmlFillColor.opacity = 0.0;
+ var strokeWeight = placemark.style.line.width;
+ if (!placemark.style.poly.outline) {
+ strokeWeight = 0;
+ kmlStrokeColor.opacity = 0.0;
+ }
+ var polyOptions = geoXML3.combineOptions(parserOptions.polygonOptions, {
+ map: parserOptions.map,
+ paths: paths,
+ title: placemark.name,
+ strokeColor: kmlStrokeColor.color,
+ strokeWeight: strokeWeight,
+ strokeOpacity: kmlStrokeColor.opacity,
+ fillColor: kmlFillColor.color,
+ fillOpacity: kmlFillColor.opacity,
+ visible: placemark.visibility
+ });
+ var p = new google.maps.Polygon(polyOptions);
+ p.bounds = bounds;
+
+ createInfoWindow(placemark, doc, p);
+ if (!!doc) doc.gpolygons.push(p);
+ placemark.polygon = p;
+ return p;
+ }
+
+ var createInfoWindow = function(placemark, doc, gObj) {
+ var bStyle = placemark.style.balloon;
+ var vars = placemark.vars;
+
+ if (!placemark.balloonVisibility || bStyle.displayMode === 'hide') return;
+
+ // define geDirections
+ if (placemark.latlng &&
+ (!parserOptions.suppressDirections || !parserOptions.suppressDirections)) {
+ vars.directions.push('sll=' + placemark.latlng.toUrlValue());
+
+ var url = 'http://maps.google.com/maps?' + vars.directions.join('&');
+ var address = encodeURIComponent( vars.val.address || placemark.latlng.toUrlValue() ).replace(/\%20/g, '+');
+
+ vars.val.geDirections = '<a href="' + url + '&daddr=' + address + '" target=_blank>To Here</a> - <a href="' + url + '&saddr=' + address + '" target=_blank>From Here</a>';
+ }
+ else vars.val.geDirections = '';
+
+ // add in the variables
+ var iwText = bStyle.text.replace(/\$\[(\w+(\/displayName)?)\]/g, function(txt, n, dn) { return dn ? vars.display[n] : vars.val[n]; });
+ var classTxt = 'geoxml3_infowindow geoxml3_style_' + placemark.styleID;
+
+ // color styles
+ var styleArr = [];
+ if (bStyle.bgColor != 'ffffffff') styleArr.push('background: ' + kmlColor(bStyle.bgColor ).color + ';');
+ if (bStyle.textColor != 'ff000000') styleArr.push('color: ' + kmlColor(bStyle.textColor).color + ';');
+ var styleProp = styleArr.length ? ' style="' + styleArr.join(' ') + '"' : '';
+
+ var infoWindowOptions = geoXML3.combineOptions(parserOptions.infoWindowOptions, {
+ content: '<div class="' + classTxt + '"' + styleProp + '>' + iwText + '</div>',
+ pixelOffset: new google.maps.Size(0, 2)
+ });
+
+ gObj.infoWindow = parserOptions.infoWindow || new google.maps.InfoWindow(infoWindowOptions);
+ gObj.infoWindowOptions = infoWindowOptions;
+
+ // Info Window-opening event handler
+ google.maps.event.addListener(gObj, 'click', function(e) {
+ var iW = this.infoWindow;
+ iW.close();
+ iW.setOptions(this.infoWindowOptions);
+
+ if (e && e.latLng) iW.setPosition(e.latLng);
+ else if (this.bounds) iW.setPosition(this.bounds.getCenter());
+
+ iW.setContent("<div id='geoxml3_infowindow'>"+iW.getContent()+"</div>");
+ google.maps.event.addListenerOnce(iW, "domready", function() {
+ var node = document.getElementById('geoxml3_infowindow');
+ var imgArray = node.getElementsByTagName('img');
+ for (var i = 0; i < imgArray.length; i++)
+ {
+ var imgUrlIE = imgArray[i].getAttribute("src");
+ var imgUrl = cleanURL(doc.baseDir, imgUrlIE);
+
+ if (kmzMetaData[imgUrl]) {
+ imgArray[i].src = kmzMetaData[imgUrl].dataUrl;
+ } else if (kmzMetaData[imgUrlIE]) {
+ imgArray[i].src = kmzMetaData[imgUrlIE].dataUrl;
+ }
+ }
+ });
+ iW.open(this.map, this.bounds ? null : this);
+ });
+
+ }
+
+ return {
+ // Expose some properties and methods
+
+ options: parserOptions,
+ docs: docs,
+ docsByUrl: docsByUrl,
+ kmzMetaData: kmzMetaData,
+
+ parse: parse,
+ render: render,
+ parseKmlString: parseKmlString,
+ hideDocument: hideDocument,
+ showDocument: showDocument,
+ processStyles: processStyles,
+ createMarker: createMarker,
+ createOverlay: createOverlay,
+ createPolyline: createPolyline,
+ createPolygon: createPolygon
+ };
};
// End of KML Parser
// Helper objects and functions
geoXML3.getOpacity = function (kmlColor) {
- // Extract opacity encoded in a KML color value. Returns a number between 0 and 1.
- if (!!kmlColor &&
- (kmlColor !== '') &&
- (kmlColor.length == 8)) {
- var transparency = parseInt(kmlColor.substr(0, 2), 16);
- return transparency / 255;
- } else {
- return 1;
- }
+ // Extract opacity encoded in a KML color value. Returns a number between 0 and 1.
+ if (!!kmlColor &&
+ (kmlColor !== '') &&
+ (kmlColor.length == 8)) {
+ var transparency = parseInt(kmlColor.substr(0, 2), 16);
+ return transparency / 255;
+ } else {
+ return 1;
+ }
};
// Log a message to the debugging console, if one exists
geoXML3.log = function(msg) {
- if (!!window.console) {
- console.log(msg);
- } else { alert("log:"+msg); }
+ if (!!window.console) {
+ console.log(msg);
+ } else { alert("log:"+msg); }
};
/**
@@ -1290,61 +1454,61 @@ geoXML3.log = function(msg) {
* @property {ProjectedOverlay.options} overlayOptions If the parser is adding ProjectedOverlays to the map itself, any options specified here will be applied to them.
*/
geoXML3.parserOptions = function (overrides) {
- this.map = null,
- /** If true, the parser will automatically move the map to a best-fit of the geodata after parsing of a KML document completes.
- * @type Boolean
- * @default true
- */
- this.zoom = true,
- /**#@+ @type Boolean
- * @default false */
- /** If true, only a single Marker created by the parser will be able to have its InfoWindow open at once (simulating the behavior of GMaps API v2). */
- this.singleInfoWindow = false,
- /** If true, suppresses the rendering of info windows. */
- this.suppressInfoWindows = false,
- /**
- * Control whether to process styles now or later.
- *
- * <p>By default, the parser only processes KML &lt;Style&gt; elements into their GMaps equivalents
- * if it will be creating its own Markers (the createMarker option is null). Setting this option
- * to true will force such processing to happen anyway, useful if you're going to be calling parser.createMarker
- * yourself later. OTOH, leaving this option false removes runtime dependency on the GMaps API, enabling
- * the use of geoXML3 as a standalone KML parser.</p>
- */
- this.processStyles = false,
- /**#@-*/
-
- this.markerOptions = {},
- this.infoWindowOptions = {},
- this.overlayOptions = {},
-
- /**#@+ @event */
- /** This function will be called when parsing of a KML document is complete.
- * @param {geoXML3.parser#docs} doc Parsed KML data. */
- this.afterParse = null,
- /** This function will be called when parsing of a KML document is complete.
- * @param {geoXML3.parser#docs} doc Parsed KML data. */
- this.failedParse = null,
- /**
- * If supplied, this function will be called once for each marker <Placemark> in the KML document, instead of the parser adding its own Marker to the map.
- * @param {geoXML3.parser.render#placemark} placemark Placemark object.
- * @param {geoXML3.parser#docs} doc Parsed KML data.
- */
- this.createMarker = null,
- /**
- * If supplied, this function will be called once for each <GroundOverlay> in the KML document, instead of the parser adding its own ProjectedOverlay to the map.
- * @param {geoXML3.parser.render#groundOverlay} groundOverlay GroundOverlay object.
- * @param {geoXML3.parser#docs} doc Parsed KML data.
- */
- this.createOverlay = null
- /**#@-*/
-
- if (overrides) {
- for (var prop in overrides) {
- if (overrides.hasOwnProperty(prop)) this[prop] = overrides[prop];
- }
- }
- return this;
+ this.map = null,
+ /** If true, the parser will automatically move the map to a best-fit of the geodata after parsing of a KML document completes.
+ * @type Boolean
+ * @default true
+ */
+ this.zoom = true,
+ /**#@+ @type Boolean
+ * @default false */
+ /** If true, only a single Marker created by the parser will be able to have its InfoWindow open at once (simulating the behavior of GMaps API v2). */
+ this.singleInfoWindow = false,
+ /** If true, suppresses the rendering of info windows. */
+ this.suppressInfoWindows = false,
+ /**
+ * Control whether to process styles now or later.
+ *
+ * <p>By default, the parser only processes KML &lt;Style&gt; elements into their GMaps equivalents
+ * if it will be creating its own Markers (the createMarker option is null). Setting this option
+ * to true will force such processing to happen anyway, useful if you're going to be calling parser.createMarker
+ * yourself later. OTOH, leaving this option false removes runtime dependency on the GMaps API, enabling
+ * the use of geoXML3 as a standalone KML parser.</p>
+ */
+ this.processStyles = false,
+ /**#@-*/
+
+ this.markerOptions = {},
+ this.infoWindowOptions = {},
+ this.overlayOptions = {},
+
+ /**#@+ @event */
+ /** This function will be called when parsing of a KML document is complete.
+ * @param {geoXML3.parser#docs} doc Parsed KML data. */
+ this.afterParse = null,
+ /** This function will be called when parsing of a KML document is complete.
+ * @param {geoXML3.parser#docs} doc Parsed KML data. */
+ this.failedParse = null,
+ /**
+ * If supplied, this function will be called once for each marker <Placemark> in the KML document, instead of the parser adding its own Marker to the map.
+ * @param {geoXML3.parser.render#placemark} placemark Placemark object.
+ * @param {geoXML3.parser#docs} doc Parsed KML data.
+ */
+ this.createMarker = null,
+ /**
+ * If supplied, this function will be called once for each <GroundOverlay> in the KML document, instead of the parser adding its own ProjectedOverlay to the map.
+ * @param {geoXML3.parser.render#groundOverlay} groundOverlay GroundOverlay object.
+ * @param {geoXML3.parser#docs} doc Parsed KML data.
+ */
+ this.createOverlay = null
+ /**#@-*/
+
+ if (overrides) {
+ for (var prop in overrides) {
+ if (overrides.hasOwnProperty(prop)) this[prop] = overrides[prop];
+ }
+ }
+ return this;
};
/**
@@ -1356,18 +1520,18 @@ geoXML3.parserOptions = function (overrides) {
* @return {geoXML3.parserOptions} Combined result.
*/
geoXML3.combineOptions = function (overrides, defaults) {
- var result = {};
- if (!!overrides) {
- for (var prop in overrides) {
- if (overrides.hasOwnProperty(prop)) result[prop] = overrides[prop];
- }
- }
- if (!!defaults) {
- for (prop in defaults) {
- if (defaults.hasOwnProperty(prop) && result[prop] === undefined) result[prop] = defaults[prop];
- }
- }
- return result;
+ var result = {};
+ if (!!overrides) {
+ for (var prop in overrides) {
+ if (overrides.hasOwnProperty(prop)) result[prop] = overrides[prop];
+ }
+ }
+ if (!!defaults) {
+ for (prop in defaults) {
+ if (defaults.hasOwnProperty(prop) && result[prop] === undefined) result[prop] = defaults[prop];
+ }
+ }
+ return result;
};
/**
@@ -1394,35 +1558,43 @@ geoXML3.fetchers = [];
* @return {Element|Document} DOM.
*/
geoXML3.xmlParse = function (str) {
- if (!!window.DOMParser) return (new DOMParser()).parseFromString(str, 'text/xml');
- else if (!!window.ActiveXObject) {
- var doc;
-
- // the many versions of IE's DOM parsers
- var AXOs = [
- 'MSXML2.DOMDocument.6.0',
- 'MSXML2.DOMDocument.5.0',
- 'MSXML2.DOMDocument.4.0',
- 'MSXML2.DOMDocument.3.0',
- 'MSXML2.DOMDocument',
- 'Microsoft.XMLDOM',
- 'MSXML.DOMDocument'
- ];
- for (var i = 0; i < AXOs.length; i++) {
- try { doc = new ActiveXObject(AXOs[i]); break; }
- catch(e) { continue; }
- }
- if (!doc) return createElement('div', null);
-
- if (doc.async) doc.async = false;
- doc.loadXML(str);
- return doc;
- }
-
- return createElement('div', null);
+ if ((typeof ActiveXObject != 'undefined') || ("ActiveXObject" in window)) {
+ var doc = new ActiveXObject('Microsoft.XMLDOM');
+ doc.loadXML(str);
+ return doc;
+ }
+
+ if (typeof DOMParser != 'undefined') {
+ return (new DOMParser()).parseFromString(str, 'text/xml');
+ }
+
+ return document.createElement('div', null);
}
/**
+ * Checks for XML parse error.
+ *
+ * @param {xmlDOM} XML DOM.
+ * @return boolean.
+ */
+// from http://stackoverflow.com/questions/11563554/how-do-i-detect-xml-parsing-errors-when-using-javascripts-domparser-in-a-cross
+geoXML3.isParseError = function(parsedDocument) {
+ if ((typeof ActiveXObject != 'undefined') || ("ActiveXObject" in window))
+ return false;
+ // parser and parsererrorNS could be cached on startup for efficiency
+ var p = new DOMParser(),
+ errorneousParse = p.parseFromString('<', 'text/xml'),
+ parsererrorNS = errorneousParse.getElementsByTagName("parsererror")[0].namespaceURI;
+
+ if (parsererrorNS === 'http://www.w3.org/1999/xhtml') {
+ // In PhantomJS the parseerror element doesn't seem to have a special namespace, so we are just guessing here :(
+ return parsedDocument.getElementsByTagName("parsererror").length > 0;
+ }
+
+ return parsedDocument.getElementsByTagNameNS(parsererrorNS, 'parsererror').length > 0;
+};
+
+/**
* Fetches a XML document.
*
* <p>Fetches/parses the given XML URL and passes the parsed document (in a
@@ -1433,62 +1605,87 @@ geoXML3.xmlParse = function (str) {
* @param {Function(Document)} callback Function to call when the document is processed.
*/
geoXML3.fetchXML = function (url, callback) {
- function timeoutHandler() { callback(); };
-
- var xhrFetcher = new Object();
- if (!!geoXML3.fetchers.length) xhrFetcher = geoXML3.fetchers.pop();
- else if (!!window.XMLHttpRequest) xhrFetcher.fetcher = new window.XMLHttpRequest(); // Most browsers
- else if (!!window.ActiveXObject) { // Some IE
- // the many versions of IE's XML fetchers
- var AXOs = [
- 'MSXML2.XMLHTTP.6.0',
- 'MSXML2.XMLHTTP.5.0',
- 'MSXML2.XMLHTTP.4.0',
- 'MSXML2.XMLHTTP.3.0',
- 'MSXML2.XMLHTTP',
- 'Microsoft.XMLHTTP',
- 'MSXML.XMLHTTP'
- ];
- for (var i = 0; i < AXOs.length; i++) {
- try { xhrFetcher.fetcher = new ActiveXObject(AXOs[i]); break; }
- catch(e) { continue; }
- }
- if (!xhrFetcher.fetcher) {
- geoXML3.log('Unable to create XHR object');
- callback(null);
- return null;
- }
- }
-
- if (!!xhrFetcher.fetcher.overrideMimeType) xhrFetcher.fetcher.overrideMimeType('text/xml');
- xhrFetcher.fetcher.open('GET', url, true);
- xhrFetcher.fetcher.onreadystatechange = function () {
- if (xhrFetcher.fetcher.readyState === 4) {
- // Retrieval complete
- if (!!xhrFetcher.xhrtimeout) clearTimeout(xhrFetcher.xhrtimeout);
- if (xhrFetcher.fetcher.status >= 400) {
- geoXML3.log('HTTP error ' + xhrFetcher.fetcher.status + ' retrieving ' + url);
- callback();
- }
- // Returned successfully
- else {
- if (xhrFetcher.fetcher.responseXML) {
- // Sometimes IE will get the data, but won't bother loading it as an XML doc
- var xmlDoc = xhrFetcher.fetcher.responseXML;
- if (xmlDoc && !xmlDoc.documentElement && !xmlDoc.ownerElement) xmlDoc.loadXML(xhrFetcher.fetcher.responseText);
- callback(xmlDoc);
- } else // handle valid xml sent with wrong MIME type
- callback(geoXML3.xmlParse(xhrFetcher.fetcher.responseText));
- }
-
- // We're done with this fetcher object
- geoXML3.fetchers.push(xhrFetcher);
- }
- };
-
- xhrFetcher.xhrtimeout = setTimeout(timeoutHandler, 60000);
- xhrFetcher.fetcher.send(null);
- return null;
+ function timeoutHandler() { callback(); };
+
+ var xhrFetcher = new Object();
+ if (!!geoXML3.fetchers.length) xhrFetcher = geoXML3.fetchers.pop();
+ else if (!!window.XMLHttpRequest) xhrFetcher.fetcher = new window.XMLHttpRequest(); // Most browsers
+ else if (!!window.ActiveXObject) { // Some IE
+ // the many versions of IE's XML fetchers
+ var AXOs = [
+ 'MSXML2.XMLHTTP.6.0',
+ 'MSXML2.XMLHTTP.5.0',
+ 'MSXML2.XMLHTTP.4.0',
+ 'MSXML2.XMLHTTP.3.0',
+ 'MSXML2.XMLHTTP',
+ 'Microsoft.XMLHTTP',
+ 'MSXML.XMLHTTP'
+ ];
+ for (var i = 0; i < AXOs.length; i++) {
+ try { xhrFetcher.fetcher = new ActiveXObject(AXOs[i]); break; }
+ catch(e) { continue; }
+ }
+ if (!xhrFetcher.fetcher) {
+ geoXML3.log('Unable to create XHR object');
+ callback(null);
+ return null;
+ }
+ }
+
+ xhrFetcher.fetcher.open('GET', url, true);
+ if (!!xhrFetcher.fetcher.overrideMimeType) xhrFetcher.fetcher.overrideMimeType('text/xml');
+ xhrFetcher.fetcher.onreadystatechange = function () {
+ if (xhrFetcher.fetcher.readyState === 4) {
+ // Retrieval complete
+ if (!!xhrFetcher.xhrtimeout) clearTimeout(xhrFetcher.xhrtimeout);
+ if (xhrFetcher.fetcher.status >= 400) {
+ geoXML3.log('HTTP error ' + xhrFetcher.fetcher.status + ' retrieving ' + url);
+ callback();
+ }
+ // Returned successfully
+ else {
+ if (xhrFetcher.fetcher.responseXML) {
+ // Sometimes IE will get the data, but won't bother loading it as an XML doc
+ var xml = xhrFetcher.fetcher.responseXML;
+ if (xml && !xml.documentElement && !xml.ownerElement) {
+ xml.loadXML(xhrFetcher.fetcher.responseText);
+ }
+ } else {// handle valid xml sent with wrong MIME type
+ xml=geoXML3.xmlParse(xhrFetcher.fetcher.responseText);
+ }
+ // handle parse errors
+ if (xml.parseError && (xml.parseError.errorCode != 0)) {
+ geoXML3.log("XML parse error "+xml.parseError.errorCode+", "+xml.parseError.reason+"\nLine:"+xml.parseError.line+", Position:"+xml.parseError.linepos+", srcText:"+xml.parseError.srcText);
+ xml = "failed parse"
+ } else if (geoXML3.isParseError(xml)) {
+ geoXML3.log("XML parse error");
+ xml = "failed parse"
+ }
+ callback(xml);
+ }
+ // We're done with this fetcher object
+ geoXML3.fetchers.push(xhrFetcher);
+ }
+ };
+
+ xhrFetcher.xhrtimeout = setTimeout(timeoutHandler, 60000);
+ xhrFetcher.fetcher.send(null);
+ return null;
+};
+
+var IEversion = function() {
+ // http://msdn.microsoft.com/workshop/author/dhtml/overview/browserdetection.asp
+ // Returns the version of Internet Explorer or a -1
+ // (indicating the use of another browser).
+ var rv = -1; // Return value assumes failure
+ if (navigator.appName == 'Microsoft Internet Explorer') {
+ var ua = navigator.userAgent;
+ var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
+ if (re.exec(ua) != null) {
+ rv = parseFloat( RegExp.$1 );
+ }
+ }
+ return rv;
};
/**
@@ -1507,117 +1704,131 @@ geoXML3.fetchXML = function (url, callback) {
* @see http://code.google.com/apis/kml/documentation/kmzarchives.html
*/
geoXML3.fetchZIP = function (url, callback, parser) {
- // Just need a single 'new' declaration with a really long function...
- var zipFile = new ZipFile(url, function (zip) {
- // Retrieval complete
-
- // Check for ERRORs in zip.status
- for (var i = 0; i < zip.status.length; i++) {
- var msg = zip.status[i];
- if (msg.indexOf("ERROR") == 0) {
- geoXML3.log('HTTP/ZIP error retrieving ' + url + ': ' + msg);
- callback();
- return;
- }
- else if (msg.indexOf("WARNING") == 0) { // non-fatal, but still might be useful
- geoXML3.log('HTTP/ZIP warning retrieving ' + url + ': ' + msg);
- }
- }
-
- // Make sure KMZ structure is according to spec (with a single KML file in the root dir)
- var KMLCount = 0;
- var KML;
- for (var i = 0; i < zip.entries.length; i++) {
- var name = zip.entries[i].name;
- if (!/\.kml$/.test(name)) continue;
-
- KMLCount++;
- if (KMLCount == 1) KML = i;
- else {
- geoXML3.log('KMZ warning retrieving ' + url + ': found extra KML "' + name + '" in KMZ; discarding...');
- }
- }
-
- // Returned successfully, but still needs extracting
- var baseUrl = cleanURL(defileURL(url), url) + '/';
- var kmlProcessing = { // this is an object just so it gets passed properly
- timer: null,
- extractLeft: 0,
- timerCalls: 0
- };
- var extractCb = function(entry, entryContent) {
- var mdUrl = cleanURL(baseUrl, entry.name);
- var ext = entry.name.substring(entry.name.lastIndexOf(".") + 1).toLowerCase();
- kmlProcessing.extractLeft--;
-
- if ((typeof entryContent.description == "string") && (entryContent.name == "Error")) {
- geoXML3.log('KMZ error extracting ' + mdUrl + ': ' + entryContent.description);
- callback();
- return;
- }
-
- // MIME types that can be used in KML
- var mime;
- if (ext === 'jpg') ext = 'jpeg';
- if (/^(gif|jpeg|png)$/.test(ext)) mime = 'image/' + ext;
- else if (ext === 'mp3') mime = 'audio/mpeg';
- else if (ext === 'm4a') mime = 'audio/mp4';
- else if (ext === 'm4a') mime = 'audio/MP4-LATM';
- else mime = 'application/octet-stream';
-
- parser.kmzMetaData[mdUrl] = {};
- parser.kmzMetaData[mdUrl].entry = entry;
- // data:image/gif;base64,R0lGODlhEAAOALMA...
- parser.kmzMetaData[mdUrl].dataUrl = 'data:' + mime + ';base64,' + base64Encode(entryContent);
-
- // IE cannot handle GET requests beyond 2071 characters, even if it's an inline image
- if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent) && parser.kmzMetaData[mdUrl].dataUrl.length > 2071)
- parser.kmzMetaData[mdUrl].dataUrl =
- // this is a simple IE icon; to hint at the problem...
- 'data:image/gif;base64,R0lGODlhDwAQAOMPADBPvSpQ1Dpoyz1p6FhwvU2A6ECP63CM04CWxYCk+V6x+UK++Jao3rvC3fj7+v///yH5BAEKAA8ALAAAAAAPABAAAASC8Mk5mwCAUMlWwcLRHEelLA' +
- 'oGDMgzSsiyGCAhCETDPMh5XQCBwYBrNBIKWmg0MCQHj8MJU5IoroYCY6AAAgrDIbbQDGIK6DR5UPhlNo0JAlSUNAiDgH7eNAxEDWAKCQM2AAFheVxYAA0AIkFOJ1gBcQQaUQKKA5w7LpcEBwkJaKMUEQA7';
- };
- var kmlExtractCb = function(entry, entryContent) {
- if ((typeof entryContent.description == "string") && (entryContent.name == "Error")) {
- geoXML3.log('KMZ error extracting ' + mdUrl + ': ' + entryContent.description);
- callback();
- return;
- }
-
- // check to see if the KML is the last file extracted
- clearTimeout(kmlProcessing.timer);
- if (kmlProcessing.extractLeft <= 1) {
- kmlProcessing.extractLeft--;
- callback(geoXML3.xmlParse(entryContent));
- return;
- }
- else {
- // KML file isn't last yet; it may need to use those files, so wait a bit (100ms)
- kmlProcessing.timerCalls++;
- if (kmlProcessing.timerCalls < 100) {
- kmlProcessing.timer = setTimeout(function() { kmlExtractCb(entry, entryContent); }, 100);
- }
- else {
- geoXML3.log('KMZ warning extracting ' + url + ': entire ZIP has not been extracted after 10 seconds; running through KML, anyway...');
- kmlProcessing.extractLeft--;
- callback(geoXML3.xmlParse(entryContent));
- }
- }
- return;
- };
- for (var i = 0; i < zip.entries.length; i++) {
- var entry = zip.entries[i];
- var ext = entry.name.substring(entry.name.lastIndexOf(".") + 1).toLowerCase();
- if (!/^(gif|jpe?g|png|kml)$/.test(ext)) continue; // not going to bother to extract files we don't support
- if (ext === "kml" && i != KML) continue; // extra KMLs get discarded
- if (!parser && ext != "kml") continue; // cannot store images without a parser object
-
- // extract asynchronously
- kmlProcessing.extractLeft++;
- if (ext === "kml") entry.extract(kmlExtractCb);
- else entry.extract(extractCb);
- }
- });
+ // Just need a single 'new' declaration with a really long function...
+ var zipFile = new ZipFile(url, function (zip) {
+ // Retrieval complete
+
+ // Check for ERRORs in zip.status
+ for (var i = 0; i < zip.status.length; i++) {
+ var msg = zip.status[i];
+ if (msg.indexOf("ERROR") == 0) {
+ geoXML3.log('HTTP/ZIP error retrieving ' + url + ': ' + msg);
+ callback();
+ return;
+ }
+ else if (msg.indexOf("EXCEPTION") == 0) {
+ geoXML3.log('HTTP/ZIP exception retrieving ' + url + ': ' + msg);
+ callback();
+ return;
+ } else if (msg.indexOf("WARNING") == 0) { // non-fatal, but still might be useful
+ geoXML3.log('HTTP/ZIP warning retrieving ' + url + ': ' + msg);
+ } else if (msg.indexOf("INFO") == 0) { // non-fatal, but still might be useful
+ geoXML3.log('HTTP/ZIP info retrieving ' + url + ': ' + msg);
+ }
+ }
+
+ // Make sure KMZ structure is according to spec (with a single KML file in the root dir)
+ var KMLCount = 0;
+ var KML;
+ for (var i = 0; i < zip.entries.length; i++) {
+ var name = zip.entries[i].name;
+ if (!/\.kml$/.test(name)) continue;
+
+ KMLCount++;
+ if (KMLCount == 1) KML = i;
+ else {
+ geoXML3.log('KMZ warning retrieving ' + url + ': found extra KML "' + name + '" in KMZ; discarding...');
+ }
+ }
+
+ // Returned successfully, but still needs extracting
+ var baseUrl = cleanURL(defileURL(url), url) + '/';
+ var kmlProcessing = { // this is an object just so it gets passed properly
+ timer: null,
+ extractLeft: 0,
+ timerCalls: 0
+ };
+ var extractCb = function(entry, entryContent) {
+ var mdUrl = cleanURL(baseUrl, entry.name);
+ var ext = entry.name.substring(entry.name.lastIndexOf(".") + 1).toLowerCase();
+ kmlProcessing.extractLeft--;
+
+ if ((typeof entryContent.description == "string") && (entryContent.name == "Error")) {
+ geoXML3.log('KMZ error extracting ' + mdUrl + ': ' + entryContent.description);
+ callback();
+ return;
+ }
+
+ // MIME types that can be used in KML
+ var mime;
+ if (ext === 'jpg') ext = 'jpeg';
+ if (/^(gif|jpeg|png)$/.test(ext)) mime = 'image/' + ext;
+ else if (ext === 'mp3') mime = 'audio/mpeg';
+ else if (ext === 'm4a') mime = 'audio/mp4';
+ else if (ext === 'm4a') mime = 'audio/MP4-LATM';
+ else mime = 'application/octet-stream';
+
+ parser.kmzMetaData[mdUrl] = {};
+ parser.kmzMetaData[mdUrl].entry = entry;
+ // data:image/gif;base64,R0lGODlhEAAOALMA...
+ parser.kmzMetaData[mdUrl].dataUrl = 'data:' + mime + ';base64,' + base64Encode(entryContent);
+ // IE cannot handle GET requests beyond 2071 characters, even if it's an inline image
+ if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent))
+ {
+ if (((IEversion() < 8.0) &&
+ (parser.kmzMetaData[mdUrl].dataUrl.length > 2071)) ||
+ ((IEversion < 9.0) &&
+ (parser.kmzMetaData[mdUrl].dataUrl.length > 32767))) {
+ parser.kmzMetaData[mdUrl].dataUrl =
+ // this is a simple IE icon; to hint at the problem...
+ 'data:image/gif;base64,R0lGODlhDwAQAOMPADBPvSpQ1Dpoyz1p6FhwvU2A6ECP63CM04CWxYCk+V6x+UK++Jao3rvC3fj7+v///yH5BAEKAA8ALAAAAAAPABAAAASC8Mk5mwCAUMlWwcLRHEelLA' +
+ 'oGDMgzSsiyGCAhCETDPMh5XQCBwYBrNBIKWmg0MCQHj8MJU5IoroYCY6AAAgrDIbbQDGIK6DR5UPhlNo0JAlSUNAiDgH7eNAxEDWAKCQM2AAFheVxYAA0AIkFOJ1gBcQQaUQKKA5w7LpcEBwkJaKMUEQA7';
+ }
+ }
+ parser.kmzMetaData[internalSrc(entry.name)]=parser.kmzMetaData[mdUrl];
+
+ };
+ var kmlExtractCb = function(entry, entryContent) {
+ if ((typeof entryContent.description == "string") && (entryContent.name == "Error")) {
+ geoXML3.log('KMZ error extracting ' + entry.name + ': ' + entryContent.description);
+ callback();
+ return;
+ }
+
+ // check to see if the KML is the last file extracted
+ clearTimeout(kmlProcessing.timer);
+ if (kmlProcessing.extractLeft <= 1) {
+ kmlProcessing.extractLeft--;
+ callback(geoXML3.xmlParse(entryContent));
+ return;
+ }
+ else {
+ // KML file isn't last yet; it may need to use those files, so wait a bit (100ms)
+ kmlProcessing.timerCalls++;
+ if (kmlProcessing.timerCalls < 100) {
+ kmlProcessing.timer = setTimeout(function() { kmlExtractCb(entry, entryContent); }, 100);
+ }
+ else {
+ geoXML3.log('KMZ warning extracting ' + url + ': entire ZIP has not been extracted after 10 seconds; running through KML, anyway...');
+ kmlProcessing.extractLeft--;
+ callback(geoXML3.xmlParse(entryContent));
+ }
+ }
+ return;
+ };
+ for (var i = 0; i < zip.entries.length; i++) {
+ var entry = zip.entries[i];
+ var ext = entry.name.substring(entry.name.lastIndexOf(".") + 1).toLowerCase();
+ if (!/^(gif|jpe?g|png|kml)$/.test(ext)) continue; // not going to bother to extract files we don't support
+ if (ext === "kml" && i != KML) continue; // extra KMLs get discarded
+ if (!parser && ext != "kml") continue; // cannot store images without a parser object
+
+ // extract asynchronously
+ kmlProcessing.extractLeft++;
+ if (ext === "kml") entry.extract(kmlExtractCb);
+ else entry.extract(extractCb);
+ }
+ }); //,3 for most verbose logging
};
@@ -1629,18 +1840,18 @@ geoXML3.fetchZIP = function (url, callback, parser) {
* @return {String|Null}
*/
geoXML3.nodeValue = function(node, defVal) {
- var retStr="";
- if (!node) {
- return (typeof defVal === 'undefined' || defVal === null) ? null : defVal;
- }
- if(node.nodeType==3||node.nodeType==4||node.nodeType==2){
- retStr+=node.nodeValue;
- }else if(node.nodeType==1||node.nodeType==9||node.nodeType==11){
- for(var i=0;i<node.childNodes.length;++i){
- retStr+=arguments.callee(node.childNodes[i]);
- }
- }
- return retStr;
+ var retStr="";
+ if (!node) {
+ return (typeof defVal === 'undefined' || defVal === null) ? null : defVal;
+ }
+ if(node.nodeType==3||node.nodeType==4||node.nodeType==2){
+ retStr+=node.nodeValue;
+ }else if(node.nodeType==1||node.nodeType==9||node.nodeType==11){
+ for(var i=0;i<node.childNodes.length;++i){
+ retStr+=arguments.callee(node.childNodes[i]);
+ }
+ }
+ return retStr;
};
/**
@@ -1651,12 +1862,12 @@ geoXML3.nodeValue = function(node, defVal) {
* @return {Boolean|Null}
*/
geoXML3.getBooleanValue = function(node, defVal) {
- var nodeContents = geoXML3.nodeValue(node);
- if (nodeContents === null) return defVal || false;
- nodeContents = parseInt(nodeContents);
- if (isNaN(nodeContents)) return true;
- if (nodeContents == 0) return false;
- else return true;
+ var nodeContents = geoXML3.nodeValue(node);
+ if (nodeContents === null) return defVal || false;
+ nodeContents = parseInt(nodeContents);
+ if (isNaN(nodeContents)) return true;
+ if (nodeContents == 0) return false;
+ else return true;
}
/**
@@ -1671,32 +1882,32 @@ geoXML3.getBooleanValue = function(node, defVal) {
* @author Brendan Byrd
*/
geoXML3.getElementsByTagNameNS = function(node, namespace, tagname) {
- if (node && typeof node.getElementsByTagNameNS != 'undefined') return node.getElementsByTagNameNS(namespace, tagname);
- if (!node) return [];
-
- var root = node.documentElement || node.ownerDocument && node.ownerDocument.documentElement;
- if (!root || !root.attributes) return [];
-
- // search for namespace prefix
- for (var i = 0; i < root.attributes.length; i++) {
- var attr = root.attributes[i];
- if (attr.prefix === 'xmlns' && attr.nodeValue === namespace) return node.getElementsByTagName(attr.baseName + ':' + tagname);
- else if (attr.nodeName === 'xmlns' && attr.nodeValue === namespace) {
- // default namespace
- if (typeof node.selectNodes != 'undefined') {
- // Newer IEs have the SelectionNamespace property that can be used with selectNodes
- if (!root.ownerDocument.getProperty('SelectionNamespaces'))
- root.ownerDocument.setProperty('SelectionNamespaces', "xmlns:defaultNS='" + namespace + "'");
- return node.selectNodes('.//defaultNS:' + tagname);
- }
- else {
- // Otherwise, you can still try to tack on the 'xmlns' attribute to root
- root.setAttribute('xmlns:defaultNS', namespace);
- return node.getElementsByTagName('defaultNS:' + tagname);
- }
- }
- }
- return geoXML3.getElementsByTagName(node, tagname); // try the unqualified version
+ if (node && typeof node.getElementsByTagNameNS != 'undefined') return node.getElementsByTagNameNS(namespace, tagname);
+ if (!node) return [];
+
+ var root = node.documentElement || node.ownerDocument && node.ownerDocument.documentElement;
+ if (!root || !root.attributes) return [];
+
+ // search for namespace prefix
+ for (var i = 0; i < root.attributes.length; i++) {
+ var attr = root.attributes[i];
+ if (attr.prefix === 'xmlns' && attr.nodeValue === namespace) return node.getElementsByTagName(attr.baseName + ':' + tagname);
+ else if (attr.nodeName === 'xmlns' && attr.nodeValue === namespace) {
+ // default namespace
+ if (typeof node.selectNodes != 'undefined') {
+ // Newer IEs have the SelectionNamespace property that can be used with selectNodes
+ if (!root.ownerDocument.getProperty('SelectionNamespaces'))
+ root.ownerDocument.setProperty('SelectionNamespaces', "xmlns:defaultNS='" + namespace + "'");
+ return node.selectNodes('.//defaultNS:' + tagname);
+ }
+ else {
+ // Otherwise, you can still try to tack on the 'xmlns' attribute to root
+ root.setAttribute('xmlns:defaultNS', namespace);
+ return node.getElementsByTagName('defaultNS:' + tagname);
+ }
+ }
+ }
+ return geoXML3.getElementsByTagName(node, tagname); // try the unqualified version
};
/**
@@ -1711,9 +1922,9 @@ geoXML3.getElementsByTagNameNS = function(node, namespace, tagname) {
* @author Brendan Byrd
*/
geoXML3.getElementsByTagName = function(node, tagname) {
- if (node && typeof node.getElementsByTagNameNS != 'undefined') return node.getElementsByTagName(tagname); // if it has both functions, it should be accurate
+ if (node && typeof node.getElementsByTagNameNS != 'undefined') return node.getElementsByTagName(tagname); // if it has both functions, it should be accurate
// if (node && typeof node.selectNodes != 'undefined') return node.selectNodes(".//*[local-name()='" + tagname + "']");
- return node.getElementsByTagName(tagname); // hope for the best...
+ return node.getElementsByTagName(tagname); // hope for the best...
}
/**
@@ -1726,21 +1937,30 @@ geoXML3.getElementsByTagName = function(node, tagname) {
* @author Brendan Byrd
*/
var toAbsURL = function (d, s) {
- var p, f, i;
- var h = location.protocol + "://" + location.host;
+ var p, f, i;
+ var h = location.protocol + "://" + location.host;
+
+ if (!s.length) return '';
+ if (/^\w+:/.test(s)) return s;
+ if (s.indexOf('/') == 0) return h + s;
- if (!s.length) return '';
- if (/^\w+:/.test(s)) return s;
- if (s.indexOf('/') == 0) return h + s;
+ p = d.replace(/\/[^\/]*$/, '');
+ f = s.match(/\.\.\//g);
+ if (f) {
+ s = s.substring(f.length * 3);
+ for (i = f.length; i--;) { p = p.substring(0, p.lastIndexOf('/')); }
+ }
- p = d.replace(/\/[^\/]*$/, '');
- f = s.match(/\.\.\//g);
- if (f) {
- s = s.substring(f.length * 3);
- for (i = f.length; i--;) { p = p.substring(0, p.lastIndexOf('/')); }
- }
+ return h + p + '/' + s;
+}
- return h + p + '/' + s;
+var internalSrc = function(src) {
+ //this gets the full url
+ var url = document.location.href;
+ //this removes everything after the last slash in the path
+ url = url.substring(0,url.lastIndexOf("/") + 1);
+ var internalPath= url+src;
+ return internalPath;
}
/**
@@ -1752,9 +1972,9 @@ var toAbsURL = function (d, s) {
* @author Brendan Byrd
*/
var dehostURL = function (s) {
- var h = location.protocol + "://" + location.host;
- h = h.replace(/([\.\\\+\*\?\[\^\]\$\(\)])/g, '\\$1'); // quotemeta
- return s.replace(new RegExp('^' + h, 'i'), '');
+ var h = location.protocol + "://" + location.host;
+ h = h.replace(/([\.\\\+\*\?\[\^\]\$\(\)])/g, '\\$1'); // quotemeta
+ return s.replace(new RegExp('^' + h, 'i'), '');
}
/**
@@ -1782,46 +2002,46 @@ var defileURL = function (s) { return s ? s.substr(0, s.lastIndexOf('/') + 1)
// Some extra Array subs for ease of use
// http://stackoverflow.com/questions/143847/best-way-to-find-an-item-in-a-javascript-array
Array.prototype.hasObject = (
- !Array.indexOf ? function (obj) {
- var l = this.length + 1;
- while (l--) {
- if (this[l - 1] === obj) return true;
- }
- return false;
- } : function (obj) {
- return (this.indexOf(obj) !== -1);
- }
+ !Array.indexOf ? function (obj) {
+ var l = this.length + 1;
+ while (l--) {
+ if (this[l - 1] === obj) return true;
+ }
+ return false;
+ } : function (obj) {
+ return (this.indexOf(obj) !== -1);
+ }
);
Array.prototype.hasItemInObj = function (name, item) {
- var l = this.length + 1;
- while (l--) {
- if (this[l - 1][name] === item) return true;
- }
- return false;
+ var l = this.length + 1;
+ while (l--) {
+ if (this[l - 1][name] === item) return true;
+ }
+ return false;
};
if (!Array.prototype.indexOf) {
- Array.prototype.indexOf = function (obj, fromIndex) {
- if (fromIndex == null) {
- fromIndex = 0;
- } else if (fromIndex < 0) {
- fromIndex = Math.max(0, this.length + fromIndex);
- }
- for (var i = fromIndex, j = this.length; i < j; i++) {
- if (this[i] === obj) return i;
- }
- return -1;
- };
+ Array.prototype.indexOf = function (obj, fromIndex) {
+ if (fromIndex == null) {
+ fromIndex = 0;
+ } else if (fromIndex < 0) {
+ fromIndex = Math.max(0, this.length + fromIndex);
+ }
+ for (var i = fromIndex, j = this.length; i < j; i++) {
+ if (this[i] === obj) return i;
+ }
+ return -1;
+ };
}
Array.prototype.indexOfObjWithItem = function (name, item, fromIndex) {
- if (fromIndex == null) {
- fromIndex = 0;
- } else if (fromIndex < 0) {
- fromIndex = Math.max(0, this.length + fromIndex);
- }
- for (var i = fromIndex, j = this.length; i < j; i++) {
- if (this[i][name] === item) return i;
- }
- return -1;
+ if (fromIndex == null) {
+ fromIndex = 0;
+ } else if (fromIndex < 0) {
+ fromIndex = Math.max(0, this.length + fromIndex);
+ }
+ for (var i = fromIndex, j = this.length; i < j; i++) {
+ if (this[i][name] === item) return i;
+ }
+ return -1;
};
/**
@@ -1833,23 +2053,23 @@ Array.prototype.indexOfObjWithItem = function (name, item, fromIndex) {
* @author Brendan Byrd
*/
var base64Encode = function(input) {
- var keyString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
- var output = "";
- var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
- var i = 0;
- while (i < input.length) {
- chr1 = input[i++];
- chr2 = input[i++];
- chr3 = input[i++];
- enc1 = chr1 >> 2;
- enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
- enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
- enc4 = chr3 & 63;
-
- if (chr2 == undefined) enc3 = enc4 = 64;
- else if (chr3 == undefined) enc4 = 64;
-
- output = output + keyString.charAt(enc1) + keyString.charAt(enc2) + keyString.charAt(enc3) + keyString.charAt(enc4);
- }
- return output;
+ var keyString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+ var output = "";
+ var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+ var i = 0;
+ while (i < input.length) {
+ chr1 = input[i++];
+ chr2 = input[i++];
+ chr3 = input[i++];
+ enc1 = chr1 >> 2;
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+ enc4 = chr3 & 63;
+
+ if (chr2 == undefined) enc3 = enc4 = 64;
+ else if (chr3 == undefined) enc4 = 64;
+
+ output = output + keyString.charAt(enc1) + keyString.charAt(enc2) + keyString.charAt(enc3) + keyString.charAt(enc4);
+ }
+ return output;
};
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/ext.sm.googlemaps3ajax.js b/www/wiki/extensions/Maps/resources/GoogleMaps/googlemaps3ajax.js
index 9f8c658a..aa86319e 100644
--- a/www/wiki/extensions/Maps/resources/GoogleMaps/ext.sm.googlemaps3ajax.js
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/googlemaps3ajax.js
@@ -17,7 +17,7 @@
if( typeof google === 'undefined' ) {
return;
}
- $( window.maps.googlemapsList ).each( function( index, map ) {
+ $( window.mapsGoogleList ).each( function( index, map ) {
if( !map.options.ajaxquery || !map.options.ajaxcoordproperty ) {
return;
}
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/jquery.googlemap.js b/www/wiki/extensions/Maps/resources/GoogleMaps/jquery.googlemap.js
index d0317d16..d76460d5 100644
--- a/www/wiki/extensions/Maps/resources/GoogleMaps/jquery.googlemap.js
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/jquery.googlemap.js
@@ -233,7 +233,9 @@
var geoXml = new geoXML3.parser({
map:_this.map,
zoom:options.kmlrezoom,
- failedParse:function(){
+ failedParse:function(document){
+ console.log(options.kml);
+ console.log(document);
alert(mw.msg('maps-kml-parsing-failed'));
}
});
@@ -454,7 +456,7 @@
};
this.createMarkerCluster = function() {
- if ( !options.markercluster ) {
+ if ( !options.cluster ) {
return;
}
if (this.markercluster) {
@@ -751,7 +753,7 @@
//Add custom controls
// - Fullscreen
- if(options.enablefullscreen){
+ if(options.fullscreen){
this.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(new FullscreenControl(this.map));
}
};
@@ -886,7 +888,7 @@
//Complete path to OpenLayers WMS layer
- if (!options.markercluster) {
+ if (!options.cluster) {
this.setup();
} else {
mw.loader.using( 'ext.maps.gm3.markercluster', function() {
diff --git a/www/wiki/extensions/Maps/resources/MapSaver.js b/www/wiki/extensions/Maps/resources/MapSaver.js
new file mode 100644
index 00000000..2dd22ac1
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/MapSaver.js
@@ -0,0 +1,57 @@
+(function(mw) {
+ 'use strict';
+
+ function getUserHasPermission(permission, callback) {
+ mw.user.getRights(
+ function(rights) {
+ callback(rights.includes(permission))
+ }
+ );
+ }
+
+ function ifUserHasPermission(permission, callback) {
+ getUserHasPermission(
+ permission,
+ function(hasPermission) {
+ if (hasPermission) {
+ callback();
+ }
+ }
+ );
+ }
+
+ let MapSaver = function(pageName) {
+ let self = {};
+
+ // parameters.newContent: required string
+ // parameters.summary: required string
+ // parameters.done: required callback function
+ self.save = function(paremeters) {
+ new mw.Api().edit(
+ pageName,
+ function(revision) {
+ let editApiParameters = {
+ text: paremeters.newContent,
+ summary: paremeters.summary,
+ minor: false
+ };
+
+ ifUserHasPermission(
+ "applychangetags",
+ function() {
+ editApiParameters.tags = ['maps-visual-edit'];
+ }
+ );
+
+ return editApiParameters;
+ }
+ ).then(paremeters.done);
+ };
+
+ return self;
+ };
+
+ if (!window.maps) {window.maps = {};}
+
+ window.maps.MapSaver = MapSaver;
+})(window.mediaWiki);
diff --git a/www/wiki/extensions/Maps/resources/editor/css/jquery.miniColors.css b/www/wiki/extensions/Maps/resources/WikitextEditor/css/jquery.miniColors.css
index 664c2fd4..664c2fd4 100644
--- a/www/wiki/extensions/Maps/resources/editor/css/jquery.miniColors.css
+++ b/www/wiki/extensions/Maps/resources/WikitextEditor/css/jquery.miniColors.css
diff --git a/www/wiki/extensions/Maps/resources/editor/css/mapeditor.css b/www/wiki/extensions/Maps/resources/WikitextEditor/css/mapeditor.css
index 9c3fe7c4..9c3fe7c4 100644
--- a/www/wiki/extensions/Maps/resources/editor/css/mapeditor.css
+++ b/www/wiki/extensions/Maps/resources/WikitextEditor/css/mapeditor.css
diff --git a/www/wiki/extensions/Maps/resources/editor/images/circle.gif b/www/wiki/extensions/Maps/resources/WikitextEditor/images/circle.gif
index 599f7f13..599f7f13 100644
--- a/www/wiki/extensions/Maps/resources/editor/images/circle.gif
+++ b/www/wiki/extensions/Maps/resources/WikitextEditor/images/circle.gif
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/editor/images/gradient.png b/www/wiki/extensions/Maps/resources/WikitextEditor/images/gradient.png
index 486a9f6d..486a9f6d 100644
--- a/www/wiki/extensions/Maps/resources/editor/images/gradient.png
+++ b/www/wiki/extensions/Maps/resources/WikitextEditor/images/gradient.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/editor/images/line.gif b/www/wiki/extensions/Maps/resources/WikitextEditor/images/line.gif
index 9eb19837..9eb19837 100644
--- a/www/wiki/extensions/Maps/resources/editor/images/line.gif
+++ b/www/wiki/extensions/Maps/resources/WikitextEditor/images/line.gif
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/editor/images/rainbow.png b/www/wiki/extensions/Maps/resources/WikitextEditor/images/rainbow.png
index d16fcd86..d16fcd86 100644
--- a/www/wiki/extensions/Maps/resources/editor/images/rainbow.png
+++ b/www/wiki/extensions/Maps/resources/WikitextEditor/images/rainbow.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/editor/images/trigger.png b/www/wiki/extensions/Maps/resources/WikitextEditor/images/trigger.png
index 20ec282b..20ec282b 100644
--- a/www/wiki/extensions/Maps/resources/editor/images/trigger.png
+++ b/www/wiki/extensions/Maps/resources/WikitextEditor/images/trigger.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/editor/js/README b/www/wiki/extensions/Maps/resources/WikitextEditor/js/README
index 53598d48..53598d48 100644
--- a/www/wiki/extensions/Maps/resources/editor/js/README
+++ b/www/wiki/extensions/Maps/resources/WikitextEditor/js/README
diff --git a/www/wiki/extensions/Maps/resources/editor/js/jquery.miniColors.js b/www/wiki/extensions/Maps/resources/WikitextEditor/js/jquery.miniColors.js
index dfbce542..dfbce542 100644
--- a/www/wiki/extensions/Maps/resources/editor/js/jquery.miniColors.js
+++ b/www/wiki/extensions/Maps/resources/WikitextEditor/js/jquery.miniColors.js
diff --git a/www/wiki/extensions/Maps/resources/editor/js/mapeditor.iefixes.js b/www/wiki/extensions/Maps/resources/WikitextEditor/js/mapeditor.iefixes.js
index a49e42fb..a49e42fb 100644
--- a/www/wiki/extensions/Maps/resources/editor/js/mapeditor.iefixes.js
+++ b/www/wiki/extensions/Maps/resources/WikitextEditor/js/mapeditor.iefixes.js
diff --git a/www/wiki/extensions/Maps/resources/editor/js/mapeditor.js b/www/wiki/extensions/Maps/resources/WikitextEditor/js/mapeditor.js
index 795fcc53..eb2efd61 100644
--- a/www/wiki/extensions/Maps/resources/editor/js/mapeditor.js
+++ b/www/wiki/extensions/Maps/resources/WikitextEditor/js/mapeditor.js
@@ -56,7 +56,7 @@ var mapEditor = {
copycoords: {
values:['on','off']
},
- markercluster: {
+ cluster: {
values:['on','off']
},
searchmarkers:{
diff --git a/www/wiki/extensions/Maps/resources/api.js b/www/wiki/extensions/Maps/resources/api.js
new file mode 100644
index 00000000..5cb70741
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/api.js
@@ -0,0 +1,44 @@
+(function($, mw) {
+ 'use strict';
+
+ function canEditPage(pageName) {
+ let deferred = $.Deferred();
+
+ new mw.Api().get({
+ action: 'query',
+ format: 'json',
+ titles: pageName,
+ prop: 'info',
+ intestactions: 'edit'
+ }).done(function(response) {
+ // Next level usability in the MW API:
+ let canEdit = response.query.pages[Object.keys(response.query.pages)[0]].actions.hasOwnProperty('edit');
+ deferred.resolve(canEdit);
+ });
+
+ return deferred.promise();
+ }
+
+ function getLatestRevision(pageName) {
+ let deferred = $.Deferred();
+
+ new mw.Api().post({
+ action: 'query',
+ prop: 'revisions',
+ rvlimit: 1,
+ rvprop: [ 'ids', 'content' ],
+ titles: pageName
+ }).done(function(response) {
+ deferred.resolve(response.query.pages[Object.keys(response.query.pages)[0]].revisions[0]);
+ });
+
+ return deferred.promise();
+ }
+
+ if (!window.maps) {window.maps = {};}
+
+ window.maps.api = {
+ canEditPage: canEditPage,
+ getLatestRevision: getLatestRevision
+ };
+})(window.jQuery, window.mediaWiki);
diff --git a/www/wiki/extensions/Maps/resources/geoJsonPage.js b/www/wiki/extensions/Maps/resources/geoJsonPage.js
new file mode 100644
index 00000000..8c6c7c14
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/geoJsonPage.js
@@ -0,0 +1,96 @@
+(function( $, mw, maps ) {
+
+ function hideLoadingMessage(map, $content) {
+ map.on(
+ 'load',
+ function() {
+ $content.find('div.maps-loading-message').hide();
+ }
+ );
+ }
+
+ function addZoomControl(map) {
+ map.addControl(new L.Control.Zoom());
+ }
+
+ function addTitleLayer(map) {
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
+ }).addTo(map);
+ }
+
+ function fitContent(map, geoJsonLayer) {
+ map.fitWorld();
+ let bounds = geoJsonLayer.getBounds();
+
+ if (bounds.isValid()) {
+ if (bounds.getNorthEast().equals(bounds.getSouthWest())) {
+ map.setView(
+ bounds.getCenter(),
+ 14
+ );
+ }
+ else {
+ map.fitBounds(bounds);
+ }
+ }
+ }
+
+ function initializeWithEditor(map) {
+ let editor = maps.leaflet.LeafletEditor(
+ map,
+ new maps.MapSaver(mw.config.get('wgPageName'))
+ );
+
+ editor.onSaved(function() {
+ alert(mw.msg('maps-json-editor-changes-saved'));
+ });
+
+ editor.initialize(window.GeoJson);
+
+ fitContent(map, editor.getLayer());
+ }
+
+ function initializePlainMap(map) {
+ fitContent(
+ map,
+ maps.leaflet.GeoJson.newGeoJsonLayer(L, window.GeoJson).addTo(map)
+ );
+ }
+
+ function initializeGeoJsonAndEditorUi(map) {
+ if (mw.config.get('wgCurRevisionId') === mw.config.get('wgRevisionId')) {
+
+ maps.api.canEditPage(mw.config.get('wgPageName')).done(
+ function(canEdit) {
+ if (canEdit) {
+ initializeWithEditor(map);
+ }
+ else {
+ initializePlainMap(map);
+ }
+ }
+ );
+ }
+ else {
+ initializePlainMap(map);
+ }
+ }
+
+ mw.hook( 'wikipage.content' ).add( function ( $content ) {
+ let map = L.map(
+ 'GeoJsonMap',
+ {
+ fullscreenControl: true,
+ fullscreenControlOptions: {position: 'topright'},
+ zoomControl: false
+ }
+ );
+
+ hideLoadingMessage(map, $content);
+ addZoomControl(map);
+ addTitleLayer(map);
+ initializeGeoJsonAndEditorUi(map);
+ } );
+
+})( window.jQuery, window.mediaWiki, window.maps );
diff --git a/www/wiki/extensions/Maps/resources/geojson.new.page.js b/www/wiki/extensions/Maps/resources/geojson.new.page.js
new file mode 100644
index 00000000..30980a7a
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/geojson.new.page.js
@@ -0,0 +1,38 @@
+$( document ).ready( function() {
+ function getErrorMessageForFailure(failureReason) {
+ if (failureReason === 'assertuserfailed') {
+ return 'Could not create page because your session expired. The page will be reloaded';
+ }
+
+ return 'Failed to create the page: ' + failureReason;
+ }
+
+ $('#maps-geojson-new').click(
+ function() {
+ $(this).prop('disabled', true);
+ $(this).text(mw.msg('maps-geo-json-create-page-creating'));
+
+ new mw.Api().create(
+ mw.config.get('wgPageName'),
+ {
+ summary: mw.msg('maps-geo-json-create-page-summary')
+ },
+ '{"type": "FeatureCollection", "features": []}'
+ ).then(
+ function(editData) {
+ if (editData.result !== 'Success') {
+ console.log(editData);
+ alert('Failed to create the page');
+ }
+
+ location.reload();
+ }
+ ).fail(
+ function(reason) {
+ alert(getErrorMessageForFailure(reason));
+ location.reload();
+ }
+ );
+ }
+ );
+} );
diff --git a/www/wiki/extensions/Maps/resources/leaflet/FeatureBuilder.js b/www/wiki/extensions/Maps/resources/leaflet/FeatureBuilder.js
new file mode 100644
index 00000000..fad0adae
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/FeatureBuilder.js
@@ -0,0 +1,220 @@
+// Builds markers, polygons, etc from the options object (serialized data coming from PHP)
+(function($, mw) {
+ 'use strict';
+
+ /**
+ * Creates a new marker with the provided data and returns it.
+ * @param {Object} properties Contains the fields lat, lon, title, text and icon
+ * @param {Object} options Map options
+ * @return {L.Marker}
+ */
+ function createMarker(properties, options) {
+ let markerOptions = {
+ title:properties.title
+ };
+
+ let marker = L.marker( [properties.lat, properties.lon], markerOptions );
+
+ if (properties.hasOwnProperty('icon') && properties.icon !== '') {
+ marker.setOpacity(0);
+
+ let img = new Image();
+ img.onload = function() {
+ let icon = new L.Icon({
+ iconUrl: properties.icon,
+ iconSize: [ img.width, img.height ],
+ iconAnchor: [ img.width / 2, img.height ],
+ popupAnchor: [ -img.width % 2, -img.height*2/3 ]
+ });
+
+ marker.setIcon(icon);
+ marker.setOpacity(1);
+ };
+ img.src = properties.icon;
+ }
+
+ if( properties.hasOwnProperty('text') && properties.text.length > 0 ) {
+ marker.bindPopup( properties.text );
+ }
+
+ if ( options.copycoords ) {
+ marker.on(
+ 'contextmenu',
+ function( e ) {
+ prompt(mw.msg('maps-copycoords-prompt'), e.latlng.lat + ',' + e.latlng.lng);
+ }
+ );
+ }
+
+ return marker;
+ }
+
+ function newLineFromProperties(properties) {
+ var latlngs = [];
+
+ for (var x = 0; x < properties.pos.length; x++) {
+ latlngs.push([properties.pos[x].lat, properties.pos[x].lon]);
+ }
+
+ let line = L.polyline(
+ latlngs,
+ {
+ color: properties.strokeColor,
+ weight: properties.strokeWeight,
+ opacity: properties.strokeOpacity
+ }
+ );
+
+ // TODO: maybe bind via feature group
+ if ( properties.hasOwnProperty('text') && properties.text.trim().length > 0 ) {
+ line.bindPopup( properties.text );
+ }
+
+ return line;
+ }
+
+ function newPolygonFromProperties(properties) {
+ let polygon = L.polygon(
+ properties.pos.map(function(position) {
+ return [position.lat, position.lon];
+ }),
+ {
+ color: properties.strokeColor,
+ weight:properties.strokeWeight,
+ opacity:properties.strokeOpacity,
+ fillColor:properties.fillColor,
+ fillOpacity:properties.fillOpacity
+ }
+ );
+
+ if( properties.hasOwnProperty('text') && properties.text.trim().length > 0 ) {
+ polygon.bindPopup( properties.text );
+ }
+
+ return polygon;
+ }
+
+ function newCircleFromProperties(properties) {
+ let circle = L.circle(
+ [properties.centre.lat, properties.centre.lon],
+ {
+ radius: properties.radius,
+ color: properties.strokeColor,
+ weight:properties.strokeWeight,
+ opacity:properties.strokeOpacity,
+ fillColor:properties.fillColor,
+ fillOpacity:properties.fillOpacity,
+ }
+ );
+
+ if( properties.hasOwnProperty('text') && properties.text.trim().length > 0 ) {
+ circle.bindPopup( properties.text );
+ }
+
+ return circle;
+ }
+
+ function newRectangleFromProperties(properties) {
+ let rectangle = L.rectangle(
+ [
+ [properties.sw.lat, properties.sw.lon],
+ [properties.ne.lat, properties.ne.lon]
+ ],
+ {
+ color: properties.strokeColor,
+ weight: properties.strokeWeight,
+ opacity: properties.strokeOpacity,
+ fillColor: properties.fillColor,
+ fillOpacity: properties.fillOpacity
+ }
+ );
+
+ if( properties.hasOwnProperty('text') && properties.text.trim().length > 0 ) {
+ rectangle.bindPopup( properties.text );
+ }
+
+ return rectangle;
+ }
+
+ /**
+ * Caution: mutates markerLayer
+ * @param {Object} options
+ * @param {L.LayerGroup} markerLayer
+ * @return {L.GeoJSON}
+ */
+ function newGeoJsonLayer(options, markerLayer) {
+ return L.geoJSON(
+ options.geojson,
+ {
+ style: function (feature) {
+ return maps.leaflet.GeoJson.simpleStyleToLeafletPathOptions(feature.properties);
+ },
+ pointToLayer: function(feature, latlng) {
+ markerLayer.addLayer(
+ createMarker(
+ {
+ lat: latlng.lat,
+ lon: latlng.lng,
+ title: feature.properties.title || '',
+ text: maps.leaflet.GeoJson.popupContentFromProperties(feature.properties),
+ icon: ''
+ },
+ options
+ )
+ );
+ },
+ onEachFeature: function (feature, layer) {
+ if (feature.geometry.type !== 'Point') {
+ let popupContent = maps.leaflet.GeoJson.popupContentFromProperties(feature.properties);
+ if (popupContent !== '') {
+ layer.bindPopup(popupContent);
+ }
+ }
+ }
+ }
+ );
+ }
+
+ function getMarkersAndShapes(options) {
+ let features = L.featureGroup();
+
+ $.each(options.lines, function(index, properties) {
+ features.addLayer(newLineFromProperties(properties));
+ });
+
+ $.each(options.polygons, function(index, properties) {
+ features.addLayer(newPolygonFromProperties(properties));
+ });
+
+ $.each(options.circles, function(index, properties) {
+ features.addLayer(newCircleFromProperties(properties));
+ });
+
+ $.each(options.rectangles, function(index, properties) {
+ features.addLayer(newRectangleFromProperties(properties));
+ });
+
+ let markers = options.cluster ? maps.leaflet.LeafletCluster.newLayer(options) : L.featureGroup();
+
+ features.addLayer(markers);
+ features.markerLayer = markers;
+
+ $.each(options.locations, function(index, properties) {
+ markers.addLayer(createMarker(properties, options));
+ });
+
+ if (options.geojson !== '') {
+ features.addLayer(newGeoJsonLayer(options, markers));
+ }
+
+ return features
+ }
+
+ if (!window.maps) {window.maps = {};}
+ if (!window.maps.leaflet) {window.maps.leaflet = {};}
+
+ window.maps.leaflet.FeatureBuilder = {
+ contentLayerFromOptions: getMarkersAndShapes,
+ createMarker: createMarker
+ };
+})(window.jQuery, window.mediaWiki);
diff --git a/www/wiki/extensions/Maps/resources/leaflet/GeoJson.js b/www/wiki/extensions/Maps/resources/leaflet/GeoJson.js
new file mode 100644
index 00000000..1914738f
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/GeoJson.js
@@ -0,0 +1,74 @@
+(function() {
+ 'use strict';
+
+ let GeoJson = {};
+
+ // https://github.com/Leaflet/Leaflet/blob/f8e09f993292579a1af88261c9b461730f22e4e6/src/layer/GeoJSON.js#L49-L57
+ // https://github.com/mapbox/simplestyle-spec/tree/master/1.1.0
+ // https://leafletjs.com/reference-1.6.0.html#path
+ GeoJson.simpleStyleToLeafletPathOptions = function(featureProperties) {
+ let simpleStyleToLeaflet = {
+ 'stroke': 'color',
+ 'stroke-width': 'weight',
+ 'stroke-opacity': 'opacity',
+ 'fill': 'fillColor',
+ 'fill-opacity': 'fillOpacity',
+ };
+
+ let pathOptions = {};
+
+ for (let [key, value] of Object.entries(simpleStyleToLeaflet)) {
+ if (featureProperties[key]) {
+ pathOptions[value] = featureProperties[key];
+ }
+ }
+
+ return pathOptions;
+ };
+
+ function escapeHTML(unsafeText) {
+ let div = document.createElement('div');
+ div.innerText = unsafeText;
+ return div.innerHTML;
+ }
+
+ GeoJson.popupContentFromProperties = function(properties) {
+ if (!properties.title && !properties.description) {
+ return '';
+ }
+
+ if (!properties.description) {
+ return escapeHTML(properties.title);
+ }
+
+ if (!properties.title) {
+ return escapeHTML(properties.description);
+ }
+
+ // CHANGE: allows html format in description of GeoJSON element
+ return '<strong>' + escapeHTML(properties.title) + '</strong><br><br>'
+ + properties.description;
+ };
+
+ GeoJson.newGeoJsonLayer = function(L, json) {
+ return L.geoJSON(
+ json,
+ {
+ style: function (feature) {
+ return GeoJson.simpleStyleToLeafletPathOptions(feature.properties);
+ },
+ onEachFeature: function (feature, layer) {
+ let popupContent = GeoJson.popupContentFromProperties(feature.properties);
+ if (popupContent !== '') {
+ layer.bindPopup(popupContent);
+ }
+ }
+ }
+ );
+ };
+
+ if (!window.maps) {window.maps = {};}
+ if (!window.maps.leaflet) {window.maps.leaflet = {};}
+
+ window.maps.leaflet.GeoJson = GeoJson;
+})();
diff --git a/www/wiki/extensions/Maps/resources/leaflet/LeafletCluster.js b/www/wiki/extensions/Maps/resources/leaflet/LeafletCluster.js
new file mode 100644
index 00000000..e6ec2566
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/LeafletCluster.js
@@ -0,0 +1,69 @@
+(function($, mw, L) {
+ 'use strict';
+
+ if (!window.maps) {window.maps = {};}
+ if (!window.maps.leaflet) {window.maps.leaflet = {};}
+
+ window.maps.leaflet.LeafletCluster = {
+ newLayer: function(options) {
+ return new L.MarkerClusterGroup({
+ maxClusterRadius: options.clustermaxradius,
+ disableClusteringAtZoom: options.clustermaxzoom + 1,
+ zoomToBoundsOnClick: options.clusterzoomonclick,
+ spiderfyOnMaxZoom: options.clusterspiderfy,
+ iconCreateFunction: function(cluster) {
+ var childCount = cluster.getChildCount();
+
+ var imagePath = mw.config.get( 'egMapsScriptPath' ) + '/resources/leaflet/cluster/';
+
+ var styles = [
+ {
+ iconUrl: imagePath + 'm1.png',
+ iconSize: [53, 52]
+ },
+ {
+ iconUrl: imagePath + 'm2.png',
+ iconSize: [56, 55]
+ },
+ {
+ iconUrl: imagePath + 'm3.png',
+ iconSize: [66, 65]
+ },
+ {
+ iconUrl: imagePath + 'm4.png',
+ iconSize: [78, 77]
+ },
+ {
+ iconUrl: imagePath + 'm5.png',
+ iconSize: [90, 89]
+ }
+ ];
+
+ var index = 0;
+ var dv = childCount;
+ while (dv !== 0) {
+ dv = parseInt(dv / 10, 10);
+ index++;
+ }
+ var index = Math.min(index, styles.length);
+ index = Math.max(0, index - 1);
+ index = Math.min(styles.length - 1, index);
+ var style = styles[index];
+
+ return new L.divIcon({
+ iconSize: style.iconSize,
+ className: '',
+ html: '<img style="' +
+ '" src="' + style.iconUrl + '" />' +
+ '<span style="' +
+ 'position: absolute; font-size: 11px; font-weight: bold; text-align: center; ' +
+ 'top: 0; left: 0; ' +
+ 'line-height: ' + style.iconSize[1] + 'px;' +
+ 'width: ' + style.iconSize[0] + 'px;' +
+ '">' + childCount + '</span>'
+ });
+ }
+ });
+ }
+ };
+})(window.jQuery, window.mediaWiki, window.L);
diff --git a/www/wiki/extensions/Maps/resources/leaflet/LeafletEditor.js b/www/wiki/extensions/Maps/resources/leaflet/LeafletEditor.js
new file mode 100644
index 00000000..8c8e1398
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/LeafletEditor.js
@@ -0,0 +1,256 @@
+(function( $, mw, maps ) {
+
+ function initializeMessages() {
+ let buttons = L.drawLocal.draw.toolbar.buttons;
+
+ buttons.marker = mw.msg('maps-json-editor-button-marker');
+ buttons.polyline = mw.msg('maps-json-editor-button-line');
+ buttons.polygon = mw.msg('maps-json-editor-button-polygon');
+ buttons.rectangle = mw.msg('maps-json-editor-button-rectangle');
+ buttons.circle = mw.msg('maps-json-editor-button-circle');
+
+ let handlers = L.drawLocal.draw.handlers;
+
+ handlers.marker.tooltip.start = mw.msg('maps-json-editor-tooltip-marker');
+ handlers.polyline.tooltip.start = mw.msg('maps-json-editor-tooltip-line');
+ handlers.polygon.tooltip.start = mw.msg('maps-json-editor-tooltip-polygon');
+ handlers.rectangle.tooltip.start = mw.msg('maps-json-editor-tooltip-rectangle');
+ handlers.circle.tooltip.start = mw.msg('maps-json-editor-tooltip-circle');
+
+ let toolbar = L.drawLocal.edit.toolbar;
+
+ toolbar.actions.save.title = mw.msg('maps-json-editor-toolbar-save-title');
+ toolbar.actions.save.text = mw.msg('maps-json-editor-toolbar-save-text');
+ toolbar.actions.cancel.title = mw.msg('maps-json-editor-toolbar-cancel-title');
+ toolbar.actions.cancel.text = mw.msg('maps-json-editor-toolbar-cancel-text');
+ toolbar.actions.clearAll.title = mw.msg('maps-json-editor-toolbar-clear-title');
+ toolbar.actions.clearAll.text = mw.msg('maps-json-editor-toolbar-clear-text');
+
+ toolbar.buttons.edit = mw.msg('maps-json-editor-toolbar-button-edit');
+ toolbar.buttons.editDisabled = mw.msg('maps-json-editor-toolbar-button-edit-disabled');
+ toolbar.buttons.remove = mw.msg('maps-json-editor-toolbar-button-remove');
+ toolbar.buttons.removeDisabled = mw.msg('maps-json-editor-toolbar-button-remove-disabled');
+ }
+
+ let MapEditor = function(map, mapSaver) {
+ let self = {
+ isFirstInitialization: true,
+ unsavedChanges: false
+ };
+
+ self.initialize = function(json) {
+ self.map = map;
+
+ self.geoJsonLayer = self.newGeoJsonLayer(json).addTo(self.map);
+ self.addDrawControls();
+
+ self.firstInitialize();
+ };
+
+ self.firstInitialize = function() {
+ if (!self.isFirstInitialization) {
+ return;
+ }
+
+ self.isFirstInitialization = false;
+
+ self.map.on(
+ L.Draw.Event.CREATED,
+ function (event) {
+ self.geoJsonLayer.addData(event.layer.toGeoJSON());
+ self._showSaveButton();
+ }
+ );
+
+ self.map.on(
+ L.Draw.Event.EDITED,
+ self._showSaveButton
+ );
+
+ self.map.on(
+ L.Draw.Event.DELETED,
+ self._showSaveButton
+ );
+
+ $(window).bind('beforeunload', function() {
+ if (self.unsavedChanges) {
+ return 'The map has unsaved changes. Are you sure you want to leave the page?';
+ }
+ });
+
+ initializeMessages();
+ };
+
+ self.newGeoJsonLayer = function(json) {
+ return L.geoJSON(
+ json,
+ {
+ style: function (feature) {
+ return maps.leaflet.GeoJson.simpleStyleToLeafletPathOptions(feature.properties);
+ },
+ onEachFeature: self._onEditableFeature
+ }
+ );
+ };
+
+ self.newSaveButton = function() {
+ return L.easyButton(
+ '<img src="' + mw.config.get('egMapsScriptPath') + 'resources/leaflet/images/save-solid.svg">',
+ function() {
+ let editSummary = prompt(
+ 'Enter an edit summary for your changes to the map',
+ 'Visual map edit'
+ ); // TODO: i18n
+
+ if (editSummary!== null) {
+ self.saveButton.remove();
+
+ mapSaver.save(
+ {
+ newContent: JSON.stringify(self.geoJsonLayer.toGeoJSON()),
+ summary: editSummary,
+ done: function(response) {
+ if (response.result === 'Success') {
+ self.unsavedChanges = false;
+ self.onSaved();
+ }
+ else {
+ console.log(response);
+ self._showSaveButton();
+ alert(mw.msg('maps-json-editor-edit-failed'));
+ }
+ }
+ }
+ );
+ }
+ },
+ mw.msg('maps-json-editor-toolbar-button-save')
+ );
+ };
+
+ self._showSaveButton = function() {
+ self.unsavedChanges = true;
+
+ if (!self.saveButton) {
+ self.saveButton = self.newSaveButton();
+ }
+
+ self.saveButton.addTo(self.map);
+ };
+
+ function onSizeChange(element, callback) {
+ let currentWidth = element[0].clientWidth;
+ let currentHeight = element[0].clientHeight;
+
+ element.bind('mouseup', function() {
+ if (element[0].clientWidth !== currentWidth || element[0].clientHeight !== currentHeight) {
+ currentWidth = element[0].clientWidth;
+ currentHeight = element[0].clientHeight;
+ callback(element);
+ }
+ });
+ }
+
+ self._onEditableFeature = function(feature, layer) {
+ let titleInput = $('<textarea cols="50" rows="1" />').text(feature.properties.title);
+ let descriptionInput = $('<textarea cols="50" rows="2" />').text(feature.properties.description);
+ let button = $('<button style="width: 100%">').text(mw.msg('maps-json-editor-toolbar-save-text'));
+
+ layer.on("popupopen", function () {
+ let v = titleInput.val();
+ titleInput.focus().val('').val(v);
+ });
+
+ let popup = L.popup({
+ minWidth: 250,
+ maxWidth: 9000,
+ keepInView: true,
+ closeButton: false,
+ autoClose: false,
+ closeOnEscapeKey: false,
+ closeOnClick: false
+ });
+
+ let onSizeChangedHandler = function(element) {
+ popup.update(); element.focus();
+ };
+
+ onSizeChange(titleInput, onSizeChangedHandler);
+ onSizeChange(descriptionInput, onSizeChangedHandler);
+
+ button.click(function() {
+ popup.remove();
+
+ if (titleInput.val() !== titleInput.text() || descriptionInput.val() !== descriptionInput.text()) {
+ feature.properties["title"] = titleInput.val();
+ feature.properties["description"] = descriptionInput.val();
+ self._showSaveButton();
+ }
+ });
+
+ popup.setContent($('<div />').append(titleInput, descriptionInput, button)[0]);
+ layer.bindPopup(popup);
+ };
+
+ self.addDrawControls = function() {
+ // self.map.addControl(L.control.styleEditor({
+ // position: "topleft",
+ // useGrouping: false,
+ // openOnLeafletDraw: false,
+ // }));
+
+ if (!self.drawControl) {
+ self.drawControl = self.newDrawControl();
+ }
+
+ self.drawControl.addTo(self.map);
+ };
+
+ self.newDrawControl = function() {
+ return new L.Control.Draw({
+ edit: {
+ featureGroup: self.geoJsonLayer,
+ poly: {
+ allowIntersection: true
+ }
+ },
+ draw: {
+ polygon: {
+ allowIntersection: true,
+ showArea: true
+ },
+ circlemarker: false, // Do not want this one
+ circle: false // Is not showing properly after save
+ }
+ })
+ };
+
+ self.remove = function() {
+ self.drawControl.remove();
+ self.saveButton.remove();
+ self.geoJsonLayer.remove();
+ };
+
+ self.onSaved = function() {};
+
+ let exports = {};
+
+ exports.initialize = self.initialize;
+ exports.remove = self.remove;
+
+ exports.getLayer = function() {
+ return self.geoJsonLayer;
+ };
+
+ exports.onSaved = function(f) {
+ self.onSaved = f;
+ };
+
+ return exports;
+ };
+
+ if (!maps.leaflet) {maps.leaflet = {};}
+
+ maps.leaflet.LeafletEditor = MapEditor;
+
+})( window.jQuery, window.mediaWiki, window.maps );
diff --git a/www/wiki/extensions/Maps/resources/leaflet/LeafletLoader.js b/www/wiki/extensions/Maps/resources/leaflet/LeafletLoader.js
new file mode 100644
index 00000000..15776cf7
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/LeafletLoader.js
@@ -0,0 +1,15 @@
+window.mapsLeafletList = [];
+
+(function( $, mw ) {
+ mw.hook( 'wikipage.content' ).add( function ( $content ) {
+ $content.find( '.maps-leaflet' ).each( function() {
+ let $this = $( this );
+
+ let jqueryMap = $this.leafletmaps(
+ JSON.parse( $this.find( 'div.mapdata' ).text() )
+ );
+
+ window.mapsLeafletList.push(jqueryMap);
+ } );
+ } );
+})( window.jQuery, window.mediaWiki );
diff --git a/www/wiki/extensions/Maps/resources/leaflet/cluster/m1.png b/www/wiki/extensions/Maps/resources/leaflet/cluster/m1.png
index 3346508f..329ff524 100644
--- a/www/wiki/extensions/Maps/resources/leaflet/cluster/m1.png
+++ b/www/wiki/extensions/Maps/resources/leaflet/cluster/m1.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/cluster/m2.png b/www/wiki/extensions/Maps/resources/leaflet/cluster/m2.png
index 64786829..b999cbcf 100644
--- a/www/wiki/extensions/Maps/resources/leaflet/cluster/m2.png
+++ b/www/wiki/extensions/Maps/resources/leaflet/cluster/m2.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/cluster/m3.png b/www/wiki/extensions/Maps/resources/leaflet/cluster/m3.png
index 772ed91a..9f30b309 100644
--- a/www/wiki/extensions/Maps/resources/leaflet/cluster/m3.png
+++ b/www/wiki/extensions/Maps/resources/leaflet/cluster/m3.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/cluster/m4.png b/www/wiki/extensions/Maps/resources/leaflet/cluster/m4.png
index eb33557a..0d3f8263 100644
--- a/www/wiki/extensions/Maps/resources/leaflet/cluster/m4.png
+++ b/www/wiki/extensions/Maps/resources/leaflet/cluster/m4.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/cluster/m5.png b/www/wiki/extensions/Maps/resources/leaflet/cluster/m5.png
index 6ca9c490..61387d2a 100644
--- a/www/wiki/extensions/Maps/resources/leaflet/cluster/m5.png
+++ b/www/wiki/extensions/Maps/resources/leaflet/cluster/m5.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/ext.maps.leaflet.js b/www/wiki/extensions/Maps/resources/leaflet/ext.maps.leaflet.js
deleted file mode 100644
index 087d4acf..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/ext.maps.leaflet.js
+++ /dev/null
@@ -1,4 +0,0 @@
-
-mediaWiki.loader.using( [ 'ext.maps.leaflet' ] ).done( function () {
- ( new maps.services( jQuery( document ) ) ).leaflet();
-} ); \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/leaflet/ext.sm.leafletajax.js b/www/wiki/extensions/Maps/resources/leaflet/ext.sm.leafletajax.js
deleted file mode 100644
index fb9f7341..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/ext.sm.leafletajax.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * JavaScript for Leaflet in the Semantic Maps extension.
- * @see https://www.mediawiki.org/wiki/Extension:Semantic_Maps
- *
- * @licence GNU GPL v2+
- * @author Peter Grassberger < petertheone@gmail.com >
- */
-
-
-(function( $, sm ) {
- var ajaxRequest = null;
-
- var mapEvents = ['dragend', 'zoomend'];
-
- $( document ).ready( function() {
- // todo: find a way to remove setTimeout.
- setTimeout( function() {
- $( window.maps.leafletList ).each( function( index, map ) {
- if( !map.options.ajaxquery || !map.options.ajaxcoordproperty ) {
- return;
- }
- map.map.on( mapEvents.join( ' ' ), function() {
- var bounds = map.map.getBounds();
- var query = sm.buildQueryString(
- decodeURIComponent( map.options.ajaxquery.replace( /\+/g, ' ' ) ),
- map.options.ajaxcoordproperty,
- bounds.getNorthEast().lat,
- bounds.getNorthEast().lng,
- bounds.getSouthWest().lat,
- bounds.getSouthWest().lng
- );
-
- if( ajaxRequest !== null ) {
- ajaxRequest.abort();
- }
- ajaxRequest = sm.ajaxUpdateMarker( map, query, map.options.icon ).done( function() {
- map.createMarkerCluster();
- ajaxRequest = null;
- } );
- } );
- } );
- }, 1000 );
- } );
-})( window.jQuery, window.sm );
diff --git a/www/wiki/extensions/Maps/resources/leaflet/images/edit-solid.svg b/www/wiki/extensions/Maps/resources/leaflet/images/edit-solid.svg
new file mode 100644
index 00000000..e757323b
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/images/edit-solid.svg
@@ -0,0 +1 @@
+<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="edit" class="svg-inline--fa fa-edit fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M402.6 83.2l90.2 90.2c3.8 3.8 3.8 10 0 13.8L274.4 405.6l-92.8 10.3c-12.4 1.4-22.9-9.1-21.5-21.5l10.3-92.8L388.8 83.2c3.8-3.8 10-3.8 13.8 0zm162-22.9l-48.8-48.8c-15.2-15.2-39.9-15.2-55.2 0l-35.4 35.4c-3.8 3.8-3.8 10 0 13.8l90.2 90.2c3.8 3.8 10 3.8 13.8 0l35.4-35.4c15.2-15.3 15.2-40 0-55.2zM384 346.2V448H64V128h229.8c3.2 0 6.2-1.3 8.5-3.5l40-40c7.6-7.6 2.2-20.5-8.5-20.5H48C21.5 64 0 85.5 0 112v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V306.2c0-10.7-12.9-16-20.5-8.5l-40 40c-2.2 2.3-3.5 5.3-3.5 8.5z"></path></svg> \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/leaflet/images/save-solid.svg b/www/wiki/extensions/Maps/resources/leaflet/images/save-solid.svg
new file mode 100644
index 00000000..a27b3f55
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/images/save-solid.svg
@@ -0,0 +1 @@
+<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="save" class="svg-inline--fa fa-save fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M433.941 129.941l-83.882-83.882A48 48 0 0 0 316.118 32H48C21.49 32 0 53.49 0 80v352c0 26.51 21.49 48 48 48h352c26.51 0 48-21.49 48-48V163.882a48 48 0 0 0-14.059-33.941zM224 416c-35.346 0-64-28.654-64-64 0-35.346 28.654-64 64-64s64 28.654 64 64c0 35.346-28.654 64-64 64zm96-304.52V212c0 6.627-5.373 12-12 12H76c-6.627 0-12-5.373-12-12V108c0-6.627 5.373-12 12-12h228.52c3.183 0 6.235 1.264 8.485 3.515l3.48 3.48A11.996 11.996 0 0 1 320 111.48z"></path></svg> \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/leaflet/jquery.leaflet.js b/www/wiki/extensions/Maps/resources/leaflet/jquery.leaflet.js
index 3483ec3a..5012c766 100644
--- a/www/wiki/extensions/Maps/resources/leaflet/jquery.leaflet.js
+++ b/www/wiki/extensions/Maps/resources/leaflet/jquery.leaflet.js
@@ -4,423 +4,321 @@
*
* @author Pavel Astakhov < pastakhov@yandex.ru >
* @author Peter Grassberger < petertheone@gmail.com >
+ * @author Jeroen De Dauw
*/
-(function ($, mw, L, MQ) {
- $.fn.leafletmaps = function ( options ) {
- var _this = this;
- this.map = null;
- this.options = options;
- this.markers = [];
- this.markercluster = null;
- var apikeys = mw.config.get('egMapsLeafletLayersApiKeys') ;
-
- /**
- * array point of all map elements (markers, lines, polygons, etc.)
- * for map fit
- */
- this.points = [];
-
- /**
- * Creates a new marker with the provided data and returns it.
- * @param {Object} properties Contains the fields lat, lon, title, text and icon
- * @return {L.Marker}
- */
- this.createMarker = function (properties) {
- this.points.push( new L.LatLng(properties.lat, properties.lon) );
-
- var markerOptions = {
- title:properties.title
+(function ($, mw, L, maps, sm) {
+
+ function getMapOptions(options) {
+ let mapOptions = {};
+ if (options.minzoom !== false) mapOptions.minZoom = options.minzoom;
+ if (options.maxzoom !== false) mapOptions.maxZoom = options.maxzoom;
+
+ if (options.fullscreen) {
+ mapOptions.fullscreenControl = true;
+ mapOptions.fullscreenControlOptions= {
+ position: 'topleft'
};
+ }
- var marker = L.marker( [properties.lat, properties.lon], markerOptions );
-
- if (properties.hasOwnProperty('icon') && properties.icon !== '') {
- marker.setOpacity(0);
-
- var img = new Image();
- img.onload = function() {
- var icon = new L.Icon({
- iconUrl: properties.icon,
- iconSize: [ img.width, img.height ],
- iconAnchor: [ img.width / 2, img.height ],
- popupAnchor: [ -img.width % 2, -img.height*2/3 ]
- });
-
- marker.setIcon(icon);
- marker.setOpacity(1);
- };
- img.src = properties.icon;
- }
+ mapOptions.scrollWheelZoom = options.scrollwheelzoom;
- if( properties.hasOwnProperty('text') && properties.text.length > 0 ) {
- marker.bindPopup( properties.text );
- }
+ if (options.static) {
+ mapOptions.scrollWheelZoom = false;
+ mapOptions.doubleClickZoom = false;
+ mapOptions.touchZoom = false;
+ mapOptions.boxZoom = false;
+ mapOptions.tap = false;
+ mapOptions.keyboard = false;
+ mapOptions.zoomControl = false;
+ mapOptions.dragging = false;
+ }
- if ( options.copycoords ) {
- marker.on(
- 'contextmenu',
- function( e ) {
- prompt(mw.msg('maps-copycoords-prompt'), e.latlng.lat + ',' + e.latlng.lng);
- }
- );
- }
-
- return marker;
- };
+ return mapOptions;
+ }
- /**
- * Creates a new marker with the provided data, adds it to the map
- * and returns it.
- * @param {Object} properties Contains the fields lat, lon, title, text and icon
- * @return {L.Marker}
- */
- this.addMarker = function (properties) {
- var marker = this.createMarker(properties);
- if (!this.options.markercluster) {
- marker.addTo( this.map );
- }
- this.markers.push( marker );
- return marker;
- };
+ $.fn.leafletmaps = function ( options ) {
+ let _this = this;
+ _this.options = options; // needed by LeafletAjax.js
- this.removeMarker = function (marker) {
- this.map.removeLayer(marker);
- this.points = [];
- this.markers = this.markers.filter(function(object) {
- return object !== marker;
- });
- };
+ this.setup = function() {
+ this.map = L.map( this.get(0), getMapOptions(options) );
+ this.mapContent = maps.leaflet.FeatureBuilder.contentLayerFromOptions(options).addTo(this.map);
- this.removeMarkers = function () {
- if (this.markercluster) {
- this.map.removeLayer(this.markercluster);
- this.markercluster = null;
- }
- var map = this.map;
- $.each(this.markers, function(index, marker) {
- map.removeLayer(marker);
- });
+ this.hideLoadingMessage();
+ this.addLayersAndOverlays(this.map);
+ this.centerAndZoomMap();
+ this.bindClickTarget();
+ this.applyResizable();
+ this.bindAjaxEvents();
- this.points = [];
- this.markers = [];
+ this.maybeAddEditButton();
};
- this.addLine = function (properties) {
- var options = {
- color: properties.strokeColor,
- weight:properties.strokeWeight,
- opacity:properties.strokeOpacity
- };
+ this.shouldLoadEditorJs = function() {
+ if ( options.geojson === '' || options.GeoJsonSource === null ) {
+ return false;
+ }
- var latlngs = [];
- for (var x = 0; x < properties.pos.length; x++) {
- latlngs.push([properties.pos[x].lat, properties.pos[x].lon]);
- this.points.push( new L.LatLng(properties.pos[x].lat, properties.pos[x].lon) );
+ if (mw.config.get('wgCurRevisionId') !== mw.config.get('wgRevisionId')) {
+ return false;
}
- var line = L.polyline(latlngs, options).addTo(this.map);
+ return true;
+ };
- if( properties.hasOwnProperty('text') && properties.text.trim().length > 0 ) {
- line.bindPopup( properties.text );
+ this.maybeAddEditButton = function() {
+ if ( this.shouldLoadEditorJs() ) {
+ maps.api.canEditPage('GeoJson:' + options.GeoJsonSource).done(
+ function(canEdit) {
+ if (canEdit) {
+ _this.addEditButton();
+ }
+ }
+ );
}
};
- this.addPolygon = function (properties) {
- properties.pos.forEach(function(position) {
- _this.points.push( new L.LatLng(position.lat, position.lon) );
- });
+ this.addEditButton = function() {
+ this.editButton = L.easyButton(
+ '<img src="' + mw.config.get('egMapsScriptPath') + 'resources/leaflet/images/edit-solid.svg">',
+ this.startEditMode,
+ mw.msg('maps-editor-edit-geojson')
+ ).addTo(this.map);
+ };
+
+ this.startEditMode = function() {
+ _this.removeEditButton();
+ _this.mapContent.remove();
- var polygon = L.polygon(
- properties.pos.map(function(position) {
- return [position.lat, position.lon];
- }),
- {
- color: properties.strokeColor,
- weight:properties.strokeWeight,
- opacity:properties.strokeOpacity,
- fillColor:properties.fillColor,
- fillOpacity:properties.fillOpacity
+ maps.api.getLatestRevision('GeoJson:' + options.GeoJsonSource).done(
+ function(revision) {
+ if (revision.revid === options.GeoJsonRevisionId) {
+ _this.initializeEditor(options.geojson);
+ }
+ else {
+ _this.purgePage();
+ _this.initializeEditor(JSON.parse(revision['*']));
+ }
}
);
+ };
- polygon.addTo(this.map);
+ this.initializeEditor = function(geoJson) {
+ let editor = _this.getEditor();
+ editor.initialize(geoJson);
- if( properties.hasOwnProperty('text') && properties.text.trim().length > 0 ) {
- console.log(properties.text);
- polygon.bindPopup( properties.text );
- }
- };
+ editor.onSaved(function() {
+ _this.purgePage();
- this.addCircle = function (properties) {
- var circle = L.circle(
- [properties.centre.lat, properties.centre.lon],
- {
- radius: properties.radius,
- color: properties.strokeColor,
- weight:properties.strokeWeight,
- opacity:properties.strokeOpacity,
- fillColor:properties.fillColor,
- fillOpacity:properties.fillOpacity,
- }
- ).addTo(this.map);
+ editor.remove();
+ options.geojson = editor.getLayer().toGeoJSON();
+ _this.mapContent = maps.leaflet.FeatureBuilder.contentLayerFromOptions(options).addTo(_this.map);
- this.points.push( new L.LatLng(properties.centre.lat, properties.centre.lon) );
+ alert(mw.msg('maps-json-editor-changes-saved'));
+ _this.addEditButton();
+ });
+ };
- if( properties.hasOwnProperty('text') && properties.text.trim().length > 0 ) {
- circle.bindPopup( properties.text );
+ this.getEditor = function() {
+ if (!this.editor) {
+ this.editor = maps.leaflet.LeafletEditor(
+ _this.map,
+ new maps.MapSaver('GeoJson:' + options.GeoJsonSource)
+ );
}
+
+ return this.editor;
};
- this.addRectangle = function (properties) {
- this.points.push( new L.LatLng(properties.sw.lat, properties.sw.lon) );
- this.points.push( new L.LatLng(properties.ne.lat, properties.ne.lon) );
+ this.purgePage = function() {
+ new mw.Api().post({
+ action: 'purge',
+ titles: mw.config.get( 'wgPageName' )
+ }).then(function(response) {
- var options = {
- color: properties.strokeColor,
- weight:properties.strokeWeight,
- opacity:properties.strokeOpacity,
- fillColor:properties.fillColor,
- fillOpacity:properties.fillOpacity
- };
+ });
+ };
- var bounds = [[properties.sw.lat, properties.sw.lon], [properties.ne.lat, properties.ne.lon]];
+ this.removeEditButton = function() {
+ if (this.editButton) {
+ this.editButton.remove();
+ this.editButton = null;
+ }
+ };
- var rectangle = L.rectangle( bounds, options ).addTo(this.map);
+ this.hideLoadingMessage = function() {
+ this.map.on(
+ 'load',
+ function() {
+ $(_this).find('div.maps-loading-message').hide();
+ }
+ );
+ };
- if( properties.hasOwnProperty('text') && properties.text.trim().length > 0 ) {
- rectangle.bindPopup( properties.text );
+ this.applyResizable = function() {
+ if (options.resizable) {
+ _this.resizable();
}
};
- this.createMarkerCluster = function () {
- if ( !options.markercluster ) {
- return;
- }
- var markers = this.markers;
-
- var markercluster = new L.MarkerClusterGroup({
- maxClusterRadius: options.clustermaxradius,
- disableClusteringAtZoom: options.clustermaxzoom + 1,
- zoomToBoundsOnClick: options.clusterzoomonclick,
- spiderfyOnMaxZoom: options.clusterspiderfy,
- iconCreateFunction: function(cluster) {
- var childCount = cluster.getChildCount();
-
- var imagePath = mw.config.get( 'egMapsScriptPath' ) + '/resources/leaflet/cluster/';
-
- var styles = [
- {
- iconUrl: imagePath + 'm1.png',
- iconSize: [53, 52]
- },
- {
- iconUrl: imagePath + 'm2.png',
- iconSize: [56, 55]
- },
- {
- iconUrl: imagePath + 'm3.png',
- iconSize: [66, 65]
- },
- {
- iconUrl: imagePath + 'm4.png',
- iconSize: [78, 77]
- },
- {
- iconUrl: imagePath + 'm5.png',
- iconSize: [90, 89]
- }
- ];
+ // Caution: used by ajaxUpdateMarker
+ this.addMarker = function (properties) {
+ this.mapContent.markerLayer.addLayer(maps.leaflet.FeatureBuilder.createMarker(properties, options));
+ };
- var index = 0;
- var dv = childCount;
- while (dv !== 0) {
- dv = parseInt(dv / 10, 10);
- index++;
- }
- var index = Math.min(index, styles.length);
- index = Math.max(0, index - 1);
- index = Math.min(styles.length - 1, index);
- var style = styles[index];
-
- return new L.divIcon({
- iconSize: style.iconSize,
- className: '',
- html: '<img style="' +
- '" src="' + style.iconUrl + '" />' +
- '<span style="' +
- 'position: absolute; font-size: 11px; font-weight: bold; text-align: center; ' +
- 'top: 0; left: 0; ' +
- 'line-height: ' + style.iconSize[1] + 'px;' +
- 'width: ' + style.iconSize[0] + 'px;' +
- '">' + childCount + '</span>'
- });
- }
- });
- $.each(this.markers, function(index, marker) {
- markercluster.addLayer(marker);
- });
- if (this.markercluster) {
- this.map.removeLayer(this.markercluster);
- this.markercluster = null;
- }
- this.map.addLayer(markercluster);
- this.markercluster = markercluster;
+ // Caution: used by ajaxUpdateMarker
+ this.removeMarkers = function () {
+ this.mapContent.markerLayer.clearLayers();
};
- this.addGeoJson = function(options) {
- if (options.geojson !== '') {
- var geoJson = options.geojson;
- var geoJsonLayer = L.geoJSON( geoJson ).addTo( this.map );
+ this.bindClickTarget = function() {
+ function newClickTargetUrl(latlng) {
+ return options.clicktarget
+ .replace( /%lat%/g, latlng.lat )
+ .replace( /%long%/g, latlng.lng );
+ }
- this.points.push( geoJsonLayer.getBounds().getNorthEast() );
- this.points.push( geoJsonLayer.getBounds().getSouthWest() );
+ if (options.clicktarget !== '') {
+ this.map.on(
+ 'click',
+ function(e) {
+ window.location.href = newClickTargetUrl(e.latlng);
+ }
+ );
}
};
- this.setup = function () {
-
- var mapOptions = {};
- if (options.minzoom !== false ) mapOptions.minZoom = options.minzoom;
- if (options.maxzoom !== false ) mapOptions.maxZoom = options.maxzoom;
+ this.isUserUsesDarkMode = function () {
+ return window.matchMedia( '(prefers-color-scheme: dark)' ).matches;
+ };
- if (options.enablefullscreen) {
- mapOptions.fullscreenControl = true;
- mapOptions.fullscreenControlOptions= {
- position: 'topleft'
- };
- }
-
- mapOptions.scrollWheelZoom = options.scrollwheelzoom;
-
- if (options.static) {
- mapOptions.scrollWheelZoom = false;
- mapOptions.doubleClickZoom = false;
- mapOptions.touchZoom = false;
- mapOptions.boxZoom = false;
- mapOptions.tap = false;
- mapOptions.keyboard = false;
- mapOptions.zoomControl = false;
- mapOptions.dragging = false;
+ this.getLayerNames = function () {
+ if ( this.isUserUsesDarkMode() ) {
+ return mw.config.get('egMapsLeafletLayersDark');
}
- var map = L.map( this.get(0), mapOptions ).fitWorld();
- this.map = map;
+ return options.layers;
+ };
+
+ this.addLayers = function() {
+ let apiKeys = mw.config.get('egMapsLeafletLayersApiKeys');
+ let layers = {};
- var layers = {};
- $.each(options.layers.reverse(), function(index, layerName) {
+ $.each( this.getLayerNames().reverse(), function(index, layerName) {
var options = {} ;
var providerName = layerName.split('.')[0] ;
- if (apikeys.hasOwnProperty(providerName) && apikeys[providerName] !== '') {
- options.apikey = apikeys[providerName] ;
- }
+ if (apiKeys.hasOwnProperty(providerName) && apiKeys[providerName] !== '') {
+ options.apikey = apiKeys[providerName] ;
+ }
if (layerName === 'MapQuestOpen') {
- layers[layerName] = new MQ.TileLayer().addTo(map);
+ layers[layerName] = new window.MQ.TileLayer().addTo(_this.map);
} else {
- layers[layerName] = new L.tileLayer.provider(layerName,options).addTo(map);
+ layers[layerName] = new L.tileLayer.provider(layerName,options).addTo(_this.map);
}
});
- var overlaylayers = {};
- $.each(options.overlaylayers, function(index, overlaylayerName) {
- overlaylayers[overlaylayerName] = new L.tileLayer.provider(overlaylayerName).addTo(_this.map);
+ return layers;
+ };
+
+ this.addOverlays = function() {
+ let overlays = {};
+
+ $.each(options.overlays, function(index, overlayName) {
+ overlays[overlayName] = new L.tileLayer.provider(overlayName).addTo(_this.map);
});
- if (options.layers.length > 1) {
- L.control.layers(layers, overlaylayers).addTo(map);
- }
+ return overlays;
+ };
- if (options.resizable) {
- //TODO: Fix moving map when resized
- _this.resizable();
- }
+ this.addLayersAndOverlays = function() {
+ let layers = this.addLayers();
+ let overlays = this.addOverlays();
- if (!options.locations) {
- options.locations = [];
+ if (options.layers.length > 1 || options.overlays.length > 0) {
+ L.control.layers(layers, overlays).addTo(this.map);
}
+ };
- // Add the markers.
- for (var i = options.locations.length - 1; i >= 0; i--) {
- this.addMarker(options.locations[i]);
+ this.bindAjaxEvents = function() {
+ if ( !options.ajaxquery || !options.ajaxcoordproperty ) {
+ return;
}
- // Add markercluster
- if (options.markercluster) {
- this.createMarkerCluster();
- }
+ let ajaxRequest = null;
- // Add lines
- if (options.lines) {
- for (var i = 0; i < options.lines.length; i++) {
- this.addLine(options.lines[i]);
- }
- }
+ this.map.on( 'dragend zoomend', function() {
+ let bounds = _this.map.getBounds();
- // Add polygons
- if (options.polygons) {
- for (var i = 0; i < options.polygons.length; i++) {
- this.addPolygon(options.polygons[i]);
- }
- }
+ let query = sm.buildQueryString(
+ decodeURIComponent( options.ajaxquery.replace( /\+/g, ' ' ) ),
+ options.ajaxcoordproperty,
+ bounds.getNorthEast().lat,
+ bounds.getNorthEast().lng,
+ bounds.getSouthWest().lat,
+ bounds.getSouthWest().lng
+ );
- // Add circles
- if (options.circles) {
- for (var i = 0; i < options.circles.length; i++) {
- this.addCircle(options.circles[i]);
+ if( ajaxRequest !== null ) {
+ ajaxRequest.abort();
}
+
+ ajaxRequest = sm.ajaxUpdateMarker( _this, query, options.icon ).done( function() {
+ ajaxRequest = null;
+ } );
+ } );
+ };
+
+ this.centerAndZoomMap = function() {
+ this.map.fitWorld();
+ this.fitContent();
+
+ if (options.zoom !== false) {
+ this.map.setZoom(options.zoom);
}
- // Add rectangles
- if (options.rectangles) {
- for (var i = 0; i < options.rectangles.length; i++) {
- this.addRectangle(options.rectangles[i]);
- }
+ if (options.centre !== false) {
+ this.map.setView(
+ new L.LatLng(options.centre.lat, options.centre.lon),
+ this.map.getZoom()
+ );
}
+ };
- this.addGeoJson(options);
-
- // Set map position (centre and zoom)
- var centre;
- if (options.centre === false) {
- switch ( this.points.length ) {
- case 0:
- centre = new L.LatLng(0, 0);
- break;
- case 1:
- centre = this.points[0];
- break;
- default:
- var bounds = new L.LatLngBounds( this.points );
- if (options.zoom === false) {
- map.fitBounds( bounds );
- centre = false;
- } else {
- centre = bounds.getCenter();
- }
- break;
+ this.fitContent = function() {
+ let bounds = this.mapContent.getBounds();
+
+ if (bounds.isValid()) {
+ if (bounds.getNorthEast().equals(bounds.getSouthWest())) {
+ this.map.setView(
+ bounds.getCenter(),
+ options.defzoom
+ );
+ }
+ else {
+ this.map.fitBounds(bounds);
}
- this.points = [];
- } else {
- centre = new L.LatLng(options.centre.lat, options.centre.lon);
- }
- if(centre) {
- map.setView( centre, options.zoom !== false ? options.zoom : options.defzoom );
}
};
this.getDependencies = function ( options ) {
var dependencies = [];
- if (options.layers !== ['MapQuestOpen'] || options.overlaylayers.length > 0) {
- dependencies.push( 'ext.maps.leaflet.providers' );
+
+ if (this.shouldLoadEditorJs()) {
+ dependencies.push( 'ext.maps.leaflet.editor' );
}
- if (options.enablefullscreen) {
+
+ if (options.fullscreen) {
dependencies.push( 'ext.maps.leaflet.fullscreen' );
}
+
if (options.resizable) {
dependencies.push( 'ext.maps.resizable' );
}
- if (options.markercluster) {
+
+ if (options.cluster) {
dependencies.push( 'ext.maps.leaflet.markercluster' );
}
+
return dependencies;
};
@@ -429,6 +327,5 @@
} );
return this;
-
};
-})(jQuery, window.mediaWiki, L, window.MQ);
+})(window.jQuery, window.mediaWiki, window.L, window.maps, window.sm);
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.editable/Leaflet.Editable.js b/www/wiki/extensions/Maps/resources/leaflet/leaflet.editable/Leaflet.Editable.js
deleted file mode 100644
index e259a14c..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet.editable/Leaflet.Editable.js
+++ /dev/null
@@ -1,1945 +0,0 @@
-'use strict';
-(function (factory, window) {// 1.2 alpha
- /*globals define, module, require*/
-
- // define an AMD module that relies on 'leaflet'
- if (typeof define === 'function' && define.amd) {
- define(['leaflet'], factory);
-
-
- // define a Common JS module that relies on 'leaflet'
- } else if (typeof exports === 'object') {
- module.exports = factory(require('leaflet'));
- }
-
- // attach your plugin to the global 'L' variable
- if(typeof window !== 'undefined' && window.L){
- factory(window.L);
- }
-
-}(function (L) {
- // 🍂miniclass CancelableEvent (Event objects)
- // 🍂method cancel()
- // Cancel any subsequent action.
-
- // 🍂miniclass VertexEvent (Event objects)
- // 🍂property vertex: VertexMarker
- // The vertex that fires the event.
-
- // 🍂miniclass ShapeEvent (Event objects)
- // 🍂property shape: Array
- // The shape (LatLngs array) subject of the action.
-
- // 🍂miniclass CancelableVertexEvent (Event objects)
- // 🍂inherits VertexEvent
- // 🍂inherits CancelableEvent
-
- // 🍂miniclass CancelableShapeEvent (Event objects)
- // 🍂inherits ShapeEvent
- // 🍂inherits CancelableEvent
-
- // 🍂miniclass LayerEvent (Event objects)
- // 🍂property layer: object
- // The Layer (Marker, Polyline…) subject of the action.
-
- // 🍂namespace Editable; 🍂class Editable; 🍂aka L.Editable
- // Main edition handler. By default, it is attached to the map
- // as `map.editTools` property.
- // Leaflet.Editable is made to be fully extendable. You have three ways to customize
- // the behaviour: using options, listening to events, or extending.
- L.Editable = L.Evented.extend({
-
- statics: {
- FORWARD: 1,
- BACKWARD: -1
- },
-
- options: {
-
- // You can pass them when creating a map using the `editOptions` key.
- // 🍂option zIndex: int = 1000
- // The default zIndex of the editing tools.
- zIndex: 1000,
-
- // 🍂option polygonClass: class = L.Polygon
- // Class to be used when creating a new Polygon.
- polygonClass: L.Polygon,
-
- // 🍂option polylineClass: class = L.Polyline
- // Class to be used when creating a new Polyline.
- polylineClass: L.Polyline,
-
- // 🍂option markerClass: class = L.Marker
- // Class to be used when creating a new Marker.
- markerClass: L.Marker,
-
- // 🍂option rectangleClass: class = L.Rectangle
- // Class to be used when creating a new Rectangle.
- rectangleClass: L.Rectangle,
-
- // 🍂option circleClass: class = L.Circle
- // Class to be used when creating a new Circle.
- circleClass: L.Circle,
-
- // 🍂option drawingCSSClass: string = 'leaflet-editable-drawing'
- // CSS class to be added to the map container while drawing.
- drawingCSSClass: 'leaflet-editable-drawing',
-
- // 🍂option drawingCursor: const = 'crosshair'
- // Cursor mode set to the map while drawing.
- drawingCursor: 'crosshair',
-
- // 🍂option editLayer: Layer = new L.LayerGroup()
- // Layer used to store edit tools (vertex, line guide…).
- editLayer: undefined,
-
- // 🍂option featuresLayer: Layer = new L.LayerGroup()
- // Default layer used to store drawn features (Marker, Polyline…).
- featuresLayer: undefined,
-
- // 🍂option polylineEditorClass: class = PolylineEditor
- // Class to be used as Polyline editor.
- polylineEditorClass: undefined,
-
- // 🍂option polygonEditorClass: class = PolygonEditor
- // Class to be used as Polygon editor.
- polygonEditorClass: undefined,
-
- // 🍂option markerEditorClass: class = MarkerEditor
- // Class to be used as Marker editor.
- markerEditorClass: undefined,
-
- // 🍂option rectangleEditorClass: class = RectangleEditor
- // Class to be used as Rectangle editor.
- rectangleEditorClass: undefined,
-
- // 🍂option circleEditorClass: class = CircleEditor
- // Class to be used as Circle editor.
- circleEditorClass: undefined,
-
- // 🍂option lineGuideOptions: hash = {}
- // Options to be passed to the line guides.
- lineGuideOptions: {},
-
- // 🍂option skipMiddleMarkers: boolean = false
- // Set this to true if you don't want middle markers.
- skipMiddleMarkers: false
-
- },
-
- initialize: function (map, options) {
- L.setOptions(this, options);
- this._lastZIndex = this.options.zIndex;
- this.map = map;
- this.editLayer = this.createEditLayer();
- this.featuresLayer = this.createFeaturesLayer();
- this.forwardLineGuide = this.createLineGuide();
- this.backwardLineGuide = this.createLineGuide();
- },
-
- fireAndForward: function (type, e) {
- e = e || {};
- e.editTools = this;
- this.fire(type, e);
- this.map.fire(type, e);
- },
-
- createLineGuide: function () {
- var options = L.extend({dashArray: '5,10', weight: 1, interactive: false}, this.options.lineGuideOptions);
- return L.polyline([], options);
- },
-
- createVertexIcon: function (options) {
- return L.Browser.mobile && L.Browser.touch ? new L.Editable.TouchVertexIcon(options) : new L.Editable.VertexIcon(options);
- },
-
- createEditLayer: function () {
- return this.options.editLayer || new L.LayerGroup().addTo(this.map);
- },
-
- createFeaturesLayer: function () {
- return this.options.featuresLayer || new L.LayerGroup().addTo(this.map);
- },
-
- moveForwardLineGuide: function (latlng) {
- if (this.forwardLineGuide._latlngs.length) {
- this.forwardLineGuide._latlngs[1] = latlng;
- this.forwardLineGuide._bounds.extend(latlng);
- this.forwardLineGuide.redraw();
- }
- },
-
- moveBackwardLineGuide: function (latlng) {
- if (this.backwardLineGuide._latlngs.length) {
- this.backwardLineGuide._latlngs[1] = latlng;
- this.backwardLineGuide._bounds.extend(latlng);
- this.backwardLineGuide.redraw();
- }
- },
-
- anchorForwardLineGuide: function (latlng) {
- this.forwardLineGuide._latlngs[0] = latlng;
- this.forwardLineGuide._bounds.extend(latlng);
- this.forwardLineGuide.redraw();
- },
-
- anchorBackwardLineGuide: function (latlng) {
- this.backwardLineGuide._latlngs[0] = latlng;
- this.backwardLineGuide._bounds.extend(latlng);
- this.backwardLineGuide.redraw();
- },
-
- attachForwardLineGuide: function () {
- this.editLayer.addLayer(this.forwardLineGuide);
- },
-
- attachBackwardLineGuide: function () {
- this.editLayer.addLayer(this.backwardLineGuide);
- },
-
- detachForwardLineGuide: function () {
- this.forwardLineGuide.setLatLngs([]);
- this.editLayer.removeLayer(this.forwardLineGuide);
- },
-
- detachBackwardLineGuide: function () {
- this.backwardLineGuide.setLatLngs([]);
- this.editLayer.removeLayer(this.backwardLineGuide);
- },
-
- blockEvents: function () {
- // Hack: force map not to listen to other layers events while drawing.
- if (!this._oldTargets) {
- this._oldTargets = this.map._targets;
- this.map._targets = {};
- }
- },
-
- unblockEvents: function () {
- if (this._oldTargets) {
- // Reset, but keep targets created while drawing.
- this.map._targets = L.extend(this.map._targets, this._oldTargets);
- delete this._oldTargets;
- }
- },
-
- registerForDrawing: function (editor) {
- if (this._drawingEditor) this.unregisterForDrawing(this._drawingEditor);
- this.blockEvents();
- editor.reset(); // Make sure editor tools still receive events.
- this._drawingEditor = editor;
- this.map.on('mousemove touchmove', editor.onDrawingMouseMove, editor);
- this.map.on('mousedown', this.onMousedown, this);
- this.map.on('mouseup', this.onMouseup, this);
- L.DomUtil.addClass(this.map._container, this.options.drawingCSSClass);
- this.defaultMapCursor = this.map._container.style.cursor;
- this.map._container.style.cursor = this.options.drawingCursor;
- },
-
- unregisterForDrawing: function (editor) {
- this.unblockEvents();
- L.DomUtil.removeClass(this.map._container, this.options.drawingCSSClass);
- this.map._container.style.cursor = this.defaultMapCursor;
- editor = editor || this._drawingEditor;
- if (!editor) return;
- this.map.off('mousemove touchmove', editor.onDrawingMouseMove, editor);
- this.map.off('mousedown', this.onMousedown, this);
- this.map.off('mouseup', this.onMouseup, this);
- if (editor !== this._drawingEditor) return;
- delete this._drawingEditor;
- if (editor._drawing) editor.cancelDrawing();
- },
-
- onMousedown: function (e) {
- this._mouseDown = e;
- this._drawingEditor.onDrawingMouseDown(e);
- },
-
- onMouseup: function (e) {
- if (this._mouseDown) {
- var editor = this._drawingEditor,
- mouseDown = this._mouseDown;
- this._mouseDown = null;
- editor.onDrawingMouseUp(e);
- if (this._drawingEditor !== editor) return; // onDrawingMouseUp may call unregisterFromDrawing.
- var origin = L.point(mouseDown.originalEvent.clientX, mouseDown.originalEvent.clientY);
- var distance = L.point(e.originalEvent.clientX, e.originalEvent.clientY).distanceTo(origin);
- if (Math.abs(distance) < 9 * (window.devicePixelRatio || 1)) this._drawingEditor.onDrawingClick(e);
- }
- },
-
- // 🍂section Public methods
- // You will generally access them by the `map.editTools`
- // instance:
- //
- // `map.editTools.startPolyline();`
-
- // 🍂method drawing(): boolean
- // Return true if any drawing action is ongoing.
- drawing: function () {
- return this._drawingEditor && this._drawingEditor.drawing();
- },
-
- // 🍂method stopDrawing()
- // When you need to stop any ongoing drawing, without needing to know which editor is active.
- stopDrawing: function () {
- this.unregisterForDrawing();
- },
-
- // 🍂method commitDrawing()
- // When you need to commit any ongoing drawing, without needing to know which editor is active.
- commitDrawing: function (e) {
- if (!this._drawingEditor) return;
- this._drawingEditor.commitDrawing(e);
- },
-
- connectCreatedToMap: function (layer) {
- return this.featuresLayer.addLayer(layer);
- },
-
- // 🍂method startPolyline(latlng: L.LatLng, options: hash): L.Polyline
- // Start drawing a Polyline. If `latlng` is given, a first point will be added. In any case, continuing on user click.
- // If `options` is given, it will be passed to the Polyline class constructor.
- startPolyline: function (latlng, options) {
- var line = this.createPolyline([], options);
- line.enableEdit(this.map).newShape(latlng);
- return line;
- },
-
- // 🍂method startPolygon(latlng: L.LatLng, options: hash): L.Polygon
- // Start drawing a Polygon. If `latlng` is given, a first point will be added. In any case, continuing on user click.
- // If `options` is given, it will be passed to the Polygon class constructor.
- startPolygon: function (latlng, options) {
- var polygon = this.createPolygon([], options);
- polygon.enableEdit(this.map).newShape(latlng);
- return polygon;
- },
-
- // 🍂method startMarker(latlng: L.LatLng, options: hash): L.Marker
- // Start adding a Marker. If `latlng` is given, the Marker will be shown first at this point.
- // In any case, it will follow the user mouse, and will have a final `latlng` on next click (or touch).
- // If `options` is given, it will be passed to the Marker class constructor.
- startMarker: function (latlng, options) {
- latlng = latlng || this.map.getCenter().clone();
- var marker = this.createMarker(latlng, options);
- marker.enableEdit(this.map).startDrawing();
- return marker;
- },
-
- // 🍂method startRectangle(latlng: L.LatLng, options: hash): L.Rectangle
- // Start drawing a Rectangle. If `latlng` is given, the Rectangle anchor will be added. In any case, continuing on user drag.
- // If `options` is given, it will be passed to the Rectangle class constructor.
- startRectangle: function(latlng, options) {
- var corner = latlng || L.latLng([0, 0]);
- var bounds = new L.LatLngBounds(corner, corner);
- var rectangle = this.createRectangle(bounds, options);
- rectangle.enableEdit(this.map).startDrawing();
- return rectangle;
- },
-
- // 🍂method startCircle(latlng: L.LatLng, options: hash): L.Circle
- // Start drawing a Circle. If `latlng` is given, the Circle anchor will be added. In any case, continuing on user drag.
- // If `options` is given, it will be passed to the Circle class constructor.
- startCircle: function (latlng, options) {
- latlng = latlng || this.map.getCenter().clone();
- var circle = this.createCircle(latlng, options);
- circle.enableEdit(this.map).startDrawing();
- return circle;
- },
-
- startHole: function (editor, latlng) {
- editor.newHole(latlng);
- },
-
- createLayer: function (klass, latlngs, options) {
- options = L.Util.extend({editOptions: {editTools: this}}, options);
- var layer = new klass(latlngs, options);
- // 🍂namespace Editable
- // 🍂event editable:created: LayerEvent
- // Fired when a new feature (Marker, Polyline…) is created.
- this.fireAndForward('editable:created', {layer: layer});
- return layer;
- },
-
- createPolyline: function (latlngs, options) {
- return this.createLayer(options && options.polylineClass || this.options.polylineClass, latlngs, options);
- },
-
- createPolygon: function (latlngs, options) {
- return this.createLayer(options && options.polygonClass || this.options.polygonClass, latlngs, options);
- },
-
- createMarker: function (latlng, options) {
- return this.createLayer(options && options.markerClass || this.options.markerClass, latlng, options);
- },
-
- createRectangle: function (bounds, options) {
- return this.createLayer(options && options.rectangleClass || this.options.rectangleClass, bounds, options);
- },
-
- createCircle: function (latlng, options) {
- return this.createLayer(options && options.circleClass || this.options.circleClass, latlng, options);
- }
-
- });
-
- L.extend(L.Editable, {
-
- makeCancellable: function (e) {
- e.cancel = function () {
- e._cancelled = true;
- };
- }
-
- });
-
- // 🍂namespace Map; 🍂class Map
- // Leaflet.Editable add options and events to the `L.Map` object.
- // See `Editable` events for the list of events fired on the Map.
- // 🍂example
- //
- // ```js
- // var map = L.map('map', {
- // editable: true,
- // editOptions: {
- // …
- // }
- // });
- // ```
- // 🍂section Editable Map Options
- L.Map.mergeOptions({
-
- // 🍂namespace Map
- // 🍂section Map Options
- // 🍂option editToolsClass: class = L.Editable
- // Class to be used as vertex, for path editing.
- editToolsClass: L.Editable,
-
- // 🍂option editable: boolean = false
- // Whether to create a L.Editable instance at map init.
- editable: false,
-
- // 🍂option editOptions: hash = {}
- // Options to pass to L.Editable when instantiating.
- editOptions: {}
-
- });
-
- L.Map.addInitHook(function () {
-
- this.whenReady(function () {
- if (this.options.editable) {
- this.editTools = new this.options.editToolsClass(this, this.options.editOptions);
- }
- });
-
- });
-
- L.Editable.VertexIcon = L.DivIcon.extend({
-
- options: {
- iconSize: new L.Point(8, 8)
- }
-
- });
-
- L.Editable.TouchVertexIcon = L.Editable.VertexIcon.extend({
-
- options: {
- iconSize: new L.Point(20, 20)
- }
-
- });
-
-
- // 🍂namespace Editable; 🍂class VertexMarker; Handler for dragging path vertices.
- L.Editable.VertexMarker = L.Marker.extend({
-
- options: {
- draggable: true,
- className: 'leaflet-div-icon leaflet-vertex-icon'
- },
-
-
- // 🍂section Public methods
- // The marker used to handle path vertex. You will usually interact with a `VertexMarker`
- // instance when listening for events like `editable:vertex:ctrlclick`.
-
- initialize: function (latlng, latlngs, editor, options) {
- // We don't use this._latlng, because on drag Leaflet replace it while
- // we want to keep reference.
- this.latlng = latlng;
- this.latlngs = latlngs;
- this.editor = editor;
- L.Marker.prototype.initialize.call(this, latlng, options);
- this.options.icon = this.editor.tools.createVertexIcon({className: this.options.className});
- this.latlng.__vertex = this;
- this.editor.editLayer.addLayer(this);
- this.setZIndexOffset(editor.tools._lastZIndex + 1);
- },
-
- onAdd: function (map) {
- L.Marker.prototype.onAdd.call(this, map);
- this.on('drag', this.onDrag);
- this.on('dragstart', this.onDragStart);
- this.on('dragend', this.onDragEnd);
- this.on('mouseup', this.onMouseup);
- this.on('click', this.onClick);
- this.on('contextmenu', this.onContextMenu);
- this.on('mousedown touchstart', this.onMouseDown);
- this.on('mouseover', this.onMouseOver);
- this.on('mouseout', this.onMouseOut);
- this.addMiddleMarkers();
- },
-
- onRemove: function (map) {
- if (this.middleMarker) this.middleMarker.delete();
- delete this.latlng.__vertex;
- this.off('drag', this.onDrag);
- this.off('dragstart', this.onDragStart);
- this.off('dragend', this.onDragEnd);
- this.off('mouseup', this.onMouseup);
- this.off('click', this.onClick);
- this.off('contextmenu', this.onContextMenu);
- this.off('mousedown touchstart', this.onMouseDown);
- this.off('mouseover', this.onMouseOver);
- this.off('mouseout', this.onMouseOut);
- L.Marker.prototype.onRemove.call(this, map);
- },
-
- onDrag: function (e) {
- e.vertex = this;
- this.editor.onVertexMarkerDrag(e);
- var iconPos = L.DomUtil.getPosition(this._icon),
- latlng = this._map.layerPointToLatLng(iconPos);
- this.latlng.update(latlng);
- this._latlng = this.latlng; // Push back to Leaflet our reference.
- this.editor.refresh();
- if (this.middleMarker) this.middleMarker.updateLatLng();
- var next = this.getNext();
- if (next && next.middleMarker) next.middleMarker.updateLatLng();
- },
-
- onDragStart: function (e) {
- e.vertex = this;
- this.editor.onVertexMarkerDragStart(e);
- },
-
- onDragEnd: function (e) {
- e.vertex = this;
- this.editor.onVertexMarkerDragEnd(e);
- },
-
- onClick: function (e) {
- e.vertex = this;
- this.editor.onVertexMarkerClick(e);
- },
-
- onMouseup: function (e) {
- L.DomEvent.stop(e);
- e.vertex = this;
- this.editor.map.fire('mouseup', e);
- },
-
- onContextMenu: function (e) {
- e.vertex = this;
- this.editor.onVertexMarkerContextMenu(e);
- },
-
- onMouseDown: function (e) {
- e.vertex = this;
- this.editor.onVertexMarkerMouseDown(e);
- },
-
- onMouseOver: function (e) {
- e.vertex = this;
- this.editor.onVertexMarkerMouseOver(e);
- },
-
- onMouseOut: function (e) {
- e.vertex = this;
- this.editor.onVertexMarkerMouseOut(e);
- },
-
- // 🍂method delete()
- // Delete a vertex and the related LatLng.
- delete: function () {
- var next = this.getNext(); // Compute before changing latlng
- this.latlngs.splice(this.getIndex(), 1);
- this.editor.editLayer.removeLayer(this);
- this.editor.onVertexDeleted({latlng: this.latlng, vertex: this});
- if (!this.latlngs.length) this.editor.deleteShape(this.latlngs);
- if (next) next.resetMiddleMarker();
- this.editor.refresh();
- },
-
- // 🍂method getIndex(): int
- // Get the index of the current vertex among others of the same LatLngs group.
- getIndex: function () {
- return this.latlngs.indexOf(this.latlng);
- },
-
- // 🍂method getLastIndex(): int
- // Get last vertex index of the LatLngs group of the current vertex.
- getLastIndex: function () {
- return this.latlngs.length - 1;
- },
-
- // 🍂method getPrevious(): VertexMarker
- // Get the previous VertexMarker in the same LatLngs group.
- getPrevious: function () {
- if (this.latlngs.length < 2) return;
- var index = this.getIndex(),
- previousIndex = index - 1;
- if (index === 0 && this.editor.CLOSED) previousIndex = this.getLastIndex();
- var previous = this.latlngs[previousIndex];
- if (previous) return previous.__vertex;
- },
-
- // 🍂method getNext(): VertexMarker
- // Get the next VertexMarker in the same LatLngs group.
- getNext: function () {
- if (this.latlngs.length < 2) return;
- var index = this.getIndex(),
- nextIndex = index + 1;
- if (index === this.getLastIndex() && this.editor.CLOSED) nextIndex = 0;
- var next = this.latlngs[nextIndex];
- if (next) return next.__vertex;
- },
-
- addMiddleMarker: function (previous) {
- if (!this.editor.hasMiddleMarkers()) return;
- previous = previous || this.getPrevious();
- if (previous && !this.middleMarker) this.middleMarker = this.editor.addMiddleMarker(previous, this, this.latlngs, this.editor);
- },
-
- addMiddleMarkers: function () {
- if (!this.editor.hasMiddleMarkers()) return;
- var previous = this.getPrevious();
- if (previous) this.addMiddleMarker(previous);
- var next = this.getNext();
- if (next) next.resetMiddleMarker();
- },
-
- resetMiddleMarker: function () {
- if (this.middleMarker) this.middleMarker.delete();
- this.addMiddleMarker();
- },
-
- // 🍂method split()
- // Split the vertex LatLngs group at its index, if possible.
- split: function () {
- if (!this.editor.splitShape) return; // Only for PolylineEditor
- this.editor.splitShape(this.latlngs, this.getIndex());
- },
-
- // 🍂method continue()
- // Continue the vertex LatLngs from this vertex. Only active for first and last vertices of a Polyline.
- continue: function () {
- if (!this.editor.continueBackward) return; // Only for PolylineEditor
- var index = this.getIndex();
- if (index === 0) this.editor.continueBackward(this.latlngs);
- else if (index === this.getLastIndex()) this.editor.continueForward(this.latlngs);
- }
-
- });
-
- L.Editable.mergeOptions({
-
- // 🍂namespace Editable
- // 🍂option vertexMarkerClass: class = VertexMarker
- // Class to be used as vertex, for path editing.
- vertexMarkerClass: L.Editable.VertexMarker
-
- });
-
- L.Editable.MiddleMarker = L.Marker.extend({
-
- options: {
- opacity: 0.5,
- className: 'leaflet-div-icon leaflet-middle-icon',
- draggable: true
- },
-
- initialize: function (left, right, latlngs, editor, options) {
- this.left = left;
- this.right = right;
- this.editor = editor;
- this.latlngs = latlngs;
- L.Marker.prototype.initialize.call(this, this.computeLatLng(), options);
- this._opacity = this.options.opacity;
- this.options.icon = this.editor.tools.createVertexIcon({className: this.options.className});
- this.editor.editLayer.addLayer(this);
- this.setVisibility();
- },
-
- setVisibility: function () {
- var leftPoint = this._map.latLngToContainerPoint(this.left.latlng),
- rightPoint = this._map.latLngToContainerPoint(this.right.latlng),
- size = L.point(this.options.icon.options.iconSize);
- if (leftPoint.distanceTo(rightPoint) < size.x * 3) this.hide();
- else this.show();
- },
-
- show: function () {
- this.setOpacity(this._opacity);
- },
-
- hide: function () {
- this.setOpacity(0);
- },
-
- updateLatLng: function () {
- this.setLatLng(this.computeLatLng());
- this.setVisibility();
- },
-
- computeLatLng: function () {
- var leftPoint = this.editor.map.latLngToContainerPoint(this.left.latlng),
- rightPoint = this.editor.map.latLngToContainerPoint(this.right.latlng),
- y = (leftPoint.y + rightPoint.y) / 2,
- x = (leftPoint.x + rightPoint.x) / 2;
- return this.editor.map.containerPointToLatLng([x, y]);
- },
-
- onAdd: function (map) {
- L.Marker.prototype.onAdd.call(this, map);
- L.DomEvent.on(this._icon, 'mousedown touchstart', this.onMouseDown, this);
- map.on('zoomend', this.setVisibility, this);
- },
-
- onRemove: function (map) {
- delete this.right.middleMarker;
- L.DomEvent.off(this._icon, 'mousedown touchstart', this.onMouseDown, this);
- map.off('zoomend', this.setVisibility, this);
- L.Marker.prototype.onRemove.call(this, map);
- },
-
- onMouseDown: function (e) {
- var iconPos = L.DomUtil.getPosition(this._icon),
- latlng = this.editor.map.layerPointToLatLng(iconPos);
- e = {
- originalEvent: e,
- latlng: latlng
- };
- if (this.options.opacity === 0) return;
- L.Editable.makeCancellable(e);
- this.editor.onMiddleMarkerMouseDown(e);
- if (e._cancelled) return;
- this.latlngs.splice(this.index(), 0, e.latlng);
- this.editor.refresh();
- var icon = this._icon;
- var marker = this.editor.addVertexMarker(e.latlng, this.latlngs);
- this.editor.onNewVertex(marker);
- /* Hack to workaround browser not firing touchend when element is no more on DOM */
- var parent = marker._icon.parentNode;
- parent.removeChild(marker._icon);
- marker._icon = icon;
- parent.appendChild(marker._icon);
- marker._initIcon();
- marker._initInteraction();
- marker.setOpacity(1);
- /* End hack */
- // Transfer ongoing dragging to real marker
- L.Draggable._dragging = false;
- marker.dragging._draggable._onDown(e.originalEvent);
- this.delete();
- },
-
- delete: function () {
- this.editor.editLayer.removeLayer(this);
- },
-
- index: function () {
- return this.latlngs.indexOf(this.right.latlng);
- }
-
- });
-
- L.Editable.mergeOptions({
-
- // 🍂namespace Editable
- // 🍂option middleMarkerClass: class = VertexMarker
- // Class to be used as middle vertex, pulled by the user to create a new point in the middle of a path.
- middleMarkerClass: L.Editable.MiddleMarker
-
- });
-
- // 🍂namespace Editable; 🍂class BaseEditor; 🍂aka L.Editable.BaseEditor
- // When editing a feature (Marker, Polyline…), an editor is attached to it. This
- // editor basically knows how to handle the edition.
- L.Editable.BaseEditor = L.Handler.extend({
-
- initialize: function (map, feature, options) {
- L.setOptions(this, options);
- this.map = map;
- this.feature = feature;
- this.feature.editor = this;
- this.editLayer = new L.LayerGroup();
- this.tools = this.options.editTools || map.editTools;
- },
-
- // 🍂method enable(): this
- // Set up the drawing tools for the feature to be editable.
- addHooks: function () {
- if (this.isConnected()) this.onFeatureAdd();
- else this.feature.once('add', this.onFeatureAdd, this);
- this.onEnable();
- this.feature.on(this._getEvents(), this);
- },
-
- // 🍂method disable(): this
- // Remove the drawing tools for the feature.
- removeHooks: function () {
- this.feature.off(this._getEvents(), this);
- if (this.feature.dragging) this.feature.dragging.disable();
- this.editLayer.clearLayers();
- this.tools.editLayer.removeLayer(this.editLayer);
- this.onDisable();
- if (this._drawing) this.cancelDrawing();
- },
-
- // 🍂method drawing(): boolean
- // Return true if any drawing action is ongoing with this editor.
- drawing: function () {
- return !!this._drawing;
- },
-
- reset: function () {},
-
- onFeatureAdd: function () {
- this.tools.editLayer.addLayer(this.editLayer);
- if (this.feature.dragging) this.feature.dragging.enable();
- },
-
- hasMiddleMarkers: function () {
- return !this.options.skipMiddleMarkers && !this.tools.options.skipMiddleMarkers;
- },
-
- fireAndForward: function (type, e) {
- e = e || {};
- e.layer = this.feature;
- this.feature.fire(type, e);
- this.tools.fireAndForward(type, e);
- },
-
- onEnable: function () {
- // 🍂namespace Editable
- // 🍂event editable:enable: Event
- // Fired when an existing feature is ready to be edited.
- this.fireAndForward('editable:enable');
- },
-
- onDisable: function () {
- // 🍂namespace Editable
- // 🍂event editable:disable: Event
- // Fired when an existing feature is not ready anymore to be edited.
- this.fireAndForward('editable:disable');
- },
-
- onEditing: function () {
- // 🍂namespace Editable
- // 🍂event editable:editing: Event
- // Fired as soon as any change is made to the feature geometry.
- this.fireAndForward('editable:editing');
- },
-
- onStartDrawing: function () {
- // 🍂namespace Editable
- // 🍂section Drawing events
- // 🍂event editable:drawing:start: Event
- // Fired when a feature is to be drawn.
- this.fireAndForward('editable:drawing:start');
- },
-
- onEndDrawing: function () {
- // 🍂namespace Editable
- // 🍂section Drawing events
- // 🍂event editable:drawing:end: Event
- // Fired when a feature is not drawn anymore.
- this.fireAndForward('editable:drawing:end');
- },
-
- onCancelDrawing: function () {
- // 🍂namespace Editable
- // 🍂section Drawing events
- // 🍂event editable:drawing:cancel: Event
- // Fired when user cancel drawing while a feature is being drawn.
- this.fireAndForward('editable:drawing:cancel');
- },
-
- onCommitDrawing: function (e) {
- // 🍂namespace Editable
- // 🍂section Drawing events
- // 🍂event editable:drawing:commit: Event
- // Fired when user finish drawing a feature.
- this.fireAndForward('editable:drawing:commit', e);
- },
-
- onDrawingMouseDown: function (e) {
- // 🍂namespace Editable
- // 🍂section Drawing events
- // 🍂event editable:drawing:mousedown: Event
- // Fired when user `mousedown` while drawing.
- this.fireAndForward('editable:drawing:mousedown', e);
- },
-
- onDrawingMouseUp: function (e) {
- // 🍂namespace Editable
- // 🍂section Drawing events
- // 🍂event editable:drawing:mouseup: Event
- // Fired when user `mouseup` while drawing.
- this.fireAndForward('editable:drawing:mouseup', e);
- },
-
- startDrawing: function () {
- if (!this._drawing) this._drawing = L.Editable.FORWARD;
- this.tools.registerForDrawing(this);
- this.onStartDrawing();
- },
-
- commitDrawing: function (e) {
- this.onCommitDrawing(e);
- this.endDrawing();
- },
-
- cancelDrawing: function () {
- // If called during a vertex drag, the vertex will be removed before
- // the mouseup fires on it. This is a workaround. Maybe better fix is
- // To have L.Draggable reset it's status on disable (Leaflet side).
- L.Draggable._dragging = false;
- this.onCancelDrawing();
- this.endDrawing();
- },
-
- endDrawing: function () {
- this._drawing = false;
- this.tools.unregisterForDrawing(this);
- this.onEndDrawing();
- },
-
- onDrawingClick: function (e) {
- if (!this.drawing()) return;
- L.Editable.makeCancellable(e);
- // 🍂namespace Editable
- // 🍂section Drawing events
- // 🍂event editable:drawing:click: CancelableEvent
- // Fired when user `click` while drawing, before any internal action is being processed.
- this.fireAndForward('editable:drawing:click', e);
- if (e._cancelled) return;
- if (!this.isConnected()) this.connect(e);
- this.processDrawingClick(e);
- },
-
- isConnected: function () {
- return this.map.hasLayer(this.feature);
- },
-
- connect: function () {
- this.tools.connectCreatedToMap(this.feature);
- this.tools.editLayer.addLayer(this.editLayer);
- },
-
- onMove: function (e) {
- // 🍂namespace Editable
- // 🍂section Drawing events
- // 🍂event editable:drawing:move: Event
- // Fired when `move` mouse while drawing, while dragging a marker, and while dragging a vertex.
- this.fireAndForward('editable:drawing:move', e);
- },
-
- onDrawingMouseMove: function (e) {
- this.onMove(e);
- },
-
- _getEvents: function () {
- return {
- dragstart: this.onDragStart,
- drag: this.onDrag,
- dragend: this.onDragEnd,
- remove: this.disable
- };
- },
-
- onDragStart: function (e) {
- this.onEditing();
- // 🍂namespace Editable
- // 🍂event editable:dragstart: Event
- // Fired before a path feature is dragged.
- this.fireAndForward('editable:dragstart', e);
- },
-
- onDrag: function (e) {
- this.onMove(e);
- // 🍂namespace Editable
- // 🍂event editable:drag: Event
- // Fired when a path feature is being dragged.
- this.fireAndForward('editable:drag', e);
- },
-
- onDragEnd: function (e) {
- // 🍂namespace Editable
- // 🍂event editable:dragend: Event
- // Fired after a path feature has been dragged.
- this.fireAndForward('editable:dragend', e);
- }
-
- });
-
- // 🍂namespace Editable; 🍂class MarkerEditor; 🍂aka L.Editable.MarkerEditor
- // 🍂inherits BaseEditor
- // Editor for Marker.
- L.Editable.MarkerEditor = L.Editable.BaseEditor.extend({
-
- onDrawingMouseMove: function (e) {
- L.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e);
- if (this._drawing) this.feature.setLatLng(e.latlng);
- },
-
- processDrawingClick: function (e) {
- // 🍂namespace Editable
- // 🍂section Drawing events
- // 🍂event editable:drawing:clicked: Event
- // Fired when user `click` while drawing, after all internal actions.
- this.fireAndForward('editable:drawing:clicked', e);
- this.commitDrawing(e);
- },
-
- connect: function (e) {
- // On touch, the latlng has not been updated because there is
- // no mousemove.
- if (e) this.feature._latlng = e.latlng;
- L.Editable.BaseEditor.prototype.connect.call(this, e);
- }
-
- });
-
- // 🍂namespace Editable; 🍂class PathEditor; 🍂aka L.Editable.PathEditor
- // 🍂inherits BaseEditor
- // Base class for all path editors.
- L.Editable.PathEditor = L.Editable.BaseEditor.extend({
-
- CLOSED: false,
- MIN_VERTEX: 2,
-
- addHooks: function () {
- L.Editable.BaseEditor.prototype.addHooks.call(this);
- if (this.feature) this.initVertexMarkers();
- return this;
- },
-
- initVertexMarkers: function (latlngs) {
- if (!this.enabled()) return;
- latlngs = latlngs || this.getLatLngs();
- if (isFlat(latlngs)) this.addVertexMarkers(latlngs);
- else for (var i = 0; i < latlngs.length; i++) this.initVertexMarkers(latlngs[i]);
- },
-
- getLatLngs: function () {
- return this.feature.getLatLngs();
- },
-
- // 🍂method reset()
- // Rebuild edit elements (Vertex, MiddleMarker, etc.).
- reset: function () {
- this.editLayer.clearLayers();
- this.initVertexMarkers();
- },
-
- addVertexMarker: function (latlng, latlngs) {
- return new this.tools.options.vertexMarkerClass(latlng, latlngs, this);
- },
-
- onNewVertex: function (vertex) {
- // 🍂namespace Editable
- // 🍂section Vertex events
- // 🍂event editable:vertex:new: VertexEvent
- // Fired when a new vertex is created.
- this.fireAndForward('editable:vertex:new', {latlng: vertex.latlng, vertex: vertex});
- },
-
- addVertexMarkers: function (latlngs) {
- for (var i = 0; i < latlngs.length; i++) {
- this.addVertexMarker(latlngs[i], latlngs);
- }
- },
-
- refreshVertexMarkers: function (latlngs) {
- latlngs = latlngs || this.getDefaultLatLngs();
- for (var i = 0; i < latlngs.length; i++) {
- latlngs[i].__vertex.update();
- }
- },
-
- addMiddleMarker: function (left, right, latlngs) {
- return new this.tools.options.middleMarkerClass(left, right, latlngs, this);
- },
-
- onVertexMarkerClick: function (e) {
- L.Editable.makeCancellable(e);
- // 🍂namespace Editable
- // 🍂section Vertex events
- // 🍂event editable:vertex:click: CancelableVertexEvent
- // Fired when a `click` is issued on a vertex, before any internal action is being processed.
- this.fireAndForward('editable:vertex:click', e);
- if (e._cancelled) return;
- if (this.tools.drawing() && this.tools._drawingEditor !== this) return;
- var index = e.vertex.getIndex(), commit;
- if (e.originalEvent.ctrlKey) {
- this.onVertexMarkerCtrlClick(e);
- } else if (e.originalEvent.altKey) {
- this.onVertexMarkerAltClick(e);
- } else if (e.originalEvent.shiftKey) {
- this.onVertexMarkerShiftClick(e);
- } else if (e.originalEvent.metaKey) {
- this.onVertexMarkerMetaKeyClick(e);
- } else if (index === e.vertex.getLastIndex() && this._drawing === L.Editable.FORWARD) {
- if (index >= this.MIN_VERTEX - 1) commit = true;
- } else if (index === 0 && this._drawing === L.Editable.BACKWARD && this._drawnLatLngs.length >= this.MIN_VERTEX) {
- commit = true;
- } else if (index === 0 && this._drawing === L.Editable.FORWARD && this._drawnLatLngs.length >= this.MIN_VERTEX && this.CLOSED) {
- commit = true; // Allow to close on first point also for polygons
- } else {
- this.onVertexRawMarkerClick(e);
- }
- // 🍂namespace Editable
- // 🍂section Vertex events
- // 🍂event editable:vertex:clicked: VertexEvent
- // Fired when a `click` is issued on a vertex, after all internal actions.
- this.fireAndForward('editable:vertex:clicked', e);
- if (commit) this.commitDrawing(e);
- },
-
- onVertexRawMarkerClick: function (e) {
- // 🍂namespace Editable
- // 🍂section Vertex events
- // 🍂event editable:vertex:rawclick: CancelableVertexEvent
- // Fired when a `click` is issued on a vertex without any special key and without being in drawing mode.
- this.fireAndForward('editable:vertex:rawclick', e);
- if (e._cancelled) return;
- if (!this.vertexCanBeDeleted(e.vertex)) return;
- e.vertex.delete();
- },
-
- vertexCanBeDeleted: function (vertex) {
- return vertex.latlngs.length > this.MIN_VERTEX;
- },
-
- onVertexDeleted: function (e) {
- // 🍂namespace Editable
- // 🍂section Vertex events
- // 🍂event editable:vertex:deleted: VertexEvent
- // Fired after a vertex has been deleted by user.
- this.fireAndForward('editable:vertex:deleted', e);
- },
-
- onVertexMarkerCtrlClick: function (e) {
- // 🍂namespace Editable
- // 🍂section Vertex events
- // 🍂event editable:vertex:ctrlclick: VertexEvent
- // Fired when a `click` with `ctrlKey` is issued on a vertex.
- this.fireAndForward('editable:vertex:ctrlclick', e);
- },
-
- onVertexMarkerShiftClick: function (e) {
- // 🍂namespace Editable
- // 🍂section Vertex events
- // 🍂event editable:vertex:shiftclick: VertexEvent
- // Fired when a `click` with `shiftKey` is issued on a vertex.
- this.fireAndForward('editable:vertex:shiftclick', e);
- },
-
- onVertexMarkerMetaKeyClick: function (e) {
- // 🍂namespace Editable
- // 🍂section Vertex events
- // 🍂event editable:vertex:metakeyclick: VertexEvent
- // Fired when a `click` with `metaKey` is issued on a vertex.
- this.fireAndForward('editable:vertex:metakeyclick', e);
- },
-
- onVertexMarkerAltClick: function (e) {
- // 🍂namespace Editable
- // 🍂section Vertex events
- // 🍂event editable:vertex:altclick: VertexEvent
- // Fired when a `click` with `altKey` is issued on a vertex.
- this.fireAndForward('editable:vertex:altclick', e);
- },
-
- onVertexMarkerContextMenu: function (e) {
- // 🍂namespace Editable
- // 🍂section Vertex events
- // 🍂event editable:vertex:contextmenu: VertexEvent
- // Fired when a `contextmenu` is issued on a vertex.
- this.fireAndForward('editable:vertex:contextmenu', e);
- },
-
- onVertexMarkerMouseDown: function (e) {
- // 🍂namespace Editable
- // 🍂section Vertex events
- // 🍂event editable:vertex:mousedown: VertexEvent
- // Fired when user `mousedown` a vertex.
- this.fireAndForward('editable:vertex:mousedown', e);
- },
-
- onVertexMarkerMouseOver: function (e) {
- // 🍂namespace Editable
- // 🍂section Vertex events
- // 🍂event editable:vertex:mouseover: VertexEvent
- // Fired when a user's mouse enters the vertex
- this.fireAndForward('editable:vertex:mouseover', e);
- },
-
- onVertexMarkerMouseOut: function (e) {
- // 🍂namespace Editable
- // 🍂section Vertex events
- // 🍂event editable:vertex:mouseout: VertexEvent
- // Fired when a user's mouse leaves the vertex
- this.fireAndForward('editable:vertex:mouseout', e);
- },
-
- onMiddleMarkerMouseDown: function (e) {
- // 🍂namespace Editable
- // 🍂section MiddleMarker events
- // 🍂event editable:middlemarker:mousedown: VertexEvent
- // Fired when user `mousedown` a middle marker.
- this.fireAndForward('editable:middlemarker:mousedown', e);
- },
-
- onVertexMarkerDrag: function (e) {
- this.onMove(e);
- if (this.feature._bounds) this.extendBounds(e);
- // 🍂namespace Editable
- // 🍂section Vertex events
- // 🍂event editable:vertex:drag: VertexEvent
- // Fired when a vertex is dragged by user.
- this.fireAndForward('editable:vertex:drag', e);
- },
-
- onVertexMarkerDragStart: function (e) {
- // 🍂namespace Editable
- // 🍂section Vertex events
- // 🍂event editable:vertex:dragstart: VertexEvent
- // Fired before a vertex is dragged by user.
- this.fireAndForward('editable:vertex:dragstart', e);
- },
-
- onVertexMarkerDragEnd: function (e) {
- // 🍂namespace Editable
- // 🍂section Vertex events
- // 🍂event editable:vertex:dragend: VertexEvent
- // Fired after a vertex is dragged by user.
- this.fireAndForward('editable:vertex:dragend', e);
- },
-
- setDrawnLatLngs: function (latlngs) {
- this._drawnLatLngs = latlngs || this.getDefaultLatLngs();
- },
-
- startDrawing: function () {
- if (!this._drawnLatLngs) this.setDrawnLatLngs();
- L.Editable.BaseEditor.prototype.startDrawing.call(this);
- },
-
- startDrawingForward: function () {
- this.startDrawing();
- },
-
- endDrawing: function () {
- this.tools.detachForwardLineGuide();
- this.tools.detachBackwardLineGuide();
- if (this._drawnLatLngs && this._drawnLatLngs.length < this.MIN_VERTEX) this.deleteShape(this._drawnLatLngs);
- L.Editable.BaseEditor.prototype.endDrawing.call(this);
- delete this._drawnLatLngs;
- },
-
- addLatLng: function (latlng) {
- if (this._drawing === L.Editable.FORWARD) this._drawnLatLngs.push(latlng);
- else this._drawnLatLngs.unshift(latlng);
- this.feature._bounds.extend(latlng);
- var vertex = this.addVertexMarker(latlng, this._drawnLatLngs);
- this.onNewVertex(vertex);
- this.refresh();
- },
-
- newPointForward: function (latlng) {
- this.addLatLng(latlng);
- this.tools.attachForwardLineGuide();
- this.tools.anchorForwardLineGuide(latlng);
- },
-
- newPointBackward: function (latlng) {
- this.addLatLng(latlng);
- this.tools.anchorBackwardLineGuide(latlng);
- },
-
- // 🍂namespace PathEditor
- // 🍂method push()
- // Programmatically add a point while drawing.
- push: function (latlng) {
- if (!latlng) return console.error('L.Editable.PathEditor.push expect a valid latlng as parameter');
- if (this._drawing === L.Editable.FORWARD) this.newPointForward(latlng);
- else this.newPointBackward(latlng);
- },
-
- removeLatLng: function (latlng) {
- latlng.__vertex.delete();
- this.refresh();
- },
-
- // 🍂method pop(): L.LatLng or null
- // Programmatically remove last point (if any) while drawing.
- pop: function () {
- if (this._drawnLatLngs.length <= 1) return;
- var latlng;
- if (this._drawing === L.Editable.FORWARD) latlng = this._drawnLatLngs[this._drawnLatLngs.length - 1];
- else latlng = this._drawnLatLngs[0];
- this.removeLatLng(latlng);
- if (this._drawing === L.Editable.FORWARD) this.tools.anchorForwardLineGuide(this._drawnLatLngs[this._drawnLatLngs.length - 1]);
- else this.tools.anchorForwardLineGuide(this._drawnLatLngs[0]);
- return latlng;
- },
-
- processDrawingClick: function (e) {
- if (e.vertex && e.vertex.editor === this) return;
- if (this._drawing === L.Editable.FORWARD) this.newPointForward(e.latlng);
- else this.newPointBackward(e.latlng);
- this.fireAndForward('editable:drawing:clicked', e);
- },
-
- onDrawingMouseMove: function (e) {
- L.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e);
- if (this._drawing) {
- this.tools.moveForwardLineGuide(e.latlng);
- this.tools.moveBackwardLineGuide(e.latlng);
- }
- },
-
- refresh: function () {
- this.feature.redraw();
- this.onEditing();
- },
-
- // 🍂namespace PathEditor
- // 🍂method newShape(latlng?: L.LatLng)
- // Add a new shape (Polyline, Polygon) in a multi, and setup up drawing tools to draw it;
- // if optional `latlng` is given, start a path at this point.
- newShape: function (latlng) {
- var shape = this.addNewEmptyShape();
- if (!shape) return;
- this.setDrawnLatLngs(shape[0] || shape); // Polygon or polyline
- this.startDrawingForward();
- // 🍂namespace Editable
- // 🍂section Shape events
- // 🍂event editable:shape:new: ShapeEvent
- // Fired when a new shape is created in a multi (Polygon or Polyline).
- this.fireAndForward('editable:shape:new', {shape: shape});
- if (latlng) this.newPointForward(latlng);
- },
-
- deleteShape: function (shape, latlngs) {
- var e = {shape: shape};
- L.Editable.makeCancellable(e);
- // 🍂namespace Editable
- // 🍂section Shape events
- // 🍂event editable:shape:delete: CancelableShapeEvent
- // Fired before a new shape is deleted in a multi (Polygon or Polyline).
- this.fireAndForward('editable:shape:delete', e);
- if (e._cancelled) return;
- shape = this._deleteShape(shape, latlngs);
- if (this.ensureNotFlat) this.ensureNotFlat(); // Polygon.
- this.feature.setLatLngs(this.getLatLngs()); // Force bounds reset.
- this.refresh();
- this.reset();
- // 🍂namespace Editable
- // 🍂section Shape events
- // 🍂event editable:shape:deleted: ShapeEvent
- // Fired after a new shape is deleted in a multi (Polygon or Polyline).
- this.fireAndForward('editable:shape:deleted', {shape: shape});
- return shape;
- },
-
- _deleteShape: function (shape, latlngs) {
- latlngs = latlngs || this.getLatLngs();
- if (!latlngs.length) return;
- var self = this,
- inplaceDelete = function (latlngs, shape) {
- // Called when deleting a flat latlngs
- shape = latlngs.splice(0, Number.MAX_VALUE);
- return shape;
- },
- spliceDelete = function (latlngs, shape) {
- // Called when removing a latlngs inside an array
- latlngs.splice(latlngs.indexOf(shape), 1);
- if (!latlngs.length) self._deleteShape(latlngs);
- return shape;
- };
- if (latlngs === shape) return inplaceDelete(latlngs, shape);
- for (var i = 0; i < latlngs.length; i++) {
- if (latlngs[i] === shape) return spliceDelete(latlngs, shape);
- else if (latlngs[i].indexOf(shape) !== -1) return spliceDelete(latlngs[i], shape);
- }
- },
-
- // 🍂namespace PathEditor
- // 🍂method deleteShapeAt(latlng: L.LatLng): Array
- // Remove a path shape at the given `latlng`.
- deleteShapeAt: function (latlng) {
- var shape = this.feature.shapeAt(latlng);
- if (shape) return this.deleteShape(shape);
- },
-
- // 🍂method appendShape(shape: Array)
- // Append a new shape to the Polygon or Polyline.
- appendShape: function (shape) {
- this.insertShape(shape);
- },
-
- // 🍂method prependShape(shape: Array)
- // Prepend a new shape to the Polygon or Polyline.
- prependShape: function (shape) {
- this.insertShape(shape, 0);
- },
-
- // 🍂method insertShape(shape: Array, index: int)
- // Insert a new shape to the Polygon or Polyline at given index (default is to append).
- insertShape: function (shape, index) {
- this.ensureMulti();
- shape = this.formatShape(shape);
- if (typeof index === 'undefined') index = this.feature._latlngs.length;
- this.feature._latlngs.splice(index, 0, shape);
- this.feature.redraw();
- if (this._enabled) this.reset();
- },
-
- extendBounds: function (e) {
- this.feature._bounds.extend(e.vertex.latlng);
- },
-
- onDragStart: function (e) {
- this.editLayer.clearLayers();
- L.Editable.BaseEditor.prototype.onDragStart.call(this, e);
- },
-
- onDragEnd: function (e) {
- this.initVertexMarkers();
- L.Editable.BaseEditor.prototype.onDragEnd.call(this, e);
- }
-
- });
-
- // 🍂namespace Editable; 🍂class PolylineEditor; 🍂aka L.Editable.PolylineEditor
- // 🍂inherits PathEditor
- L.Editable.PolylineEditor = L.Editable.PathEditor.extend({
-
- startDrawingBackward: function () {
- this._drawing = L.Editable.BACKWARD;
- this.startDrawing();
- },
-
- // 🍂method continueBackward(latlngs?: Array)
- // Set up drawing tools to continue the line backward.
- continueBackward: function (latlngs) {
- if (this.drawing()) return;
- latlngs = latlngs || this.getDefaultLatLngs();
- this.setDrawnLatLngs(latlngs);
- if (latlngs.length > 0) {
- this.tools.attachBackwardLineGuide();
- this.tools.anchorBackwardLineGuide(latlngs[0]);
- }
- this.startDrawingBackward();
- },
-
- // 🍂method continueForward(latlngs?: Array)
- // Set up drawing tools to continue the line forward.
- continueForward: function (latlngs) {
- if (this.drawing()) return;
- latlngs = latlngs || this.getDefaultLatLngs();
- this.setDrawnLatLngs(latlngs);
- if (latlngs.length > 0) {
- this.tools.attachForwardLineGuide();
- this.tools.anchorForwardLineGuide(latlngs[latlngs.length - 1]);
- }
- this.startDrawingForward();
- },
-
- getDefaultLatLngs: function (latlngs) {
- latlngs = latlngs || this.feature._latlngs;
- if (!latlngs.length || latlngs[0] instanceof L.LatLng) return latlngs;
- else return this.getDefaultLatLngs(latlngs[0]);
- },
-
- ensureMulti: function () {
- if (this.feature._latlngs.length && isFlat(this.feature._latlngs)) {
- this.feature._latlngs = [this.feature._latlngs];
- }
- },
-
- addNewEmptyShape: function () {
- if (this.feature._latlngs.length) {
- var shape = [];
- this.appendShape(shape);
- return shape;
- } else {
- return this.feature._latlngs;
- }
- },
-
- formatShape: function (shape) {
- if (isFlat(shape)) return shape;
- else if (shape[0]) return this.formatShape(shape[0]);
- },
-
- // 🍂method splitShape(latlngs?: Array, index: int)
- // Split the given `latlngs` shape at index `index` and integrate new shape in instance `latlngs`.
- splitShape: function (shape, index) {
- if (!index || index >= shape.length - 1) return;
- this.ensureMulti();
- var shapeIndex = this.feature._latlngs.indexOf(shape);
- if (shapeIndex === -1) return;
- var first = shape.slice(0, index + 1),
- second = shape.slice(index);
- // We deal with reference, we don't want twice the same latlng around.
- second[0] = L.latLng(second[0].lat, second[0].lng, second[0].alt);
- this.feature._latlngs.splice(shapeIndex, 1, first, second);
- this.refresh();
- this.reset();
- }
-
- });
-
- // 🍂namespace Editable; 🍂class PolygonEditor; 🍂aka L.Editable.PolygonEditor
- // 🍂inherits PathEditor
- L.Editable.PolygonEditor = L.Editable.PathEditor.extend({
-
- CLOSED: true,
- MIN_VERTEX: 3,
-
- newPointForward: function (latlng) {
- L.Editable.PathEditor.prototype.newPointForward.call(this, latlng);
- if (!this.tools.backwardLineGuide._latlngs.length) this.tools.anchorBackwardLineGuide(latlng);
- if (this._drawnLatLngs.length === 2) this.tools.attachBackwardLineGuide();
- },
-
- addNewEmptyHole: function (latlng) {
- this.ensureNotFlat();
- var latlngs = this.feature.shapeAt(latlng);
- if (!latlngs) return;
- var holes = [];
- latlngs.push(holes);
- return holes;
- },
-
- // 🍂method newHole(latlng?: L.LatLng, index: int)
- // Set up drawing tools for creating a new hole on the Polygon. If the `latlng` param is given, a first point is created.
- newHole: function (latlng) {
- var holes = this.addNewEmptyHole(latlng);
- if (!holes) return;
- this.setDrawnLatLngs(holes);
- this.startDrawingForward();
- if (latlng) this.newPointForward(latlng);
- },
-
- addNewEmptyShape: function () {
- if (this.feature._latlngs.length && this.feature._latlngs[0].length) {
- var shape = [];
- this.appendShape(shape);
- return shape;
- } else {
- return this.feature._latlngs;
- }
- },
-
- ensureMulti: function () {
- if (this.feature._latlngs.length && isFlat(this.feature._latlngs[0])) {
- this.feature._latlngs = [this.feature._latlngs];
- }
- },
-
- ensureNotFlat: function () {
- if (!this.feature._latlngs.length || isFlat(this.feature._latlngs)) this.feature._latlngs = [this.feature._latlngs];
- },
-
- vertexCanBeDeleted: function (vertex) {
- var parent = this.feature.parentShape(vertex.latlngs),
- idx = L.Util.indexOf(parent, vertex.latlngs);
- if (idx > 0) return true; // Holes can be totally deleted without removing the layer itself.
- return L.Editable.PathEditor.prototype.vertexCanBeDeleted.call(this, vertex);
- },
-
- getDefaultLatLngs: function () {
- if (!this.feature._latlngs.length) this.feature._latlngs.push([]);
- return this.feature._latlngs[0];
- },
-
- formatShape: function (shape) {
- // [[1, 2], [3, 4]] => must be nested
- // [] => must be nested
- // [[]] => is already nested
- if (isFlat(shape) && (!shape[0] || shape[0].length !== 0)) return [shape];
- else return shape;
- }
-
- });
-
- // 🍂namespace Editable; 🍂class RectangleEditor; 🍂aka L.Editable.RectangleEditor
- // 🍂inherits PathEditor
- L.Editable.RectangleEditor = L.Editable.PathEditor.extend({
-
- CLOSED: true,
- MIN_VERTEX: 4,
-
- options: {
- skipMiddleMarkers: true
- },
-
- extendBounds: function (e) {
- var index = e.vertex.getIndex(),
- next = e.vertex.getNext(),
- previous = e.vertex.getPrevious(),
- oppositeIndex = (index + 2) % 4,
- opposite = e.vertex.latlngs[oppositeIndex],
- bounds = new L.LatLngBounds(e.latlng, opposite);
- // Update latlngs by hand to preserve order.
- previous.latlng.update([e.latlng.lat, opposite.lng]);
- next.latlng.update([opposite.lat, e.latlng.lng]);
- this.updateBounds(bounds);
- this.refreshVertexMarkers();
- },
-
- onDrawingMouseDown: function (e) {
- L.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e);
- this.connect();
- var latlngs = this.getDefaultLatLngs();
- // L.Polygon._convertLatLngs removes last latlng if it equals first point,
- // which is the case here as all latlngs are [0, 0]
- if (latlngs.length === 3) latlngs.push(e.latlng);
- var bounds = new L.LatLngBounds(e.latlng, e.latlng);
- this.updateBounds(bounds);
- this.updateLatLngs(bounds);
- this.refresh();
- this.reset();
- // Stop dragging map.
- // L.Draggable has two workflows:
- // - mousedown => mousemove => mouseup
- // - touchstart => touchmove => touchend
- // Problem: L.Map.Tap does not allow us to listen to touchstart, so we only
- // can deal with mousedown, but then when in a touch device, we are dealing with
- // simulated events (actually simulated by L.Map.Tap), which are no more taken
- // into account by L.Draggable.
- // Ref.: https://github.com/Leaflet/Leaflet.Editable/issues/103
- e.originalEvent._simulated = false;
- this.map.dragging._draggable._onUp(e.originalEvent);
- // Now transfer ongoing drag action to the bottom right corner.
- // Should we refine which corner will handle the drag according to
- // drag direction?
- latlngs[3].__vertex.dragging._draggable._onDown(e.originalEvent);
- },
-
- onDrawingMouseUp: function (e) {
- this.commitDrawing(e);
- e.originalEvent._simulated = false;
- L.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e);
- },
-
- onDrawingMouseMove: function (e) {
- e.originalEvent._simulated = false;
- L.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e);
- },
-
-
- getDefaultLatLngs: function (latlngs) {
- return latlngs || this.feature._latlngs[0];
- },
-
- updateBounds: function (bounds) {
- this.feature._bounds = bounds;
- },
-
- updateLatLngs: function (bounds) {
- var latlngs = this.getDefaultLatLngs(),
- newLatlngs = this.feature._boundsToLatLngs(bounds);
- // Keep references.
- for (var i = 0; i < latlngs.length; i++) {
- latlngs[i].update(newLatlngs[i]);
- }
- }
-
- });
-
- // 🍂namespace Editable; 🍂class CircleEditor; 🍂aka L.Editable.CircleEditor
- // 🍂inherits PathEditor
- L.Editable.CircleEditor = L.Editable.PathEditor.extend({
-
- MIN_VERTEX: 2,
-
- options: {
- skipMiddleMarkers: true
- },
-
- initialize: function (map, feature, options) {
- L.Editable.PathEditor.prototype.initialize.call(this, map, feature, options);
- this._resizeLatLng = this.computeResizeLatLng();
- },
-
- computeResizeLatLng: function () {
- // While circle is not added to the map, _radius is not set.
- var delta = (this.feature._radius || this.feature._mRadius) * Math.cos(Math.PI / 4),
- point = this.map.project(this.feature._latlng);
- return this.map.unproject([point.x + delta, point.y - delta]);
- },
-
- updateResizeLatLng: function () {
- this._resizeLatLng.update(this.computeResizeLatLng());
- this._resizeLatLng.__vertex.update();
- },
-
- getLatLngs: function () {
- return [this.feature._latlng, this._resizeLatLng];
- },
-
- getDefaultLatLngs: function () {
- return this.getLatLngs();
- },
-
- onVertexMarkerDrag: function (e) {
- if (e.vertex.getIndex() === 1) this.resize(e);
- else this.updateResizeLatLng(e);
- L.Editable.PathEditor.prototype.onVertexMarkerDrag.call(this, e);
- },
-
- resize: function (e) {
- var radius = this.feature._latlng.distanceTo(e.latlng);
- this.feature.setRadius(radius);
- },
-
- onDrawingMouseDown: function (e) {
- L.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e);
- this._resizeLatLng.update(e.latlng);
- this.feature._latlng.update(e.latlng);
- this.connect();
- // Stop dragging map.
- e.originalEvent._simulated = false;
- this.map.dragging._draggable._onUp(e.originalEvent);
- // Now transfer ongoing drag action to the radius handler.
- this._resizeLatLng.__vertex.dragging._draggable._onDown(e.originalEvent);
- },
-
- onDrawingMouseUp: function (e) {
- this.commitDrawing(e);
- e.originalEvent._simulated = false;
- L.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e);
- },
-
- onDrawingMouseMove: function (e) {
- e.originalEvent._simulated = false;
- L.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e);
- },
-
- onDrag: function (e) {
- L.Editable.PathEditor.prototype.onDrag.call(this, e);
- this.feature.dragging.updateLatLng(this._resizeLatLng);
- }
-
- });
-
- // 🍂namespace Editable; 🍂class EditableMixin
- // `EditableMixin` is included to `L.Polyline`, `L.Polygon`, `L.Rectangle`, `L.Circle`
- // and `L.Marker`. It adds some methods to them.
- // *When editing is enabled, the editor is accessible on the instance with the
- // `editor` property.*
- var EditableMixin = {
-
- createEditor: function (map) {
- map = map || this._map;
- var tools = (this.options.editOptions || {}).editTools || map.editTools;
- if (!tools) throw Error('Unable to detect Editable instance.');
- var Klass = this.options.editorClass || this.getEditorClass(tools);
- return new Klass(map, this, this.options.editOptions);
- },
-
- // 🍂method enableEdit(map?: L.Map): this.editor
- // Enable editing, by creating an editor if not existing, and then calling `enable` on it.
- enableEdit: function (map) {
- if (!this.editor) this.createEditor(map);
- this.editor.enable();
- return this.editor;
- },
-
- // 🍂method editEnabled(): boolean
- // Return true if current instance has an editor attached, and this editor is enabled.
- editEnabled: function () {
- return this.editor && this.editor.enabled();
- },
-
- // 🍂method disableEdit()
- // Disable editing, also remove the editor property reference.
- disableEdit: function () {
- if (this.editor) {
- this.editor.disable();
- delete this.editor;
- }
- },
-
- // 🍂method toggleEdit()
- // Enable or disable editing, according to current status.
- toggleEdit: function () {
- if (this.editEnabled()) this.disableEdit();
- else this.enableEdit();
- },
-
- _onEditableAdd: function () {
- if (this.editor) this.enableEdit();
- }
-
- };
-
- var PolylineMixin = {
-
- getEditorClass: function (tools) {
- return (tools && tools.options.polylineEditorClass) ? tools.options.polylineEditorClass : L.Editable.PolylineEditor;
- },
-
- shapeAt: function (latlng, latlngs) {
- // We can have those cases:
- // - latlngs are just a flat array of latlngs, use this
- // - latlngs is an array of arrays of latlngs, loop over
- var shape = null;
- latlngs = latlngs || this._latlngs;
- if (!latlngs.length) return shape;
- else if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs;
- else for (var i = 0; i < latlngs.length; i++) if (this.isInLatLngs(latlng, latlngs[i])) return latlngs[i];
- return shape;
- },
-
- isInLatLngs: function (l, latlngs) {
- if (!latlngs) return false;
- var i, k, len, part = [], p,
- w = this._clickTolerance();
- this._projectLatlngs(latlngs, part, this._pxBounds);
- part = part[0];
- p = this._map.latLngToLayerPoint(l);
-
- if (!this._pxBounds.contains(p)) { return false; }
- for (i = 1, len = part.length, k = 0; i < len; k = i++) {
-
- if (L.LineUtil.pointToSegmentDistance(p, part[k], part[i]) <= w) {
- return true;
- }
- }
- return false;
- }
-
- };
-
- var PolygonMixin = {
-
- getEditorClass: function (tools) {
- return (tools && tools.options.polygonEditorClass) ? tools.options.polygonEditorClass : L.Editable.PolygonEditor;
- },
-
- shapeAt: function (latlng, latlngs) {
- // We can have those cases:
- // - latlngs are just a flat array of latlngs, use this
- // - latlngs is an array of arrays of latlngs, this is a simple polygon (maybe with holes), use the first
- // - latlngs is an array of arrays of arrays, this is a multi, loop over
- var shape = null;
- latlngs = latlngs || this._latlngs;
- if (!latlngs.length) return shape;
- else if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs;
- else if (isFlat(latlngs[0]) && this.isInLatLngs(latlng, latlngs[0])) shape = latlngs;
- else for (var i = 0; i < latlngs.length; i++) if (this.isInLatLngs(latlng, latlngs[i][0])) return latlngs[i];
- return shape;
- },
-
- isInLatLngs: function (l, latlngs) {
- var inside = false, l1, l2, j, k, len2;
-
- for (j = 0, len2 = latlngs.length, k = len2 - 1; j < len2; k = j++) {
- l1 = latlngs[j];
- l2 = latlngs[k];
-
- if (((l1.lat > l.lat) !== (l2.lat > l.lat)) &&
- (l.lng < (l2.lng - l1.lng) * (l.lat - l1.lat) / (l2.lat - l1.lat) + l1.lng)) {
- inside = !inside;
- }
- }
-
- return inside;
- },
-
- parentShape: function (shape, latlngs) {
- latlngs = latlngs || this._latlngs;
- if (!latlngs) return;
- var idx = L.Util.indexOf(latlngs, shape);
- if (idx !== -1) return latlngs;
- for (var i = 0; i < latlngs.length; i++) {
- idx = L.Util.indexOf(latlngs[i], shape);
- if (idx !== -1) return latlngs[i];
- }
- }
-
- };
-
-
- var MarkerMixin = {
-
- getEditorClass: function (tools) {
- return (tools && tools.options.markerEditorClass) ? tools.options.markerEditorClass : L.Editable.MarkerEditor;
- }
-
- };
-
- var RectangleMixin = {
-
- getEditorClass: function (tools) {
- return (tools && tools.options.rectangleEditorClass) ? tools.options.rectangleEditorClass : L.Editable.RectangleEditor;
- }
-
- };
-
- var CircleMixin = {
-
- getEditorClass: function (tools) {
- return (tools && tools.options.circleEditorClass) ? tools.options.circleEditorClass : L.Editable.CircleEditor;
- }
-
- };
-
- var keepEditable = function () {
- // Make sure you can remove/readd an editable layer.
- this.on('add', this._onEditableAdd);
- };
-
- var isFlat = L.LineUtil.isFlat || L.LineUtil._flat || L.Polyline._flat; // <=> 1.1 compat.
-
-
- if (L.Polyline) {
- L.Polyline.include(EditableMixin);
- L.Polyline.include(PolylineMixin);
- L.Polyline.addInitHook(keepEditable);
- }
- if (L.Polygon) {
- L.Polygon.include(EditableMixin);
- L.Polygon.include(PolygonMixin);
- }
- if (L.Marker) {
- L.Marker.include(EditableMixin);
- L.Marker.include(MarkerMixin);
- L.Marker.addInitHook(keepEditable);
- }
- if (L.Rectangle) {
- L.Rectangle.include(EditableMixin);
- L.Rectangle.include(RectangleMixin);
- }
- if (L.Circle) {
- L.Circle.include(EditableMixin);
- L.Circle.include(CircleMixin);
- }
-
- L.LatLng.prototype.update = function (latlng) {
- latlng = L.latLng(latlng);
- this.lat = latlng.lat;
- this.lng = latlng.lng;
- }
-
-}, window));
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.editor.js b/www/wiki/extensions/Maps/resources/leaflet/leaflet.editor.js
deleted file mode 100644
index 6aadad18..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet.editor.js
+++ /dev/null
@@ -1,15 +0,0 @@
-(function( $, mw ) {
-
- $( document ).ready( function() {
- var map = L.map('GeoJsonMap'/*, {editable: true}*/);
-
- L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
- attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
- }).addTo(map);
-
- var geoJsonLayer = L.geoJSON(GeoJson).addTo(map);
-
- map.fitBounds(geoJsonLayer.getBounds());
- } );
-
-})( window.jQuery, mediaWiki );
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/.jshintrc b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/.jshintrc
deleted file mode 100644
index b7636119..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/.jshintrc
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "browser": true,
- "curly": true,
- "eqeqeq": true,
- "undef": true,
- "quotmark": "single",
- "trailing": true,
- "globals": {
- "L": true,
- "jQuery": true
- }
-} \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/Control.FullScreen.css b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/Control.FullScreen.css
deleted file mode 100644
index c93b1bf9..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/Control.FullScreen.css
+++ /dev/null
@@ -1,4 +0,0 @@
-.leaflet-control-zoom-fullscreen { background-image: url(icon-fullscreen.png); }
-.leaflet-retina .leaflet-control-zoom-fullscreen { background-image: url(icon-fullscreen-2x.png); background-size: 26px 26px; }
-.leaflet-container:-webkit-full-screen { width: 100% !important; height: 100% !important; z-index: 99999; }
-.leaflet-pseudo-fullscreen { position: fixed !important; width: 100% !important; height: 100% !important; top: 0px !important; left: 0px !important; z-index: 99999; } \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/Control.FullScreen.js b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/Control.FullScreen.js
deleted file mode 100644
index f1cd7ccc..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/Control.FullScreen.js
+++ /dev/null
@@ -1,164 +0,0 @@
-(function() {
-
-L.Control.FullScreen = L.Control.extend({
- options: {
- position: 'topleft',
- title: 'Full Screen',
- forceSeparateButton: false,
- forcePseudoFullscreen: false
- },
-
- onAdd: function (map) {
- var className = 'leaflet-control-zoom-fullscreen', container;
-
- if (map.zoomControl && !this.options.forceSeparateButton) {
- container = map.zoomControl._container;
- } else {
- container = L.DomUtil.create('div', 'leaflet-bar');
- }
-
- this._createButton(this.options.title, className, container, this.toggleFullScreen, this);
-
- return container;
- },
-
- _createButton: function (title, className, container, fn, context) {
- var link = L.DomUtil.create('a', className, container);
- link.href = '#';
- link.title = title;
-
- L.DomEvent
- .addListener(link, 'click', L.DomEvent.stopPropagation)
- .addListener(link, 'click', L.DomEvent.preventDefault)
- .addListener(link, 'click', fn, context);
-
- L.DomEvent
- .addListener(container, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation)
- .addListener(container, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault)
- .addListener(container, fullScreenApi.fullScreenEventName, this._handleEscKey, context);
-
- L.DomEvent
- .addListener(document, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation)
- .addListener(document, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault)
- .addListener(document, fullScreenApi.fullScreenEventName, this._handleEscKey, context);
-
- return link;
- },
-
- toggleFullScreen: function () {
- var map = this._map;
- map._exitFired = false;
- if (map._isFullscreen) {
- if (fullScreenApi.supportsFullScreen && !this.options.forcePseudoFullscreen) {
- fullScreenApi.cancelFullScreen(map._container);
- } else {
- L.DomUtil.removeClass(map._container, 'leaflet-pseudo-fullscreen');
- }
- map.invalidateSize();
- map.fire('exitFullscreen');
- map._exitFired = true;
- map._isFullscreen = false;
- }
- else {
- if (fullScreenApi.supportsFullScreen && !this.options.forcePseudoFullscreen) {
- fullScreenApi.requestFullScreen(map._container);
- } else {
- L.DomUtil.addClass(map._container, 'leaflet-pseudo-fullscreen');
- }
- map.invalidateSize();
- map.fire('enterFullscreen');
- map._isFullscreen = true;
- }
- },
-
- _handleEscKey: function () {
- var map = this._map;
- if (!fullScreenApi.isFullScreen(map) && !map._exitFired) {
- map.fire('exitFullscreen');
- map._exitFired = true;
- map._isFullscreen = false;
- }
- }
-});
-
-L.Map.addInitHook(function () {
- if (this.options.fullscreenControl) {
- this.fullscreenControl = L.control.fullscreen(this.options.fullscreenControlOptions);
- this.addControl(this.fullscreenControl);
- }
-});
-
-L.control.fullscreen = function (options) {
- return new L.Control.FullScreen(options);
-};
-
-/*
-Native FullScreen JavaScript API
--------------
-Assumes Mozilla naming conventions instead of W3C for now
-
-source : http://johndyer.name/native-fullscreen-javascript-api-plus-jquery-plugin/
-
-*/
-
- var
- fullScreenApi = {
- supportsFullScreen: false,
- isFullScreen: function() { return false; },
- requestFullScreen: function() {},
- cancelFullScreen: function() {},
- fullScreenEventName: '',
- prefix: ''
- },
- browserPrefixes = 'webkit moz o ms khtml'.split(' ');
-
- // check for native support
- if (typeof document.exitFullscreen !== 'undefined') {
- fullScreenApi.supportsFullScreen = true;
- } else {
- // check for fullscreen support by vendor prefix
- for (var i = 0, il = browserPrefixes.length; i < il; i++ ) {
- fullScreenApi.prefix = browserPrefixes[i];
- if (typeof document[fullScreenApi.prefix + 'CancelFullScreen' ] !== 'undefined' ) {
- fullScreenApi.supportsFullScreen = true;
- break;
- }
- }
- }
-
- // update methods to do something useful
- if (fullScreenApi.supportsFullScreen) {
- fullScreenApi.fullScreenEventName = fullScreenApi.prefix + 'fullscreenchange';
- fullScreenApi.isFullScreen = function() {
- switch (this.prefix) {
- case '':
- return document.fullScreen;
- case 'webkit':
- return document.webkitIsFullScreen;
- default:
- return document[this.prefix + 'FullScreen'];
- }
- };
- fullScreenApi.requestFullScreen = function(el) {
- return (this.prefix === '') ? el.requestFullscreen() : el[this.prefix + 'RequestFullScreen']();
- };
- fullScreenApi.cancelFullScreen = function(el) {
- return (this.prefix === '') ? document.exitFullscreen() : document[this.prefix + 'CancelFullScreen']();
- };
- }
-
- // jQuery plugin
- if (typeof jQuery !== 'undefined') {
- jQuery.fn.requestFullScreen = function() {
- return this.each(function() {
- var el = jQuery(this);
- if (fullScreenApi.supportsFullScreen) {
- fullScreenApi.requestFullScreen(el);
- }
- });
- };
- }
-
- // export api
- window.fullScreenApi = fullScreenApi;
-})();
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/LICENSE b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/LICENSE
deleted file mode 100644
index 07ddddcc..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/LICENSE
+++ /dev/null
@@ -1,22 +0,0 @@
-Copyright (c) 2013, Bruno Bergot
-All rights reserved.
-
-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 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 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.
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/README.md b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/README.md
deleted file mode 100644
index a111801f..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/README.md
+++ /dev/null
@@ -1,68 +0,0 @@
-Leaflet.Control.FullScreen
-============
-
-What ?
-------
-
-Simple plugin for Leaflet that adds fullscreen button to your maps.
-
-Inspired by http://elidupuis.github.com/leaflet.zoomfs/
-
-Use the native javascript fullscreen API http://johndyer.name/native-fullscreen-javascript-api-plus-jquery-plugin/
-
-Released under the MIT License http://opensource.org/licenses/mit-license.php
-
-How ?
-------
-
-Include Control.FullScreen.js and Control.FullScreen.css in your page:
-
-``` html
- <link rel="stylesheet" href="Control.FullScreen.css" />
- <script src="Control.FullScreen.js"></script>
-```
-
-Add the fullscreen control to the map:
-
-``` js
-var map = new L.Map('map', {
- fullscreenControl: true,
- fullscreenControlOptions: {
- position: 'topleft'
- }
-});
-```
-
-If your map have a zoomControl the fullscreen button will be added at the bottom of this one.
-
-If your map doesn't have a zoomContron the fullscreen button will be added to topleft corner of the map (same as the zoomcontrol).
-
-__Events and options__:
-
-``` js
-// create a fullscreen button and add it to the map
-L.control.fullscreen({
- position: 'topleft', // change the position of the button can be topleft, topright, bottomright or bottomleft, defaut topleft
- title: 'Show me the fullscreen !', // change the title of the button, default Full Screen
- forceSeparateButton: true, // force seperate button to detach from zoom buttons, default false
- forcePseudoFullscreen: true // force use of pseudo full screen even if full screen API is available, default false
-}).addTo(map);
-
-// events are fired when entering or exiting fullscreen.
-map.on('enterFullscreen', function(){
- console.log('entered fullscreen');
-});
-
-map.on('exitFullscreen', function(){
- console.log('exited fullscreen');
-});
-```
-
-Where ?
-------
-
-Source code : https://github.com/brunob/leaflet.fullscreen
-
-Downloads : https://github.com/brunob/leaflet.fullscreen/releases
-
-Demo : http://brunob.github.com/leaflet.fullscreen/
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/bower.json b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/bower.json
deleted file mode 100644
index 6de9eee8..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/bower.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
- "name": "leaflet.fullscreen",
- "version": "1.1.4",
- "homepage": "https://github.com/brunob/leaflet.fullscreen",
- "authors": [
- "brunob <brunobergot@gmail.com>"
- ],
- "description": "Leaflet.Control.FullScreen for Leaflet",
- "main": [
- "Control.FullScreen.js",
- "Control.FullScreen.css",
- "icon-fullscreen.png",
- "icon-fullscreen-2x.png"
- ],
- "keywords": [
- "leaflet",
- "plugins",
- "maps",
- "fullscreen"
- ],
- "license": "MIT",
- "ignore": [
- "**/.*",
- "node_modules",
- "bower_components",
- "test",
- "tests",
- "index.html"
- ]
-}
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/icon-fullscreen-2x.png b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/icon-fullscreen-2x.png
deleted file mode 100644
index 7320d953..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/icon-fullscreen-2x.png
+++ /dev/null
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/icon-fullscreen.png b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/icon-fullscreen.png
deleted file mode 100644
index 17478145..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/icon-fullscreen.png
+++ /dev/null
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/index.html b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/index.html
deleted file mode 100644
index 87b345c4..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/index.html
+++ /dev/null
@@ -1,48 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
- <meta charset='utf-8'>
- <title>Leaflet.Control.FullScreen Demo</title>
- <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
- <style type="text/css">
- #map { width: 700px; height: 433px; }
- .leaflet-control-zoom-fullscreen { background-image: url(icon-fullscreen.png); }
- /* on selector per rule as explained here : http://www.sitepoint.com/html5-full-screen-api/ */
- #map:-webkit-full-screen { width: 100% !important; height: 100% !important; z-index: 99999; }
- #map:-moz-full-screen { width: 100% !important; height: 100% !important; z-index: 99999; }
- #map:full-screen { width: 100% !important; height: 100% !important; z-index: 99999; }
- .leaflet-pseudo-fullscreen { position: fixed !important; width: 100% !important; height: 100% !important; top: 0px !important; left: 0px !important; z-index: 99999; }
- </style>
- <script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
- <script src="Control.FullScreen.js"></script>
-</head>
-<body>
-
- <div id="map"></div>
-
- <script>
- var base = new L.TileLayer('http://{s}.www.toolserver.org/tiles/bw-mapnik/{z}/{x}/{y}.png', {
- maxZoom: 18,
- attribution: '&copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
- });
-
- var map = new L.Map('map', {
- layers: [base],
- center: new L.LatLng(48.5, -4.5),
- zoom: 5,
- fullscreenControl: true,
- fullscreenControlOptions: { // optional
- title:"Show me the fullscreen !"
- }
- });
-
- // detect fullscreen toggling
- map.on('enterFullscreen', function(){
- if(window.console) window.console.log('enterFullscreen');
- });
- map.on('exitFullscreen', function(){
- if(window.console) window.console.log('exitFullscreen');
- });
- </script>
-</body>
-</html>
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.markercluster/leaflet.markercluster.js b/www/wiki/extensions/Maps/resources/leaflet/leaflet.markercluster/leaflet.markercluster.js
deleted file mode 100644
index ed22dc07..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet.markercluster/leaflet.markercluster.js
+++ /dev/null
@@ -1,3 +0,0 @@
-!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e.Leaflet=e.Leaflet||{},e.Leaflet.markercluster=e.Leaflet.markercluster||{}))}(this,function(e){"use strict";var t=L.MarkerClusterGroup=L.FeatureGroup.extend({options:{maxClusterRadius:80,iconCreateFunction:null,clusterPane:L.Marker.prototype.options.pane,spiderfyOnMaxZoom:!0,showCoverageOnHover:!0,zoomToBoundsOnClick:!0,singleMarkerMode:!1,disableClusteringAtZoom:null,removeOutsideVisibleBounds:!0,animate:!0,animateAddingMarkers:!1,spiderfyDistanceMultiplier:1,spiderLegPolylineOptions:{weight:1.5,color:"#222",opacity:.5},chunkedLoading:!1,chunkInterval:200,chunkDelay:50,chunkProgress:null,polygonOptions:{}},initialize:function(e){L.Util.setOptions(this,e),this.options.iconCreateFunction||(this.options.iconCreateFunction=this._defaultIconCreateFunction),this._featureGroup=L.featureGroup(),this._featureGroup.addEventParent(this),this._nonPointGroup=L.featureGroup(),this._nonPointGroup.addEventParent(this),this._inZoomAnimation=0,this._needsClustering=[],this._needsRemoving=[],this._currentShownBounds=null,this._queue=[],this._childMarkerEventHandlers={dragstart:this._childMarkerDragStart,move:this._childMarkerMoved,dragend:this._childMarkerDragEnd};var t=L.DomUtil.TRANSITION&&this.options.animate;L.extend(this,t?this._withAnimation:this._noAnimation),this._markerCluster=t?L.MarkerCluster:L.MarkerClusterNonAnimated},addLayer:function(e){if(e instanceof L.LayerGroup)return this.addLayers([e]);if(!e.getLatLng)return this._nonPointGroup.addLayer(e),this.fire("layeradd",{layer:e}),this;if(!this._map)return this._needsClustering.push(e),this.fire("layeradd",{layer:e}),this;if(this.hasLayer(e))return this;this._unspiderfy&&this._unspiderfy(),this._addLayer(e,this._maxZoom),this.fire("layeradd",{layer:e}),this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons();var t=e,i=this._zoom;if(e.__parent)for(;t.__parent._zoom>=i;)t=t.__parent;return this._currentShownBounds.contains(t.getLatLng())&&(this.options.animateAddingMarkers?this._animationAddLayer(e,t):this._animationAddLayerNonAnimated(e,t)),this},removeLayer:function(e){return e instanceof L.LayerGroup?this.removeLayers([e]):e.getLatLng?this._map?e.__parent?(this._unspiderfy&&(this._unspiderfy(),this._unspiderfyLayer(e)),this._removeLayer(e,!0),this.fire("layerremove",{layer:e}),this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons(),e.off(this._childMarkerEventHandlers,this),this._featureGroup.hasLayer(e)&&(this._featureGroup.removeLayer(e),e.clusterShow&&e.clusterShow()),this):this:(!this._arraySplice(this._needsClustering,e)&&this.hasLayer(e)&&this._needsRemoving.push({layer:e,latlng:e._latlng}),this.fire("layerremove",{layer:e}),this):(this._nonPointGroup.removeLayer(e),this.fire("layerremove",{layer:e}),this)},addLayers:function(e,t){if(!L.Util.isArray(e))return this.addLayer(e);var i,n=this._featureGroup,r=this._nonPointGroup,s=this.options.chunkedLoading,o=this.options.chunkInterval,a=this.options.chunkProgress,h=e.length,l=0,u=!0;if(this._map){var _=(new Date).getTime(),d=L.bind(function(){for(var c=(new Date).getTime();h>l;l++){if(s&&0===l%200){var p=(new Date).getTime()-c;if(p>o)break}if(i=e[l],i instanceof L.LayerGroup)u&&(e=e.slice(),u=!1),this._extractNonGroupLayers(i,e),h=e.length;else if(i.getLatLng){if(!this.hasLayer(i)&&(this._addLayer(i,this._maxZoom),t||this.fire("layeradd",{layer:i}),i.__parent&&2===i.__parent.getChildCount())){var f=i.__parent.getAllChildMarkers(),m=f[0]===i?f[1]:f[0];n.removeLayer(m)}}else r.addLayer(i),t||this.fire("layeradd",{layer:i})}a&&a(l,h,(new Date).getTime()-_),l===h?(this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons(),this._topClusterLevel._recursivelyAddChildrenToMap(null,this._zoom,this._currentShownBounds)):setTimeout(d,this.options.chunkDelay)},this);d()}else for(var c=this._needsClustering;h>l;l++)i=e[l],i instanceof L.LayerGroup?(u&&(e=e.slice(),u=!1),this._extractNonGroupLayers(i,e),h=e.length):i.getLatLng?this.hasLayer(i)||c.push(i):r.addLayer(i);return this},removeLayers:function(e){var t,i,n=e.length,r=this._featureGroup,s=this._nonPointGroup,o=!0;if(!this._map){for(t=0;n>t;t++)i=e[t],i instanceof L.LayerGroup?(o&&(e=e.slice(),o=!1),this._extractNonGroupLayers(i,e),n=e.length):(this._arraySplice(this._needsClustering,i),s.removeLayer(i),this.hasLayer(i)&&this._needsRemoving.push({layer:i,latlng:i._latlng}),this.fire("layerremove",{layer:i}));return this}if(this._unspiderfy){this._unspiderfy();var a=e.slice(),h=n;for(t=0;h>t;t++)i=a[t],i instanceof L.LayerGroup?(this._extractNonGroupLayers(i,a),h=a.length):this._unspiderfyLayer(i)}for(t=0;n>t;t++)i=e[t],i instanceof L.LayerGroup?(o&&(e=e.slice(),o=!1),this._extractNonGroupLayers(i,e),n=e.length):i.__parent?(this._removeLayer(i,!0,!0),this.fire("layerremove",{layer:i}),r.hasLayer(i)&&(r.removeLayer(i),i.clusterShow&&i.clusterShow())):(s.removeLayer(i),this.fire("layerremove",{layer:i}));return this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons(),this._topClusterLevel._recursivelyAddChildrenToMap(null,this._zoom,this._currentShownBounds),this},clearLayers:function(){return this._map||(this._needsClustering=[],delete this._gridClusters,delete this._gridUnclustered),this._noanimationUnspiderfy&&this._noanimationUnspiderfy(),this._featureGroup.clearLayers(),this._nonPointGroup.clearLayers(),this.eachLayer(function(e){e.off(this._childMarkerEventHandlers,this),delete e.__parent},this),this._map&&this._generateInitialClusters(),this},getBounds:function(){var e=new L.LatLngBounds;this._topClusterLevel&&e.extend(this._topClusterLevel._bounds);for(var t=this._needsClustering.length-1;t>=0;t--)e.extend(this._needsClustering[t].getLatLng());return e.extend(this._nonPointGroup.getBounds()),e},eachLayer:function(e,t){var i,n,r,s=this._needsClustering.slice(),o=this._needsRemoving;for(this._topClusterLevel&&this._topClusterLevel.getAllChildMarkers(s),n=s.length-1;n>=0;n--){for(i=!0,r=o.length-1;r>=0;r--)if(o[r].layer===s[n]){i=!1;break}i&&e.call(t,s[n])}this._nonPointGroup.eachLayer(e,t)},getLayers:function(){var e=[];return this.eachLayer(function(t){e.push(t)}),e},getLayer:function(e){var t=null;return e=parseInt(e,10),this.eachLayer(function(i){L.stamp(i)===e&&(t=i)}),t},hasLayer:function(e){if(!e)return!1;var t,i=this._needsClustering;for(t=i.length-1;t>=0;t--)if(i[t]===e)return!0;for(i=this._needsRemoving,t=i.length-1;t>=0;t--)if(i[t].layer===e)return!1;return!(!e.__parent||e.__parent._group!==this)||this._nonPointGroup.hasLayer(e)},zoomToShowLayer:function(e,t){"function"!=typeof t&&(t=function(){});var i=function(){!e._icon&&!e.__parent._icon||this._inZoomAnimation||(this._map.off("moveend",i,this),this.off("animationend",i,this),e._icon?t():e.__parent._icon&&(this.once("spiderfied",t,this),e.__parent.spiderfy()))};e._icon&&this._map.getBounds().contains(e.getLatLng())?t():e.__parent._zoom<Math.round(this._map._zoom)?(this._map.on("moveend",i,this),this._map.panTo(e.getLatLng())):(this._map.on("moveend",i,this),this.on("animationend",i,this),e.__parent.zoomToBounds())},onAdd:function(e){this._map=e;var t,i,n;if(!isFinite(this._map.getMaxZoom()))throw"Map has no maxZoom specified";for(this._featureGroup.addTo(e),this._nonPointGroup.addTo(e),this._gridClusters||this._generateInitialClusters(),this._maxLat=e.options.crs.projection.MAX_LATITUDE,t=0,i=this._needsRemoving.length;i>t;t++)n=this._needsRemoving[t],n.newlatlng=n.layer._latlng,n.layer._latlng=n.latlng;for(t=0,i=this._needsRemoving.length;i>t;t++)n=this._needsRemoving[t],this._removeLayer(n.layer,!0),n.layer._latlng=n.newlatlng;this._needsRemoving=[],this._zoom=Math.round(this._map._zoom),this._currentShownBounds=this._getExpandedVisibleBounds(),this._map.on("zoomend",this._zoomEnd,this),this._map.on("moveend",this._moveEnd,this),this._spiderfierOnAdd&&this._spiderfierOnAdd(),this._bindEvents(),i=this._needsClustering,this._needsClustering=[],this.addLayers(i,!0)},onRemove:function(e){e.off("zoomend",this._zoomEnd,this),e.off("moveend",this._moveEnd,this),this._unbindEvents(),this._map._mapPane.className=this._map._mapPane.className.replace(" leaflet-cluster-anim",""),this._spiderfierOnRemove&&this._spiderfierOnRemove(),delete this._maxLat,this._hideCoverage(),this._featureGroup.remove(),this._nonPointGroup.remove(),this._featureGroup.clearLayers(),this._map=null},getVisibleParent:function(e){for(var t=e;t&&!t._icon;)t=t.__parent;return t||null},_arraySplice:function(e,t){for(var i=e.length-1;i>=0;i--)if(e[i]===t)return e.splice(i,1),!0},_removeFromGridUnclustered:function(e,t){for(var i=this._map,n=this._gridUnclustered,r=Math.floor(this._map.getMinZoom());t>=r&&n[t].removeObject(e,i.project(e.getLatLng(),t));t--);},_childMarkerDragStart:function(e){e.target.__dragStart=e.target._latlng},_childMarkerMoved:function(e){if(!this._ignoreMove&&!e.target.__dragStart){var t=e.target._popup&&e.target._popup.isOpen();this._moveChild(e.target,e.oldLatLng,e.latlng),t&&e.target.openPopup()}},_moveChild:function(e,t,i){e._latlng=t,this.removeLayer(e),e._latlng=i,this.addLayer(e)},_childMarkerDragEnd:function(e){e.target.__dragStart&&this._moveChild(e.target,e.target.__dragStart,e.target._latlng),delete e.target.__dragStart},_removeLayer:function(e,t,i){var n=this._gridClusters,r=this._gridUnclustered,s=this._featureGroup,o=this._map,a=Math.floor(this._map.getMinZoom());t&&this._removeFromGridUnclustered(e,this._maxZoom);var h,l=e.__parent,u=l._markers;for(this._arraySplice(u,e);l&&(l._childCount--,l._boundsNeedUpdate=!0,!(l._zoom<a));)t&&l._childCount<=1?(h=l._markers[0]===e?l._markers[1]:l._markers[0],n[l._zoom].removeObject(l,o.project(l._cLatLng,l._zoom)),r[l._zoom].addObject(h,o.project(h.getLatLng(),l._zoom)),this._arraySplice(l.__parent._childClusters,l),l.__parent._markers.push(h),h.__parent=l.__parent,l._icon&&(s.removeLayer(l),i||s.addLayer(h))):l._iconNeedsUpdate=!0,l=l.__parent;delete e.__parent},_isOrIsParent:function(e,t){for(;t;){if(e===t)return!0;t=t.parentNode}return!1},fire:function(e,t,i){if(t&&t.layer instanceof L.MarkerCluster){if(t.originalEvent&&this._isOrIsParent(t.layer._icon,t.originalEvent.relatedTarget))return;e="cluster"+e}L.FeatureGroup.prototype.fire.call(this,e,t,i)},listens:function(e,t){return L.FeatureGroup.prototype.listens.call(this,e,t)||L.FeatureGroup.prototype.listens.call(this,"cluster"+e,t)},_defaultIconCreateFunction:function(e){var t=e.getChildCount(),i=" marker-cluster-";return i+=10>t?"small":100>t?"medium":"large",new L.DivIcon({html:"<div><span>"+t+"</span></div>",className:"marker-cluster"+i,iconSize:new L.Point(40,40)})},_bindEvents:function(){var e=this._map,t=this.options.spiderfyOnMaxZoom,i=this.options.showCoverageOnHover,n=this.options.zoomToBoundsOnClick;(t||n)&&this.on("clusterclick",this._zoomOrSpiderfy,this),i&&(this.on("clustermouseover",this._showCoverage,this),this.on("clustermouseout",this._hideCoverage,this),e.on("zoomend",this._hideCoverage,this))},_zoomOrSpiderfy:function(e){for(var t=e.layer,i=t;1===i._childClusters.length;)i=i._childClusters[0];i._zoom===this._maxZoom&&i._childCount===t._childCount&&this.options.spiderfyOnMaxZoom?t.spiderfy():this.options.zoomToBoundsOnClick&&t.zoomToBounds(),e.originalEvent&&13===e.originalEvent.keyCode&&this._map._container.focus()},_showCoverage:function(e){var t=this._map;this._inZoomAnimation||(this._shownPolygon&&t.removeLayer(this._shownPolygon),e.layer.getChildCount()>2&&e.layer!==this._spiderfied&&(this._shownPolygon=new L.Polygon(e.layer.getConvexHull(),this.options.polygonOptions),t.addLayer(this._shownPolygon)))},_hideCoverage:function(){this._shownPolygon&&(this._map.removeLayer(this._shownPolygon),this._shownPolygon=null)},_unbindEvents:function(){var e=this.options.spiderfyOnMaxZoom,t=this.options.showCoverageOnHover,i=this.options.zoomToBoundsOnClick,n=this._map;(e||i)&&this.off("clusterclick",this._zoomOrSpiderfy,this),t&&(this.off("clustermouseover",this._showCoverage,this),this.off("clustermouseout",this._hideCoverage,this),n.off("zoomend",this._hideCoverage,this))},_zoomEnd:function(){this._map&&(this._mergeSplitClusters(),this._zoom=Math.round(this._map._zoom),this._currentShownBounds=this._getExpandedVisibleBounds())},_moveEnd:function(){if(!this._inZoomAnimation){var e=this._getExpandedVisibleBounds();this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,Math.floor(this._map.getMinZoom()),this._zoom,e),this._topClusterLevel._recursivelyAddChildrenToMap(null,Math.round(this._map._zoom),e),this._currentShownBounds=e}},_generateInitialClusters:function(){var e=Math.ceil(this._map.getMaxZoom()),t=Math.floor(this._map.getMinZoom()),i=this.options.maxClusterRadius,n=i;"function"!=typeof i&&(n=function(){return i}),null!==this.options.disableClusteringAtZoom&&(e=this.options.disableClusteringAtZoom-1),this._maxZoom=e,this._gridClusters={},this._gridUnclustered={};for(var r=e;r>=t;r--)this._gridClusters[r]=new L.DistanceGrid(n(r)),this._gridUnclustered[r]=new L.DistanceGrid(n(r));this._topClusterLevel=new this._markerCluster(this,t-1)},_addLayer:function(e,t){var i,n,r=this._gridClusters,s=this._gridUnclustered,o=Math.floor(this._map.getMinZoom());for(this.options.singleMarkerMode&&this._overrideMarkerIcon(e),e.on(this._childMarkerEventHandlers,this);t>=o;t--){i=this._map.project(e.getLatLng(),t);var a=r[t].getNearObject(i);if(a)return a._addChild(e),e.__parent=a,void 0;if(a=s[t].getNearObject(i)){var h=a.__parent;h&&this._removeLayer(a,!1);var l=new this._markerCluster(this,t,a,e);r[t].addObject(l,this._map.project(l._cLatLng,t)),a.__parent=l,e.__parent=l;var u=l;for(n=t-1;n>h._zoom;n--)u=new this._markerCluster(this,n,u),r[n].addObject(u,this._map.project(a.getLatLng(),n));return h._addChild(u),this._removeFromGridUnclustered(a,t),void 0}s[t].addObject(e,i)}this._topClusterLevel._addChild(e),e.__parent=this._topClusterLevel},_refreshClustersIcons:function(){this._featureGroup.eachLayer(function(e){e instanceof L.MarkerCluster&&e._iconNeedsUpdate&&e._updateIcon()})},_enqueue:function(e){this._queue.push(e),this._queueTimeout||(this._queueTimeout=setTimeout(L.bind(this._processQueue,this),300))},_processQueue:function(){for(var e=0;e<this._queue.length;e++)this._queue[e].call(this);this._queue.length=0,clearTimeout(this._queueTimeout),this._queueTimeout=null},_mergeSplitClusters:function(){var e=Math.round(this._map._zoom);this._processQueue(),this._zoom<e&&this._currentShownBounds.intersects(this._getExpandedVisibleBounds())?(this._animationStart(),this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,Math.floor(this._map.getMinZoom()),this._zoom,this._getExpandedVisibleBounds()),this._animationZoomIn(this._zoom,e)):this._zoom>e?(this._animationStart(),this._animationZoomOut(this._zoom,e)):this._moveEnd()},_getExpandedVisibleBounds:function(){return this.options.removeOutsideVisibleBounds?L.Browser.mobile?this._checkBoundsMaxLat(this._map.getBounds()):this._checkBoundsMaxLat(this._map.getBounds().pad(1)):this._mapBoundsInfinite},_checkBoundsMaxLat:function(e){var t=this._maxLat;return void 0!==t&&(e.getNorth()>=t&&(e._northEast.lat=1/0),e.getSouth()<=-t&&(e._southWest.lat=-1/0)),e},_animationAddLayerNonAnimated:function(e,t){if(t===e)this._featureGroup.addLayer(e);else if(2===t._childCount){t._addToMap();var i=t.getAllChildMarkers();this._featureGroup.removeLayer(i[0]),this._featureGroup.removeLayer(i[1])}else t._updateIcon()},_extractNonGroupLayers:function(e,t){var i,n=e.getLayers(),r=0;for(t=t||[];r<n.length;r++)i=n[r],i instanceof L.LayerGroup?this._extractNonGroupLayers(i,t):t.push(i);return t},_overrideMarkerIcon:function(e){var t=e.options.icon=this.options.iconCreateFunction({getChildCount:function(){return 1},getAllChildMarkers:function(){return[e]}});return t}});L.MarkerClusterGroup.include({_mapBoundsInfinite:new L.LatLngBounds(new L.LatLng(-1/0,-1/0),new L.LatLng(1/0,1/0))}),L.MarkerClusterGroup.include({_noAnimation:{_animationStart:function(){},_animationZoomIn:function(e,t){this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,Math.floor(this._map.getMinZoom()),e),this._topClusterLevel._recursivelyAddChildrenToMap(null,t,this._getExpandedVisibleBounds()),this.fire("animationend")},_animationZoomOut:function(e,t){this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,Math.floor(this._map.getMinZoom()),e),this._topClusterLevel._recursivelyAddChildrenToMap(null,t,this._getExpandedVisibleBounds()),this.fire("animationend")},_animationAddLayer:function(e,t){this._animationAddLayerNonAnimated(e,t)}},_withAnimation:{_animationStart:function(){this._map._mapPane.className+=" leaflet-cluster-anim",this._inZoomAnimation++},_animationZoomIn:function(e,t){var i,n=this._getExpandedVisibleBounds(),r=this._featureGroup,s=Math.floor(this._map.getMinZoom());this._ignoreMove=!0,this._topClusterLevel._recursively(n,e,s,function(s){var o,a=s._latlng,h=s._markers;for(n.contains(a)||(a=null),s._isSingleParent()&&e+1===t?(r.removeLayer(s),s._recursivelyAddChildrenToMap(null,t,n)):(s.clusterHide(),s._recursivelyAddChildrenToMap(a,t,n)),i=h.length-1;i>=0;i--)o=h[i],n.contains(o._latlng)||r.removeLayer(o)}),this._forceLayout(),this._topClusterLevel._recursivelyBecomeVisible(n,t),r.eachLayer(function(e){e instanceof L.MarkerCluster||!e._icon||e.clusterShow()}),this._topClusterLevel._recursively(n,e,t,function(e){e._recursivelyRestoreChildPositions(t)}),this._ignoreMove=!1,this._enqueue(function(){this._topClusterLevel._recursively(n,e,s,function(e){r.removeLayer(e),e.clusterShow()}),this._animationEnd()})},_animationZoomOut:function(e,t){this._animationZoomOutSingle(this._topClusterLevel,e-1,t),this._topClusterLevel._recursivelyAddChildrenToMap(null,t,this._getExpandedVisibleBounds()),this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,Math.floor(this._map.getMinZoom()),e,this._getExpandedVisibleBounds())},_animationAddLayer:function(e,t){var i=this,n=this._featureGroup;n.addLayer(e),t!==e&&(t._childCount>2?(t._updateIcon(),this._forceLayout(),this._animationStart(),e._setPos(this._map.latLngToLayerPoint(t.getLatLng())),e.clusterHide(),this._enqueue(function(){n.removeLayer(e),e.clusterShow(),i._animationEnd()})):(this._forceLayout(),i._animationStart(),i._animationZoomOutSingle(t,this._map.getMaxZoom(),this._zoom)))}},_animationZoomOutSingle:function(e,t,i){var n=this._getExpandedVisibleBounds(),r=Math.floor(this._map.getMinZoom());e._recursivelyAnimateChildrenInAndAddSelfToMap(n,r,t+1,i);var s=this;this._forceLayout(),e._recursivelyBecomeVisible(n,i),this._enqueue(function(){if(1===e._childCount){var o=e._markers[0];this._ignoreMove=!0,o.setLatLng(o.getLatLng()),this._ignoreMove=!1,o.clusterShow&&o.clusterShow()}else e._recursively(n,i,r,function(e){e._recursivelyRemoveChildrenFromMap(n,r,t+1)});s._animationEnd()})},_animationEnd:function(){this._map&&(this._map._mapPane.className=this._map._mapPane.className.replace(" leaflet-cluster-anim","")),this._inZoomAnimation--,this.fire("animationend")},_forceLayout:function(){L.Util.falseFn(document.body.offsetWidth)}}),L.markerClusterGroup=function(e){return new L.MarkerClusterGroup(e)};var i=L.MarkerCluster=L.Marker.extend({options:L.Icon.prototype.options,initialize:function(e,t,i,n){L.Marker.prototype.initialize.call(this,i?i._cLatLng||i.getLatLng():new L.LatLng(0,0),{icon:this,pane:e.options.clusterPane}),this._group=e,this._zoom=t,this._markers=[],this._childClusters=[],this._childCount=0,this._iconNeedsUpdate=!0,this._boundsNeedUpdate=!0,this._bounds=new L.LatLngBounds,i&&this._addChild(i),n&&this._addChild(n)},getAllChildMarkers:function(e){e=e||[];for(var t=this._childClusters.length-1;t>=0;t--)this._childClusters[t].getAllChildMarkers(e);for(var i=this._markers.length-1;i>=0;i--)e.push(this._markers[i]);return e},getChildCount:function(){return this._childCount},zoomToBounds:function(e){for(var t,i=this._childClusters.slice(),n=this._group._map,r=n.getBoundsZoom(this._bounds),s=this._zoom+1,o=n.getZoom();i.length>0&&r>s;){s++;var a=[];for(t=0;t<i.length;t++)a=a.concat(i[t]._childClusters);i=a}r>s?this._group._map.setView(this._latlng,s):o>=r?this._group._map.setView(this._latlng,o+1):this._group._map.fitBounds(this._bounds,e)},getBounds:function(){var e=new L.LatLngBounds;return e.extend(this._bounds),e},_updateIcon:function(){this._iconNeedsUpdate=!0,this._icon&&this.setIcon(this)},createIcon:function(){return this._iconNeedsUpdate&&(this._iconObj=this._group.options.iconCreateFunction(this),this._iconNeedsUpdate=!1),this._iconObj.createIcon()},createShadow:function(){return this._iconObj.createShadow()},_addChild:function(e,t){this._iconNeedsUpdate=!0,this._boundsNeedUpdate=!0,this._setClusterCenter(e),e instanceof L.MarkerCluster?(t||(this._childClusters.push(e),e.__parent=this),this._childCount+=e._childCount):(t||this._markers.push(e),this._childCount++),this.__parent&&this.__parent._addChild(e,!0)},_setClusterCenter:function(e){this._cLatLng||(this._cLatLng=e._cLatLng||e._latlng)},_resetBounds:function(){var e=this._bounds;e._southWest&&(e._southWest.lat=1/0,e._southWest.lng=1/0),e._northEast&&(e._northEast.lat=-1/0,e._northEast.lng=-1/0)},_recalculateBounds:function(){var e,t,i,n,r=this._markers,s=this._childClusters,o=0,a=0,h=this._childCount;if(0!==h){for(this._resetBounds(),e=0;e<r.length;e++)i=r[e]._latlng,this._bounds.extend(i),o+=i.lat,a+=i.lng;for(e=0;e<s.length;e++)t=s[e],t._boundsNeedUpdate&&t._recalculateBounds(),this._bounds.extend(t._bounds),i=t._wLatLng,n=t._childCount,o+=i.lat*n,a+=i.lng*n;this._latlng=this._wLatLng=new L.LatLng(o/h,a/h),this._boundsNeedUpdate=!1}},_addToMap:function(e){e&&(this._backupLatlng=this._latlng,this.setLatLng(e)),this._group._featureGroup.addLayer(this)},_recursivelyAnimateChildrenIn:function(e,t,i){this._recursively(e,this._group._map.getMinZoom(),i-1,function(e){var i,n,r=e._markers;for(i=r.length-1;i>=0;i--)n=r[i],n._icon&&(n._setPos(t),n.clusterHide())},function(e){var i,n,r=e._childClusters;for(i=r.length-1;i>=0;i--)n=r[i],n._icon&&(n._setPos(t),n.clusterHide())})},_recursivelyAnimateChildrenInAndAddSelfToMap:function(e,t,i,n){this._recursively(e,n,t,function(r){r._recursivelyAnimateChildrenIn(e,r._group._map.latLngToLayerPoint(r.getLatLng()).round(),i),r._isSingleParent()&&i-1===n?(r.clusterShow(),r._recursivelyRemoveChildrenFromMap(e,t,i)):r.clusterHide(),r._addToMap()})},_recursivelyBecomeVisible:function(e,t){this._recursively(e,this._group._map.getMinZoom(),t,null,function(e){e.clusterShow()})},_recursivelyAddChildrenToMap:function(e,t,i){this._recursively(i,this._group._map.getMinZoom()-1,t,function(n){if(t!==n._zoom)for(var r=n._markers.length-1;r>=0;r--){var s=n._markers[r];i.contains(s._latlng)&&(e&&(s._backupLatlng=s.getLatLng(),s.setLatLng(e),s.clusterHide&&s.clusterHide()),n._group._featureGroup.addLayer(s))}},function(t){t._addToMap(e)})},_recursivelyRestoreChildPositions:function(e){for(var t=this._markers.length-1;t>=0;t--){var i=this._markers[t];i._backupLatlng&&(i.setLatLng(i._backupLatlng),delete i._backupLatlng)}if(e-1===this._zoom)for(var n=this._childClusters.length-1;n>=0;n--)this._childClusters[n]._restorePosition();else for(var r=this._childClusters.length-1;r>=0;r--)this._childClusters[r]._recursivelyRestoreChildPositions(e)},_restorePosition:function(){this._backupLatlng&&(this.setLatLng(this._backupLatlng),delete this._backupLatlng)},_recursivelyRemoveChildrenFromMap:function(e,t,i,n){var r,s;this._recursively(e,t-1,i-1,function(e){for(s=e._markers.length-1;s>=0;s--)r=e._markers[s],n&&n.contains(r._latlng)||(e._group._featureGroup.removeLayer(r),r.clusterShow&&r.clusterShow())},function(e){for(s=e._childClusters.length-1;s>=0;s--)r=e._childClusters[s],n&&n.contains(r._latlng)||(e._group._featureGroup.removeLayer(r),r.clusterShow&&r.clusterShow())})},_recursively:function(e,t,i,n,r){var s,o,a=this._childClusters,h=this._zoom;if(h>=t&&(n&&n(this),r&&h===i&&r(this)),t>h||i>h)for(s=a.length-1;s>=0;s--)o=a[s],e.intersects(o._bounds)&&o._recursively(e,t,i,n,r)},_isSingleParent:function(){return this._childClusters.length>0&&this._childClusters[0]._childCount===this._childCount}});L.Marker.include({clusterHide:function(){return this.options.opacityWhenUnclustered=this.options.opacity||1,this.setOpacity(0)},clusterShow:function(){var e=this.setOpacity(this.options.opacity||this.options.opacityWhenUnclustered);return delete this.options.opacityWhenUnclustered,e}}),L.DistanceGrid=function(e){this._cellSize=e,this._sqCellSize=e*e,this._grid={},this._objectPoint={}},L.DistanceGrid.prototype={addObject:function(e,t){var i=this._getCoord(t.x),n=this._getCoord(t.y),r=this._grid,s=r[n]=r[n]||{},o=s[i]=s[i]||[],a=L.Util.stamp(e);this._objectPoint[a]=t,o.push(e)},updateObject:function(e,t){this.removeObject(e),this.addObject(e,t)},removeObject:function(e,t){var i,n,r=this._getCoord(t.x),s=this._getCoord(t.y),o=this._grid,a=o[s]=o[s]||{},h=a[r]=a[r]||[];for(delete this._objectPoint[L.Util.stamp(e)],i=0,n=h.length;n>i;i++)if(h[i]===e)return h.splice(i,1),1===n&&delete a[r],!0},eachObject:function(e,t){var i,n,r,s,o,a,h,l=this._grid;for(i in l){o=l[i];for(n in o)for(a=o[n],r=0,s=a.length;s>r;r++)h=e.call(t,a[r]),h&&(r--,s--)}},getNearObject:function(e){var t,i,n,r,s,o,a,h,l=this._getCoord(e.x),u=this._getCoord(e.y),_=this._objectPoint,d=this._sqCellSize,c=null;for(t=u-1;u+1>=t;t++)if(r=this._grid[t])for(i=l-1;l+1>=i;i++)if(s=r[i])for(n=0,o=s.length;o>n;n++)a=s[n],h=this._sqDist(_[L.Util.stamp(a)],e),(d>h||d>=h&&null===c)&&(d=h,c=a);return c},_getCoord:function(e){var t=Math.floor(e/this._cellSize);return isFinite(t)?t:e},_sqDist:function(e,t){var i=t.x-e.x,n=t.y-e.y;return i*i+n*n}},function(){L.QuickHull={getDistant:function(e,t){var i=t[1].lat-t[0].lat,n=t[0].lng-t[1].lng;return n*(e.lat-t[0].lat)+i*(e.lng-t[0].lng)},findMostDistantPointFromBaseLine:function(e,t){var i,n,r,s=0,o=null,a=[];for(i=t.length-1;i>=0;i--)n=t[i],r=this.getDistant(n,e),r>0&&(a.push(n),r>s&&(s=r,o=n));return{maxPoint:o,newPoints:a}},buildConvexHull:function(e,t){var i=[],n=this.findMostDistantPointFromBaseLine(e,t);return n.maxPoint?(i=i.concat(this.buildConvexHull([e[0],n.maxPoint],n.newPoints)),i=i.concat(this.buildConvexHull([n.maxPoint,e[1]],n.newPoints))):[e[0]]},getConvexHull:function(e){var t,i=!1,n=!1,r=!1,s=!1,o=null,a=null,h=null,l=null,u=null,_=null;for(t=e.length-1;t>=0;t--){var d=e[t];(i===!1||d.lat>i)&&(o=d,i=d.lat),(n===!1||d.lat<n)&&(a=d,n=d.lat),(r===!1||d.lng>r)&&(h=d,r=d.lng),(s===!1||d.lng<s)&&(l=d,s=d.lng)}n!==i?(_=a,u=o):(_=l,u=h);var c=[].concat(this.buildConvexHull([_,u],e),this.buildConvexHull([u,_],e));return c}}}(),L.MarkerCluster.include({getConvexHull:function(){var e,t,i=this.getAllChildMarkers(),n=[];for(t=i.length-1;t>=0;t--)e=i[t].getLatLng(),n.push(e);return L.QuickHull.getConvexHull(n)}}),L.MarkerCluster.include({_2PI:2*Math.PI,_circleFootSeparation:25,_circleStartAngle:0,_spiralFootSeparation:28,_spiralLengthStart:11,_spiralLengthFactor:5,_circleSpiralSwitchover:9,spiderfy:function(){if(this._group._spiderfied!==this&&!this._group._inZoomAnimation){var e,t=this.getAllChildMarkers(),i=this._group,n=i._map,r=n.latLngToLayerPoint(this._latlng);this._group._unspiderfy(),this._group._spiderfied=this,t.length>=this._circleSpiralSwitchover?e=this._generatePointsSpiral(t.length,r):(r.y+=10,e=this._generatePointsCircle(t.length,r)),this._animationSpiderfy(t,e)}},unspiderfy:function(e){this._group._inZoomAnimation||(this._animationUnspiderfy(e),this._group._spiderfied=null)},_generatePointsCircle:function(e,t){var i,n,r=this._group.options.spiderfyDistanceMultiplier*this._circleFootSeparation*(2+e),s=r/this._2PI,o=this._2PI/e,a=[];for(s=Math.max(s,35),a.length=e,i=0;e>i;i++)n=this._circleStartAngle+i*o,a[i]=new L.Point(t.x+s*Math.cos(n),t.y+s*Math.sin(n))._round();return a},_generatePointsSpiral:function(e,t){var i,n=this._group.options.spiderfyDistanceMultiplier,r=n*this._spiralLengthStart,s=n*this._spiralFootSeparation,o=n*this._spiralLengthFactor*this._2PI,a=0,h=[];for(h.length=e,i=e;i>=0;i--)e>i&&(h[i]=new L.Point(t.x+r*Math.cos(a),t.y+r*Math.sin(a))._round()),a+=s/r+5e-4*i,r+=o/a;return h},_noanimationUnspiderfy:function(){var e,t,i=this._group,n=i._map,r=i._featureGroup,s=this.getAllChildMarkers();for(i._ignoreMove=!0,this.setOpacity(1),t=s.length-1;t>=0;t--)e=s[t],r.removeLayer(e),e._preSpiderfyLatlng&&(e.setLatLng(e._preSpiderfyLatlng),delete e._preSpiderfyLatlng),e.setZIndexOffset&&e.setZIndexOffset(0),e._spiderLeg&&(n.removeLayer(e._spiderLeg),delete e._spiderLeg);i.fire("unspiderfied",{cluster:this,markers:s}),i._ignoreMove=!1,i._spiderfied=null}}),L.MarkerClusterNonAnimated=L.MarkerCluster.extend({_animationSpiderfy:function(e,t){var i,n,r,s,o=this._group,a=o._map,h=o._featureGroup,l=this._group.options.spiderLegPolylineOptions;for(o._ignoreMove=!0,i=0;i<e.length;i++)s=a.layerPointToLatLng(t[i]),n=e[i],r=new L.Polyline([this._latlng,s],l),a.addLayer(r),n._spiderLeg=r,n._preSpiderfyLatlng=n._latlng,n.setLatLng(s),n.setZIndexOffset&&n.setZIndexOffset(1e6),h.addLayer(n);this.setOpacity(.3),o._ignoreMove=!1,o.fire("spiderfied",{cluster:this,markers:e})},_animationUnspiderfy:function(){this._noanimationUnspiderfy()}}),L.MarkerCluster.include({_animationSpiderfy:function(e,t){var i,n,r,s,o,a,h=this,l=this._group,u=l._map,_=l._featureGroup,d=this._latlng,c=u.latLngToLayerPoint(d),p=L.Path.SVG,f=L.extend({},this._group.options.spiderLegPolylineOptions),m=f.opacity;for(void 0===m&&(m=L.MarkerClusterGroup.prototype.options.spiderLegPolylineOptions.opacity),p?(f.opacity=0,f.className=(f.className||"")+" leaflet-cluster-spider-leg"):f.opacity=m,l._ignoreMove=!0,i=0;i<e.length;i++)n=e[i],a=u.layerPointToLatLng(t[i]),r=new L.Polyline([d,a],f),u.addLayer(r),n._spiderLeg=r,p&&(s=r._path,o=s.getTotalLength()+.1,s.style.strokeDasharray=o,s.style.strokeDashoffset=o),n.setZIndexOffset&&n.setZIndexOffset(1e6),n.clusterHide&&n.clusterHide(),_.addLayer(n),n._setPos&&n._setPos(c);for(l._forceLayout(),l._animationStart(),i=e.length-1;i>=0;i--)a=u.layerPointToLatLng(t[i]),n=e[i],n._preSpiderfyLatlng=n._latlng,n.setLatLng(a),n.clusterShow&&n.clusterShow(),p&&(r=n._spiderLeg,s=r._path,s.style.strokeDashoffset=0,r.setStyle({opacity:m}));this.setOpacity(.3),l._ignoreMove=!1,setTimeout(function(){l._animationEnd(),l.fire("spiderfied",{cluster:h,markers:e})},200)},_animationUnspiderfy:function(e){var t,i,n,r,s,o,a=this,h=this._group,l=h._map,u=h._featureGroup,_=e?l._latLngToNewLayerPoint(this._latlng,e.zoom,e.center):l.latLngToLayerPoint(this._latlng),d=this.getAllChildMarkers(),c=L.Path.SVG;for(h._ignoreMove=!0,h._animationStart(),this.setOpacity(1),i=d.length-1;i>=0;i--)t=d[i],t._preSpiderfyLatlng&&(t.closePopup(),t.setLatLng(t._preSpiderfyLatlng),delete t._preSpiderfyLatlng,o=!0,t._setPos&&(t._setPos(_),o=!1),t.clusterHide&&(t.clusterHide(),o=!1),o&&u.removeLayer(t),c&&(n=t._spiderLeg,r=n._path,s=r.getTotalLength()+.1,r.style.strokeDashoffset=s,n.setStyle({opacity:0})));h._ignoreMove=!1,setTimeout(function(){var e=0;for(i=d.length-1;i>=0;i--)t=d[i],t._spiderLeg&&e++;for(i=d.length-1;i>=0;i--)t=d[i],t._spiderLeg&&(t.clusterShow&&t.clusterShow(),t.setZIndexOffset&&t.setZIndexOffset(0),e>1&&u.removeLayer(t),l.removeLayer(t._spiderLeg),delete t._spiderLeg);h._animationEnd(),h.fire("unspiderfied",{cluster:a,markers:d})},200)}}),L.MarkerClusterGroup.include({_spiderfied:null,unspiderfy:function(){this._unspiderfy.apply(this,arguments)},_spiderfierOnAdd:function(){this._map.on("click",this._unspiderfyWrapper,this),this._map.options.zoomAnimation&&this._map.on("zoomstart",this._unspiderfyZoomStart,this),this._map.on("zoomend",this._noanimationUnspiderfy,this),L.Browser.touch||this._map.getRenderer(this)},_spiderfierOnRemove:function(){this._map.off("click",this._unspiderfyWrapper,this),this._map.off("zoomstart",this._unspiderfyZoomStart,this),this._map.off("zoomanim",this._unspiderfyZoomAnim,this),this._map.off("zoomend",this._noanimationUnspiderfy,this),this._noanimationUnspiderfy()
-},_unspiderfyZoomStart:function(){this._map&&this._map.on("zoomanim",this._unspiderfyZoomAnim,this)},_unspiderfyZoomAnim:function(e){L.DomUtil.hasClass(this._map._mapPane,"leaflet-touching")||(this._map.off("zoomanim",this._unspiderfyZoomAnim,this),this._unspiderfy(e))},_unspiderfyWrapper:function(){this._unspiderfy()},_unspiderfy:function(e){this._spiderfied&&this._spiderfied.unspiderfy(e)},_noanimationUnspiderfy:function(){this._spiderfied&&this._spiderfied._noanimationUnspiderfy()},_unspiderfyLayer:function(e){e._spiderLeg&&(this._featureGroup.removeLayer(e),e.clusterShow&&e.clusterShow(),e.setZIndexOffset&&e.setZIndexOffset(0),this._map.removeLayer(e._spiderLeg),delete e._spiderLeg)}}),L.MarkerClusterGroup.include({refreshClusters:function(e){return e?e instanceof L.MarkerClusterGroup?e=e._topClusterLevel.getAllChildMarkers():e instanceof L.LayerGroup?e=e._layers:e instanceof L.MarkerCluster?e=e.getAllChildMarkers():e instanceof L.Marker&&(e=[e]):e=this._topClusterLevel.getAllChildMarkers(),this._flagParentsIconsNeedUpdate(e),this._refreshClustersIcons(),this.options.singleMarkerMode&&this._refreshSingleMarkerModeMarkers(e),this},_flagParentsIconsNeedUpdate:function(e){var t,i;for(t in e)for(i=e[t].__parent;i;)i._iconNeedsUpdate=!0,i=i.__parent},_refreshSingleMarkerModeMarkers:function(e){var t,i;for(t in e)i=e[t],this.hasLayer(i)&&i.setIcon(this._overrideMarkerIcon(i))}}),L.Marker.include({refreshIconOptions:function(e,t){var i=this.options.icon;return L.setOptions(i,e),this.setIcon(i),t&&this.__parent&&this.__parent._group.refreshClusters(this),this}}),e.MarkerClusterGroup=t,e.MarkerCluster=i});
-//# sourceMappingURL=leaflet.markercluster.js.map \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-icon-2x.png b/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-icon-2x.png
deleted file mode 100644
index 1c26e9fc..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-icon-2x.png
+++ /dev/null
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-icon.png b/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-icon.png
deleted file mode 100644
index 3e64e06d..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-icon.png
+++ /dev/null
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet/leaflet.js b/www/wiki/extensions/Maps/resources/leaflet/leaflet/leaflet.js
deleted file mode 100644
index 3b628aba..00000000
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet/leaflet.js
+++ /dev/null
@@ -1,5 +0,0 @@
-/* @preserve
- * Leaflet 1.3.4+Detached: 0e566b2ad5e696ba9f79a9d48a7e51c8f4892441.0e566b2, a JS library for interactive maps. http://leafletjs.com
- * (c) 2010-2018 Vladimir Agafonkin, (c) 2010-2011 CloudMade
- */
-!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i(t.L={})}(this,function(t){"use strict";function i(t){var i,e,n,o;for(e=1,n=arguments.length;e<n;e++){o=arguments[e];for(i in o)t[i]=o[i]}return t}function e(t,i){var e=Array.prototype.slice;if(t.bind)return t.bind.apply(t,e.call(arguments,1));var n=e.call(arguments,2);return function(){return t.apply(i,n.length?n.concat(e.call(arguments)):arguments)}}function n(t){return t._leaflet_id=t._leaflet_id||++ei,t._leaflet_id}function o(t,i,e){var n,o,s,r;return r=function(){n=!1,o&&(s.apply(e,o),o=!1)},s=function(){n?o=arguments:(t.apply(e,arguments),setTimeout(r,i),n=!0)}}function s(t,i,e){var n=i[1],o=i[0],s=n-o;return t===n&&e?t:((t-o)%s+s)%s+o}function r(){return!1}function a(t,i){var e=Math.pow(10,void 0===i?6:i);return Math.round(t*e)/e}function h(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}function u(t){return h(t).split(/\s+/)}function l(t,i){t.hasOwnProperty("options")||(t.options=t.options?ii(t.options):{});for(var e in i)t.options[e]=i[e];return t.options}function c(t,i,e){var n=[];for(var o in t)n.push(encodeURIComponent(e?o.toUpperCase():o)+"="+encodeURIComponent(t[o]));return(i&&-1!==i.indexOf("?")?"&":"?")+n.join("&")}function _(t,i){return t.replace(ni,function(t,e){var n=i[e];if(void 0===n)throw new Error("No value provided for variable "+t);return"function"==typeof n&&(n=n(i)),n})}function d(t,i){for(var e=0;e<t.length;e++)if(t[e]===i)return e;return-1}function p(t){return window["webkit"+t]||window["moz"+t]||window["ms"+t]}function m(t){var i=+new Date,e=Math.max(0,16-(i-ri));return ri=i+e,window.setTimeout(t,e)}function f(t,i,n){if(!n||ai!==m)return ai.call(window,e(t,i));t.call(i)}function g(t){t&&hi.call(window,t)}function v(){}function y(t){if("undefined"!=typeof L&&L&&L.Mixin){t=oi(t)?t:[t];for(var i=0;i<t.length;i++)t[i]===L.Mixin.Events&&console.warn("Deprecated include of L.Mixin.Events: this property will be removed in future releases, please inherit from L.Evented instead.",(new Error).stack)}}function x(t,i,e){this.x=e?Math.round(t):t,this.y=e?Math.round(i):i}function w(t,i,e){return t instanceof x?t:oi(t)?new x(t[0],t[1]):void 0===t||null===t?t:"object"==typeof t&&"x"in t&&"y"in t?new x(t.x,t.y):new x(t,i,e)}function P(t,i){if(t)for(var e=i?[t,i]:t,n=0,o=e.length;n<o;n++)this.extend(e[n])}function b(t,i){return!t||t instanceof P?t:new P(t,i)}function T(t,i){if(t)for(var e=i?[t,i]:t,n=0,o=e.length;n<o;n++)this.extend(e[n])}function z(t,i){return t instanceof T?t:new T(t,i)}function M(t,i,e){if(isNaN(t)||isNaN(i))throw new Error("Invalid LatLng object: ("+t+", "+i+")");this.lat=+t,this.lng=+i,void 0!==e&&(this.alt=+e)}function C(t,i,e){return t instanceof M?t:oi(t)&&"object"!=typeof t[0]?3===t.length?new M(t[0],t[1],t[2]):2===t.length?new M(t[0],t[1]):null:void 0===t||null===t?t:"object"==typeof t&&"lat"in t?new M(t.lat,"lng"in t?t.lng:t.lon,t.alt):void 0===i?null:new M(t,i,e)}function S(t,i,e,n){if(oi(t))return this._a=t[0],this._b=t[1],this._c=t[2],void(this._d=t[3]);this._a=t,this._b=i,this._c=e,this._d=n}function Z(t,i,e,n){return new S(t,i,e,n)}function E(t){return document.createElementNS("http://www.w3.org/2000/svg",t)}function k(t,i){var e,n,o,s,r,a,h="";for(e=0,o=t.length;e<o;e++){for(n=0,s=(r=t[e]).length;n<s;n++)a=r[n],h+=(n?"L":"M")+a.x+" "+a.y;h+=i?Ji?"z":"x":""}return h||"M0 0"}function A(t){return navigator.userAgent.toLowerCase().indexOf(t)>=0}function B(t,i,e,n){return"touchstart"===i?O(t,e,n):"touchmove"===i?W(t,e,n):"touchend"===i&&H(t,e,n),this}function I(t,i,e){var n=t["_leaflet_"+i+e];return"touchstart"===i?t.removeEventListener(te,n,!1):"touchmove"===i?t.removeEventListener(ie,n,!1):"touchend"===i&&(t.removeEventListener(ee,n,!1),t.removeEventListener(ne,n,!1)),this}function O(t,i,n){var o=e(function(t){if("mouse"!==t.pointerType&&t.MSPOINTER_TYPE_MOUSE&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE){if(!(oe.indexOf(t.target.tagName)<0))return;Pt(t)}j(t,i)});t["_leaflet_touchstart"+n]=o,t.addEventListener(te,o,!1),re||(document.documentElement.addEventListener(te,R,!0),document.documentElement.addEventListener(ie,N,!0),document.documentElement.addEventListener(ee,D,!0),document.documentElement.addEventListener(ne,D,!0),re=!0)}function R(t){se[t.pointerId]=t,ae++}function N(t){se[t.pointerId]&&(se[t.pointerId]=t)}function D(t){delete se[t.pointerId],ae--}function j(t,i){t.touches=[];for(var e in se)t.touches.push(se[e]);t.changedTouches=[t],i(t)}function W(t,i,e){var n=function(t){(t.pointerType!==t.MSPOINTER_TYPE_MOUSE&&"mouse"!==t.pointerType||0!==t.buttons)&&j(t,i)};t["_leaflet_touchmove"+e]=n,t.addEventListener(ie,n,!1)}function H(t,i,e){var n=function(t){j(t,i)};t["_leaflet_touchend"+e]=n,t.addEventListener(ee,n,!1),t.addEventListener(ne,n,!1)}function F(t,i,e){function n(t){var i;if(Vi){if(!bi||"mouse"===t.pointerType)return;i=ae}else i=t.touches.length;if(!(i>1)){var e=Date.now(),n=e-(s||e);r=t.touches?t.touches[0]:t,a=n>0&&n<=h,s=e}}function o(t){if(a&&!r.cancelBubble){if(Vi){if(!bi||"mouse"===t.pointerType)return;var e,n,o={};for(n in r)e=r[n],o[n]=e&&e.bind?e.bind(r):e;r=o}r.type="dblclick",i(r),s=null}}var s,r,a=!1,h=250;return t[le+he+e]=n,t[le+ue+e]=o,t[le+"dblclick"+e]=i,t.addEventListener(he,n,!1),t.addEventListener(ue,o,!1),t.addEventListener("dblclick",i,!1),this}function U(t,i){var e=t[le+he+i],n=t[le+ue+i],o=t[le+"dblclick"+i];return t.removeEventListener(he,e,!1),t.removeEventListener(ue,n,!1),bi||t.removeEventListener("dblclick",o,!1),this}function V(t){return"string"==typeof t?document.getElementById(t):t}function q(t,i){var e=t.style[i]||t.currentStyle&&t.currentStyle[i];if((!e||"auto"===e)&&document.defaultView){var n=document.defaultView.getComputedStyle(t,null);e=n?n[i]:null}return"auto"===e?null:e}function G(t,i,e){var n=document.createElement(t);return n.className=i||"",e&&e.appendChild(n),n}function K(t){var i=t.parentNode;i&&i.removeChild(t)}function Y(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function X(t){var i=t.parentNode;i.lastChild!==t&&i.appendChild(t)}function J(t){var i=t.parentNode;i.firstChild!==t&&i.insertBefore(t,i.firstChild)}function $(t,i){if(void 0!==t.classList)return t.classList.contains(i);var e=et(t);return e.length>0&&new RegExp("(^|\\s)"+i+"(\\s|$)").test(e)}function Q(t,i){if(void 0!==t.classList)for(var e=u(i),n=0,o=e.length;n<o;n++)t.classList.add(e[n]);else if(!$(t,i)){var s=et(t);it(t,(s?s+" ":"")+i)}}function tt(t,i){void 0!==t.classList?t.classList.remove(i):it(t,h((" "+et(t)+" ").replace(" "+i+" "," ")))}function it(t,i){void 0===t.className.baseVal?t.className=i:t.className.baseVal=i}function et(t){return void 0===t.className.baseVal?t.className:t.className.baseVal}function nt(t,i){"opacity"in t.style?t.style.opacity=i:"filter"in t.style&&ot(t,i)}function ot(t,i){var e=!1,n="DXImageTransform.Microsoft.Alpha";try{e=t.filters.item(n)}catch(t){if(1===i)return}i=Math.round(100*i),e?(e.Enabled=100!==i,e.Opacity=i):t.style.filter+=" progid:"+n+"(opacity="+i+")"}function st(t){for(var i=document.documentElement.style,e=0;e<t.length;e++)if(t[e]in i)return t[e];return!1}function rt(t,i,e){var n=i||new x(0,0);t.style[ce]=(Ri?"translate("+n.x+"px,"+n.y+"px)":"translate3d("+n.x+"px,"+n.y+"px,0)")+(e?" scale("+e+")":"")}function at(t,i){t._leaflet_pos=i,ji?rt(t,i):(t.style.left=i.x+"px",t.style.top=i.y+"px")}function ht(t){return t._leaflet_pos||new x(0,0)}function ut(){mt(window,"dragstart",Pt)}function lt(){ft(window,"dragstart",Pt)}function ct(t){for(;-1===t.tabIndex;)t=t.parentNode;t.style&&(_t(),me=t,fe=t.style.outline,t.style.outline="none",mt(window,"keydown",_t))}function _t(){me&&(me.style.outline=fe,me=void 0,fe=void 0,ft(window,"keydown",_t))}function dt(t){do{t=t.parentNode}while(!(t.offsetWidth&&t.offsetHeight||t===document.body));return t}function pt(t){var i=t.getBoundingClientRect();return{x:i.width/t.offsetWidth||1,y:i.height/t.offsetHeight||1,boundingClientRect:i}}function mt(t,i,e,n){if("object"==typeof i)for(var o in i)gt(t,o,i[o],e);else for(var s=0,r=(i=u(i)).length;s<r;s++)gt(t,i[s],e,n);return this}function ft(t,i,e,n){if("object"==typeof i)for(var o in i)vt(t,o,i[o],e);else if(i)for(var s=0,r=(i=u(i)).length;s<r;s++)vt(t,i[s],e,n);else{for(var a in t[ye])vt(t,a,t[ye][a]);delete t[ye]}return this}function gt(t,i,e,o){var s=i+n(e)+(o?"_"+n(o):"");if(t[ye]&&t[ye][s])return this;var r=function(i){return e.call(o||t,i||window.event)},a=r;Vi&&0===i.indexOf("touch")?B(t,i,r,s):!qi||"dblclick"!==i||!F||Vi&&Ei?"addEventListener"in t?"mousewheel"===i?t.addEventListener("onwheel"in t?"wheel":"mousewheel",r,!1):"mouseenter"===i||"mouseleave"===i?(r=function(i){i=i||window.event,Ct(t,i)&&a(i)},t.addEventListener("mouseenter"===i?"mouseover":"mouseout",r,!1)):("click"===i&&zi&&(r=function(t){St(t,a)}),t.addEventListener(i,r,!1)):"attachEvent"in t&&t.attachEvent("on"+i,r):F(t,r,s),t[ye]=t[ye]||{},t[ye][s]=r}function vt(t,i,e,o){var s=i+n(e)+(o?"_"+n(o):""),r=t[ye]&&t[ye][s];if(!r)return this;Vi&&0===i.indexOf("touch")?I(t,i,s):!qi||"dblclick"!==i||!U||Vi&&Ei?"removeEventListener"in t?"mousewheel"===i?t.removeEventListener("onwheel"in t?"wheel":"mousewheel",r,!1):t.removeEventListener("mouseenter"===i?"mouseover":"mouseleave"===i?"mouseout":i,r,!1):"detachEvent"in t&&t.detachEvent("on"+i,r):U(t,s),t[ye][s]=null}function yt(t){return t.stopPropagation?t.stopPropagation():t.originalEvent?t.originalEvent._stopped=!0:t.cancelBubble=!0,Mt(t),this}function xt(t){return gt(t,"mousewheel",yt),this}function wt(t){return mt(t,"mousedown touchstart dblclick",yt),gt(t,"click",zt),this}function Pt(t){return t.preventDefault?t.preventDefault():t.returnValue=!1,this}function Lt(t){return Pt(t),yt(t),this}function bt(t,i){if(!i)return new x(t.clientX,t.clientY);var e=pt(i),n=e.boundingClientRect;return new x((t.clientX-n.left)/e.x-i.clientLeft,(t.clientY-n.top)/e.y-i.clientTop)}function Tt(t){return bi?t.wheelDeltaY/2:t.deltaY&&0===t.deltaMode?-t.deltaY/xe:t.deltaY&&1===t.deltaMode?20*-t.deltaY:t.deltaY&&2===t.deltaMode?60*-t.deltaY:t.deltaX||t.deltaZ?0:t.wheelDelta?(t.wheelDeltaY||t.wheelDelta)/2:t.detail&&Math.abs(t.detail)<32765?20*-t.detail:t.detail?t.detail/-32765*60:0}function zt(t){we[t.type]=!0}function Mt(t){var i=we[t.type];return we[t.type]=!1,i}function Ct(t,i){var e=i.relatedTarget;if(!e)return!0;try{for(;e&&e!==t;)e=e.parentNode}catch(t){return!1}return e!==t}function St(t,i){var e=t.timeStamp||t.originalEvent&&t.originalEvent.timeStamp,n=ge&&e-ge;n&&n>100&&n<500||t.target._simulatedClick&&!t._simulated?Lt(t):(ge=e,i(t))}function Zt(t,i){if(!i||!t.length)return t.slice();var e=i*i;return t=Bt(t,e),t=kt(t,e)}function Et(t,i,e){return Math.sqrt(Dt(t,i,e,!0))}function kt(t,i){var e=t.length,n=new(typeof Uint8Array!=void 0+""?Uint8Array:Array)(e);n[0]=n[e-1]=1,At(t,n,i,0,e-1);var o,s=[];for(o=0;o<e;o++)n[o]&&s.push(t[o]);return s}function At(t,i,e,n,o){var s,r,a,h=0;for(r=n+1;r<=o-1;r++)(a=Dt(t[r],t[n],t[o],!0))>h&&(s=r,h=a);h>e&&(i[s]=1,At(t,i,e,n,s),At(t,i,e,s,o))}function Bt(t,i){for(var e=[t[0]],n=1,o=0,s=t.length;n<s;n++)Nt(t[n],t[o])>i&&(e.push(t[n]),o=n);return o<s-1&&e.push(t[s-1]),e}function It(t,i,e,n,o){var s,r,a,h=n?ke:Rt(t,e),u=Rt(i,e);for(ke=u;;){if(!(h|u))return[t,i];if(h&u)return!1;a=Rt(r=Ot(t,i,s=h||u,e,o),e),s===h?(t=r,h=a):(i=r,u=a)}}function Ot(t,i,e,n,o){var s,r,a=i.x-t.x,h=i.y-t.y,u=n.min,l=n.max;return 8&e?(s=t.x+a*(l.y-t.y)/h,r=l.y):4&e?(s=t.x+a*(u.y-t.y)/h,r=u.y):2&e?(s=l.x,r=t.y+h*(l.x-t.x)/a):1&e&&(s=u.x,r=t.y+h*(u.x-t.x)/a),new x(s,r,o)}function Rt(t,i){var e=0;return t.x<i.min.x?e|=1:t.x>i.max.x&&(e|=2),t.y<i.min.y?e|=4:t.y>i.max.y&&(e|=8),e}function Nt(t,i){var e=i.x-t.x,n=i.y-t.y;return e*e+n*n}function Dt(t,i,e,n){var o,s=i.x,r=i.y,a=e.x-s,h=e.y-r,u=a*a+h*h;return u>0&&((o=((t.x-s)*a+(t.y-r)*h)/u)>1?(s=e.x,r=e.y):o>0&&(s+=a*o,r+=h*o)),a=t.x-s,h=t.y-r,n?a*a+h*h:new x(s,r)}function jt(t){return!oi(t[0])||"object"!=typeof t[0][0]&&void 0!==t[0][0]}function Wt(t){return console.warn("Deprecated use of _flat, please use L.LineUtil.isFlat instead."),jt(t)}function Ht(t,i,e){var n,o,s,r,a,h,u,l,c,_=[1,4,2,8];for(o=0,u=t.length;o<u;o++)t[o]._code=Rt(t[o],i);for(r=0;r<4;r++){for(l=_[r],n=[],o=0,s=(u=t.length)-1;o<u;s=o++)a=t[o],h=t[s],a._code&l?h._code&l||((c=Ot(h,a,l,i,e))._code=Rt(c,i),n.push(c)):(h._code&l&&((c=Ot(h,a,l,i,e))._code=Rt(c,i),n.push(c)),n.push(a));t=n}return t}function Ft(t,i){var e,n,o,s,r="Feature"===t.type?t.geometry:t,a=r?r.coordinates:null,h=[],u=i&&i.pointToLayer,l=i&&i.coordsToLatLng||Ut;if(!a&&!r)return null;switch(r.type){case"Point":return e=l(a),u?u(t,e):new $e(e);case"MultiPoint":for(o=0,s=a.length;o<s;o++)e=l(a[o]),h.push(u?u(t,e):new $e(e));return new Ke(h);case"LineString":case"MultiLineString":return n=Vt(a,"LineString"===r.type?0:1,l),new nn(n,i);case"Polygon":case"MultiPolygon":return n=Vt(a,"Polygon"===r.type?1:2,l),new on(n,i);case"GeometryCollection":for(o=0,s=r.geometries.length;o<s;o++){var c=Ft({geometry:r.geometries[o],type:"Feature",properties:t.properties},i);c&&h.push(c)}return new Ke(h);default:throw new Error("Invalid GeoJSON object.")}}function Ut(t){return new M(t[1],t[0],t[2])}function Vt(t,i,e){for(var n,o=[],s=0,r=t.length;s<r;s++)n=i?Vt(t[s],i-1,e):(e||Ut)(t[s]),o.push(n);return o}function qt(t,i){return i="number"==typeof i?i:6,void 0!==t.alt?[a(t.lng,i),a(t.lat,i),a(t.alt,i)]:[a(t.lng,i),a(t.lat,i)]}function Gt(t,i,e,n){for(var o=[],s=0,r=t.length;s<r;s++)o.push(i?Gt(t[s],i-1,e,n):qt(t[s],n));return!i&&e&&o.push(o[0]),o}function Kt(t,e){return t.feature?i({},t.feature,{geometry:e}):Yt(e)}function Yt(t){return"Feature"===t.type||"FeatureCollection"===t.type?t:{type:"Feature",properties:{},geometry:t}}function Xt(t,i){return new sn(t,i)}function Jt(t,i){return new mn(t,i)}function $t(t){return Xi?new vn(t):null}function Qt(t){return Ji||$i?new Pn(t):null}var ti=Object.freeze;Object.freeze=function(t){return t};var ii=Object.create||function(){function t(){}return function(i){return t.prototype=i,new t}}(),ei=0,ni=/\{ *([\w_-]+) *\}/g,oi=Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)},si="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=",ri=0,ai=window.requestAnimationFrame||p("RequestAnimationFrame")||m,hi=window.cancelAnimationFrame||p("CancelAnimationFrame")||p("CancelRequestAnimationFrame")||function(t){window.clearTimeout(t)},ui=(Object.freeze||Object)({freeze:ti,extend:i,create:ii,bind:e,lastId:ei,stamp:n,throttle:o,wrapNum:s,falseFn:r,formatNum:a,trim:h,splitWords:u,setOptions:l,getParamString:c,template:_,isArray:oi,indexOf:d,emptyImageUrl:si,requestFn:ai,cancelFn:hi,requestAnimFrame:f,cancelAnimFrame:g});v.extend=function(t){var e=function(){this.initialize&&this.initialize.apply(this,arguments),this.callInitHooks()},n=e.__super__=this.prototype,o=ii(n);o.constructor=e,e.prototype=o;for(var s in this)this.hasOwnProperty(s)&&"prototype"!==s&&"__super__"!==s&&(e[s]=this[s]);return t.statics&&(i(e,t.statics),delete t.statics),t.includes&&(y(t.includes),i.apply(null,[o].concat(t.includes)),delete t.includes),o.options&&(t.options=i(ii(o.options),t.options)),i(o,t),o._initHooks=[],o.callInitHooks=function(){if(!this._initHooksCalled){n.callInitHooks&&n.callInitHooks.call(this),this._initHooksCalled=!0;for(var t=0,i=o._initHooks.length;t<i;t++)o._initHooks[t].call(this)}},e},v.include=function(t){return i(this.prototype,t),this},v.mergeOptions=function(t){return i(this.prototype.options,t),this},v.addInitHook=function(t){var i=Array.prototype.slice.call(arguments,1),e="function"==typeof t?t:function(){this[t].apply(this,i)};return this.prototype._initHooks=this.prototype._initHooks||[],this.prototype._initHooks.push(e),this};var li={on:function(t,i,e){if("object"==typeof t)for(var n in t)this._on(n,t[n],i);else for(var o=0,s=(t=u(t)).length;o<s;o++)this._on(t[o],i,e);return this},off:function(t,i,e){if(t)if("object"==typeof t)for(var n in t)this._off(n,t[n],i);else for(var o=0,s=(t=u(t)).length;o<s;o++)this._off(t[o],i,e);else delete this._events;return this},_on:function(t,i,e){this._events=this._events||{};var n=this._events[t];n||(n=[],this._events[t]=n),e===this&&(e=void 0);for(var o={fn:i,ctx:e},s=n,r=0,a=s.length;r<a;r++)if(s[r].fn===i&&s[r].ctx===e)return;s.push(o)},_off:function(t,i,e){var n,o,s;if(this._events&&(n=this._events[t]))if(i){if(e===this&&(e=void 0),n)for(o=0,s=n.length;o<s;o++){var a=n[o];if(a.ctx===e&&a.fn===i)return a.fn=r,this._firingCount&&(this._events[t]=n=n.slice()),void n.splice(o,1)}}else{for(o=0,s=n.length;o<s;o++)n[o].fn=r;delete this._events[t]}},fire:function(t,e,n){if(!this.listens(t,n))return this;var o=i({},e,{type:t,target:this,sourceTarget:e&&e.sourceTarget||this});if(this._events){var s=this._events[t];if(s){this._firingCount=this._firingCount+1||1;for(var r=0,a=s.length;r<a;r++){var h=s[r];h.fn.call(h.ctx||this,o)}this._firingCount--}}return n&&this._propagateEvent(o),this},listens:function(t,i){var e=this._events&&this._events[t];if(e&&e.length)return!0;if(i)for(var n in this._eventParents)if(this._eventParents[n].listens(t,i))return!0;return!1},once:function(t,i,n){if("object"==typeof t){for(var o in t)this.once(o,t[o],i);return this}var s=e(function(){this.off(t,i,n).off(t,s,n)},this);return this.on(t,i,n).on(t,s,n)},addEventParent:function(t){return this._eventParents=this._eventParents||{},this._eventParents[n(t)]=t,this},removeEventParent:function(t){return this._eventParents&&delete this._eventParents[n(t)],this},_propagateEvent:function(t){for(var e in this._eventParents)this._eventParents[e].fire(t.type,i({layer:t.target,propagatedFrom:t.target},t),!0)}};li.addEventListener=li.on,li.removeEventListener=li.clearAllEventListeners=li.off,li.addOneTimeEventListener=li.once,li.fireEvent=li.fire,li.hasEventListeners=li.listens;var ci=v.extend(li),_i=Math.trunc||function(t){return t>0?Math.floor(t):Math.ceil(t)};x.prototype={clone:function(){return new x(this.x,this.y)},add:function(t){return this.clone()._add(w(t))},_add:function(t){return this.x+=t.x,this.y+=t.y,this},subtract:function(t){return this.clone()._subtract(w(t))},_subtract:function(t){return this.x-=t.x,this.y-=t.y,this},divideBy:function(t){return this.clone()._divideBy(t)},_divideBy:function(t){return this.x/=t,this.y/=t,this},multiplyBy:function(t){return this.clone()._multiplyBy(t)},_multiplyBy:function(t){return this.x*=t,this.y*=t,this},scaleBy:function(t){return new x(this.x*t.x,this.y*t.y)},unscaleBy:function(t){return new x(this.x/t.x,this.y/t.y)},round:function(){return this.clone()._round()},_round:function(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},floor:function(){return this.clone()._floor()},_floor:function(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this},ceil:function(){return this.clone()._ceil()},_ceil:function(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this},trunc:function(){return this.clone()._trunc()},_trunc:function(){return this.x=_i(this.x),this.y=_i(this.y),this},distanceTo:function(t){var i=(t=w(t)).x-this.x,e=t.y-this.y;return Math.sqrt(i*i+e*e)},equals:function(t){return(t=w(t)).x===this.x&&t.y===this.y},contains:function(t){return t=w(t),Math.abs(t.x)<=Math.abs(this.x)&&Math.abs(t.y)<=Math.abs(this.y)},toString:function(){return"Point("+a(this.x)+", "+a(this.y)+")"}},P.prototype={extend:function(t){return t=w(t),this.min||this.max?(this.min.x=Math.min(t.x,this.min.x),this.max.x=Math.max(t.x,this.max.x),this.min.y=Math.min(t.y,this.min.y),this.max.y=Math.max(t.y,this.max.y)):(this.min=t.clone(),this.max=t.clone()),this},getCenter:function(t){return new x((this.min.x+this.max.x)/2,(this.min.y+this.max.y)/2,t)},getBottomLeft:function(){return new x(this.min.x,this.max.y)},getTopRight:function(){return new x(this.max.x,this.min.y)},getTopLeft:function(){return this.min},getBottomRight:function(){return this.max},getSize:function(){return this.max.subtract(this.min)},contains:function(t){var i,e;return(t="number"==typeof t[0]||t instanceof x?w(t):b(t))instanceof P?(i=t.min,e=t.max):i=e=t,i.x>=this.min.x&&e.x<=this.max.x&&i.y>=this.min.y&&e.y<=this.max.y},intersects:function(t){t=b(t);var i=this.min,e=this.max,n=t.min,o=t.max,s=o.x>=i.x&&n.x<=e.x,r=o.y>=i.y&&n.y<=e.y;return s&&r},overlaps:function(t){t=b(t);var i=this.min,e=this.max,n=t.min,o=t.max,s=o.x>i.x&&n.x<e.x,r=o.y>i.y&&n.y<e.y;return s&&r},isValid:function(){return!(!this.min||!this.max)}},T.prototype={extend:function(t){var i,e,n=this._southWest,o=this._northEast;if(t instanceof M)i=t,e=t;else{if(!(t instanceof T))return t?this.extend(C(t)||z(t)):this;if(i=t._southWest,e=t._northEast,!i||!e)return this}return n||o?(n.lat=Math.min(i.lat,n.lat),n.lng=Math.min(i.lng,n.lng),o.lat=Math.max(e.lat,o.lat),o.lng=Math.max(e.lng,o.lng)):(this._southWest=new M(i.lat,i.lng),this._northEast=new M(e.lat,e.lng)),this},pad:function(t){var i=this._southWest,e=this._northEast,n=Math.abs(i.lat-e.lat)*t,o=Math.abs(i.lng-e.lng)*t;return new T(new M(i.lat-n,i.lng-o),new M(e.lat+n,e.lng+o))},getCenter:function(){return new M((this._southWest.lat+this._northEast.lat)/2,(this._southWest.lng+this._northEast.lng)/2)},getSouthWest:function(){return this._southWest},getNorthEast:function(){return this._northEast},getNorthWest:function(){return new M(this.getNorth(),this.getWest())},getSouthEast:function(){return new M(this.getSouth(),this.getEast())},getWest:function(){return this._southWest.lng},getSouth:function(){return this._southWest.lat},getEast:function(){return this._northEast.lng},getNorth:function(){return this._northEast.lat},contains:function(t){t="number"==typeof t[0]||t instanceof M||"lat"in t?C(t):z(t);var i,e,n=this._southWest,o=this._northEast;return t instanceof T?(i=t.getSouthWest(),e=t.getNorthEast()):i=e=t,i.lat>=n.lat&&e.lat<=o.lat&&i.lng>=n.lng&&e.lng<=o.lng},intersects:function(t){t=z(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),s=o.lat>=i.lat&&n.lat<=e.lat,r=o.lng>=i.lng&&n.lng<=e.lng;return s&&r},overlaps:function(t){t=z(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),s=o.lat>i.lat&&n.lat<e.lat,r=o.lng>i.lng&&n.lng<e.lng;return s&&r},toBBoxString:function(){return[this.getWest(),this.getSouth(),this.getEast(),this.getNorth()].join(",")},equals:function(t,i){return!!t&&(t=z(t),this._southWest.equals(t.getSouthWest(),i)&&this._northEast.equals(t.getNorthEast(),i))},isValid:function(){return!(!this._southWest||!this._northEast)}},M.prototype={equals:function(t,i){return!!t&&(t=C(t),Math.max(Math.abs(this.lat-t.lat),Math.abs(this.lng-t.lng))<=(void 0===i?1e-9:i))},toString:function(t){return"LatLng("+a(this.lat,t)+", "+a(this.lng,t)+")"},distanceTo:function(t){return pi.distance(this,C(t))},wrap:function(){return pi.wrapLatLng(this)},toBounds:function(t){var i=180*t/40075017,e=i/Math.cos(Math.PI/180*this.lat);return z([this.lat-i,this.lng-e],[this.lat+i,this.lng+e])},clone:function(){return new M(this.lat,this.lng,this.alt)}};var di={latLngToPoint:function(t,i){var e=this.projection.project(t),n=this.scale(i);return this.transformation._transform(e,n)},pointToLatLng:function(t,i){var e=this.scale(i),n=this.transformation.untransform(t,e);return this.projection.unproject(n)},project:function(t){return this.projection.project(t)},unproject:function(t){return this.projection.unproject(t)},scale:function(t){return 256*Math.pow(2,t)},zoom:function(t){return Math.log(t/256)/Math.LN2},getProjectedBounds:function(t){if(this.infinite)return null;var i=this.projection.bounds,e=this.scale(t);return new P(this.transformation.transform(i.min,e),this.transformation.transform(i.max,e))},infinite:!1,wrapLatLng:function(t){var i=this.wrapLng?s(t.lng,this.wrapLng,!0):t.lng;return new M(this.wrapLat?s(t.lat,this.wrapLat,!0):t.lat,i,t.alt)},wrapLatLngBounds:function(t){var i=t.getCenter(),e=this.wrapLatLng(i),n=i.lat-e.lat,o=i.lng-e.lng;if(0===n&&0===o)return t;var s=t.getSouthWest(),r=t.getNorthEast();return new T(new M(s.lat-n,s.lng-o),new M(r.lat-n,r.lng-o))}},pi=i({},di,{wrapLng:[-180,180],R:6371e3,distance:function(t,i){var e=Math.PI/180,n=t.lat*e,o=i.lat*e,s=Math.sin((i.lat-t.lat)*e/2),r=Math.sin((i.lng-t.lng)*e/2),a=s*s+Math.cos(n)*Math.cos(o)*r*r,h=2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));return this.R*h}}),mi={R:6378137,MAX_LATITUDE:85.0511287798,project:function(t){var i=Math.PI/180,e=this.MAX_LATITUDE,n=Math.max(Math.min(e,t.lat),-e),o=Math.sin(n*i);return new x(this.R*t.lng*i,this.R*Math.log((1+o)/(1-o))/2)},unproject:function(t){var i=180/Math.PI;return new M((2*Math.atan(Math.exp(t.y/this.R))-Math.PI/2)*i,t.x*i/this.R)},bounds:function(){var t=6378137*Math.PI;return new P([-t,-t],[t,t])}()};S.prototype={transform:function(t,i){return this._transform(t.clone(),i)},_transform:function(t,i){return i=i||1,t.x=i*(this._a*t.x+this._b),t.y=i*(this._c*t.y+this._d),t},untransform:function(t,i){return i=i||1,new x((t.x/i-this._b)/this._a,(t.y/i-this._d)/this._c)}};var fi,gi,vi,yi=i({},pi,{code:"EPSG:3857",projection:mi,transformation:function(){var t=.5/(Math.PI*mi.R);return Z(t,.5,-t,.5)}()}),xi=i({},yi,{code:"EPSG:900913"}),wi=document.documentElement.style,Pi="ActiveXObject"in window,Li=Pi&&!document.addEventListener,bi="msLaunchUri"in navigator&&!("documentMode"in document),Ti=A("webkit"),zi=A("android"),Mi=A("android 2")||A("android 3"),Ci=parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1],10),Si=zi&&A("Google")&&Ci<537&&!("AudioNode"in window),Zi=!!window.opera,Ei=A("chrome"),ki=A("gecko")&&!Ti&&!Zi&&!Pi,Ai=!Ei&&A("safari"),Bi=A("phantom"),Ii="OTransition"in wi,Oi=0===navigator.platform.indexOf("Win"),Ri=Pi&&"transition"in wi,Ni="WebKitCSSMatrix"in window&&"m11"in new window.WebKitCSSMatrix&&!Mi,Di="MozPerspective"in wi,ji=!window.L_DISABLE_3D&&(Ri||Ni||Di)&&!Ii&&!Bi,Wi="undefined"!=typeof orientation||A("mobile"),Hi=Wi&&Ti,Fi=Wi&&Ni,Ui=!window.PointerEvent&&window.MSPointerEvent,Vi=!(!window.PointerEvent&&!Ui),qi=!window.L_NO_TOUCH&&(Vi||"ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch),Gi=Wi&&Zi,Ki=Wi&&ki,Yi=(window.devicePixelRatio||window.screen.deviceXDPI/window.screen.logicalXDPI)>1,Xi=!!document.createElement("canvas").getContext,Ji=!(!document.createElementNS||!E("svg").createSVGRect),$i=!Ji&&function(){try{var t=document.createElement("div");t.innerHTML='<v:shape adj="1"/>';var i=t.firstChild;return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(t){return!1}}(),Qi=(Object.freeze||Object)({ie:Pi,ielt9:Li,edge:bi,webkit:Ti,android:zi,android23:Mi,androidStock:Si,opera:Zi,chrome:Ei,gecko:ki,safari:Ai,phantom:Bi,opera12:Ii,win:Oi,ie3d:Ri,webkit3d:Ni,gecko3d:Di,any3d:ji,mobile:Wi,mobileWebkit:Hi,mobileWebkit3d:Fi,msPointer:Ui,pointer:Vi,touch:qi,mobileOpera:Gi,mobileGecko:Ki,retina:Yi,canvas:Xi,svg:Ji,vml:$i}),te=Ui?"MSPointerDown":"pointerdown",ie=Ui?"MSPointerMove":"pointermove",ee=Ui?"MSPointerUp":"pointerup",ne=Ui?"MSPointerCancel":"pointercancel",oe=["INPUT","SELECT","OPTION"],se={},re=!1,ae=0,he=Ui?"MSPointerDown":Vi?"pointerdown":"touchstart",ue=Ui?"MSPointerUp":Vi?"pointerup":"touchend",le="_leaflet_",ce=st(["transform","webkitTransform","OTransform","MozTransform","msTransform"]),_e=st(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),de="webkitTransition"===_e||"OTransition"===_e?_e+"End":"transitionend";if("onselectstart"in document)fi=function(){mt(window,"selectstart",Pt)},gi=function(){ft(window,"selectstart",Pt)};else{var pe=st(["userSelect","WebkitUserSelect","OUserSelect","MozUserSelect","msUserSelect"]);fi=function(){if(pe){var t=document.documentElement.style;vi=t[pe],t[pe]="none"}},gi=function(){pe&&(document.documentElement.style[pe]=vi,vi=void 0)}}var me,fe,ge,ve=(Object.freeze||Object)({TRANSFORM:ce,TRANSITION:_e,TRANSITION_END:de,get:V,getStyle:q,create:G,remove:K,empty:Y,toFront:X,toBack:J,hasClass:$,addClass:Q,removeClass:tt,setClass:it,getClass:et,setOpacity:nt,testProp:st,setTransform:rt,setPosition:at,getPosition:ht,disableTextSelection:fi,enableTextSelection:gi,disableImageDrag:ut,enableImageDrag:lt,preventOutline:ct,restoreOutline:_t,getSizedParentNode:dt,getScale:pt}),ye="_leaflet_events",xe=Oi&&Ei?2*window.devicePixelRatio:ki?window.devicePixelRatio:1,we={},Pe=(Object.freeze||Object)({on:mt,off:ft,stopPropagation:yt,disableScrollPropagation:xt,disableClickPropagation:wt,preventDefault:Pt,stop:Lt,getMousePosition:bt,getWheelDelta:Tt,fakeStop:zt,skipped:Mt,isExternalTarget:Ct,addListener:mt,removeListener:ft}),Le=ci.extend({run:function(t,i,e,n){this.stop(),this._el=t,this._inProgress=!0,this._duration=e||.25,this._easeOutPower=1/Math.max(n||.5,.2),this._startPos=ht(t),this._offset=i.subtract(this._startPos),this._startTime=+new Date,this.fire("start"),this._animate()},stop:function(){this._inProgress&&(this._step(!0),this._complete())},_animate:function(){this._animId=f(this._animate,this),this._step()},_step:function(t){var i=+new Date-this._startTime,e=1e3*this._duration;i<e?this._runFrame(this._easeOut(i/e),t):(this._runFrame(1),this._complete())},_runFrame:function(t,i){var e=this._startPos.add(this._offset.multiplyBy(t));i&&e._round(),at(this._el,e),this.fire("step")},_complete:function(){g(this._animId),this._inProgress=!1,this.fire("end")},_easeOut:function(t){return 1-Math.pow(1-t,this._easeOutPower)}}),be=ci.extend({options:{crs:yi,center:void 0,zoom:void 0,minZoom:void 0,maxZoom:void 0,layers:[],maxBounds:void 0,renderer:void 0,zoomAnimation:!0,zoomAnimationThreshold:4,fadeAnimation:!0,markerZoomAnimation:!0,transform3DLimit:8388608,zoomSnap:1,zoomDelta:1,trackResize:!0},initialize:function(t,i){i=l(this,i),this._initContainer(t),this._initLayout(),this._onResize=e(this._onResize,this),this._initEvents(),i.maxBounds&&this.setMaxBounds(i.maxBounds),void 0!==i.zoom&&(this._zoom=this._limitZoom(i.zoom)),i.center&&void 0!==i.zoom&&this.setView(C(i.center),i.zoom,{reset:!0}),this._handlers=[],this._layers={},this._zoomBoundLayers={},this._sizeChanged=!0,this.callInitHooks(),this._zoomAnimated=_e&&ji&&!Gi&&this.options.zoomAnimation,this._zoomAnimated&&(this._createAnimProxy(),mt(this._proxy,de,this._catchTransitionEnd,this)),this._addLayers(this.options.layers)},setView:function(t,e,n){return e=void 0===e?this._zoom:this._limitZoom(e),t=this._limitCenter(C(t),e,this.options.maxBounds),n=n||{},this._stop(),this._loaded&&!n.reset&&!0!==n&&(void 0!==n.animate&&(n.zoom=i({animate:n.animate},n.zoom),n.pan=i({animate:n.animate,duration:n.duration},n.pan)),this._zoom!==e?this._tryAnimatedZoom&&this._tryAnimatedZoom(t,e,n.zoom):this._tryAnimatedPan(t,n.pan))?(clearTimeout(this._sizeTimer),this):(this._resetView(t,e),this)},setZoom:function(t,i){return this._loaded?this.setView(this.getCenter(),t,{zoom:i}):(this._zoom=t,this)},zoomIn:function(t,i){return t=t||(ji?this.options.zoomDelta:1),this.setZoom(this._zoom+t,i)},zoomOut:function(t,i){return t=t||(ji?this.options.zoomDelta:1),this.setZoom(this._zoom-t,i)},setZoomAround:function(t,i,e){var n=this.getZoomScale(i),o=this.getSize().divideBy(2),s=(t instanceof x?t:this.latLngToContainerPoint(t)).subtract(o).multiplyBy(1-1/n),r=this.containerPointToLatLng(o.add(s));return this.setView(r,i,{zoom:e})},_getBoundsCenterZoom:function(t,i){i=i||{},t=t.getBounds?t.getBounds():z(t);var e=w(i.paddingTopLeft||i.padding||[0,0]),n=w(i.paddingBottomRight||i.padding||[0,0]),o=this.getBoundsZoom(t,!1,e.add(n));if((o="number"==typeof i.maxZoom?Math.min(i.maxZoom,o):o)===1/0)return{center:t.getCenter(),zoom:o};var s=n.subtract(e).divideBy(2),r=this.project(t.getSouthWest(),o),a=this.project(t.getNorthEast(),o);return{center:this.unproject(r.add(a).divideBy(2).add(s),o),zoom:o}},fitBounds:function(t,i){if(!(t=z(t)).isValid())throw new Error("Bounds are not valid.");var e=this._getBoundsCenterZoom(t,i);return this.setView(e.center,e.zoom,i)},fitWorld:function(t){return this.fitBounds([[-90,-180],[90,180]],t)},panTo:function(t,i){return this.setView(t,this._zoom,{pan:i})},panBy:function(t,i){if(t=w(t).round(),i=i||{},!t.x&&!t.y)return this.fire("moveend");if(!0!==i.animate&&!this.getSize().contains(t))return this._resetView(this.unproject(this.project(this.getCenter()).add(t)),this.getZoom()),this;if(this._panAnim||(this._panAnim=new Le,this._panAnim.on({step:this._onPanTransitionStep,end:this._onPanTransitionEnd},this)),i.noMoveStart||this.fire("movestart"),!1!==i.animate){Q(this._mapPane,"leaflet-pan-anim");var e=this._getMapPanePos().subtract(t).round();this._panAnim.run(this._mapPane,e,i.duration||.25,i.easeLinearity)}else this._rawPanBy(t),this.fire("move").fire("moveend");return this},flyTo:function(t,i,e){function n(t){var i=(g*g-m*m+(t?-1:1)*x*x*v*v)/(2*(t?g:m)*x*v),e=Math.sqrt(i*i+1)-i;return e<1e-9?-18:Math.log(e)}function o(t){return(Math.exp(t)-Math.exp(-t))/2}function s(t){return(Math.exp(t)+Math.exp(-t))/2}function r(t){return o(t)/s(t)}function a(t){return m*(s(w)/s(w+y*t))}function h(t){return m*(s(w)*r(w+y*t)-o(w))/x}function u(t){return 1-Math.pow(1-t,1.5)}function l(){var e=(Date.now()-P)/b,n=u(e)*L;e<=1?(this._flyToFrame=f(l,this),this._move(this.unproject(c.add(_.subtract(c).multiplyBy(h(n)/v)),p),this.getScaleZoom(m/a(n),p),{flyTo:!0})):this._move(t,i)._moveEnd(!0)}if(!1===(e=e||{}).animate||!ji)return this.setView(t,i,e);this._stop();var c=this.project(this.getCenter()),_=this.project(t),d=this.getSize(),p=this._zoom;t=C(t),i=void 0===i?p:i;var m=Math.max(d.x,d.y),g=m*this.getZoomScale(p,i),v=_.distanceTo(c)||1,y=1.42,x=y*y,w=n(0),P=Date.now(),L=(n(1)-w)/y,b=e.duration?1e3*e.duration:1e3*L*.8;return this._moveStart(!0,e.noMoveStart),l.call(this),this},flyToBounds:function(t,i){var e=this._getBoundsCenterZoom(t,i);return this.flyTo(e.center,e.zoom,i)},setMaxBounds:function(t){return(t=z(t)).isValid()?(this.options.maxBounds&&this.off("moveend",this._panInsideMaxBounds),this.options.maxBounds=t,this._loaded&&this._panInsideMaxBounds(),this.on("moveend",this._panInsideMaxBounds)):(this.options.maxBounds=null,this.off("moveend",this._panInsideMaxBounds))},setMinZoom:function(t){var i=this.options.minZoom;return this.options.minZoom=t,this._loaded&&i!==t&&(this.fire("zoomlevelschange"),this.getZoom()<this.options.minZoom)?this.setZoom(t):this},setMaxZoom:function(t){var i=this.options.maxZoom;return this.options.maxZoom=t,this._loaded&&i!==t&&(this.fire("zoomlevelschange"),this.getZoom()>this.options.maxZoom)?this.setZoom(t):this},panInsideBounds:function(t,i){this._enforcingBounds=!0;var e=this.getCenter(),n=this._limitCenter(e,this._zoom,z(t));return e.equals(n)||this.panTo(n,i),this._enforcingBounds=!1,this},invalidateSize:function(t){if(!this._loaded)return this;t=i({animate:!1,pan:!0},!0===t?{animate:!0}:t);var n=this.getSize();this._sizeChanged=!0,this._lastCenter=null;var o=this.getSize(),s=n.divideBy(2).round(),r=o.divideBy(2).round(),a=s.subtract(r);return a.x||a.y?(t.animate&&t.pan?this.panBy(a):(t.pan&&this._rawPanBy(a),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(e(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:n,newSize:o})):this},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire("viewreset"),this._stop()},locate:function(t){if(t=this._locateOptions=i({timeout:1e4,watch:!1},t),!("geolocation"in navigator))return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var n=e(this._handleGeolocationResponse,this),o=e(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(n,o,t):navigator.geolocation.getCurrentPosition(n,o,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var i=t.code,e=t.message||(1===i?"permission denied":2===i?"position unavailable":"timeout");this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:i,message:"Geolocation error: "+e+"."})},_handleGeolocationResponse:function(t){var i=new M(t.coords.latitude,t.coords.longitude),e=i.toBounds(2*t.coords.accuracy),n=this._locateOptions;if(n.setView){var o=this.getBoundsZoom(e);this.setView(i,n.maxZoom?Math.min(o,n.maxZoom):o)}var s={latlng:i,bounds:e,timestamp:t.timestamp};for(var r in t.coords)"number"==typeof t.coords[r]&&(s[r]=t.coords[r]);this.fire("locationfound",s)},addHandler:function(t,i){if(!i)return this;var e=this[t]=new i(this);return this._handlers.push(e),this.options[t]&&e.enable(),this},remove:function(){if(this._initEvents(!0),this._containerId!==this._container._leaflet_id)throw new Error("Map container is being reused by another instance");try{delete this._container._leaflet_id,delete this._containerId}catch(t){this._container._leaflet_id=void 0,this._containerId=void 0}void 0!==this._locationWatchId&&this.stopLocate(),this._stop(),K(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._resizeRequest&&(g(this._resizeRequest),this._resizeRequest=null),this._clearHandlers(),this._loaded&&this.fire("unload");var t;for(t in this._layers)this._layers[t].remove();for(t in this._panes)K(this._panes[t]);return this._layers=[],this._panes=[],delete this._mapPane,delete this._renderer,this},createPane:function(t,i){var e=G("div","leaflet-pane"+(t?" leaflet-"+t.replace("Pane","")+"-pane":""),i||this._mapPane);return t&&(this._panes[t]=e),e},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter:this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds();return new T(this.unproject(t.getBottomLeft()),this.unproject(t.getTopRight()))},getMinZoom:function(){return void 0===this.options.minZoom?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return void 0===this.options.maxZoom?void 0===this._layersMaxZoom?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,i,e){t=z(t),e=w(e||[0,0]);var n=this.getZoom()||0,o=this.getMinZoom(),s=this.getMaxZoom(),r=t.getNorthWest(),a=t.getSouthEast(),h=this.getSize().subtract(e),u=b(this.project(a,n),this.project(r,n)).getSize(),l=ji?this.options.zoomSnap:1,c=h.x/u.x,_=h.y/u.y,d=i?Math.max(c,_):Math.min(c,_);return n=this.getScaleZoom(d,n),l&&(n=Math.round(n/(l/100))*(l/100),n=i?Math.ceil(n/l)*l:Math.floor(n/l)*l),Math.max(o,Math.min(s,n))},getSize:function(){return this._size&&!this._sizeChanged||(this._size=new x(this._container.clientWidth||0,this._container.clientHeight||0),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(t,i){var e=this._getTopLeftPoint(t,i);return new P(e,e.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(t){return this.options.crs.getProjectedBounds(void 0===t?this.getZoom():t)},getPane:function(t){return"string"==typeof t?this._panes[t]:t},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t,i){var e=this.options.crs;return i=void 0===i?this._zoom:i,e.scale(t)/e.scale(i)},getScaleZoom:function(t,i){var e=this.options.crs;i=void 0===i?this._zoom:i;var n=e.zoom(t*e.scale(i));return isNaN(n)?1/0:n},project:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.latLngToPoint(C(t),i)},unproject:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.pointToLatLng(w(t),i)},layerPointToLatLng:function(t){var i=w(t).add(this.getPixelOrigin());return this.unproject(i)},latLngToLayerPoint:function(t){return this.project(C(t))._round()._subtract(this.getPixelOrigin())},wrapLatLng:function(t){return this.options.crs.wrapLatLng(C(t))},wrapLatLngBounds:function(t){return this.options.crs.wrapLatLngBounds(z(t))},distance:function(t,i){return this.options.crs.distance(C(t),C(i))},containerPointToLayerPoint:function(t){return w(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return w(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){var i=this.containerPointToLayerPoint(w(t));return this.layerPointToLatLng(i)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(C(t)))},mouseEventToContainerPoint:function(t){return bt(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){var i=this._container=V(t);if(!i)throw new Error("Map container not found.");if(i._leaflet_id)throw new Error("Map container is already initialized.");mt(i,"scroll",this._onScroll,this),this._containerId=n(i)},_initLayout:function(){var t=this._container;this._fadeAnimated=this.options.fadeAnimation&&ji,Q(t,"leaflet-container"+(qi?" leaflet-touch":"")+(Yi?" leaflet-retina":"")+(Li?" leaflet-oldie":"")+(Ai?" leaflet-safari":"")+(this._fadeAnimated?" leaflet-fade-anim":""));var i=q(t,"position");"absolute"!==i&&"relative"!==i&&"fixed"!==i&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._paneRenderers={},this._mapPane=this.createPane("mapPane",this._container),at(this._mapPane,new x(0,0)),this.createPane("tilePane"),this.createPane("shadowPane"),this.createPane("overlayPane"),this.createPane("markerPane"),this.createPane("tooltipPane"),this.createPane("popupPane"),this.options.markerZoomAnimation||(Q(t.markerPane,"leaflet-zoom-hide"),Q(t.shadowPane,"leaflet-zoom-hide"))},_resetView:function(t,i){at(this._mapPane,new x(0,0));var e=!this._loaded;this._loaded=!0,i=this._limitZoom(i),this.fire("viewprereset");var n=this._zoom!==i;this._moveStart(n,!1)._move(t,i)._moveEnd(n),this.fire("viewreset"),e&&this.fire("load")},_moveStart:function(t,i){return t&&this.fire("zoomstart"),i||this.fire("movestart"),this},_move:function(t,i,e){void 0===i&&(i=this._zoom);var n=this._zoom!==i;return this._zoom=i,this._lastCenter=t,this._pixelOrigin=this._getNewPixelOrigin(t),(n||e&&e.pinch)&&this.fire("zoom",e),this.fire("move",e)},_moveEnd:function(t){return t&&this.fire("zoomend"),this.fire("moveend")},_stop:function(){return g(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(t){at(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(t){this._targets={},this._targets[n(this._container)]=this;var i=t?ft:mt;i(this._container,"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress",this._handleDOMEvent,this),this.options.trackResize&&i(window,"resize",this._onResize,this),ji&&this.options.transform3DLimit&&(t?this.off:this.on).call(this,"moveend",this._onMoveEnd)},_onResize:function(){g(this._resizeRequest),this._resizeRequest=f(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var t=this._getMapPanePos();Math.max(Math.abs(t.x),Math.abs(t.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,i){for(var e,o=[],s="mouseout"===i||"mouseover"===i,r=t.target||t.srcElement,a=!1;r;){if((e=this._targets[n(r)])&&("click"===i||"preclick"===i)&&!t._simulated&&this._draggableMoved(e)){a=!0;break}if(e&&e.listens(i,!0)){if(s&&!Ct(r,t))break;if(o.push(e),s)break}if(r===this._container)break;r=r.parentNode}return o.length||a||s||!Ct(r,t)||(o=[this]),o},_handleDOMEvent:function(t){if(this._loaded&&!Mt(t)){var i=t.type;"mousedown"!==i&&"keypress"!==i||ct(t.target||t.srcElement),this._fireDOMEvent(t,i)}},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(t,e,n){if("click"===t.type){var o=i({},t);o.type="preclick",this._fireDOMEvent(o,o.type,n)}if(!t._stopped&&(n=(n||[]).concat(this._findEventTargets(t,e))).length){var s=n[0];"contextmenu"===e&&s.listens(e,!0)&&Pt(t);var r={originalEvent:t};if("keypress"!==t.type){var a=s.getLatLng&&(!s._radius||s._radius<=10);r.containerPoint=a?this.latLngToContainerPoint(s.getLatLng()):this.mouseEventToContainerPoint(t),r.layerPoint=this.containerPointToLayerPoint(r.containerPoint),r.latlng=a?s.getLatLng():this.layerPointToLatLng(r.layerPoint)}for(var h=0;h<n.length;h++)if(n[h].fire(e,r,!0),r.originalEvent._stopped||!1===n[h].options.bubblingMouseEvents&&-1!==d(this._mouseEvents,e))return}},_draggableMoved:function(t){return(t=t.dragging&&t.dragging.enabled()?t:this).dragging&&t.dragging.moved()||this.boxZoom&&this.boxZoom.moved()},_clearHandlers:function(){for(var t=0,i=this._handlers.length;t<i;t++)this._handlers[t].disable()},whenReady:function(t,i){return this._loaded?t.call(i||this,{target:this}):this.on("load",t,i),this},_getMapPanePos:function(){return ht(this._mapPane)||new x(0,0)},_moved:function(){var t=this._getMapPanePos();return t&&!t.equals([0,0])},_getTopLeftPoint:function(t,i){return(t&&void 0!==i?this._getNewPixelOrigin(t,i):this.getPixelOrigin()).subtract(this._getMapPanePos())},_getNewPixelOrigin:function(t,i){var e=this.getSize()._divideBy(2);return this.project(t,i)._subtract(e)._add(this._getMapPanePos())._round()},_latLngToNewLayerPoint:function(t,i,e){var n=this._getNewPixelOrigin(e,i);return this.project(t,i)._subtract(n)},_latLngBoundsToNewLayerBounds:function(t,i,e){var n=this._getNewPixelOrigin(e,i);return b([this.project(t.getSouthWest(),i)._subtract(n),this.project(t.getNorthWest(),i)._subtract(n),this.project(t.getSouthEast(),i)._subtract(n),this.project(t.getNorthEast(),i)._subtract(n)])},_getCenterLayerPoint:function(){return this.containerPointToLayerPoint(this.getSize()._divideBy(2))},_getCenterOffset:function(t){return this.latLngToLayerPoint(t).subtract(this._getCenterLayerPoint())},_limitCenter:function(t,i,e){if(!e)return t;var n=this.project(t,i),o=this.getSize().divideBy(2),s=new P(n.subtract(o),n.add(o)),r=this._getBoundsOffset(s,e,i);return r.round().equals([0,0])?t:this.unproject(n.add(r),i)},_limitOffset:function(t,i){if(!i)return t;var e=this.getPixelBounds(),n=new P(e.min.add(t),e.max.add(t));return t.add(this._getBoundsOffset(n,i))},_getBoundsOffset:function(t,i,e){var n=b(this.project(i.getNorthEast(),e),this.project(i.getSouthWest(),e)),o=n.min.subtract(t.min),s=n.max.subtract(t.max);return new x(this._rebound(o.x,-s.x),this._rebound(o.y,-s.y))},_rebound:function(t,i){return t+i>0?Math.round(t-i)/2:Math.max(0,Math.ceil(t))-Math.max(0,Math.floor(i))},_limitZoom:function(t){var i=this.getMinZoom(),e=this.getMaxZoom(),n=ji?this.options.zoomSnap:1;return n&&(t=Math.round(t/n)*n),Math.max(i,Math.min(e,t))},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){tt(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_tryAnimatedPan:function(t,i){var e=this._getCenterOffset(t)._trunc();return!(!0!==(i&&i.animate)&&!this.getSize().contains(e))&&(this.panBy(e,i),!0)},_createAnimProxy:function(){var t=this._proxy=G("div","leaflet-proxy leaflet-zoom-animated");this._panes.mapPane.appendChild(t),this.on("zoomanim",function(t){var i=ce,e=this._proxy.style[i];rt(this._proxy,this.project(t.center,t.zoom),this.getZoomScale(t.zoom,1)),e===this._proxy.style[i]&&this._animatingZoom&&this._onZoomTransitionEnd()},this),this.on("load moveend",function(){var t=this.getCenter(),i=this.getZoom();rt(this._proxy,this.project(t,i),this.getZoomScale(i,1))},this),this._on("unload",this._destroyAnimProxy,this)},_destroyAnimProxy:function(){K(this._proxy),delete this._proxy},_catchTransitionEnd:function(t){this._animatingZoom&&t.propertyName.indexOf("transform")>=0&&this._onZoomTransitionEnd()},_nothingToAnimate:function(){return!this._container.getElementsByClassName("leaflet-zoom-animated").length},_tryAnimatedZoom:function(t,i,e){if(this._animatingZoom)return!0;if(e=e||{},!this._zoomAnimated||!1===e.animate||this._nothingToAnimate()||Math.abs(i-this._zoom)>this.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(i),o=this._getCenterOffset(t)._divideBy(1-1/n);return!(!0!==e.animate&&!this.getSize().contains(o))&&(f(function(){this._moveStart(!0,!1)._animateZoom(t,i,!0)},this),!0)},_animateZoom:function(t,i,n,o){this._mapPane&&(n&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=i,Q(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:i,noUpdate:o}),setTimeout(e(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&tt(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom),f(function(){this._moveEnd(!0)},this))}}),Te=v.extend({options:{position:"topright"},initialize:function(t){l(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var i=this._map;return i&&i.removeControl(this),this.options.position=t,i&&i.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var i=this._container=this.onAdd(t),e=this.getPosition(),n=t._controlCorners[e];return Q(i,"leaflet-control"),-1!==e.indexOf("bottom")?n.insertBefore(i,n.firstChild):n.appendChild(i),this},remove:function(){return this._map?(K(this._container),this.onRemove&&this.onRemove(this._map),this._map=null,this):this},_refocusOnMap:function(t){this._map&&t&&t.screenX>0&&t.screenY>0&&this._map.getContainer().focus()}}),ze=function(t){return new Te(t)};be.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.remove(),this},_initControlPos:function(){function t(t,o){var s=e+t+" "+e+o;i[t+o]=G("div",s,n)}var i=this._controlCorners={},e="leaflet-",n=this._controlContainer=G("div",e+"control-container",this._container);t("top","left"),t("top","right"),t("bottom","left"),t("bottom","right")},_clearControlPos:function(){for(var t in this._controlCorners)K(this._controlCorners[t]);K(this._controlContainer),delete this._controlCorners,delete this._controlContainer}});var Me=Te.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0,hideSingleBase:!1,sortLayers:!1,sortFunction:function(t,i,e,n){return e<n?-1:n<e?1:0}},initialize:function(t,i,e){l(this,e),this._layerControlInputs=[],this._layers=[],this._lastZIndex=0,this._handlingClick=!1;for(var n in t)this._addLayer(t[n],n);for(n in i)this._addLayer(i[n],n,!0)},onAdd:function(t){this._initLayout(),this._update(),this._map=t,t.on("zoomend",this._checkDisabledLayers,this);for(var i=0;i<this._layers.length;i++)this._layers[i].layer.on("add remove",this._onLayerChange,this);return this._container},addTo:function(t){return Te.prototype.addTo.call(this,t),this._expandIfNotCollapsed()},onRemove:function(){this._map.off("zoomend",this._checkDisabledLayers,this);for(var t=0;t<this._layers.length;t++)this._layers[t].layer.off("add remove",this._onLayerChange,this)},addBaseLayer:function(t,i){return this._addLayer(t,i),this._map?this._update():this},addOverlay:function(t,i){return this._addLayer(t,i,!0),this._map?this._update():this},removeLayer:function(t){t.off("add remove",this._onLayerChange,this);var i=this._getLayer(n(t));return i&&this._layers.splice(this._layers.indexOf(i),1),this._map?this._update():this},expand:function(){Q(this._container,"leaflet-control-layers-expanded"),this._form.style.height=null;var t=this._map.getSize().y-(this._container.offsetTop+50);return t<this._form.clientHeight?(Q(this._form,"leaflet-control-layers-scrollbar"),this._form.style.height=t+"px"):tt(this._form,"leaflet-control-layers-scrollbar"),this._checkDisabledLayers(),this},collapse:function(){return tt(this._container,"leaflet-control-layers-expanded"),this},_initLayout:function(){var t="leaflet-control-layers",i=this._container=G("div",t),e=this.options.collapsed;i.setAttribute("aria-haspopup",!0),wt(i),xt(i);var n=this._form=G("form",t+"-list");e&&(this._map.on("click",this.collapse,this),zi||mt(i,{mouseenter:this.expand,mouseleave:this.collapse},this));var o=this._layersLink=G("a",t+"-toggle",i);o.href="#",o.title="Layers",qi?(mt(o,"click",Lt),mt(o,"click",this.expand,this)):mt(o,"focus",this.expand,this),e||this.expand(),this._baseLayersList=G("div",t+"-base",n),this._separator=G("div",t+"-separator",n),this._overlaysList=G("div",t+"-overlays",n),i.appendChild(n)},_getLayer:function(t){for(var i=0;i<this._layers.length;i++)if(this._layers[i]&&n(this._layers[i].layer)===t)return this._layers[i]},_addLayer:function(t,i,n){this._map&&t.on("add remove",this._onLayerChange,this),this._layers.push({layer:t,name:i,overlay:n}),this.options.sortLayers&&this._layers.sort(e(function(t,i){return this.options.sortFunction(t.layer,i.layer,t.name,i.name)},this)),this.options.autoZIndex&&t.setZIndex&&(this._lastZIndex++,t.setZIndex(this._lastZIndex)),this._expandIfNotCollapsed()},_update:function(){if(!this._container)return this;Y(this._baseLayersList),Y(this._overlaysList),this._layerControlInputs=[];var t,i,e,n,o=0;for(e=0;e<this._layers.length;e++)n=this._layers[e],this._addItem(n),i=i||n.overlay,t=t||!n.overlay,o+=n.overlay?0:1;return this.options.hideSingleBase&&(t=t&&o>1,this._baseLayersList.style.display=t?"":"none"),this._separator.style.display=i&&t?"":"none",this},_onLayerChange:function(t){this._handlingClick||this._update();var i=this._getLayer(n(t.target)),e=i.overlay?"add"===t.type?"overlayadd":"overlayremove":"add"===t.type?"baselayerchange":null;e&&this._map.fire(e,i)},_createRadioElement:function(t,i){var e='<input type="radio" class="leaflet-control-layers-selector" name="'+t+'"'+(i?' checked="checked"':"")+"/>",n=document.createElement("div");return n.innerHTML=e,n.firstChild},_addItem:function(t){var i,e=document.createElement("label"),o=this._map.hasLayer(t.layer);t.overlay?((i=document.createElement("input")).type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=o):i=this._createRadioElement("leaflet-base-layers",o),this._layerControlInputs.push(i),i.layerId=n(t.layer),mt(i,"click",this._onInputClick,this);var s=document.createElement("span");s.innerHTML=" "+t.name;var r=document.createElement("div");return e.appendChild(r),r.appendChild(i),r.appendChild(s),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(e),this._checkDisabledLayers(),e},_onInputClick:function(){var t,i,e=this._layerControlInputs,n=[],o=[];this._handlingClick=!0;for(var s=e.length-1;s>=0;s--)t=e[s],i=this._getLayer(t.layerId).layer,t.checked?n.push(i):t.checked||o.push(i);for(s=0;s<o.length;s++)this._map.hasLayer(o[s])&&this._map.removeLayer(o[s]);for(s=0;s<n.length;s++)this._map.hasLayer(n[s])||this._map.addLayer(n[s]);this._handlingClick=!1,this._refocusOnMap()},_checkDisabledLayers:function(){for(var t,i,e=this._layerControlInputs,n=this._map.getZoom(),o=e.length-1;o>=0;o--)t=e[o],i=this._getLayer(t.layerId).layer,t.disabled=void 0!==i.options.minZoom&&n<i.options.minZoom||void 0!==i.options.maxZoom&&n>i.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this},_expand:function(){return this.expand()},_collapse:function(){return this.collapse()}}),Ce=Te.extend({options:{position:"topleft",zoomInText:"+",zoomInTitle:"Zoom in",zoomOutText:"&#x2212;",zoomOutTitle:"Zoom out"},onAdd:function(t){var i="leaflet-control-zoom",e=G("div",i+" leaflet-bar"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,i+"-in",e,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,i+"-out",e,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),e},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoom<this._map.getMaxZoom()&&this._map.zoomIn(this._map.options.zoomDelta*(t.shiftKey?3:1))},_zoomOut:function(t){!this._disabled&&this._map._zoom>this._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,i,e,n,o){var s=G("a",e,n);return s.innerHTML=t,s.href="#",s.title=i,s.setAttribute("role","button"),s.setAttribute("aria-label",i),wt(s),mt(s,"click",Lt),mt(s,"click",o,this),mt(s,"click",this._refocusOnMap,this),s},_updateDisabled:function(){var t=this._map,i="leaflet-disabled";tt(this._zoomInButton,i),tt(this._zoomOutButton,i),(this._disabled||t._zoom===t.getMinZoom())&&Q(this._zoomOutButton,i),(this._disabled||t._zoom===t.getMaxZoom())&&Q(this._zoomInButton,i)}});be.mergeOptions({zoomControl:!0}),be.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new Ce,this.addControl(this.zoomControl))});var Se=Te.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var i=G("div","leaflet-control-scale"),e=this.options;return this._addScales(e,"leaflet-control-scale-line",i),t.on(e.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,i,e){t.metric&&(this._mScale=G("div",i,e)),t.imperial&&(this._iScale=G("div",i,e))},_update:function(){var t=this._map,i=t.getSize().y/2,e=t.distance(t.containerPointToLatLng([0,i]),t.containerPointToLatLng([this.options.maxWidth,i]));this._updateScales(e)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var i=this._getRoundNum(t),e=i<1e3?i+" m":i/1e3+" km";this._updateScale(this._mScale,e,i/t)},_updateImperial:function(t){var i,e,n,o=3.2808399*t;o>5280?(i=o/5280,e=this._getRoundNum(i),this._updateScale(this._iScale,e+" mi",e/i)):(n=this._getRoundNum(o),this._updateScale(this._iScale,n+" ft",n/o))},_updateScale:function(t,i,e){t.style.width=Math.round(this.options.maxWidth*e)+"px",t.innerHTML=i},_getRoundNum:function(t){var i=Math.pow(10,(Math.floor(t)+"").length-1),e=t/i;return e=e>=10?10:e>=5?5:e>=3?3:e>=2?2:1,i*e}}),Ze=Te.extend({options:{position:"bottomright",prefix:'<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'},initialize:function(t){l(this,t),this._attributions={}},onAdd:function(t){t.attributionControl=this,this._container=G("div","leaflet-control-attribution"),wt(this._container);for(var i in t._layers)t._layers[i].getAttribution&&this.addAttribution(t._layers[i].getAttribution());return this._update(),this._container},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t?(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update(),this):this},removeAttribution:function(t){return t?(this._attributions[t]&&(this._attributions[t]--,this._update()),this):this},_update:function(){if(this._map){var t=[];for(var i in this._attributions)this._attributions[i]&&t.push(i);var e=[];this.options.prefix&&e.push(this.options.prefix),t.length&&e.push(t.join(", ")),this._container.innerHTML=e.join(" | ")}}});be.mergeOptions({attributionControl:!0}),be.addInitHook(function(){this.options.attributionControl&&(new Ze).addTo(this)});Te.Layers=Me,Te.Zoom=Ce,Te.Scale=Se,Te.Attribution=Ze,ze.layers=function(t,i,e){return new Me(t,i,e)},ze.zoom=function(t){return new Ce(t)},ze.scale=function(t){return new Se(t)},ze.attribution=function(t){return new Ze(t)};var Ee=v.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled?this:(this._enabled=!0,this.addHooks(),this)},disable:function(){return this._enabled?(this._enabled=!1,this.removeHooks(),this):this},enabled:function(){return!!this._enabled}});Ee.addTo=function(t,i){return t.addHandler(i,this),this};var ke,Ae={Events:li},Be=qi?"touchstart mousedown":"mousedown",Ie={mousedown:"mouseup",touchstart:"touchend",pointerdown:"touchend",MSPointerDown:"touchend"},Oe={mousedown:"mousemove",touchstart:"touchmove",pointerdown:"touchmove",MSPointerDown:"touchmove"},Re=ci.extend({options:{clickTolerance:3},initialize:function(t,i,e,n){l(this,n),this._element=t,this._dragStartTarget=i||t,this._preventOutline=e},enable:function(){this._enabled||(mt(this._dragStartTarget,Be,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(Re._dragging===this&&this.finishDrag(),ft(this._dragStartTarget,Be,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){if(!t._simulated&&this._enabled&&(this._moved=!1,!$(this._element,"leaflet-zoom-anim")&&!(Re._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||(Re._dragging=this,this._preventOutline&&ct(this._element),ut(),fi(),this._moving)))){this.fire("down");var i=t.touches?t.touches[0]:t,e=dt(this._element);this._startPoint=new x(i.clientX,i.clientY),this._parentScale=pt(e),mt(document,Oe[t.type],this._onMove,this),mt(document,Ie[t.type],this._onUp,this)}},_onMove:function(t){if(!t._simulated&&this._enabled)if(t.touches&&t.touches.length>1)this._moved=!0;else{var i=t.touches&&1===t.touches.length?t.touches[0]:t,e=new x(i.clientX,i.clientY)._subtract(this._startPoint);(e.x||e.y)&&(Math.abs(e.x)+Math.abs(e.y)<this.options.clickTolerance||(e.x/=this._parentScale.x,e.y/=this._parentScale.y,Pt(t),this._moved||(this.fire("dragstart"),this._moved=!0,this._startPos=ht(this._element).subtract(e),Q(document.body,"leaflet-dragging"),this._lastTarget=t.target||t.srcElement,window.SVGElementInstance&&this._lastTarget instanceof SVGElementInstance&&(this._lastTarget=this._lastTarget.correspondingUseElement),Q(this._lastTarget,"leaflet-drag-target")),this._newPos=this._startPos.add(e),this._moving=!0,g(this._animRequest),this._lastEvent=t,this._animRequest=f(this._updatePosition,this,!0)))}},_updatePosition:function(){var t={originalEvent:this._lastEvent};this.fire("predrag",t),at(this._element,this._newPos),this.fire("drag",t)},_onUp:function(t){!t._simulated&&this._enabled&&this.finishDrag()},finishDrag:function(){tt(document.body,"leaflet-dragging"),this._lastTarget&&(tt(this._lastTarget,"leaflet-drag-target"),this._lastTarget=null);for(var t in Oe)ft(document,Oe[t],this._onMove,this),ft(document,Ie[t],this._onUp,this);lt(),gi(),this._moved&&this._moving&&(g(this._animRequest),this.fire("dragend",{distance:this._newPos.distanceTo(this._startPos)})),this._moving=!1,Re._dragging=!1}}),Ne=(Object.freeze||Object)({simplify:Zt,pointToSegmentDistance:Et,closestPointOnSegment:function(t,i,e){return Dt(t,i,e)},clipSegment:It,_getEdgeIntersection:Ot,_getBitCode:Rt,_sqClosestPointOnSegment:Dt,isFlat:jt,_flat:Wt}),De=(Object.freeze||Object)({clipPolygon:Ht}),je={project:function(t){return new x(t.lng,t.lat)},unproject:function(t){return new M(t.y,t.x)},bounds:new P([-180,-90],[180,90])},We={R:6378137,R_MINOR:6356752.314245179,bounds:new P([-20037508.34279,-15496570.73972],[20037508.34279,18764656.23138]),project:function(t){var i=Math.PI/180,e=this.R,n=t.lat*i,o=this.R_MINOR/e,s=Math.sqrt(1-o*o),r=s*Math.sin(n),a=Math.tan(Math.PI/4-n/2)/Math.pow((1-r)/(1+r),s/2);return n=-e*Math.log(Math.max(a,1e-10)),new x(t.lng*i*e,n)},unproject:function(t){for(var i,e=180/Math.PI,n=this.R,o=this.R_MINOR/n,s=Math.sqrt(1-o*o),r=Math.exp(-t.y/n),a=Math.PI/2-2*Math.atan(r),h=0,u=.1;h<15&&Math.abs(u)>1e-7;h++)i=s*Math.sin(a),i=Math.pow((1-i)/(1+i),s/2),a+=u=Math.PI/2-2*Math.atan(r*i)-a;return new M(a*e,t.x*e/n)}},He=(Object.freeze||Object)({LonLat:je,Mercator:We,SphericalMercator:mi}),Fe=i({},pi,{code:"EPSG:3395",projection:We,transformation:function(){var t=.5/(Math.PI*We.R);return Z(t,.5,-t,.5)}()}),Ue=i({},pi,{code:"EPSG:4326",projection:je,transformation:Z(1/180,1,-1/180,.5)}),Ve=i({},di,{projection:je,transformation:Z(1,0,-1,0),scale:function(t){return Math.pow(2,t)},zoom:function(t){return Math.log(t)/Math.LN2},distance:function(t,i){var e=i.lng-t.lng,n=i.lat-t.lat;return Math.sqrt(e*e+n*n)},infinite:!0});di.Earth=pi,di.EPSG3395=Fe,di.EPSG3857=yi,di.EPSG900913=xi,di.EPSG4326=Ue,di.Simple=Ve;var qe=ci.extend({options:{pane:"overlayPane",attribution:null,bubblingMouseEvents:!0},addTo:function(t){return t.addLayer(this),this},remove:function(){return this.removeFrom(this._map||this._mapToAdd)},removeFrom:function(t){return t&&t.removeLayer(this),this},getPane:function(t){return this._map.getPane(t?this.options[t]||t:this.options.pane)},addInteractiveTarget:function(t){return this._map._targets[n(t)]=this,this},removeInteractiveTarget:function(t){return delete this._map._targets[n(t)],this},getAttribution:function(){return this.options.attribution},_layerAdd:function(t){var i=t.target;if(i.hasLayer(this)){if(this._map=i,this._zoomAnimated=i._zoomAnimated,this.getEvents){var e=this.getEvents();i.on(e,this),this.once("remove",function(){i.off(e,this)},this)}this.onAdd(i),this.getAttribution&&i.attributionControl&&i.attributionControl.addAttribution(this.getAttribution()),this.fire("add"),i.fire("layeradd",{layer:this})}}});be.include({addLayer:function(t){if(!t._layerAdd)throw new Error("The provided object is not a Layer.");var i=n(t);return this._layers[i]?this:(this._layers[i]=t,t._mapToAdd=this,t.beforeAdd&&t.beforeAdd(this),this.whenReady(t._layerAdd,t),this)},removeLayer:function(t){var i=n(t);return this._layers[i]?(this._loaded&&t.onRemove(this),t.getAttribution&&this.attributionControl&&this.attributionControl.removeAttribution(t.getAttribution()),delete this._layers[i],this._loaded&&(this.fire("layerremove",{layer:t}),t.fire("remove")),t._map=t._mapToAdd=null,this):this},hasLayer:function(t){return!!t&&n(t)in this._layers},eachLayer:function(t,i){for(var e in this._layers)t.call(i,this._layers[e]);return this},_addLayers:function(t){for(var i=0,e=(t=t?oi(t)?t:[t]:[]).length;i<e;i++)this.addLayer(t[i])},_addZoomLimit:function(t){!isNaN(t.options.maxZoom)&&isNaN(t.options.minZoom)||(this._zoomBoundLayers[n(t)]=t,this._updateZoomLevels())},_removeZoomLimit:function(t){var i=n(t);this._zoomBoundLayers[i]&&(delete this._zoomBoundLayers[i],this._updateZoomLevels())},_updateZoomLevels:function(){var t=1/0,i=-1/0,e=this._getZoomSpan();for(var n in this._zoomBoundLayers){var o=this._zoomBoundLayers[n].options;t=void 0===o.minZoom?t:Math.min(t,o.minZoom),i=void 0===o.maxZoom?i:Math.max(i,o.maxZoom)}this._layersMaxZoom=i===-1/0?void 0:i,this._layersMinZoom=t===1/0?void 0:t,e!==this._getZoomSpan()&&this.fire("zoomlevelschange"),void 0===this.options.maxZoom&&this._layersMaxZoom&&this.getZoom()>this._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()<this._layersMinZoom&&this.setZoom(this._layersMinZoom)}});var Ge=qe.extend({initialize:function(t,i){l(this,i),this._layers={};var e,n;if(t)for(e=0,n=t.length;e<n;e++)this.addLayer(t[e])},addLayer:function(t){var i=this.getLayerId(t);return this._layers[i]=t,this._map&&this._map.addLayer(t),this},removeLayer:function(t){var i=t in this._layers?t:this.getLayerId(t);return this._map&&this._layers[i]&&this._map.removeLayer(this._layers[i]),delete this._layers[i],this},hasLayer:function(t){return!!t&&(t in this._layers||this.getLayerId(t)in this._layers)},clearLayers:function(){return this.eachLayer(this.removeLayer,this)},invoke:function(t){var i,e,n=Array.prototype.slice.call(arguments,1);for(i in this._layers)(e=this._layers[i])[t]&&e[t].apply(e,n);return this},onAdd:function(t){this.eachLayer(t.addLayer,t)},onRemove:function(t){this.eachLayer(t.removeLayer,t)},eachLayer:function(t,i){for(var e in this._layers)t.call(i,this._layers[e]);return this},getLayer:function(t){return this._layers[t]},getLayers:function(){var t=[];return this.eachLayer(t.push,t),t},setZIndex:function(t){return this.invoke("setZIndex",t)},getLayerId:function(t){return n(t)}}),Ke=Ge.extend({addLayer:function(t){return this.hasLayer(t)?this:(t.addEventParent(this),Ge.prototype.addLayer.call(this,t),this.fire("layeradd",{layer:t}))},removeLayer:function(t){return this.hasLayer(t)?(t in this._layers&&(t=this._layers[t]),t.removeEventParent(this),Ge.prototype.removeLayer.call(this,t),this.fire("layerremove",{layer:t})):this},setStyle:function(t){return this.invoke("setStyle",t)},bringToFront:function(){return this.invoke("bringToFront")},bringToBack:function(){return this.invoke("bringToBack")},getBounds:function(){var t=new T;for(var i in this._layers){var e=this._layers[i];t.extend(e.getBounds?e.getBounds():e.getLatLng())}return t}}),Ye=v.extend({options:{popupAnchor:[0,0],tooltipAnchor:[0,0]},initialize:function(t){l(this,t)},createIcon:function(t){return this._createIcon("icon",t)},createShadow:function(t){return this._createIcon("shadow",t)},_createIcon:function(t,i){var e=this._getIconUrl(t);if(!e){if("icon"===t)throw new Error("iconUrl not set in Icon options (see the docs).");return null}var n=this._createImg(e,i&&"IMG"===i.tagName?i:null);return this._setIconStyles(n,t),n},_setIconStyles:function(t,i){var e=this.options,n=e[i+"Size"];"number"==typeof n&&(n=[n,n]);var o=w(n),s=w("shadow"===i&&e.shadowAnchor||e.iconAnchor||o&&o.divideBy(2,!0));t.className="leaflet-marker-"+i+" "+(e.className||""),s&&(t.style.marginLeft=-s.x+"px",t.style.marginTop=-s.y+"px"),o&&(t.style.width=o.x+"px",t.style.height=o.y+"px")},_createImg:function(t,i){return i=i||document.createElement("img"),i.src=t,i},_getIconUrl:function(t){return Yi&&this.options[t+"RetinaUrl"]||this.options[t+"Url"]}}),Xe=Ye.extend({options:{iconUrl:"marker-icon.png",iconRetinaUrl:"marker-icon-2x.png",shadowUrl:"marker-shadow.png",iconSize:[25,41],iconAnchor:[12,41],popupAnchor:[1,-34],tooltipAnchor:[16,-28],shadowSize:[41,41]},_getIconUrl:function(t){return Xe.imagePath||(Xe.imagePath=this._detectIconPath()),(this.options.imagePath||Xe.imagePath)+Ye.prototype._getIconUrl.call(this,t)},_detectIconPath:function(){var t=G("div","leaflet-default-icon-path",document.body),i=q(t,"background-image")||q(t,"backgroundImage");return document.body.removeChild(t),i=null===i||0!==i.indexOf("url")?"":i.replace(/^url\(["']?/,"").replace(/marker-icon\.png["']?\)$/,"")}}),Je=Ee.extend({initialize:function(t){this._marker=t},addHooks:function(){var t=this._marker._icon;this._draggable||(this._draggable=new Re(t,t,!0)),this._draggable.on({dragstart:this._onDragStart,predrag:this._onPreDrag,drag:this._onDrag,dragend:this._onDragEnd},this).enable(),Q(t,"leaflet-marker-draggable")},removeHooks:function(){this._draggable.off({dragstart:this._onDragStart,predrag:this._onPreDrag,drag:this._onDrag,dragend:this._onDragEnd},this).disable(),this._marker._icon&&tt(this._marker._icon,"leaflet-marker-draggable")},moved:function(){return this._draggable&&this._draggable._moved},_adjustPan:function(t){var i=this._marker,e=i._map,n=this._marker.options.autoPanSpeed,o=this._marker.options.autoPanPadding,s=ht(i._icon),r=e.getPixelBounds(),a=e.getPixelOrigin(),h=b(r.min._subtract(a).add(o),r.max._subtract(a).subtract(o));if(!h.contains(s)){var u=w((Math.max(h.max.x,s.x)-h.max.x)/(r.max.x-h.max.x)-(Math.min(h.min.x,s.x)-h.min.x)/(r.min.x-h.min.x),(Math.max(h.max.y,s.y)-h.max.y)/(r.max.y-h.max.y)-(Math.min(h.min.y,s.y)-h.min.y)/(r.min.y-h.min.y)).multiplyBy(n);e.panBy(u,{animate:!1}),this._draggable._newPos._add(u),this._draggable._startPos._add(u),at(i._icon,this._draggable._newPos),this._onDrag(t),this._panRequest=f(this._adjustPan.bind(this,t))}},_onDragStart:function(){this._oldLatLng=this._marker.getLatLng(),this._marker.closePopup().fire("movestart").fire("dragstart")},_onPreDrag:function(t){this._marker.options.autoPan&&(g(this._panRequest),this._panRequest=f(this._adjustPan.bind(this,t)))},_onDrag:function(t){var i=this._marker,e=i._shadow,n=ht(i._icon),o=i._map.layerPointToLatLng(n);e&&at(e,n),i._latlng=o,t.latlng=o,t.oldLatLng=this._oldLatLng,i.fire("move",t).fire("drag",t)},_onDragEnd:function(t){g(this._panRequest),delete this._oldLatLng,this._marker.fire("moveend").fire("dragend",t)}}),$e=qe.extend({options:{icon:new Xe,interactive:!0,keyboard:!0,title:"",alt:"",zIndexOffset:0,opacity:1,riseOnHover:!1,riseOffset:250,pane:"markerPane",bubblingMouseEvents:!1,draggable:!1,autoPan:!1,autoPanPadding:[50,50],autoPanSpeed:10},initialize:function(t,i){l(this,i),this._latlng=C(t)},onAdd:function(t){this._zoomAnimated=this._zoomAnimated&&t.options.markerZoomAnimation,this._zoomAnimated&&t.on("zoomanim",this._animateZoom,this),this._initIcon(),this.update()},onRemove:function(t){this.dragging&&this.dragging.enabled()&&(this.options.draggable=!0,this.dragging.removeHooks()),delete this.dragging,this._zoomAnimated&&t.off("zoomanim",this._animateZoom,this),this._removeIcon(),this._removeShadow()},getEvents:function(){return{zoom:this.update,viewreset:this.update}},getLatLng:function(){return this._latlng},setLatLng:function(t){var i=this._latlng;return this._latlng=C(t),this.update(),this.fire("move",{oldLatLng:i,latlng:this._latlng})},setZIndexOffset:function(t){return this.options.zIndexOffset=t,this.update()},setIcon:function(t){return this.options.icon=t,this._map&&(this._initIcon(),this.update()),this._popup&&this.bindPopup(this._popup,this._popup.options),this},getElement:function(){return this._icon},update:function(){if(this._icon&&this._map){var t=this._map.latLngToLayerPoint(this._latlng).round();this._setPos(t)}return this},_initIcon:function(){var t=this.options,i="leaflet-zoom-"+(this._zoomAnimated?"animated":"hide"),e=t.icon.createIcon(this._icon),n=!1;e!==this._icon&&(this._icon&&this._removeIcon(),n=!0,t.title&&(e.title=t.title),"IMG"===e.tagName&&(e.alt=t.alt||"")),Q(e,i),t.keyboard&&(e.tabIndex="0"),this._icon=e,t.riseOnHover&&this.on({mouseover:this._bringToFront,mouseout:this._resetZIndex});var o=t.icon.createShadow(this._shadow),s=!1;o!==this._shadow&&(this._removeShadow(),s=!0),o&&(Q(o,i),o.alt=""),this._shadow=o,t.opacity<1&&this._updateOpacity(),n&&this.getPane().appendChild(this._icon),this._initInteraction(),o&&s&&this.getPane("shadowPane").appendChild(this._shadow)},_removeIcon:function(){this.options.riseOnHover&&this.off({mouseover:this._bringToFront,mouseout:this._resetZIndex}),K(this._icon),this.removeInteractiveTarget(this._icon),this._icon=null},_removeShadow:function(){this._shadow&&K(this._shadow),this._shadow=null},_setPos:function(t){at(this._icon,t),this._shadow&&at(this._shadow,t),this._zIndex=t.y+this.options.zIndexOffset,this._resetZIndex()},_updateZIndex:function(t){this._icon.style.zIndex=this._zIndex+t},_animateZoom:function(t){var i=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center).round();this._setPos(i)},_initInteraction:function(){if(this.options.interactive&&(Q(this._icon,"leaflet-interactive"),this.addInteractiveTarget(this._icon),Je)){var t=this.options.draggable;this.dragging&&(t=this.dragging.enabled(),this.dragging.disable()),this.dragging=new Je(this),t&&this.dragging.enable()}},setOpacity:function(t){return this.options.opacity=t,this._map&&this._updateOpacity(),this},_updateOpacity:function(){var t=this.options.opacity;nt(this._icon,t),this._shadow&&nt(this._shadow,t)},_bringToFront:function(){this._updateZIndex(this.options.riseOffset)},_resetZIndex:function(){this._updateZIndex(0)},_getPopupAnchor:function(){return this.options.icon.options.popupAnchor},_getTooltipAnchor:function(){return this.options.icon.options.tooltipAnchor}}),Qe=qe.extend({options:{stroke:!0,color:"#3388ff",weight:3,opacity:1,lineCap:"round",lineJoin:"round",dashArray:null,dashOffset:null,fill:!1,fillColor:null,fillOpacity:.2,fillRule:"evenodd",interactive:!0,bubblingMouseEvents:!0},beforeAdd:function(t){this._renderer=t.getRenderer(this)},onAdd:function(){this._renderer._initPath(this),this._reset(),this._renderer._addPath(this)},onRemove:function(){this._renderer._removePath(this)},redraw:function(){return this._map&&this._renderer._updatePath(this),this},setStyle:function(t){return l(this,t),this._renderer&&this._renderer._updateStyle(this),this},bringToFront:function(){return this._renderer&&this._renderer._bringToFront(this),this},bringToBack:function(){return this._renderer&&this._renderer._bringToBack(this),this},getElement:function(){return this._path},_reset:function(){this._project(),this._update()},_clickTolerance:function(){return(this.options.stroke?this.options.weight/2:0)+this._renderer.options.tolerance}}),tn=Qe.extend({options:{fill:!0,radius:10},initialize:function(t,i){l(this,i),this._latlng=C(t),this._radius=this.options.radius},setLatLng:function(t){return this._latlng=C(t),this.redraw(),this.fire("move",{latlng:this._latlng})},getLatLng:function(){return this._latlng},setRadius:function(t){return this.options.radius=this._radius=t,this.redraw()},getRadius:function(){return this._radius},setStyle:function(t){var i=t&&t.radius||this._radius;return Qe.prototype.setStyle.call(this,t),this.setRadius(i),this},_project:function(){this._point=this._map.latLngToLayerPoint(this._latlng),this._updateBounds()},_updateBounds:function(){var t=this._radius,i=this._radiusY||t,e=this._clickTolerance(),n=[t+e,i+e];this._pxBounds=new P(this._point.subtract(n),this._point.add(n))},_update:function(){this._map&&this._updatePath()},_updatePath:function(){this._renderer._updateCircle(this)},_empty:function(){return this._radius&&!this._renderer._bounds.intersects(this._pxBounds)},_containsPoint:function(t){return t.distanceTo(this._point)<=this._radius+this._clickTolerance()}}),en=tn.extend({initialize:function(t,e,n){if("number"==typeof e&&(e=i({},n,{radius:e})),l(this,e),this._latlng=C(t),isNaN(this.options.radius))throw new Error("Circle radius cannot be NaN");this._mRadius=this.options.radius},setRadius:function(t){return this._mRadius=t,this.redraw()},getRadius:function(){return this._mRadius},getBounds:function(){var t=[this._radius,this._radiusY||this._radius];return new T(this._map.layerPointToLatLng(this._point.subtract(t)),this._map.layerPointToLatLng(this._point.add(t)))},setStyle:Qe.prototype.setStyle,_project:function(){var t=this._latlng.lng,i=this._latlng.lat,e=this._map,n=e.options.crs;if(n.distance===pi.distance){var o=Math.PI/180,s=this._mRadius/pi.R/o,r=e.project([i+s,t]),a=e.project([i-s,t]),h=r.add(a).divideBy(2),u=e.unproject(h).lat,l=Math.acos((Math.cos(s*o)-Math.sin(i*o)*Math.sin(u*o))/(Math.cos(i*o)*Math.cos(u*o)))/o;(isNaN(l)||0===l)&&(l=s/Math.cos(Math.PI/180*i)),this._point=h.subtract(e.getPixelOrigin()),this._radius=isNaN(l)?0:h.x-e.project([u,t-l]).x,this._radiusY=h.y-r.y}else{var c=n.unproject(n.project(this._latlng).subtract([this._mRadius,0]));this._point=e.latLngToLayerPoint(this._latlng),this._radius=this._point.x-e.latLngToLayerPoint(c).x}this._updateBounds()}}),nn=Qe.extend({options:{smoothFactor:1,noClip:!1},initialize:function(t,i){l(this,i),this._setLatLngs(t)},getLatLngs:function(){return this._latlngs},setLatLngs:function(t){return this._setLatLngs(t),this.redraw()},isEmpty:function(){return!this._latlngs.length},closestLayerPoint:function(t){for(var i,e,n=1/0,o=null,s=Dt,r=0,a=this._parts.length;r<a;r++)for(var h=this._parts[r],u=1,l=h.length;u<l;u++){var c=s(t,i=h[u-1],e=h[u],!0);c<n&&(n=c,o=s(t,i,e))}return o&&(o.distance=Math.sqrt(n)),o},getCenter:function(){if(!this._map)throw new Error("Must add layer to map before using getCenter()");var t,i,e,n,o,s,r,a=this._rings[0],h=a.length;if(!h)return null;for(t=0,i=0;t<h-1;t++)i+=a[t].distanceTo(a[t+1])/2;if(0===i)return this._map.layerPointToLatLng(a[0]);for(t=0,n=0;t<h-1;t++)if(o=a[t],s=a[t+1],e=o.distanceTo(s),(n+=e)>i)return r=(n-i)/e,this._map.layerPointToLatLng([s.x-r*(s.x-o.x),s.y-r*(s.y-o.y)])},getBounds:function(){return this._bounds},addLatLng:function(t,i){return i=i||this._defaultShape(),t=C(t),i.push(t),this._bounds.extend(t),this.redraw()},_setLatLngs:function(t){this._bounds=new T,this._latlngs=this._convertLatLngs(t)},_defaultShape:function(){return jt(this._latlngs)?this._latlngs:this._latlngs[0]},_convertLatLngs:function(t){for(var i=[],e=jt(t),n=0,o=t.length;n<o;n++)e?(i[n]=C(t[n]),this._bounds.extend(i[n])):i[n]=this._convertLatLngs(t[n]);return i},_project:function(){var t=new P;this._rings=[],this._projectLatlngs(this._latlngs,this._rings,t);var i=this._clickTolerance(),e=new x(i,i);this._bounds.isValid()&&t.isValid()&&(t.min._subtract(e),t.max._add(e),this._pxBounds=t)},_projectLatlngs:function(t,i,e){var n,o,s=t[0]instanceof M,r=t.length;if(s){for(o=[],n=0;n<r;n++)o[n]=this._map.latLngToLayerPoint(t[n]),e.extend(o[n]);i.push(o)}else for(n=0;n<r;n++)this._projectLatlngs(t[n],i,e)},_clipPoints:function(){var t=this._renderer._bounds;if(this._parts=[],this._pxBounds&&this._pxBounds.intersects(t))if(this.options.noClip)this._parts=this._rings;else{var i,e,n,o,s,r,a,h=this._parts;for(i=0,n=0,o=this._rings.length;i<o;i++)for(e=0,s=(a=this._rings[i]).length;e<s-1;e++)(r=It(a[e],a[e+1],t,e,!0))&&(h[n]=h[n]||[],h[n].push(r[0]),r[1]===a[e+1]&&e!==s-2||(h[n].push(r[1]),n++))}},_simplifyPoints:function(){for(var t=this._parts,i=this.options.smoothFactor,e=0,n=t.length;e<n;e++)t[e]=Zt(t[e],i)},_update:function(){this._map&&(this._clipPoints(),this._simplifyPoints(),this._updatePath())},_updatePath:function(){this._renderer._updatePoly(this)},_containsPoint:function(t,i){var e,n,o,s,r,a,h=this._clickTolerance();if(!this._pxBounds||!this._pxBounds.contains(t))return!1;for(e=0,s=this._parts.length;e<s;e++)for(n=0,o=(r=(a=this._parts[e]).length)-1;n<r;o=n++)if((i||0!==n)&&Et(t,a[o],a[n])<=h)return!0;return!1}});nn._flat=Wt;var on=nn.extend({options:{fill:!0},isEmpty:function(){return!this._latlngs.length||!this._latlngs[0].length},getCenter:function(){if(!this._map)throw new Error("Must add layer to map before using getCenter()");var t,i,e,n,o,s,r,a,h,u=this._rings[0],l=u.length;if(!l)return null;for(s=r=a=0,t=0,i=l-1;t<l;i=t++)e=u[t],n=u[i],o=e.y*n.x-n.y*e.x,r+=(e.x+n.x)*o,a+=(e.y+n.y)*o,s+=3*o;return h=0===s?u[0]:[r/s,a/s],this._map.layerPointToLatLng(h)},_convertLatLngs:function(t){var i=nn.prototype._convertLatLngs.call(this,t),e=i.length;return e>=2&&i[0]instanceof M&&i[0].equals(i[e-1])&&i.pop(),i},_setLatLngs:function(t){nn.prototype._setLatLngs.call(this,t),jt(this._latlngs)&&(this._latlngs=[this._latlngs])},_defaultShape:function(){return jt(this._latlngs[0])?this._latlngs[0]:this._latlngs[0][0]},_clipPoints:function(){var t=this._renderer._bounds,i=this.options.weight,e=new x(i,i);if(t=new P(t.min.subtract(e),t.max.add(e)),this._parts=[],this._pxBounds&&this._pxBounds.intersects(t))if(this.options.noClip)this._parts=this._rings;else for(var n,o=0,s=this._rings.length;o<s;o++)(n=Ht(this._rings[o],t,!0)).length&&this._parts.push(n)},_updatePath:function(){this._renderer._updatePoly(this,!0)},_containsPoint:function(t){var i,e,n,o,s,r,a,h,u=!1;if(!this._pxBounds||!this._pxBounds.contains(t))return!1;for(o=0,a=this._parts.length;o<a;o++)for(s=0,r=(h=(i=this._parts[o]).length)-1;s<h;r=s++)e=i[s],n=i[r],e.y>t.y!=n.y>t.y&&t.x<(n.x-e.x)*(t.y-e.y)/(n.y-e.y)+e.x&&(u=!u);return u||nn.prototype._containsPoint.call(this,t,!0)}}),sn=Ke.extend({initialize:function(t,i){l(this,i),this._layers={},t&&this.addData(t)},addData:function(t){var i,e,n,o=oi(t)?t:t.features;if(o){for(i=0,e=o.length;i<e;i++)((n=o[i]).geometries||n.geometry||n.features||n.coordinates)&&this.addData(n);return this}var s=this.options;if(s.filter&&!s.filter(t))return this;var r=Ft(t,s);return r?(r.feature=Yt(t),r.defaultOptions=r.options,this.resetStyle(r),s.onEachFeature&&s.onEachFeature(t,r),this.addLayer(r)):this},resetStyle:function(t){return t.options=i({},t.defaultOptions),this._setLayerStyle(t,this.options.style),this},setStyle:function(t){return this.eachLayer(function(i){this._setLayerStyle(i,t)},this)},_setLayerStyle:function(t,i){"function"==typeof i&&(i=i(t.feature)),t.setStyle&&t.setStyle(i)}}),rn={toGeoJSON:function(t){return Kt(this,{type:"Point",coordinates:qt(this.getLatLng(),t)})}};$e.include(rn),en.include(rn),tn.include(rn),nn.include({toGeoJSON:function(t){var i=!jt(this._latlngs),e=Gt(this._latlngs,i?1:0,!1,t);return Kt(this,{type:(i?"Multi":"")+"LineString",coordinates:e})}}),on.include({toGeoJSON:function(t){var i=!jt(this._latlngs),e=i&&!jt(this._latlngs[0]),n=Gt(this._latlngs,e?2:i?1:0,!0,t);return i||(n=[n]),Kt(this,{type:(e?"Multi":"")+"Polygon",coordinates:n})}}),Ge.include({toMultiPoint:function(t){var i=[];return this.eachLayer(function(e){i.push(e.toGeoJSON(t).geometry.coordinates)}),Kt(this,{type:"MultiPoint",coordinates:i})},toGeoJSON:function(t){var i=this.feature&&this.feature.geometry&&this.feature.geometry.type;if("MultiPoint"===i)return this.toMultiPoint(t);var e="GeometryCollection"===i,n=[];return this.eachLayer(function(i){if(i.toGeoJSON){var o=i.toGeoJSON(t);if(e)n.push(o.geometry);else{var s=Yt(o);"FeatureCollection"===s.type?n.push.apply(n,s.features):n.push(s)}}}),e?Kt(this,{geometries:n,type:"GeometryCollection"}):{type:"FeatureCollection",features:n}}});var an=Xt,hn=qe.extend({options:{opacity:1,alt:"",interactive:!1,crossOrigin:!1,errorOverlayUrl:"",zIndex:1,className:""},initialize:function(t,i,e){this._url=t,this._bounds=z(i),l(this,e)},onAdd:function(){this._image||(this._initImage(),this.options.opacity<1&&this._updateOpacity()),this.options.interactive&&(Q(this._image,"leaflet-interactive"),this.addInteractiveTarget(this._image)),this.getPane().appendChild(this._image),this._reset()},onRemove:function(){K(this._image),this.options.interactive&&this.removeInteractiveTarget(this._image)},setOpacity:function(t){return this.options.opacity=t,this._image&&this._updateOpacity(),this},setStyle:function(t){return t.opacity&&this.setOpacity(t.opacity),this},bringToFront:function(){return this._map&&X(this._image),this},bringToBack:function(){return this._map&&J(this._image),this},setUrl:function(t){return this._url=t,this._image&&(this._image.src=t),this},setBounds:function(t){return this._bounds=z(t),this._map&&this._reset(),this},getEvents:function(){var t={zoom:this._reset,viewreset:this._reset};return this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},setZIndex:function(t){return this.options.zIndex=t,this._updateZIndex(),this},getBounds:function(){return this._bounds},getElement:function(){return this._image},_initImage:function(){var t="IMG"===this._url.tagName,i=this._image=t?this._url:G("img");Q(i,"leaflet-image-layer"),this._zoomAnimated&&Q(i,"leaflet-zoom-animated"),this.options.className&&Q(i,this.options.className),i.onselectstart=r,i.onmousemove=r,i.onload=e(this.fire,this,"load"),i.onerror=e(this._overlayOnError,this,"error"),(this.options.crossOrigin||""===this.options.crossOrigin)&&(i.crossOrigin=!0===this.options.crossOrigin?"":this.options.crossOrigin),this.options.zIndex&&this._updateZIndex(),t?this._url=i.src:(i.src=this._url,i.alt=this.options.alt)},_animateZoom:function(t){var i=this._map.getZoomScale(t.zoom),e=this._map._latLngBoundsToNewLayerBounds(this._bounds,t.zoom,t.center).min;rt(this._image,e,i)},_reset:function(){var t=this._image,i=new P(this._map.latLngToLayerPoint(this._bounds.getNorthWest()),this._map.latLngToLayerPoint(this._bounds.getSouthEast())),e=i.getSize();at(t,i.min),t.style.width=e.x+"px",t.style.height=e.y+"px"},_updateOpacity:function(){nt(this._image,this.options.opacity)},_updateZIndex:function(){this._image&&void 0!==this.options.zIndex&&null!==this.options.zIndex&&(this._image.style.zIndex=this.options.zIndex)},_overlayOnError:function(){this.fire("error");var t=this.options.errorOverlayUrl;t&&this._url!==t&&(this._url=t,this._image.src=t)}}),un=hn.extend({options:{autoplay:!0,loop:!0},_initImage:function(){var t="VIDEO"===this._url.tagName,i=this._image=t?this._url:G("video");if(Q(i,"leaflet-image-layer"),this._zoomAnimated&&Q(i,"leaflet-zoom-animated"),i.onselectstart=r,i.onmousemove=r,i.onloadeddata=e(this.fire,this,"load"),t){for(var n=i.getElementsByTagName("source"),o=[],s=0;s<n.length;s++)o.push(n[s].src);this._url=n.length>0?o:[i.src]}else{oi(this._url)||(this._url=[this._url]),i.autoplay=!!this.options.autoplay,i.loop=!!this.options.loop;for(var a=0;a<this._url.length;a++){var h=G("source");h.src=this._url[a],i.appendChild(h)}}}}),ln=qe.extend({options:{offset:[0,7],className:"",pane:"popupPane"},initialize:function(t,i){l(this,t),this._source=i},onAdd:function(t){this._zoomAnimated=t._zoomAnimated,this._container||this._initLayout(),t._fadeAnimated&&nt(this._container,0),clearTimeout(this._removeTimeout),this.getPane().appendChild(this._container),this.update(),t._fadeAnimated&&nt(this._container,1),this.bringToFront()},onRemove:function(t){t._fadeAnimated?(nt(this._container,0),this._removeTimeout=setTimeout(e(K,void 0,this._container),200)):K(this._container)},getLatLng:function(){return this._latlng},setLatLng:function(t){return this._latlng=C(t),this._map&&(this._updatePosition(),this._adjustPan()),this},getContent:function(){return this._content},setContent:function(t){return this._content=t,this.update(),this},getElement:function(){return this._container},update:function(){this._map&&(this._container.style.visibility="hidden",this._updateContent(),this._updateLayout(),this._updatePosition(),this._container.style.visibility="",this._adjustPan())},getEvents:function(){var t={zoom:this._updatePosition,viewreset:this._updatePosition};return this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},isOpen:function(){return!!this._map&&this._map.hasLayer(this)},bringToFront:function(){return this._map&&X(this._container),this},bringToBack:function(){return this._map&&J(this._container),this},_updateContent:function(){if(this._content){var t=this._contentNode,i="function"==typeof this._content?this._content(this._source||this):this._content;if("string"==typeof i)t.innerHTML=i;else{for(;t.hasChildNodes();)t.removeChild(t.firstChild);t.appendChild(i)}this.fire("contentupdate")}},_updatePosition:function(){if(this._map){var t=this._map.latLngToLayerPoint(this._latlng),i=w(this.options.offset),e=this._getAnchor();this._zoomAnimated?at(this._container,t.add(e)):i=i.add(t).add(e);var n=this._containerBottom=-i.y,o=this._containerLeft=-Math.round(this._containerWidth/2)+i.x;this._container.style.bottom=n+"px",this._container.style.left=o+"px"}},_getAnchor:function(){return[0,0]}}),cn=ln.extend({options:{maxWidth:300,minWidth:50,maxHeight:null,autoPan:!0,autoPanPaddingTopLeft:null,autoPanPaddingBottomRight:null,autoPanPadding:[5,5],keepInView:!1,closeButton:!0,autoClose:!0,closeOnEscapeKey:!0,className:""},openOn:function(t){return t.openPopup(this),this},onAdd:function(t){ln.prototype.onAdd.call(this,t),t.fire("popupopen",{popup:this}),this._source&&(this._source.fire("popupopen",{popup:this},!0),this._source instanceof Qe||this._source.on("preclick",yt))},onRemove:function(t){ln.prototype.onRemove.call(this,t),t.fire("popupclose",{popup:this}),this._source&&(this._source.fire("popupclose",{popup:this},!0),this._source instanceof Qe||this._source.off("preclick",yt))},getEvents:function(){var t=ln.prototype.getEvents.call(this);return(void 0!==this.options.closeOnClick?this.options.closeOnClick:this._map.options.closePopupOnClick)&&(t.preclick=this._close),this.options.keepInView&&(t.moveend=this._adjustPan),t},_close:function(){this._map&&this._map.closePopup(this)},_initLayout:function(){var t="leaflet-popup",i=this._container=G("div",t+" "+(this.options.className||"")+" leaflet-zoom-animated"),e=this._wrapper=G("div",t+"-content-wrapper",i);if(this._contentNode=G("div",t+"-content",e),wt(e),xt(this._contentNode),mt(e,"contextmenu",yt),this._tipContainer=G("div",t+"-tip-container",i),this._tip=G("div",t+"-tip",this._tipContainer),this.options.closeButton){var n=this._closeButton=G("a",t+"-close-button",i);n.href="#close",n.innerHTML="&#215;",mt(n,"click",this._onCloseButtonClick,this)}},_updateLayout:function(){var t=this._contentNode,i=t.style;i.width="",i.whiteSpace="nowrap";var e=t.offsetWidth;e=Math.min(e,this.options.maxWidth),e=Math.max(e,this.options.minWidth),i.width=e+1+"px",i.whiteSpace="",i.height="";var n=t.offsetHeight,o=this.options.maxHeight;o&&n>o?(i.height=o+"px",Q(t,"leaflet-popup-scrolled")):tt(t,"leaflet-popup-scrolled"),this._containerWidth=this._container.offsetWidth},_animateZoom:function(t){var i=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center),e=this._getAnchor();at(this._container,i.add(e))},_adjustPan:function(){if(!(!this.options.autoPan||this._map._panAnim&&this._map._panAnim._inProgress)){var t=this._map,i=parseInt(q(this._container,"marginBottom"),10)||0,e=this._container.offsetHeight+i,n=this._containerWidth,o=new x(this._containerLeft,-e-this._containerBottom);o._add(ht(this._container));var s=t.layerPointToContainerPoint(o),r=w(this.options.autoPanPadding),a=w(this.options.autoPanPaddingTopLeft||r),h=w(this.options.autoPanPaddingBottomRight||r),u=t.getSize(),l=0,c=0;s.x+n+h.x>u.x&&(l=s.x+n-u.x+h.x),s.x-l-a.x<0&&(l=s.x-a.x),s.y+e+h.y>u.y&&(c=s.y+e-u.y+h.y),s.y-c-a.y<0&&(c=s.y-a.y),(l||c)&&t.fire("autopanstart").panBy([l,c])}},_onCloseButtonClick:function(t){this._close(),Lt(t)},_getAnchor:function(){return w(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}});be.mergeOptions({closePopupOnClick:!0}),be.include({openPopup:function(t,i,e){return t instanceof cn||(t=new cn(e).setContent(t)),i&&t.setLatLng(i),this.hasLayer(t)?this:(this._popup&&this._popup.options.autoClose&&this.closePopup(),this._popup=t,this.addLayer(t))},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&this.removeLayer(t),this}}),qe.include({bindPopup:function(t,i){return t instanceof cn?(l(t,i),this._popup=t,t._source=this):(this._popup&&!i||(this._popup=new cn(i,this)),this._popup.setContent(t)),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t,i){if(t instanceof qe||(i=t,t=this),t instanceof Ke)for(var e in this._layers){t=this._layers[e];break}return i||(i=t.getCenter?t.getCenter():t.getLatLng()),this._popup&&this._map&&(this._popup._source=t,this._popup.update(),this._map.openPopup(this._popup,i)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(t){return this._popup&&(this._popup._map?this.closePopup():this.openPopup(t)),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var i=t.layer||t.target;this._popup&&this._map&&(Lt(t),i instanceof Qe?this.openPopup(t.layer||t.target,t.latlng):this._map.hasLayer(this._popup)&&this._popup._source===i?this.closePopup():this.openPopup(i,t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}});var _n=ln.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,interactive:!1,opacity:.9},onAdd:function(t){ln.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&this._source.fire("tooltipopen",{tooltip:this},!0)},onRemove:function(t){ln.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&this._source.fire("tooltipclose",{tooltip:this},!0)},getEvents:function(){var t=ln.prototype.getEvents.call(this);return qi&&!this.options.permanent&&(t.preclick=this._close),t},_close:function(){this._map&&this._map.closeTooltip(this)},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=G("div",t)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var i=this._map,e=this._container,n=i.latLngToContainerPoint(i.getCenter()),o=i.layerPointToContainerPoint(t),s=this.options.direction,r=e.offsetWidth,a=e.offsetHeight,h=w(this.options.offset),u=this._getAnchor();"top"===s?t=t.add(w(-r/2+h.x,-a+h.y+u.y,!0)):"bottom"===s?t=t.subtract(w(r/2-h.x,-h.y,!0)):"center"===s?t=t.subtract(w(r/2+h.x,a/2-u.y+h.y,!0)):"right"===s||"auto"===s&&o.x<n.x?(s="right",t=t.add(w(h.x+u.x,u.y-a/2+h.y,!0))):(s="left",t=t.subtract(w(r+u.x-h.x,a/2-u.y-h.y,!0))),tt(e,"leaflet-tooltip-right"),tt(e,"leaflet-tooltip-left"),tt(e,"leaflet-tooltip-top"),tt(e,"leaflet-tooltip-bottom"),Q(e,"leaflet-tooltip-"+s),at(e,t)},_updatePosition:function(){var t=this._map.latLngToLayerPoint(this._latlng);this._setPosition(t)},setOpacity:function(t){this.options.opacity=t,this._container&&nt(this._container,t)},_animateZoom:function(t){var i=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center);this._setPosition(i)},_getAnchor:function(){return w(this._source&&this._source._getTooltipAnchor&&!this.options.sticky?this._source._getTooltipAnchor():[0,0])}});be.include({openTooltip:function(t,i,e){return t instanceof _n||(t=new _n(e).setContent(t)),i&&t.setLatLng(i),this.hasLayer(t)?this:this.addLayer(t)},closeTooltip:function(t){return t&&this.removeLayer(t),this}}),qe.include({bindTooltip:function(t,i){return t instanceof _n?(l(t,i),this._tooltip=t,t._source=this):(this._tooltip&&!i||(this._tooltip=new _n(i,this)),this._tooltip.setContent(t)),this._initTooltipInteractions(),this._tooltip.options.permanent&&this._map&&this._map.hasLayer(this)&&this.openTooltip(),this},unbindTooltip:function(){return this._tooltip&&(this._initTooltipInteractions(!0),this.closeTooltip(),this._tooltip=null),this},_initTooltipInteractions:function(t){if(t||!this._tooltipHandlersAdded){var i=t?"off":"on",e={remove:this.closeTooltip,move:this._moveTooltip};this._tooltip.options.permanent?e.add=this._openTooltip:(e.mouseover=this._openTooltip,e.mouseout=this.closeTooltip,this._tooltip.options.sticky&&(e.mousemove=this._moveTooltip),qi&&(e.click=this._openTooltip)),this[i](e),this._tooltipHandlersAdded=!t}},openTooltip:function(t,i){if(t instanceof qe||(i=t,t=this),t instanceof Ke)for(var e in this._layers){t=this._layers[e];break}return i||(i=t.getCenter?t.getCenter():t.getLatLng()),this._tooltip&&this._map&&(this._tooltip._source=t,this._tooltip.update(),this._map.openTooltip(this._tooltip,i),this._tooltip.options.interactive&&this._tooltip._container&&(Q(this._tooltip._container,"leaflet-clickable"),this.addInteractiveTarget(this._tooltip._container))),this},closeTooltip:function(){return this._tooltip&&(this._tooltip._close(),this._tooltip.options.interactive&&this._tooltip._container&&(tt(this._tooltip._container,"leaflet-clickable"),this.removeInteractiveTarget(this._tooltip._container))),this},toggleTooltip:function(t){return this._tooltip&&(this._tooltip._map?this.closeTooltip():this.openTooltip(t)),this},isTooltipOpen:function(){return this._tooltip.isOpen()},setTooltipContent:function(t){return this._tooltip&&this._tooltip.setContent(t),this},getTooltip:function(){return this._tooltip},_openTooltip:function(t){var i=t.layer||t.target;this._tooltip&&this._map&&this.openTooltip(i,this._tooltip.options.sticky?t.latlng:void 0)},_moveTooltip:function(t){var i,e,n=t.latlng;this._tooltip.options.sticky&&t.originalEvent&&(i=this._map.mouseEventToContainerPoint(t.originalEvent),e=this._map.containerPointToLayerPoint(i),n=this._map.layerPointToLatLng(e)),this._tooltip.setLatLng(n)}});var dn=Ye.extend({options:{iconSize:[12,12],html:!1,bgPos:null,className:"leaflet-div-icon"},createIcon:function(t){var i=t&&"DIV"===t.tagName?t:document.createElement("div"),e=this.options;if(i.innerHTML=!1!==e.html?e.html:"",e.bgPos){var n=w(e.bgPos);i.style.backgroundPosition=-n.x+"px "+-n.y+"px"}return this._setIconStyles(i,"icon"),i},createShadow:function(){return null}});Ye.Default=Xe;var pn=qe.extend({options:{tileSize:256,opacity:1,updateWhenIdle:Wi,updateWhenZooming:!0,updateInterval:200,zIndex:1,bounds:null,minZoom:0,maxZoom:void 0,maxNativeZoom:void 0,minNativeZoom:void 0,noWrap:!1,pane:"tilePane",className:"",keepBuffer:2},initialize:function(t){l(this,t)},onAdd:function(){this._initContainer(),this._levels={},this._tiles={},this._resetView(),this._update()},beforeAdd:function(t){t._addZoomLimit(this)},onRemove:function(t){this._removeAllTiles(),K(this._container),t._removeZoomLimit(this),this._container=null,this._tileZoom=void 0},bringToFront:function(){return this._map&&(X(this._container),this._setAutoZIndex(Math.max)),this},bringToBack:function(){return this._map&&(J(this._container),this._setAutoZIndex(Math.min)),this},getContainer:function(){return this._container},setOpacity:function(t){return this.options.opacity=t,this._updateOpacity(),this},setZIndex:function(t){return this.options.zIndex=t,this._updateZIndex(),this},isLoading:function(){return this._loading},redraw:function(){return this._map&&(this._removeAllTiles(),this._update()),this},getEvents:function(){var t={viewprereset:this._invalidateAll,viewreset:this._resetView,zoom:this._resetView,moveend:this._onMoveEnd};return this.options.updateWhenIdle||(this._onMove||(this._onMove=o(this._onMoveEnd,this.options.updateInterval,this)),t.move=this._onMove),this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},createTile:function(){return document.createElement("div")},getTileSize:function(){var t=this.options.tileSize;return t instanceof x?t:new x(t,t)},_updateZIndex:function(){this._container&&void 0!==this.options.zIndex&&null!==this.options.zIndex&&(this._container.style.zIndex=this.options.zIndex)},_setAutoZIndex:function(t){for(var i,e=this.getPane().children,n=-t(-1/0,1/0),o=0,s=e.length;o<s;o++)i=e[o].style.zIndex,e[o]!==this._container&&i&&(n=t(n,+i));isFinite(n)&&(this.options.zIndex=n+t(-1,1),this._updateZIndex())},_updateOpacity:function(){if(this._map&&!Li){nt(this._container,this.options.opacity);var t=+new Date,i=!1,e=!1;for(var n in this._tiles){var o=this._tiles[n];if(o.current&&o.loaded){var s=Math.min(1,(t-o.loaded)/200);nt(o.el,s),s<1?i=!0:(o.active?e=!0:this._onOpaqueTile(o),o.active=!0)}}e&&!this._noPrune&&this._pruneTiles(),i&&(g(this._fadeFrame),this._fadeFrame=f(this._updateOpacity,this))}},_onOpaqueTile:r,_initContainer:function(){this._container||(this._container=G("div","leaflet-layer "+(this.options.className||"")),this._updateZIndex(),this.options.opacity<1&&this._updateOpacity(),this.getPane().appendChild(this._container))},_updateLevels:function(){var t=this._tileZoom,i=this.options.maxZoom;if(void 0!==t){for(var e in this._levels)this._levels[e].el.children.length||e===t?(this._levels[e].el.style.zIndex=i-Math.abs(t-e),this._onUpdateLevel(e)):(K(this._levels[e].el),this._removeTilesAtZoom(e),this._onRemoveLevel(e),delete this._levels[e]);var n=this._levels[t],o=this._map;return n||((n=this._levels[t]={}).el=G("div","leaflet-tile-container leaflet-zoom-animated",this._container),n.el.style.zIndex=i,n.origin=o.project(o.unproject(o.getPixelOrigin()),t).round(),n.zoom=t,this._setZoomTransform(n,o.getCenter(),o.getZoom()),n.el.offsetWidth,this._onCreateLevel(n)),this._level=n,n}},_onUpdateLevel:r,_onRemoveLevel:r,_onCreateLevel:r,_pruneTiles:function(){if(this._map){var t,i,e=this._map.getZoom();if(e>this.options.maxZoom||e<this.options.minZoom)this._removeAllTiles();else{for(t in this._tiles)(i=this._tiles[t]).retain=i.current;for(t in this._tiles)if((i=this._tiles[t]).current&&!i.active){var n=i.coords;this._retainParent(n.x,n.y,n.z,n.z-5)||this._retainChildren(n.x,n.y,n.z,n.z+2)}for(t in this._tiles)this._tiles[t].retain||this._removeTile(t)}}},_removeTilesAtZoom:function(t){for(var i in this._tiles)this._tiles[i].coords.z===t&&this._removeTile(i)},_removeAllTiles:function(){for(var t in this._tiles)this._removeTile(t)},_invalidateAll:function(){for(var t in this._levels)K(this._levels[t].el),this._onRemoveLevel(t),delete this._levels[t];this._removeAllTiles(),this._tileZoom=void 0},_retainParent:function(t,i,e,n){var o=Math.floor(t/2),s=Math.floor(i/2),r=e-1,a=new x(+o,+s);a.z=+r;var h=this._tileCoordsToKey(a),u=this._tiles[h];return u&&u.active?(u.retain=!0,!0):(u&&u.loaded&&(u.retain=!0),r>n&&this._retainParent(o,s,r,n))},_retainChildren:function(t,i,e,n){for(var o=2*t;o<2*t+2;o++)for(var s=2*i;s<2*i+2;s++){var r=new x(o,s);r.z=e+1;var a=this._tileCoordsToKey(r),h=this._tiles[a];h&&h.active?h.retain=!0:(h&&h.loaded&&(h.retain=!0),e+1<n&&this._retainChildren(o,s,e+1,n))}},_resetView:function(t){var i=t&&(t.pinch||t.flyTo);this._setView(this._map.getCenter(),this._map.getZoom(),i,i)},_animateZoom:function(t){this._setView(t.center,t.zoom,!0,t.noUpdate)},_clampZoom:function(t){var i=this.options;return void 0!==i.minNativeZoom&&t<i.minNativeZoom?i.minNativeZoom:void 0!==i.maxNativeZoom&&i.maxNativeZoom<t?i.maxNativeZoom:t},_setView:function(t,i,e,n){var o=this._clampZoom(Math.round(i));(void 0!==this.options.maxZoom&&o>this.options.maxZoom||void 0!==this.options.minZoom&&o<this.options.minZoom)&&(o=void 0);var s=this.options.updateWhenZooming&&o!==this._tileZoom;n&&!s||(this._tileZoom=o,this._abortLoading&&this._abortLoading(),this._updateLevels(),this._resetGrid(),void 0!==o&&this._update(t),e||this._pruneTiles(),this._noPrune=!!e),this._setZoomTransforms(t,i)},_setZoomTransforms:function(t,i){for(var e in this._levels)this._setZoomTransform(this._levels[e],t,i)},_setZoomTransform:function(t,i,e){var n=this._map.getZoomScale(e,t.zoom),o=t.origin.multiplyBy(n).subtract(this._map._getNewPixelOrigin(i,e)).round();ji?rt(t.el,o,n):at(t.el,o)},_resetGrid:function(){var t=this._map,i=t.options.crs,e=this._tileSize=this.getTileSize(),n=this._tileZoom,o=this._map.getPixelWorldBounds(this._tileZoom);o&&(this._globalTileRange=this._pxBoundsToTileRange(o)),this._wrapX=i.wrapLng&&!this.options.noWrap&&[Math.floor(t.project([0,i.wrapLng[0]],n).x/e.x),Math.ceil(t.project([0,i.wrapLng[1]],n).x/e.y)],this._wrapY=i.wrapLat&&!this.options.noWrap&&[Math.floor(t.project([i.wrapLat[0],0],n).y/e.x),Math.ceil(t.project([i.wrapLat[1],0],n).y/e.y)]},_onMoveEnd:function(){this._map&&!this._map._animatingZoom&&this._update()},_getTiledPixelBounds:function(t){var i=this._map,e=i._animatingZoom?Math.max(i._animateToZoom,i.getZoom()):i.getZoom(),n=i.getZoomScale(e,this._tileZoom),o=i.project(t,this._tileZoom).floor(),s=i.getSize().divideBy(2*n);return new P(o.subtract(s),o.add(s))},_update:function(t){var i=this._map;if(i){var e=this._clampZoom(i.getZoom());if(void 0===t&&(t=i.getCenter()),void 0!==this._tileZoom){var n=this._getTiledPixelBounds(t),o=this._pxBoundsToTileRange(n),s=o.getCenter(),r=[],a=this.options.keepBuffer,h=new P(o.getBottomLeft().subtract([a,-a]),o.getTopRight().add([a,-a]));if(!(isFinite(o.min.x)&&isFinite(o.min.y)&&isFinite(o.max.x)&&isFinite(o.max.y)))throw new Error("Attempted to load an infinite number of tiles");for(var u in this._tiles){var l=this._tiles[u].coords;l.z===this._tileZoom&&h.contains(new x(l.x,l.y))||(this._tiles[u].current=!1)}if(Math.abs(e-this._tileZoom)>1)this._setView(t,e);else{for(var c=o.min.y;c<=o.max.y;c++)for(var _=o.min.x;_<=o.max.x;_++){var d=new x(_,c);if(d.z=this._tileZoom,this._isValidTile(d)){var p=this._tiles[this._tileCoordsToKey(d)];p?p.current=!0:r.push(d)}}if(r.sort(function(t,i){return t.distanceTo(s)-i.distanceTo(s)}),0!==r.length){this._loading||(this._loading=!0,this.fire("loading"));var m=document.createDocumentFragment();for(_=0;_<r.length;_++)this._addTile(r[_],m);this._level.el.appendChild(m)}}}}},_isValidTile:function(t){var i=this._map.options.crs;if(!i.infinite){var e=this._globalTileRange;if(!i.wrapLng&&(t.x<e.min.x||t.x>e.max.x)||!i.wrapLat&&(t.y<e.min.y||t.y>e.max.y))return!1}if(!this.options.bounds)return!0;var n=this._tileCoordsToBounds(t);return z(this.options.bounds).overlaps(n)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var i=this._map,e=this.getTileSize(),n=t.scaleBy(e),o=n.add(e);return[i.unproject(n,t.z),i.unproject(o,t.z)]},_tileCoordsToBounds:function(t){var i=this._tileCoordsToNwSe(t),e=new T(i[0],i[1]);return this.options.noWrap||(e=this._map.wrapLatLngBounds(e)),e},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var i=t.split(":"),e=new x(+i[0],+i[1]);return e.z=+i[2],e},_removeTile:function(t){var i=this._tiles[t];i&&(K(i.el),delete this._tiles[t],this.fire("tileunload",{tile:i.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){Q(t,"leaflet-tile");var i=this.getTileSize();t.style.width=i.x+"px",t.style.height=i.y+"px",t.onselectstart=r,t.onmousemove=r,Li&&this.options.opacity<1&&nt(t,this.options.opacity),zi&&!Mi&&(t.style.WebkitBackfaceVisibility="hidden")},_addTile:function(t,i){var n=this._getTilePos(t),o=this._tileCoordsToKey(t),s=this.createTile(this._wrapCoords(t),e(this._tileReady,this,t));this._initTile(s),this.createTile.length<2&&f(e(this._tileReady,this,t,null,s)),at(s,n),this._tiles[o]={el:s,coords:t,current:!0},i.appendChild(s),this.fire("tileloadstart",{tile:s,coords:t})},_tileReady:function(t,i,n){i&&this.fire("tileerror",{error:i,tile:n,coords:t});var o=this._tileCoordsToKey(t);(n=this._tiles[o])&&(n.loaded=+new Date,this._map._fadeAnimated?(nt(n.el,0),g(this._fadeFrame),this._fadeFrame=f(this._updateOpacity,this)):(n.active=!0,this._pruneTiles()),i||(Q(n.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:n.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),Li||!this._map._fadeAnimated?f(this._pruneTiles,this):setTimeout(e(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var i=new x(this._wrapX?s(t.x,this._wrapX):t.x,this._wrapY?s(t.y,this._wrapY):t.y);return i.z=t.z,i},_pxBoundsToTileRange:function(t){var i=this.getTileSize();return new P(t.min.unscaleBy(i).floor(),t.max.unscaleBy(i).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}}),mn=pn.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1},initialize:function(t,i){this._url=t,(i=l(this,i)).detectRetina&&Yi&&i.maxZoom>0&&(i.tileSize=Math.floor(i.tileSize/2),i.zoomReverse?(i.zoomOffset--,i.minZoom++):(i.zoomOffset++,i.maxZoom--),i.minZoom=Math.max(0,i.minZoom)),"string"==typeof i.subdomains&&(i.subdomains=i.subdomains.split("")),zi||this.on("tileunload",this._onTileRemove)},setUrl:function(t,i){return this._url=t,i||this.redraw(),this},createTile:function(t,i){var n=document.createElement("img");return mt(n,"load",e(this._tileOnLoad,this,i,n)),mt(n,"error",e(this._tileOnError,this,i,n)),(this.options.crossOrigin||""===this.options.crossOrigin)&&(n.crossOrigin=!0===this.options.crossOrigin?"":this.options.crossOrigin),n.alt="",n.setAttribute("role","presentation"),n.src=this.getTileUrl(t),n},getTileUrl:function(t){var e={r:Yi?"@2x":"",s:this._getSubdomain(t),x:t.x,y:t.y,z:this._getZoomForUrl()};if(this._map&&!this._map.options.crs.infinite){var n=this._globalTileRange.max.y-t.y;this.options.tms&&(e.y=n),e["-y"]=n}return _(this._url,i(e,this.options))},_tileOnLoad:function(t,i){Li?setTimeout(e(t,this,null,i),0):t(null,i)},_tileOnError:function(t,i,e){var n=this.options.errorTileUrl;n&&i.getAttribute("src")!==n&&(i.src=n),t(e,i)},_onTileRemove:function(t){t.tile.onload=null},_getZoomForUrl:function(){var t=this._tileZoom,i=this.options.maxZoom,e=this.options.zoomReverse,n=this.options.zoomOffset;return e&&(t=i-t),t+n},_getSubdomain:function(t){var i=Math.abs(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[i]},_abortLoading:function(){var t,i;for(t in this._tiles)this._tiles[t].coords.z!==this._tileZoom&&((i=this._tiles[t].el).onload=r,i.onerror=r,i.complete||(i.src=si,K(i),delete this._tiles[t]))},_removeTile:function(t){var i=this._tiles[t];if(i)return Si||i.el.setAttribute("src",si),pn.prototype._removeTile.call(this,t)},_tileReady:function(t,i,e){if(this._map&&(!e||e.getAttribute("src")!==si))return pn.prototype._tileReady.call(this,t,i,e)}}),fn=mn.extend({defaultWmsParams:{service:"WMS",request:"GetMap",layers:"",styles:"",format:"image/jpeg",transparent:!1,version:"1.1.1"},options:{crs:null,uppercase:!1},initialize:function(t,e){this._url=t;var n=i({},this.defaultWmsParams);for(var o in e)o in this.options||(n[o]=e[o]);var s=(e=l(this,e)).detectRetina&&Yi?2:1,r=this.getTileSize();n.width=r.x*s,n.height=r.y*s,this.wmsParams=n},onAdd:function(t){this._crs=this.options.crs||t.options.crs,this._wmsVersion=parseFloat(this.wmsParams.version);var i=this._wmsVersion>=1.3?"crs":"srs";this.wmsParams[i]=this._crs.code,mn.prototype.onAdd.call(this,t)},getTileUrl:function(t){var i=this._tileCoordsToNwSe(t),e=this._crs,n=b(e.project(i[0]),e.project(i[1])),o=n.min,s=n.max,r=(this._wmsVersion>=1.3&&this._crs===Ue?[o.y,o.x,s.y,s.x]:[o.x,o.y,s.x,s.y]).join(","),a=mn.prototype.getTileUrl.call(this,t);return a+c(this.wmsParams,a,this.options.uppercase)+(this.options.uppercase?"&BBOX=":"&bbox=")+r},setParams:function(t,e){return i(this.wmsParams,t),e||this.redraw(),this}});mn.WMS=fn,Jt.wms=function(t,i){return new fn(t,i)};var gn=qe.extend({options:{padding:.1,tolerance:0},initialize:function(t){l(this,t),n(this),this._layers=this._layers||{}},onAdd:function(){this._container||(this._initContainer(),this._zoomAnimated&&Q(this._container,"leaflet-zoom-animated")),this.getPane().appendChild(this._container),this._update(),this.on("update",this._updatePaths,this)},onRemove:function(){this.off("update",this._updatePaths,this),this._destroyContainer()},getEvents:function(){var t={viewreset:this._reset,zoom:this._onZoom,moveend:this._update,zoomend:this._onZoomEnd};return this._zoomAnimated&&(t.zoomanim=this._onAnimZoom),t},_onAnimZoom:function(t){this._updateTransform(t.center,t.zoom)},_onZoom:function(){this._updateTransform(this._map.getCenter(),this._map.getZoom())},_updateTransform:function(t,i){var e=this._map.getZoomScale(i,this._zoom),n=ht(this._container),o=this._map.getSize().multiplyBy(.5+this.options.padding),s=this._map.project(this._center,i),r=this._map.project(t,i).subtract(s),a=o.multiplyBy(-e).add(n).add(o).subtract(r);ji?rt(this._container,a,e):at(this._container,a)},_reset:function(){this._update(),this._updateTransform(this._center,this._zoom);for(var t in this._layers)this._layers[t]._reset()},_onZoomEnd:function(){for(var t in this._layers)this._layers[t]._project()},_updatePaths:function(){for(var t in this._layers)this._layers[t]._update()},_update:function(){var t=this.options.padding,i=this._map.getSize(),e=this._map.containerPointToLayerPoint(i.multiplyBy(-t)).round();this._bounds=new P(e,e.add(i.multiplyBy(1+2*t)).round()),this._center=this._map.getCenter(),this._zoom=this._map.getZoom()}}),vn=gn.extend({getEvents:function(){var t=gn.prototype.getEvents.call(this);return t.viewprereset=this._onViewPreReset,t},_onViewPreReset:function(){this._postponeUpdatePaths=!0},onAdd:function(){gn.prototype.onAdd.call(this),this._draw()},_initContainer:function(){var t=this._container=document.createElement("canvas");mt(t,"mousemove",o(this._onMouseMove,32,this),this),mt(t,"click dblclick mousedown mouseup contextmenu",this._onClick,this),mt(t,"mouseout",this._handleMouseOut,this),this._ctx=t.getContext("2d")},_destroyContainer:function(){g(this._redrawRequest),delete this._ctx,K(this._container),ft(this._container),delete this._container},_updatePaths:function(){if(!this._postponeUpdatePaths){this._redrawBounds=null;for(var t in this._layers)this._layers[t]._update();this._redraw()}},_update:function(){if(!this._map._animatingZoom||!this._bounds){this._drawnLayers={},gn.prototype._update.call(this);var t=this._bounds,i=this._container,e=t.getSize(),n=Yi?2:1;at(i,t.min),i.width=n*e.x,i.height=n*e.y,i.style.width=e.x+"px",i.style.height=e.y+"px",Yi&&this._ctx.scale(2,2),this._ctx.translate(-t.min.x,-t.min.y),this.fire("update")}},_reset:function(){gn.prototype._reset.call(this),this._postponeUpdatePaths&&(this._postponeUpdatePaths=!1,this._updatePaths())},_initPath:function(t){this._updateDashArray(t),this._layers[n(t)]=t;var i=t._order={layer:t,prev:this._drawLast,next:null};this._drawLast&&(this._drawLast.next=i),this._drawLast=i,this._drawFirst=this._drawFirst||this._drawLast},_addPath:function(t){this._requestRedraw(t)},_removePath:function(t){var i=t._order,e=i.next,o=i.prev;e?e.prev=o:this._drawLast=o,o?o.next=e:this._drawFirst=e,delete this._drawnLayers[t._leaflet_id],delete t._order,delete this._layers[n(t)],this._requestRedraw(t)},_updatePath:function(t){this._extendRedrawBounds(t),t._project(),t._update(),this._requestRedraw(t)},_updateStyle:function(t){this._updateDashArray(t),this._requestRedraw(t)},_updateDashArray:function(t){if("string"==typeof t.options.dashArray){var i,e=t.options.dashArray.split(/[, ]+/),n=[];for(i=0;i<e.length;i++)n.push(Number(e[i]));t.options._dashArray=n}else t.options._dashArray=t.options.dashArray},_requestRedraw:function(t){this._map&&(this._extendRedrawBounds(t),this._redrawRequest=this._redrawRequest||f(this._redraw,this))},_extendRedrawBounds:function(t){if(t._pxBounds){var i=(t.options.weight||0)+1;this._redrawBounds=this._redrawBounds||new P,this._redrawBounds.extend(t._pxBounds.min.subtract([i,i])),this._redrawBounds.extend(t._pxBounds.max.add([i,i]))}},_redraw:function(){this._redrawRequest=null,this._redrawBounds&&(this._redrawBounds.min._floor(),this._redrawBounds.max._ceil()),this._clear(),this._draw(),this._redrawBounds=null},_clear:function(){var t=this._redrawBounds;if(t){var i=t.getSize();this._ctx.clearRect(t.min.x,t.min.y,i.x,i.y)}else this._ctx.clearRect(0,0,this._container.width,this._container.height)},_draw:function(){var t,i=this._redrawBounds;if(this._ctx.save(),i){var e=i.getSize();this._ctx.beginPath(),this._ctx.rect(i.min.x,i.min.y,e.x,e.y),this._ctx.clip()}this._drawing=!0;for(var n=this._drawFirst;n;n=n.next)t=n.layer,(!i||t._pxBounds&&t._pxBounds.intersects(i))&&t._updatePath();this._drawing=!1,this._ctx.restore()},_updatePoly:function(t,i){if(this._drawing){var e,n,o,s,r=t._parts,a=r.length,h=this._ctx;if(a){for(this._drawnLayers[t._leaflet_id]=t,h.beginPath(),e=0;e<a;e++){for(n=0,o=r[e].length;n<o;n++)s=r[e][n],h[n?"lineTo":"moveTo"](s.x,s.y);i&&h.closePath()}this._fillStroke(h,t)}}},_updateCircle:function(t){if(this._drawing&&!t._empty()){var i=t._point,e=this._ctx,n=Math.max(Math.round(t._radius),1),o=(Math.max(Math.round(t._radiusY),1)||n)/n;this._drawnLayers[t._leaflet_id]=t,1!==o&&(e.save(),e.scale(1,o)),e.beginPath(),e.arc(i.x,i.y/o,n,0,2*Math.PI,!1),1!==o&&e.restore(),this._fillStroke(e,t)}},_fillStroke:function(t,i){var e=i.options;e.fill&&(t.globalAlpha=e.fillOpacity,t.fillStyle=e.fillColor||e.color,t.fill(e.fillRule||"evenodd")),e.stroke&&0!==e.weight&&(t.setLineDash&&t.setLineDash(i.options&&i.options._dashArray||[]),t.globalAlpha=e.opacity,t.lineWidth=e.weight,t.strokeStyle=e.color,t.lineCap=e.lineCap,t.lineJoin=e.lineJoin,t.stroke())},_onClick:function(t){for(var i,e,n=this._map.mouseEventToLayerPoint(t),o=this._drawFirst;o;o=o.next)(i=o.layer).options.interactive&&i._containsPoint(n)&&!this._map._draggableMoved(i)&&(e=i);e&&(zt(t),this._fireEvent([e],t))},_onMouseMove:function(t){if(this._map&&!this._map.dragging.moving()&&!this._map._animatingZoom){var i=this._map.mouseEventToLayerPoint(t);this._handleMouseHover(t,i)}},_handleMouseOut:function(t){var i=this._hoveredLayer;i&&(tt(this._container,"leaflet-interactive"),this._fireEvent([i],t,"mouseout"),this._hoveredLayer=null)},_handleMouseHover:function(t,i){for(var e,n,o=this._drawFirst;o;o=o.next)(e=o.layer).options.interactive&&e._containsPoint(i)&&(n=e);n!==this._hoveredLayer&&(this._handleMouseOut(t),n&&(Q(this._container,"leaflet-interactive"),this._fireEvent([n],t,"mouseover"),this._hoveredLayer=n)),this._hoveredLayer&&this._fireEvent([this._hoveredLayer],t)},_fireEvent:function(t,i,e){this._map._fireDOMEvent(i,e||i.type,t)},_bringToFront:function(t){var i=t._order,e=i.next,n=i.prev;e&&(e.prev=n,n?n.next=e:e&&(this._drawFirst=e),i.prev=this._drawLast,this._drawLast.next=i,i.next=null,this._drawLast=i,this._requestRedraw(t))},_bringToBack:function(t){var i=t._order,e=i.next,n=i.prev;n&&(n.next=e,e?e.prev=n:n&&(this._drawLast=n),i.prev=null,i.next=this._drawFirst,this._drawFirst.prev=i,this._drawFirst=i,this._requestRedraw(t))}}),yn=function(){try{return document.namespaces.add("lvml","urn:schemas-microsoft-com:vml"),function(t){return document.createElement("<lvml:"+t+' class="lvml">')}}catch(t){return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),xn={_initContainer:function(){this._container=G("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(gn.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var i=t._container=yn("shape");Q(i,"leaflet-vml-shape "+(this.options.className||"")),i.coordsize="1 1",t._path=yn("path"),i.appendChild(t._path),this._updateStyle(t),this._layers[n(t)]=t},_addPath:function(t){var i=t._container;this._container.appendChild(i),t.options.interactive&&t.addInteractiveTarget(i)},_removePath:function(t){var i=t._container;K(i),t.removeInteractiveTarget(i),delete this._layers[n(t)]},_updateStyle:function(t){var i=t._stroke,e=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(i||(i=t._stroke=yn("stroke")),o.appendChild(i),i.weight=n.weight+"px",i.color=n.color,i.opacity=n.opacity,n.dashArray?i.dashStyle=oi(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):i.dashStyle="",i.endcap=n.lineCap.replace("butt","flat"),i.joinstyle=n.lineJoin):i&&(o.removeChild(i),t._stroke=null),n.fill?(e||(e=t._fill=yn("fill")),o.appendChild(e),e.color=n.fillColor||n.color,e.opacity=n.fillOpacity):e&&(o.removeChild(e),t._fill=null)},_updateCircle:function(t){var i=t._point.round(),e=Math.round(t._radius),n=Math.round(t._radiusY||e);this._setPath(t,t._empty()?"M0 0":"AL "+i.x+","+i.y+" "+e+","+n+" 0,23592600")},_setPath:function(t,i){t._path.v=i},_bringToFront:function(t){X(t._container)},_bringToBack:function(t){J(t._container)}},wn=$i?yn:E,Pn=gn.extend({getEvents:function(){var t=gn.prototype.getEvents.call(this);return t.zoomstart=this._onZoomStart,t},_initContainer:function(){this._container=wn("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=wn("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){K(this._container),ft(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_onZoomStart:function(){this._update()},_update:function(){if(!this._map._animatingZoom||!this._bounds){gn.prototype._update.call(this);var t=this._bounds,i=t.getSize(),e=this._container;this._svgSize&&this._svgSize.equals(i)||(this._svgSize=i,e.setAttribute("width",i.x),e.setAttribute("height",i.y)),at(e,t.min),e.setAttribute("viewBox",[t.min.x,t.min.y,i.x,i.y].join(" ")),this.fire("update")}},_initPath:function(t){var i=t._path=wn("path");t.options.className&&Q(i,t.options.className),t.options.interactive&&Q(i,"leaflet-interactive"),this._updateStyle(t),this._layers[n(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){K(t._path),t.removeInteractiveTarget(t._path),delete this._layers[n(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var i=t._path,e=t.options;i&&(e.stroke?(i.setAttribute("stroke",e.color),i.setAttribute("stroke-opacity",e.opacity),i.setAttribute("stroke-width",e.weight),i.setAttribute("stroke-linecap",e.lineCap),i.setAttribute("stroke-linejoin",e.lineJoin),e.dashArray?i.setAttribute("stroke-dasharray",e.dashArray):i.removeAttribute("stroke-dasharray"),e.dashOffset?i.setAttribute("stroke-dashoffset",e.dashOffset):i.removeAttribute("stroke-dashoffset")):i.setAttribute("stroke","none"),e.fill?(i.setAttribute("fill",e.fillColor||e.color),i.setAttribute("fill-opacity",e.fillOpacity),i.setAttribute("fill-rule",e.fillRule||"evenodd")):i.setAttribute("fill","none"))},_updatePoly:function(t,i){this._setPath(t,k(t._parts,i))},_updateCircle:function(t){var i=t._point,e=Math.max(Math.round(t._radius),1),n="a"+e+","+(Math.max(Math.round(t._radiusY),1)||e)+" 0 1,0 ",o=t._empty()?"M0 0":"M"+(i.x-e)+","+i.y+n+2*e+",0 "+n+2*-e+",0 ";this._setPath(t,o)},_setPath:function(t,i){t._path.setAttribute("d",i)},_bringToFront:function(t){X(t._path)},_bringToBack:function(t){J(t._path)}});$i&&Pn.include(xn),be.include({getRenderer:function(t){var i=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer;return i||(i=this._renderer=this._createRenderer()),this.hasLayer(i)||this.addLayer(i),i},_getPaneRenderer:function(t){if("overlayPane"===t||void 0===t)return!1;var i=this._paneRenderers[t];return void 0===i&&(i=this._createRenderer({pane:t}),this._paneRenderers[t]=i),i},_createRenderer:function(t){return this.options.preferCanvas&&$t(t)||Qt(t)}});var Ln=on.extend({initialize:function(t,i){on.prototype.initialize.call(this,this._boundsToLatLngs(t),i)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return t=z(t),[t.getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});Pn.create=wn,Pn.pointsToPath=k,sn.geometryToLayer=Ft,sn.coordsToLatLng=Ut,sn.coordsToLatLngs=Vt,sn.latLngToCoords=qt,sn.latLngsToCoords=Gt,sn.getFeature=Kt,sn.asFeature=Yt,be.mergeOptions({boxZoom:!0});var bn=Ee.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){mt(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){ft(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){K(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),fi(),ut(),this._startPoint=this._map.mouseEventToContainerPoint(t),mt(document,{contextmenu:Lt,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=G("div","leaflet-zoom-box",this._container),Q(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var i=new P(this._point,this._startPoint),e=i.getSize();at(this._box,i.min),this._box.style.width=e.x+"px",this._box.style.height=e.y+"px"},_finish:function(){this._moved&&(K(this._box),tt(this._container,"leaflet-crosshair")),gi(),lt(),ft(document,{contextmenu:Lt,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){if((1===t.which||1===t.button)&&(this._finish(),this._moved)){this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(e(this._resetState,this),0);var i=new T(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point));this._map.fitBounds(i).fire("boxzoomend",{boxZoomBounds:i})}},_onKeyDown:function(t){27===t.keyCode&&this._finish()}});be.addInitHook("addHandler","boxZoom",bn),be.mergeOptions({doubleClickZoom:!0});var Tn=Ee.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var i=this._map,e=i.getZoom(),n=i.options.zoomDelta,o=t.originalEvent.shiftKey?e-n:e+n;"center"===i.options.doubleClickZoom?i.setZoom(o):i.setZoomAround(t.containerPoint,o)}});be.addInitHook("addHandler","doubleClickZoom",Tn),be.mergeOptions({dragging:!0,inertia:!Mi,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0});var zn=Ee.extend({addHooks:function(){if(!this._draggable){var t=this._map;this._draggable=new Re(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))}Q(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){tt(this._map._container,"leaflet-grab"),tt(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t=this._map;if(t._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity){var i=z(this._map.options.maxBounds);this._offsetLimit=b(this._map.latLngToContainerPoint(i.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(i.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))}else this._offsetLimit=null;t.fire("movestart").fire("dragstart"),t.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){if(this._map.options.inertia){var i=this._lastTime=+new Date,e=this._lastPos=this._draggable._absPos||this._draggable._newPos;this._positions.push(e),this._times.push(i),this._prunePositions(i)}this._map.fire("move",t).fire("drag",t)},_prunePositions:function(t){for(;this._positions.length>1&&t-this._times[0]>50;)this._positions.shift(),this._times.shift()},_onZoomEnd:function(){var t=this._map.getSize().divideBy(2),i=this._map.latLngToLayerPoint([0,0]);this._initialWorldOffset=i.subtract(t).x,this._worldWidth=this._map.getPixelWorldBounds().getSize().x},_viscousLimit:function(t,i){return t-(t-i)*this._viscosity},_onPreDragLimit:function(){if(this._viscosity&&this._offsetLimit){var t=this._draggable._newPos.subtract(this._draggable._startPos),i=this._offsetLimit;t.x<i.min.x&&(t.x=this._viscousLimit(t.x,i.min.x)),t.y<i.min.y&&(t.y=this._viscousLimit(t.y,i.min.y)),t.x>i.max.x&&(t.x=this._viscousLimit(t.x,i.max.x)),t.y>i.max.y&&(t.y=this._viscousLimit(t.y,i.max.y)),this._draggable._newPos=this._draggable._startPos.add(t)}},_onPreDragWrap:function(){var t=this._worldWidth,i=Math.round(t/2),e=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-i+e)%t+i-e,s=(n+i+e)%t-i-e,r=Math.abs(o+e)<Math.abs(s+e)?o:s;this._draggable._absPos=this._draggable._newPos.clone(),this._draggable._newPos.x=r},_onDragEnd:function(t){var i=this._map,e=i.options,n=!e.inertia||this._times.length<2;if(i.fire("dragend",t),n)i.fire("moveend");else{this._prunePositions(+new Date);var o=this._lastPos.subtract(this._positions[0]),s=(this._lastTime-this._times[0])/1e3,r=e.easeLinearity,a=o.multiplyBy(r/s),h=a.distanceTo([0,0]),u=Math.min(e.inertiaMaxSpeed,h),l=a.multiplyBy(u/h),c=u/(e.inertiaDeceleration*r),_=l.multiplyBy(-c/2).round();_.x||_.y?(_=i._limitOffset(_,i.options.maxBounds),f(function(){i.panBy(_,{duration:c,easeLinearity:r,noMoveStart:!0,animate:!0})})):i.fire("moveend")}}});be.addInitHook("addHandler","dragging",zn),be.mergeOptions({keyboard:!0,keyboardPanDelta:80});var Mn=Ee.extend({keyCodes:{left:[37],right:[39],down:[40],up:[38],zoomIn:[187,107,61,171],zoomOut:[189,109,54,173]},initialize:function(t){this._map=t,this._setPanDelta(t.options.keyboardPanDelta),this._setZoomDelta(t.options.zoomDelta)},addHooks:function(){var t=this._map._container;t.tabIndex<=0&&(t.tabIndex="0"),mt(t,{focus:this._onFocus,blur:this._onBlur,mousedown:this._onMouseDown},this),this._map.on({focus:this._addHooks,blur:this._removeHooks},this)},removeHooks:function(){this._removeHooks(),ft(this._map._container,{focus:this._onFocus,blur:this._onBlur,mousedown:this._onMouseDown},this),this._map.off({focus:this._addHooks,blur:this._removeHooks},this)},_onMouseDown:function(){if(!this._focused){var t=document.body,i=document.documentElement,e=t.scrollTop||i.scrollTop,n=t.scrollLeft||i.scrollLeft;this._map._container.focus(),window.scrollTo(n,e)}},_onFocus:function(){this._focused=!0,this._map.fire("focus")},_onBlur:function(){this._focused=!1,this._map.fire("blur")},_setPanDelta:function(t){var i,e,n=this._panKeys={},o=this.keyCodes;for(i=0,e=o.left.length;i<e;i++)n[o.left[i]]=[-1*t,0];for(i=0,e=o.right.length;i<e;i++)n[o.right[i]]=[t,0];for(i=0,e=o.down.length;i<e;i++)n[o.down[i]]=[0,t];for(i=0,e=o.up.length;i<e;i++)n[o.up[i]]=[0,-1*t]},_setZoomDelta:function(t){var i,e,n=this._zoomKeys={},o=this.keyCodes;for(i=0,e=o.zoomIn.length;i<e;i++)n[o.zoomIn[i]]=t;for(i=0,e=o.zoomOut.length;i<e;i++)n[o.zoomOut[i]]=-t},_addHooks:function(){mt(document,"keydown",this._onKeyDown,this)},_removeHooks:function(){ft(document,"keydown",this._onKeyDown,this)},_onKeyDown:function(t){if(!(t.altKey||t.ctrlKey||t.metaKey)){var i,e=t.keyCode,n=this._map;if(e in this._panKeys)n._panAnim&&n._panAnim._inProgress||(i=this._panKeys[e],t.shiftKey&&(i=w(i).multiplyBy(3)),n.panBy(i),n.options.maxBounds&&n.panInsideBounds(n.options.maxBounds));else if(e in this._zoomKeys)n.setZoom(n.getZoom()+(t.shiftKey?3:1)*this._zoomKeys[e]);else{if(27!==e||!n._popup||!n._popup.options.closeOnEscapeKey)return;n.closePopup()}Lt(t)}}});be.addInitHook("addHandler","keyboard",Mn),be.mergeOptions({scrollWheelZoom:!0,wheelDebounceTime:40,wheelPxPerZoomLevel:60});var Cn=Ee.extend({addHooks:function(){mt(this._map._container,"mousewheel",this._onWheelScroll,this),this._delta=0},removeHooks:function(){ft(this._map._container,"mousewheel",this._onWheelScroll,this)},_onWheelScroll:function(t){var i=Tt(t),n=this._map.options.wheelDebounceTime;this._delta+=i,this._lastMousePos=this._map.mouseEventToContainerPoint(t),this._startTime||(this._startTime=+new Date);var o=Math.max(n-(+new Date-this._startTime),0);clearTimeout(this._timer),this._timer=setTimeout(e(this._performZoom,this),o),Lt(t)},_performZoom:function(){var t=this._map,i=t.getZoom(),e=this._map.options.zoomSnap||0;t._stop();var n=this._delta/(4*this._map.options.wheelPxPerZoomLevel),o=4*Math.log(2/(1+Math.exp(-Math.abs(n))))/Math.LN2,s=e?Math.ceil(o/e)*e:o,r=t._limitZoom(i+(this._delta>0?s:-s))-i;this._delta=0,this._startTime=null,r&&("center"===t.options.scrollWheelZoom?t.setZoom(i+r):t.setZoomAround(this._lastMousePos,i+r))}});be.addInitHook("addHandler","scrollWheelZoom",Cn),be.mergeOptions({tap:!0,tapTolerance:15});var Sn=Ee.extend({addHooks:function(){mt(this._map._container,"touchstart",this._onDown,this)},removeHooks:function(){ft(this._map._container,"touchstart",this._onDown,this)},_onDown:function(t){if(t.touches){if(Pt(t),this._fireClick=!0,t.touches.length>1)return this._fireClick=!1,void clearTimeout(this._holdTimeout);var i=t.touches[0],n=i.target;this._startPos=this._newPos=new x(i.clientX,i.clientY),n.tagName&&"a"===n.tagName.toLowerCase()&&Q(n,"leaflet-active"),this._holdTimeout=setTimeout(e(function(){this._isTapValid()&&(this._fireClick=!1,this._onUp(),this._simulateEvent("contextmenu",i))},this),1e3),this._simulateEvent("mousedown",i),mt(document,{touchmove:this._onMove,touchend:this._onUp},this)}},_onUp:function(t){if(clearTimeout(this._holdTimeout),ft(document,{touchmove:this._onMove,touchend:this._onUp},this),this._fireClick&&t&&t.changedTouches){var i=t.changedTouches[0],e=i.target;e&&e.tagName&&"a"===e.tagName.toLowerCase()&&tt(e,"leaflet-active"),this._simulateEvent("mouseup",i),this._isTapValid()&&this._simulateEvent("click",i)}},_isTapValid:function(){return this._newPos.distanceTo(this._startPos)<=this._map.options.tapTolerance},_onMove:function(t){var i=t.touches[0];this._newPos=new x(i.clientX,i.clientY),this._simulateEvent("mousemove",i)},_simulateEvent:function(t,i){var e=document.createEvent("MouseEvents");e._simulated=!0,i.target._simulatedClick=!0,e.initMouseEvent(t,!0,!0,window,1,i.screenX,i.screenY,i.clientX,i.clientY,!1,!1,!1,!1,0,null),i.target.dispatchEvent(e)}});qi&&!Vi&&be.addInitHook("addHandler","tap",Sn),be.mergeOptions({touchZoom:qi&&!Mi,bounceAtZoomLimits:!0});var Zn=Ee.extend({addHooks:function(){Q(this._map._container,"leaflet-touch-zoom"),mt(this._map._container,"touchstart",this._onTouchStart,this)},removeHooks:function(){tt(this._map._container,"leaflet-touch-zoom"),ft(this._map._container,"touchstart",this._onTouchStart,this)},_onTouchStart:function(t){var i=this._map;if(t.touches&&2===t.touches.length&&!i._animatingZoom&&!this._zooming){var e=i.mouseEventToContainerPoint(t.touches[0]),n=i.mouseEventToContainerPoint(t.touches[1]);this._centerPoint=i.getSize()._divideBy(2),this._startLatLng=i.containerPointToLatLng(this._centerPoint),"center"!==i.options.touchZoom&&(this._pinchStartLatLng=i.containerPointToLatLng(e.add(n)._divideBy(2))),this._startDist=e.distanceTo(n),this._startZoom=i.getZoom(),this._moved=!1,this._zooming=!0,i._stop(),mt(document,"touchmove",this._onTouchMove,this),mt(document,"touchend",this._onTouchEnd,this),Pt(t)}},_onTouchMove:function(t){if(t.touches&&2===t.touches.length&&this._zooming){var i=this._map,n=i.mouseEventToContainerPoint(t.touches[0]),o=i.mouseEventToContainerPoint(t.touches[1]),s=n.distanceTo(o)/this._startDist;if(this._zoom=i.getScaleZoom(s,this._startZoom),!i.options.bounceAtZoomLimits&&(this._zoom<i.getMinZoom()&&s<1||this._zoom>i.getMaxZoom()&&s>1)&&(this._zoom=i._limitZoom(this._zoom)),"center"===i.options.touchZoom){if(this._center=this._startLatLng,1===s)return}else{var r=n._add(o)._divideBy(2)._subtract(this._centerPoint);if(1===s&&0===r.x&&0===r.y)return;this._center=i.unproject(i.project(this._pinchStartLatLng,this._zoom).subtract(r),this._zoom)}this._moved||(i._moveStart(!0,!1),this._moved=!0),g(this._animRequest);var a=e(i._move,i,this._center,this._zoom,{pinch:!0,round:!1});this._animRequest=f(a,this,!0),Pt(t)}},_onTouchEnd:function(){this._moved&&this._zooming?(this._zooming=!1,g(this._animRequest),ft(document,"touchmove",this._onTouchMove),ft(document,"touchend",this._onTouchEnd),this._map.options.zoomAnimation?this._map._animateZoom(this._center,this._map._limitZoom(this._zoom),!0,this._map.options.zoomSnap):this._map._resetView(this._center,this._map._limitZoom(this._zoom))):this._zooming=!1}});be.addInitHook("addHandler","touchZoom",Zn),be.BoxZoom=bn,be.DoubleClickZoom=Tn,be.Drag=zn,be.Keyboard=Mn,be.ScrollWheelZoom=Cn,be.Tap=Sn,be.TouchZoom=Zn,Object.freeze=ti,t.version="1.3.4+HEAD.0e566b2",t.Control=Te,t.control=ze,t.Browser=Qi,t.Evented=ci,t.Mixin=Ae,t.Util=ui,t.Class=v,t.Handler=Ee,t.extend=i,t.bind=e,t.stamp=n,t.setOptions=l,t.DomEvent=Pe,t.DomUtil=ve,t.PosAnimation=Le,t.Draggable=Re,t.LineUtil=Ne,t.PolyUtil=De,t.Point=x,t.point=w,t.Bounds=P,t.bounds=b,t.Transformation=S,t.transformation=Z,t.Projection=He,t.LatLng=M,t.latLng=C,t.LatLngBounds=T,t.latLngBounds=z,t.CRS=di,t.GeoJSON=sn,t.geoJSON=Xt,t.geoJson=an,t.Layer=qe,t.LayerGroup=Ge,t.layerGroup=function(t,i){return new Ge(t,i)},t.FeatureGroup=Ke,t.featureGroup=function(t){return new Ke(t)},t.ImageOverlay=hn,t.imageOverlay=function(t,i,e){return new hn(t,i,e)},t.VideoOverlay=un,t.videoOverlay=function(t,i,e){return new un(t,i,e)},t.DivOverlay=ln,t.Popup=cn,t.popup=function(t,i){return new cn(t,i)},t.Tooltip=_n,t.tooltip=function(t,i){return new _n(t,i)},t.Icon=Ye,t.icon=function(t){return new Ye(t)},t.DivIcon=dn,t.divIcon=function(t){return new dn(t)},t.Marker=$e,t.marker=function(t,i){return new $e(t,i)},t.TileLayer=mn,t.tileLayer=Jt,t.GridLayer=pn,t.gridLayer=function(t){return new pn(t)},t.SVG=Pn,t.svg=Qt,t.Renderer=gn,t.Canvas=vn,t.canvas=$t,t.Path=Qe,t.CircleMarker=tn,t.circleMarker=function(t,i){return new tn(t,i)},t.Circle=en,t.circle=function(t,i,e){return new en(t,i,e)},t.Polyline=nn,t.polyline=function(t,i){return new nn(t,i)},t.Polygon=on,t.polygon=function(t,i){return new on(t,i)},t.Rectangle=Ln,t.rectangle=function(t,i){return new Ln(t,i)},t.Map=be,t.map=function(t,i){return new be(t,i)};var En=window.L;t.noConflict=function(){return window.L=En,this},window.L=t}); \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet-providers/leaflet-providers.js b/www/wiki/extensions/Maps/resources/lib/leaflet-providers/leaflet-providers.js
index 5437f1f1..5437f1f1 100644
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet-providers/leaflet-providers.js
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet-providers/leaflet-providers.js
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.EasyButton/easy-button.css b/www/wiki/extensions/Maps/resources/lib/leaflet.EasyButton/easy-button.css
new file mode 100644
index 00000000..18ce9ac1
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.EasyButton/easy-button.css
@@ -0,0 +1,56 @@
+.leaflet-bar button,
+.leaflet-bar button:hover {
+ background-color: #fff;
+ border: none;
+ border-bottom: 1px solid #ccc;
+ width: 26px;
+ height: 26px;
+ line-height: 26px;
+ display: block;
+ text-align: center;
+ text-decoration: none;
+ color: black;
+}
+
+.leaflet-bar button {
+ background-position: 50% 50%;
+ background-repeat: no-repeat;
+ overflow: hidden;
+ display: block;
+}
+
+.leaflet-bar button:hover {
+ background-color: #f4f4f4;
+}
+
+.leaflet-bar button:first-of-type {
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+}
+
+.leaflet-bar button:last-of-type {
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ border-bottom: none;
+}
+
+.leaflet-bar.disabled,
+.leaflet-bar button.disabled {
+ cursor: default;
+ pointer-events: none;
+ opacity: .4;
+}
+
+.easy-button-button .button-state{
+ display: block;
+ width: 100%;
+ height: 100%;
+ position: relative;
+}
+
+
+.leaflet-touch .leaflet-bar button {
+ width: 30px;
+ height: 30px;
+ line-height: 30px;
+}
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.EasyButton/easy-button.js b/www/wiki/extensions/Maps/resources/lib/leaflet.EasyButton/easy-button.js
new file mode 100644
index 00000000..b9968e88
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.EasyButton/easy-button.js
@@ -0,0 +1,377 @@
+// 2.4.0
+(function(){
+
+// This is for grouping buttons into a bar
+// takes an array of `L.easyButton`s and
+// then the usual `.addTo(map)`
+L.Control.EasyBar = L.Control.extend({
+
+ options: {
+ position: 'topleft', // part of leaflet's defaults
+ id: null, // an id to tag the Bar with
+ leafletClasses: true // use leaflet classes?
+ },
+
+
+ initialize: function(buttons, options){
+
+ if(options){
+ L.Util.setOptions( this, options );
+ }
+
+ this._buildContainer();
+ this._buttons = [];
+
+ for(var i = 0; i < buttons.length; i++){
+ buttons[i]._bar = this;
+ buttons[i]._container = buttons[i].button;
+ this._buttons.push(buttons[i]);
+ this.container.appendChild(buttons[i].button);
+ }
+
+ },
+
+
+ _buildContainer: function(){
+ this._container = this.container = L.DomUtil.create('div', '');
+ this.options.leafletClasses && L.DomUtil.addClass(this.container, 'leaflet-bar easy-button-container leaflet-control');
+ this.options.id && (this.container.id = this.options.id);
+ },
+
+
+ enable: function(){
+ L.DomUtil.addClass(this.container, 'enabled');
+ L.DomUtil.removeClass(this.container, 'disabled');
+ this.container.setAttribute('aria-hidden', 'false');
+ return this;
+ },
+
+
+ disable: function(){
+ L.DomUtil.addClass(this.container, 'disabled');
+ L.DomUtil.removeClass(this.container, 'enabled');
+ this.container.setAttribute('aria-hidden', 'true');
+ return this;
+ },
+
+
+ onAdd: function () {
+ return this.container;
+ },
+
+ addTo: function (map) {
+ this._map = map;
+
+ for(var i = 0; i < this._buttons.length; i++){
+ this._buttons[i]._map = map;
+ }
+
+ var container = this._container = this.onAdd(map),
+ pos = this.getPosition(),
+ corner = map._controlCorners[pos];
+
+ L.DomUtil.addClass(container, 'leaflet-control');
+
+ if (pos.indexOf('bottom') !== -1) {
+ corner.insertBefore(container, corner.firstChild);
+ } else {
+ corner.appendChild(container);
+ }
+
+ return this;
+ }
+
+});
+
+L.easyBar = function(){
+ var args = [L.Control.EasyBar];
+ for(var i = 0; i < arguments.length; i++){
+ args.push( arguments[i] );
+ }
+ return new (Function.prototype.bind.apply(L.Control.EasyBar, args));
+};
+
+// L.EasyButton is the actual buttons
+// can be called without being grouped into a bar
+L.Control.EasyButton = L.Control.extend({
+
+ options: {
+ position: 'topleft', // part of leaflet's defaults
+
+ id: null, // an id to tag the button with
+
+ type: 'replace', // [(replace|animate)]
+ // replace swaps out elements
+ // animate changes classes with all elements inserted
+
+ states: [], // state names look like this
+ // {
+ // stateName: 'untracked',
+ // onClick: function(){ handle_nav_manually(); };
+ // title: 'click to make inactive',
+ // icon: 'fa-circle', // wrapped with <a>
+ // }
+
+ leafletClasses: true, // use leaflet styles for the button
+ tagName: 'button',
+ },
+
+
+
+ initialize: function(icon, onClick, title, id){
+
+ // clear the states manually
+ this.options.states = [];
+
+ // add id to options
+ if(id != null){
+ this.options.id = id;
+ }
+
+ // storage between state functions
+ this.storage = {};
+
+ // is the last item an object?
+ if( typeof arguments[arguments.length-1] === 'object' ){
+
+ // if so, it should be the options
+ L.Util.setOptions( this, arguments[arguments.length-1] );
+ }
+
+ // if there aren't any states in options
+ // use the early params
+ if( this.options.states.length === 0 &&
+ typeof icon === 'string' &&
+ typeof onClick === 'function'){
+
+ // turn the options object into a state
+ this.options.states.push({
+ icon: icon,
+ onClick: onClick,
+ title: typeof title === 'string' ? title : ''
+ });
+ }
+
+ // curate and move user's states into
+ // the _states for internal use
+ this._states = [];
+
+ for(var i = 0; i < this.options.states.length; i++){
+ this._states.push( new State(this.options.states[i], this) );
+ }
+
+ this._buildButton();
+
+ this._activateState(this._states[0]);
+
+ },
+
+ _buildButton: function(){
+
+ this.button = L.DomUtil.create(this.options.tagName, '');
+
+ if (this.options.tagName === 'button') {
+ this.button.setAttribute('type', 'button');
+ }
+
+ if (this.options.id ){
+ this.button.id = this.options.id;
+ }
+
+ if (this.options.leafletClasses){
+ L.DomUtil.addClass(this.button, 'easy-button-button leaflet-bar-part leaflet-interactive');
+ }
+
+ // don't let double clicks and mousedown get to the map
+ L.DomEvent.addListener(this.button, 'dblclick', L.DomEvent.stop);
+ L.DomEvent.addListener(this.button, 'mousedown', L.DomEvent.stop);
+ L.DomEvent.addListener(this.button, 'mouseup', L.DomEvent.stop);
+
+ // take care of normal clicks
+ L.DomEvent.addListener(this.button,'click', function(e){
+ L.DomEvent.stop(e);
+ this._currentState.onClick(this, this._map ? this._map : null );
+ this._map && this._map.getContainer().focus();
+ }, this);
+
+ // prep the contents of the control
+ if(this.options.type == 'replace'){
+ this.button.appendChild(this._currentState.icon);
+ } else {
+ for(var i=0;i<this._states.length;i++){
+ this.button.appendChild(this._states[i].icon);
+ }
+ }
+ },
+
+
+ _currentState: {
+ // placeholder content
+ stateName: 'unnamed',
+ icon: (function(){ return document.createElement('span'); })()
+ },
+
+
+
+ _states: null, // populated on init
+
+
+
+ state: function(newState){
+
+ // when called with no args, it's a getter
+ if (arguments.length === 0) {
+ return this._currentState.stateName;
+ }
+
+ // activate by name
+ if(typeof newState == 'string'){
+
+ this._activateStateNamed(newState);
+
+ // activate by index
+ } else if (typeof newState == 'number'){
+
+ this._activateState(this._states[newState]);
+ }
+
+ return this;
+ },
+
+
+ _activateStateNamed: function(stateName){
+ for(var i = 0; i < this._states.length; i++){
+ if( this._states[i].stateName == stateName ){
+ this._activateState( this._states[i] );
+ }
+ }
+ },
+
+ _activateState: function(newState){
+
+ if( newState === this._currentState ){
+
+ // don't touch the dom if it'll just be the same after
+ return;
+
+ } else {
+
+ // swap out elements... if you're into that kind of thing
+ if( this.options.type == 'replace' ){
+ this.button.appendChild(newState.icon);
+ this.button.removeChild(this._currentState.icon);
+ }
+
+ if( newState.title ){
+ this.button.title = newState.title;
+ } else {
+ this.button.removeAttribute('title');
+ }
+
+ // update classes for animations
+ for(var i=0;i<this._states.length;i++){
+ L.DomUtil.removeClass(this._states[i].icon, this._currentState.stateName + '-active');
+ L.DomUtil.addClass(this._states[i].icon, newState.stateName + '-active');
+ }
+
+ // update classes for animations
+ L.DomUtil.removeClass(this.button, this._currentState.stateName + '-active');
+ L.DomUtil.addClass(this.button, newState.stateName + '-active');
+
+ // update the record
+ this._currentState = newState;
+
+ }
+ },
+
+ enable: function(){
+ L.DomUtil.addClass(this.button, 'enabled');
+ L.DomUtil.removeClass(this.button, 'disabled');
+ this.button.setAttribute('aria-hidden', 'false');
+ return this;
+ },
+
+ disable: function(){
+ L.DomUtil.addClass(this.button, 'disabled');
+ L.DomUtil.removeClass(this.button, 'enabled');
+ this.button.setAttribute('aria-hidden', 'true');
+ return this;
+ },
+
+ onAdd: function(map){
+ var bar = L.easyBar([this], {
+ position: this.options.position,
+ leafletClasses: this.options.leafletClasses
+ });
+ this._anonymousBar = bar;
+ this._container = bar.container;
+ return this._anonymousBar.container;
+ },
+
+ removeFrom: function (map) {
+ if (this._map === map)
+ this.remove();
+ return this;
+ },
+
+});
+
+L.easyButton = function(/* args will pass automatically */){
+ var args = Array.prototype.concat.apply([L.Control.EasyButton],arguments);
+ return new (Function.prototype.bind.apply(L.Control.EasyButton, args));
+};
+
+/*************************
+ *
+ * util functions
+ *
+ *************************/
+
+// constructor for states so only curated
+// states end up getting called
+function State(template, easyButton){
+
+ this.title = template.title;
+ this.stateName = template.stateName ? template.stateName : 'unnamed-state';
+
+ // build the wrapper
+ this.icon = L.DomUtil.create('span', '');
+
+ L.DomUtil.addClass(this.icon, 'button-state state-' + this.stateName.replace(/(^\s*|\s*$)/g,''));
+ this.icon.innerHTML = buildIcon(template.icon);
+ this.onClick = L.Util.bind(template.onClick?template.onClick:function(){}, easyButton);
+}
+
+function buildIcon(ambiguousIconString) {
+
+ var tmpIcon;
+
+ // does this look like html? (i.e. not a class)
+ if( ambiguousIconString.match(/[&;=<>"']/) ){
+
+ // if so, the user should have put in html
+ // so move forward as such
+ tmpIcon = ambiguousIconString;
+
+ // then it wasn't html, so
+ // it's a class list, figure out what kind
+ } else {
+ ambiguousIconString = ambiguousIconString.replace(/(^\s*|\s*$)/g,'');
+ tmpIcon = L.DomUtil.create('span', '');
+
+ if( ambiguousIconString.indexOf('fa-') === 0 ){
+ L.DomUtil.addClass(tmpIcon, 'fa ' + ambiguousIconString)
+ } else if ( ambiguousIconString.indexOf('glyphicon-') === 0 ) {
+ L.DomUtil.addClass(tmpIcon, 'glyphicon ' + ambiguousIconString)
+ } else {
+ L.DomUtil.addClass(tmpIcon, /*rollwithit*/ ambiguousIconString)
+ }
+
+ // make this a string so that it's easy to set innerHTML below
+ tmpIcon = tmpIcon.outerHTML;
+ }
+
+ return tmpIcon;
+}
+
+})();
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.StyleEditor/css/Leaflet.StyleEditor.min.css b/www/wiki/extensions/Maps/resources/lib/leaflet.StyleEditor/css/Leaflet.StyleEditor.min.css
new file mode 100644
index 00000000..c4397c93
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.StyleEditor/css/Leaflet.StyleEditor.min.css
@@ -0,0 +1,306 @@
+.leaflet-control-styleeditor .leaflet-control-styleeditor-interior {
+ background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiCiAgIHhtbG5zOmlua3NjYXBlPSJodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy9uYW1lc3BhY2VzL2lua3NjYXBlIgogICB3aWR0aD0iNS4wOTczOTY0bW0iCiAgIGhlaWdodD0iNS4yMDk0MzM2bW0iCiAgIHZpZXdCb3g9IjAgMCA1LjA5NzM5NjQgNS4yMDk0MzM2IgogICB2ZXJzaW9uPSIxLjEiCiAgIGlkPSJzdmczODUxIgogICBpbmtzY2FwZTp2ZXJzaW9uPSIwLjkyLjIgNWMzZTgwZCwgMjAxNy0wOC0wNiIKICAgc29kaXBvZGk6ZG9jbmFtZT0iY29udHJvbC5zdmciPgogIDxkZWZzCiAgICAgaWQ9ImRlZnMzODQ1IiAvPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0iYmFzZSIKICAgICBwYWdlY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyY29sb3I9IiM2NjY2NjYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMS4wIgogICAgIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwLjAiCiAgICAgaW5rc2NhcGU6cGFnZXNoYWRvdz0iMiIKICAgICBpbmtzY2FwZTp6b29tPSIxMC4yNCIKICAgICBpbmtzY2FwZTpjeD0iNC4zNTY3NzM4IgogICAgIGlua3NjYXBlOmN5PSIxMS40MDg0MTIiCiAgICAgaW5rc2NhcGU6ZG9jdW1lbnQtdW5pdHM9Im1tIgogICAgIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9ImxheWVyMSIKICAgICBzaG93Z3JpZD0iZmFsc2UiCiAgICAgaW5rc2NhcGU6d2luZG93LXdpZHRoPSIxMzY2IgogICAgIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9Ijc2OCIKICAgICBpbmtzY2FwZTp3aW5kb3cteD0iMCIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iMCIKICAgICBpbmtzY2FwZTp3aW5kb3ctbWF4aW1pemVkPSIwIgogICAgIGZpdC1tYXJnaW4tdG9wPSIwIgogICAgIGZpdC1tYXJnaW4tbGVmdD0iMCIKICAgICBmaXQtbWFyZ2luLXJpZ2h0PSIwIgogICAgIGZpdC1tYXJnaW4tYm90dG9tPSIwIiAvPgogIDxtZXRhZGF0YQogICAgIGlkPSJtZXRhZGF0YTM4NDgiPgogICAgPHJkZjpSREY+CiAgICAgIDxjYzpXb3JrCiAgICAgICAgIHJkZjphYm91dD0iIj4KICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3N2Zyt4bWw8L2RjOmZvcm1hdD4KICAgICAgICA8ZGM6dHlwZQogICAgICAgICAgIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiIC8+CiAgICAgICAgPGRjOnRpdGxlPjwvZGM6dGl0bGU+CiAgICAgIDwvY2M6V29yaz4KICAgIDwvcmRmOlJERj4KICA8L21ldGFkYXRhPgogIDxnCiAgICAgaW5rc2NhcGU6bGFiZWw9IkViZW5lIDEiCiAgICAgaW5rc2NhcGU6Z3JvdXBtb2RlPSJsYXllciIKICAgICBpZD0ibGF5ZXIxIgogICAgIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0wLjAzNTExNzE0LC0yOTEuODE4MykiPgogICAgPGcKICAgICAgIGlkPSJnMzg3NSIKICAgICAgIGlua3NjYXBlOmV4cG9ydC1maWxlbmFtZT0iRDpcQ29kaW5nXGxlYWZsZXQtZWRpdFN0eWxlXGltZ1xpY29uLnBuZyIKICAgICAgIGlua3NjYXBlOmV4cG9ydC14ZHBpPSI5MCIKICAgICAgIGlua3NjYXBlOmV4cG9ydC15ZHBpPSI5MCIKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KC0wLjI0ODMwODk3LDAsMCwwLjI0ODMwODk3LDE1Ni42ODQxOCwxNjQuODIxMzYpIj4KICAgICAgPHBvbHlnb24KICAgICAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMjg4LDQ5OC44OTc5KSIKICAgICAgICAgc3R5bGU9ImZpbGw6IzQ2NDY0NiIKICAgICAgICAgcG9pbnRzPSIzMjcsMzMuNTI5IDMyNywzMC42OTkgMzM1Ljg5MSwyMS44MDkgMzM4LjcxOSwyNC42MzcgMzI5LjgyOCwzMy41MjkgIgogICAgICAgICBpZD0icG9seWdvbjMwMDYiIC8+CiAgICAgIDxyZWN0CiAgICAgICAgIHN0eWxlPSJmaWxsOiM0NjQ2NDYiCiAgICAgICAgIHg9Ii04MTMuNzQ2MjgiCiAgICAgICAgIHk9Ijc0LjUzMzQwOSIKICAgICAgICAgdHJhbnNmb3JtPSJyb3RhdGUoLTEzNC45OTE5KSIKICAgICAgICAgd2lkdGg9IjMuOTk5OTYxNiIKICAgICAgICAgaGVpZ2h0PSIzLjk5OTk2MTYiCiAgICAgICAgIGlkPSJyZWN0MzAwOCIgLz4KICAgICAgPHBhdGgKICAgICAgICAgdHJhbnNmb3JtPSJtYXRyaXgoMC43ODQwMjM2MywwLDAsMC43ODQwMjM2MywyMjIuNjgwNjksMTQ4LjY4MTY4KSIKICAgICAgICAgaW5rc2NhcGU6dHJhbnNmb3JtLWNlbnRlci15PSItMC4yOTkyNDA1MiIKICAgICAgICAgaW5rc2NhcGU6dHJhbnNmb3JtLWNlbnRlci14PSIwLjI5OTI0MTE0IgogICAgICAgICBkPSJtIDUwOC41NzE0Miw0NzYuODI2NDggYyAtMC43ODI2NSwwLjc4MjY1IC00LjE0NzgxLC0xLjk1MDQyIC01LjI0MTAyLC0xLjc3NzI4IC0xLjA5MzIsMC4xNzMxNSAtMy40NDkwOSwzLjgxMjM1IC00LjQzNTI5LDMuMzA5ODYgLTAuOTg2MTksLTAuNTAyNDkgMC41NzMyMiwtNC41NDc1MiAwLjA3MDcsLTUuNTMzNzEgLTAuNTAyNDksLTAuOTg2MiAtNC42OTE1OCwtMi4xMDIyMSAtNC41MTg0NCwtMy4xOTU0MSAwLjE3MzE1LC0xLjA5MzIxIDQuNTAyMDgsLTAuODYwMSA1LjI4NDczLC0xLjY0Mjc0IDAuNzgyNjUsLTAuNzgyNjUgMC41NDk1NCwtNS4xMTE1OCAxLjY0Mjc0LC01LjI4NDczIDEuMDkzMjEsLTAuMTczMTUgMi4yMDkyMiw0LjAxNTk1IDMuMTk1NDEsNC41MTg0NCAwLjk4NjIsMC41MDI0OSA1LjAzMTIyLC0xLjA1NjkzIDUuNTMzNzEsLTAuMDcwNyAwLjUwMjQ5LDAuOTg2MTkgLTMuMTM2NzEsMy4zNDIwOCAtMy4zMDk4NSw0LjQzNTI5IC0wLjE3MzE1LDEuMDkzMiAyLjU1OTkyLDQuNDU4MzYgMS43NzcyOCw1LjI0MTAxIHoiCiAgICAgICAgIGlua3NjYXBlOnJhbmRvbWl6ZWQ9IjAiCiAgICAgICAgIGlua3NjYXBlOnJvdW5kZWQ9IjAuMiIKICAgICAgICAgaW5rc2NhcGU6ZmxhdHNpZGVkPSJmYWxzZSIKICAgICAgICAgc29kaXBvZGk6YXJnMj0iMS40MTM3MTY3IgogICAgICAgICBzb2RpcG9kaTphcmcxPSIwLjc4NTM5ODE2IgogICAgICAgICBzb2RpcG9kaTpyMj0iNC4xNjY4NzkyIgogICAgICAgICBzb2RpcG9kaTpyMT0iOC4zMzM3NTg0IgogICAgICAgICBzb2RpcG9kaTpjeT0iNDcwLjkzMzYyIgogICAgICAgICBzb2RpcG9kaTpjeD0iNTAyLjY3ODU2IgogICAgICAgICBzb2RpcG9kaTpzaWRlcz0iNSIKICAgICAgICAgaWQ9InBhdGgzMDk5IgogICAgICAgICBzdHlsZT0iZmlsbDojNDY0NjQ2O2ZpbGwtb3BhY2l0eToxO3N0cm9rZTojNDY0NjQ2O3N0cm9rZS13aWR0aDowO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2Utb3BhY2l0eToxIgogICAgICAgICBzb2RpcG9kaTp0eXBlPSJzdGFyIiAvPgogICAgPC9nPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJvcGFjaXR5OjE7dmVjdG9yLWVmZmVjdDpub25lO2ZpbGw6IzQ2NDY0NjtmaWxsLW9wYWNpdHk6MTtzdHJva2Utd2lkdGg6MC4wNDcyNDcwMztzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJtIDQuMjk4MzY0NSwyOTQuNjcwMzQgYyAwLC0wLjA5OTIgLTAuMDIyODYsLTAuMzAzOTcgLTAuMDUwNzk3LC0wLjQ1NTExIC0wLjA3NDY4OSwtMC40MDQwMiAtMC4wNDIxMzUsLTAuNDc0MTkgMC4yOTk2ODAzLC0wLjY0NTk4IDAuMzgwMDg5OCwtMC4xOTEwMiAwLjU1NDMxOCwtMC4zMTU0NSAwLjU1NDMxOCwtMC4zOTU5IDAsLTAuMDc4OSAtMC4wNjcxMTQsLTAuMTAxMTMgLTAuNTcxODgzNywtMC4xODk2MyAtMC4yMzIyNzc3LC0wLjA0MDcgLTAuNDE4NzAzLC0wLjA5OTQgLTAuNDQ5ODE1NCwtMC4xNDE2NyAtMC4wMjkyMjMsLTAuMDM5NyAtMC4wODY1NjYsLTAuMjU0MjEgLTAuMTI3NDMxMywtMC40NzY3NiAtMC4xMjE5NTk3LC0wLjY2NDIgLTAuMTkyMjQ2MiwtMC42NzE3MyAtMC41MTMxNDYxLC0wLjA1NDkgLTAuMTA3NDkyMywwLjIwNjYyIC0wLjIyNzkxNDcsMC4zODgxMyAtMC4yNjc2MDIyLDAuNDAzMzYgLTAuMDM5Njg4LDAuMDE1MiAtMC4yNzQyNTkyLDAuMDAyIC0wLjUyMTI2ODksLTAuMDI5IC0wLjM4MTI2NzIsLTAuMDQ4MiAtMC40NTkzNjE2LC0wLjA0NjUgLTAuNTE3MDA2NCwwLjAxMTIgLTAuMDU5MDM2LDAuMDU5IC0wLjAyMzkxOCwwLjExNDM3IDAuMjY4OTUxNiwwLjQyMzc5IDAuMTg1MjY2NSwwLjE5NTc0IDAuMzM2ODQ5LDAuMzk3NDggMC4zMzY4NDksMC40NDgzMSAwLDAuMDUwOCAtMC4wODcwODUsMC4yNzM5MiAtMC4xOTM1MjE2LDAuNDk1NzQgLTAuMjc0ODAxNSwwLjU3MjcgLTAuMjI2NTI4MywwLjYxMTc5IDAuMzg4MzA3OCwwLjMxNDQ3IDAuMjE0OTQyMiwtMC4xMDM5NSAwLjQxOTYxNiwtMC4xODg5OSAwLjQ1NDgyNjcsLTAuMTg4OTkgMC4wMzUyMTMsMCAwLjIyMTI2MzEsMC4xNDg4MyAwLjQxMzQ0NTksMC4zMzA3MyAwLjQwNjM0NywwLjM4NDYxIDAuNDk2MDkzNywwLjQxMTgyIDAuNDk2MDkzNywwLjE1MDQyIHoiCiAgICAgICBpZD0icGF0aDM4ODUiCiAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIiAvPgogICAgPHBhdGgKICAgICAgIHN0eWxlPSJvcGFjaXR5OjE7dmVjdG9yLWVmZmVjdDpub25lO2ZpbGw6IzQ2NDY0NjtmaWxsLW9wYWNpdHk6MTtzdHJva2Utd2lkdGg6MC4wNDcyNDcwMztzdHJva2UtbGluZWNhcDpidXR0O3N0cm9rZS1saW5lam9pbjptaXRlcjtzdHJva2UtbWl0ZXJsaW1pdDo0O3N0cm9rZS1kYXNoYXJyYXk6bm9uZTtzdHJva2UtZGFzaG9mZnNldDowO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICBkPSJtIDMuOTY3NjM1MywyOTYuNjgxMDkgdiAtMC4zNDI5MSBsIC0xLjA5OTI2OTYsLTEuMDk4NTMgLTEuMDk5MjcyMywtMS4wOTg1MiAtMC4zNDEzOTcyLDAuMzQzNjkgLTAuMzQxMzk3MSwwLjM0MzY5IDEuMDk4NDk3LDEuMDk3NzUgMS4wOTg0OTcxLDEuMDk3NzUgSCAzLjYyNTQ2MyAzLjk2NzYzNTMgWiIKICAgICAgIGlkPSJwYXRoMzg4NyIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiIC8+CiAgICA8cGF0aAogICAgICAgc3R5bGU9Im9wYWNpdHk6MTt2ZWN0b3ItZWZmZWN0Om5vbmU7ZmlsbDojNDY0NjQ2O2ZpbGwtb3BhY2l0eToxO3N0cm9rZS13aWR0aDowLjA0NzI0NzAzO3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1kYXNob2Zmc2V0OjA7c3Ryb2tlLW9wYWNpdHk6MSIKICAgICAgIGQ9Im0gMS4wNzQ2MzA5LDI5NC4yNzEwNyAwLjM0MTgyMDUsLTAuMzQ0MTIgLTAuMzMxMjM3MSwtMC4zMjg3IC0wLjMzMTIzOTc3LC0wLjMyODcgLTAuMzQxNzAxNSwwLjM0MTcgLTAuMzQxNzAxNCwwLjM0MTcgMC4zMzExMTgxLDAuMzMxMTIgMC4zMzExMTgxLDAuMzMxMTIgeiIKICAgICAgIGlkPSJwYXRoMzg4OSIKICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiIC8+CiAgPC9nPgo8L3N2Zz4K);
+ background-position: 50% 50%;
+ background-size: 50%;
+}
+.leaflet-control-styleeditor-interior:hover {
+ background-color: #07C217;
+}
+.leaflet-control-styleeditor .leaflet-control-styleeditor-cancel {
+ position: absolute;
+ top: 0;
+ left: 100%;
+ background-color: #919187;
+ -webkit-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
+ font-size: 12px;
+ line-height: 30px;
+ height: 30px;
+ color: white;
+ text-decoration: none;
+ padding-left: 10px;
+ padding-right: 10px;
+}
+.leaflet-control-styleeditor .leaflet-control-styleeditor-cancel:hover {
+ background-color: #a0a098;
+ cursor: pointer;
+}
+.enabled {
+ background-color: #b0de5c;
+}
+.enabled:hover {
+ background-color: #07C217;
+}
+.leaflet-styleeditor {
+ box-shadow: 0 1px 7px rgba(0, 0, 0, 0.65);
+ height: 100%;
+ right: 0;
+ background-color: white;
+ width: 0;
+ position: absolute;
+ margin: 0px;
+ overflow: hidden;
+ -webkit-transition-property: -webkit-transform, width;
+ transition-property: transform, width;
+ -webkit-transition-duration: 100ms;
+ transition-duration: 100ms;
+ -webkit-transition-timing-function: ease-in;
+ transition-timing-function: ease-in;
+ -ms-transform: translatex(0);
+ -webkit-transform: translatex(0);
+ transform: translatex(0);
+ z-index: 1000;
+}
+.editor-enabled {
+ width: 200px;
+}
+.leaflet-styleeditor-hidden {
+ display: none;
+}
+.leaflet-styleeditor-fill-horizontal{
+ display: flex;
+}
+.leaflet-styleeditor-fill-vertical{
+ flex-direction: column;
+ display: flex;
+}
+.leaflet-styleeditor-fill {
+ flex: 1;
+ min-width: 1px;
+ min-height: 1px;
+}
+.leaflet-styleeditor-header {
+ position: relative;
+ height: 40px;
+ background-color: #b0de5c;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+.leaflet-styleeditor-interior {
+ padding: 20px;
+ overflow: auto;
+ position: relative;
+ right: 0px;
+ height: calc(100% - 40px);
+ -webkit-box-sizing: border-box;
+ /* Safari/Chrome, other WebKit */
+ -moz-box-sizing: border-box;
+ /* Firefox, other Gecko */
+ box-sizing: border-box;
+}
+.leaflet-styleeditor-interior-geometry {
+ margin-bottom: 24px;
+}
+.leaflet-styleeditor-label {
+ width: 100px;
+ display: block;
+ font-size: medium;
+}
+.leaflet-styleeditor-input-span {
+ width: 100%;
+ display: inline-block;
+ text-align: center;
+ font-size: 0.8em;
+}
+input.leaflet-styleeditor-input {
+ height: 30px;
+ background: #fff;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: none;
+ width: 150px;
+}
+textarea.leaflet-styleeditor-input {
+ width: 100%;
+}
+.leaflet-styleeditor-select {
+ position: relative;
+ height: 32px;
+ margin-bottom: 5px;
+ padding-left: 10px;
+ background: #fff;
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ outline: none;
+ width: 150px;
+ overflow: hidden;
+ padding: 0;
+}
+.leaflet-styleeditor-select-option-wrapper {
+ position: absolute;
+ list-style: none;
+ padding: 0;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ border: 1px solid rgba(0, 0, 0, 0.1);
+ background: white;
+ top: 80px;
+ bottom: 20px;
+}
+.leaflet-styleeditor-select-option {
+ display: inline-block;
+ height: 30px;
+ width: 28px;
+ padding: 4px;
+ cursor: pointer;
+ position: relative;
+}
+.leaflet-styleeditor-select-image-wrapper {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ overflow: visible;
+ width: 100%;
+ height: 100%;
+}
+.leaflet-styleeditor-select-image-defaultmarker {
+ top: 11px;
+}
+.leaflet-styleeditor-select-image {
+ position: relative;
+ padding: 0;
+ margin: 0;
+}
+.leaflet-styleeditor-button {
+ border: 0px;
+ display: inline-block;
+ padding: 10px 20px;
+ background: #b0de5c;
+ height: 40px;
+ margin-left: 0px;
+ -moz-transition: background-color .25s ease-in-out;
+ -webkit-transition: background-color .25s ease-in-out;
+ -o-transition: background-color .25s ease-in-out;
+ -ms-transition: background-color .25s ease-in-out;
+ transition: background-color .25s ease-in-out;
+ letter-spacing: 2px;
+ text-decoration: none;
+ text-transform: uppercase;
+ font-size: 15px;
+ font-weight: 600;
+ color: #FFFFFF;
+ cursor: pointer;
+}
+.styleeditor-nextBtn {
+ float: right;
+ margin-right: 0px;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALIAAACmCAYAAAB+zdXlAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAABE/SURBVHic7Z17cFzXXce/v3Pv3adWkiXZQfIjxki1OxAakpC2aehUZWpp0ryalqEpnWEIhPQfSh+0kwaYTtqEUAaGATqE6ZQmQIfppMy0SWrjtQluAqSQQpI2SR0rxLYcybZkW7KkfdznOfyx926uVrurXUXW4/D7zHh89+q353z33s/evbq6Zw8ppRBndHT0GqXUffl8/iNgmE2CqF2hlLoFwIf3799/5zrkYZgVsURkADcAABH92U033dS5xnkYZkVQ/NTiuuuus3p7ey8CyAGAUuqHRHQJQC+AnyKiLxw6dOjv1ycqwzTGDM+JbwFwQ29v77sRSgwARPSLsdpXHMd5Ys0TMkwLmEqpzwH46DJ1U1LKm44ePXqp9gf79++/M5VKPf7EE0+ULk9Ehlkekc/n7wRwJ4DJJnW/deTIkdO1K0dGRu4lon90HOfJW2+9NXPZUjLMMggAyOfz33Jddx+AZxvUXVe7YmRk5F4AD4UP388yM+tJ9arF0aNHCwDmGtT95vDwsAkAw8PDfeG62isaGd/3zdWPyDDLU3v5bVv4/1kiuhnA/QAmAOxIJBK3j46ODiYSiRdGRkYeyufz9wF4IKx/NpVK7T948OD8GuVmmEUsuvw2MjIyCeC87/sffOqppyYBYHh42Ewmk7cppRwADwPYEZb/cT6f/8LIyMhdrus+Fh7RGWZdWCTy6OjoxxzHOVh7dWJ0dHRQKXUUb0oMAK5hGL9w8ODBn6xRVoZpCNXea1FLI4kBfCSfzz95OcMxTKs0/eWsicQfzufz37usyRimDerdawGgscREdAdLzGw06orcQGKHiO44dOjQgbWJxjCts+QcuZHESqk7Dh8+fHBN0zFMiyw5IkspP4saiQF8iCVmNjJLRCYiij10iOj2fD7/z2uYiWHaZslVCyJ6USn1OABIKR8+cuRIfu1jMUx7LHsdmWE2Aw0vvzHMZoJFZrSARWa0gEVmtIBFZrSARWa0gEVmtIBFZrSARWa0gEVmtIBFZrSARWa0gEVmtIBFZrSARWa0gEVmtIBFZrSARWa0gEVmtIBFZrRAENFDhmF8koiS6x2GYVaKMAxjyDTNPxVCnGWhmc2KAICBgQFr7969W3K53B+FQv8uEaXWOxzDtEr1HDmTyWBwcDAbCv1gKPSnWGhmM7Dkl72Y0N0dHR0PhEJ/moVmNjINr1pkMhkMDQ1FQn85FPozRJRey4AM0wrLXn6rEfpLQogzLDSz0Wj5OnIdoc8ahvFZFprZCLT9B5GY0F0xoX+PiHjWU2bdWPFf9kKhM6HQ94dCf46FZtaDt/wn6pjQnalU6ktEdIaI7l6NcAzTKqtyr4Xnebhw4YJj27Yion8A8PhqtMswrfKWJkF3XRfnzp1zLl68KAH8rZTyy0qp6VXKxjAtsyKRWWBmo9GWyDUCf11K+QALzGwEWhLZdV2cPXvWmZmZYYGZDUlTkVlgZrNQV2QWmNlsLBI5FNiemZlRYIGZTYQJVAQeHx+3Z2ZmAOBrUsoHWWBmM2ECCM6ePesREQvMbFoIwAiAF1hgZjPDU/gyWsDfa8FoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBARjs7+9/NpFIeOsdhmHaYW5u7huzs7N/CFS+oMXs7u7emsnw1B/M5mJ+fr76lW8mAFUqlUpSSrmOmRimbeLKUvjvynVLwzArZ1opVQL4m4YYTeCrFowWsMiMFrDIjBawyIwWsMiMFrDIjBawyIwWsMiMFrDIjBawyIwWsMiMFrDIjBawyIwWsMiMFlTvsM/lcv8khLgaAEzTdHzfT8aWU7Zt3+44zksAQERWLpd7gYhSdept27bPFIvF/VHb2Wz2E5ZlfUYpJYjIF0IgCAKTiJRhGG65XH68VCrdF9V3dHQ8YhjGL9W2bRiG53meKBQKNyqlzkf1nZ2dzxFRT73stm1fKhaL74lq0+n0x9Pp9B/E+4/Xu677bwsLC5+IZflzwzBuifqXUgqllEFEAQDMzc3dqpQ6Hm4Xo6ur63mlVLZeFsdxnEKhcE3UtmVZw7lc7uEgCMx69cVi8YBt25+P6pPJ5APpdPpXov6FEEEQBIkwm1sqlT5l2/bh2Hb5TyLqq9e267qqUChcX72fl+iqzs7OxwBYDbbjvxeLxXtivvyFEOKD0c+DILDi+7dcLv9JuVz+Rmw7HjUMY2eDtp1isVjdLkR0ZS6XO9DIL8dxXi4UCh+N1XdVRc5kMu/duXPnVtTBdV0cP3787QBeClcZuVzuZ/v7++uV4+TJk9vjjw3DGB0cHByqWwzg9ddfvxh/nEgkfnnPnj0769VOTU2hUChkAVRFTqfT1+7cubPup8vExESxZtX79+zZs7dRltOnTy8aKWMYxsjQ0NDP1KstFAqYm5vbAeB4uMrq7u6+qq+vj+rVnzt3zo0/JqKrduzYsTeRSNTNcvz48fmaLPsHBwffVq/W8zyMjY1dA6AqcjabvXb79u1mvfrZ2VlZKBQ6AZTCVbt6e3v39fT01M1y4sSJRTeuW5b1gT179tTdLgDw6quv3gigKnImk3nXrl27UvVqJyYmaseLbmnm1+nTp7M1q9ItnVoIIWBZ1mwrtQBgmmbQam1YX2i11rKsdpqGaZptjRwwTdNuoxYAWq63LKutLIZhlFutFUJACNHOPgKAlts3DMNptRYAEonExeWrKqxgny4ZKF19txKR8rzKz4UQ1fFQQgg4jgPP87bEn2gYBjzPW1IrpYTneUa8VgjhNGpbSgnf93M1Qf3atokqBznHWbo9l8my6M0qhCjWyxK177pupqZt1/O86s+VUrVZkrFyCVSOjkQEIlqUx7bt2gPHfJixbvYgCNI12cu1WaJ613WhlOqO1xMRmu1TAGkAc2G5E9U3yBJ/nRBCBM32qeu6vTX1qlHbrusu+QRrtk9d113yEVYd6pTJZD4EYG+4AS5FGyVaLpfLf6OUuhSuo2Qy+UkhRLpebRAEJxzHeSzqJJlMvt0wjNsatQ3gmVKp9GxUn0qlbhZC/Fy8FkABQEpKqRzH+apSqmp0IpG42zTN3pq2Z5VSW3zfP+e67qNRbSaT2Qng12qzxNr/kW3bR6L6dDo9TETvjH6OypvfC/+lyuXyw0qphVj23xFCZAEEqBzxOqK+giAQtm3/dXXjE12RTqd/o9F28X3/qOu6/xV7ne80TXM41n+mZh/9nVLqbKz+LtM0t9VrPwiCtOM4f6mUCsJ1Xclk8h4hhKiXRSn1XLlc/tfYdnkvEd3QKHsQBN91HOfVWJZfN02zv8HrnHFd92ux7ZJJJpN3N/JLSvmKbdtPxuotHrPHaAFffmO0gEVmtIBFZrSARWa0gEVmtIBFZrSARWa0gEVmtIBFZrSARWa0gEVmtIBFZrSARWa0IH4/cg+A6wEYqNyCOK6UOtboiUQ0BCAareAA+O/oNs8G9TcA2BKrT9ZZ9lB5c0UZJCpDb04ppV5p0vYeVG5BFeHznlNKzTSpvxFA1zJZapcTqExTMaGU+lGTtneHWUwAKsxyoUn9uwBE9+620r8frjNRmXrgh03a3gXgqvChB+CYUuqNJvXXAbiiQf8W3ty+Kvb63LBuVin1LBpARNvDLNG+fUEpNdWk/ioAu1rsn6oid3R0/FV/f//HohvHZ2Zm3og1tITu7u4DfX19Q0BlUpLTp0/fC+Arjeq3bNnydG9vbzQ2Db5f2R/xZcMwIKWs3rweju3D1NTUSwB+vlHb2Wz2sYGBgWuVUhBCYGJi4hEAdzWq37Zt2/c7OzuNZlkaLc/Ozp4C8NON2k4mk49ceeWV74tuBp+enj4A4OZG9T09PU/39PQkWu1fiMqHqJQS8/PzFwH0NWq7q6vr21u3br0eqGzb6enpHwC4oUmW7/f09ETjDRf1HwQBou0b9R+vKxQKBQC5+i0D2Wz2kYGBgQ9E+3ZycvI7AO5okv2prVu3bm2l/6mpqTePyEIIr7Ozs9rQpUsND67Vp3R1VQ5qrusCy5ymGIaBqL5dpqenlzsFMuLZiajuOLWIRCKx4iyzs7NN20ZlPGP1wfT0dNN6y7JWnGVhYWG5bR7E2z5//nzTmbuEECvOUiqVmmYhokX7aHJysul2ISJqNUsmk1kk36IgiUTCRYuEQ2+a1huG8Vbu4G/rXJ6Imo6ji4YKrQVCiKZj3S5nFqXUosYtyyo1qg2zrNkoCyFEy34th+/7bx6Rfd9/fXx8/Jxpmp7v+5Zt26eaPdnzvGOTk5MJ3/ctpRQZhnG8WX2xWDw5Pj7eGbUPVAYRtrLseV7D82MAkFL+eHJysjd6jpTyhWb1CwsL4+VyORPvRwgRKKWEUoqISBKRklJGpx/VuiAI/meZLM9PTk7ujup933+xWb1t2/87Pj7e02r/8WXf98eate267mvj4+O7o+c4jtPw/BgAHMf5yfj4+C4iUkQkl+u/ZrucadZ2EAQvTU5ODsX20fPN6j3Pe3liYmIw9lUJDTMUi0WenozRA778xmgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGiBCVS+rbyjo+MrSqlOADAM41IQBN2tLgsh5qWUOVSG4pSllF8vlUo/iDrp6Oj4PICrm7VBREWlVAKVIS0eKvc3Z1eSxzCM2SAItgCAlPLbpVLpO1GWbDb720T0vrXqP74+CILD5XL50ShLLpf7VaXUbavUf90+48sAiqgMmbJ83/+WbdtPRFmSyeRtlmXdsVb9A/CIyFVKZT3PO+g4zjdjvnxcSnlTbbtCiKJSylJKJYjIIyJHStlRLpfvje5Hflt/f/898Tv43wrHjh3LAYiL/Ps7duxYncbb5MSJE1kAVZHT6fQXd+/ePbAeWd54443tAB6NHgshHmw0Y9TlZmxsrANAVWTLsu7ft2/fO9Yjy8mTJ3cAqIqcSqW+uHv37sFWnus4Dl5++eUntT+1qJ1oZz3xfX/R5DZE1HTo0WXO0lGzat1uTA+CILN8VX3CETb+ZRHZMIy5+GPLstqaruxyUm9qq/VCCOEvX7U2rObQo7UkHISaik4txqempv5jamoqmtVTCiG8aEoqwzDK0VRZrSx7nvd0vLOZmZmn5ubmdhmG4QFAEAQWESkhhN1Ou02WbSllIjbzpor6kFI+Es+ysLBweGxs7B1r1X+8DyL6ZjyLbdsHx8bGFlrsw5VSGquxf0zTLPm+/914FsdxDoyNjV22/sM+M/VqHMf5l3iWcrn8vRMnTlwdHamFELaUMgFAEJEPQCmlLAAqfAO+yEOdGC3Q/hyZ+f8Bi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoQXXqhZ6entdSqdRuADBNU/m+T7XLjWZdqq2zLEt5nrfk+a0sCyEwMzPz1VKp9OlYtjOpVGrramRrZTmeP77cbh9BEATT09PblFLzAEBE+3p7e3+cSCRWtG1W8hrb3RfxWZPCPlQQBFQoFJ6bn59/T7RPuru7n8lkMu+Onh8EATWYdamt19jktSspZbUPIqq+9oWFhWeqIluW1TUwMLDcjEVrwszMzBXxx4lEIrdRsrVDoVAwp6enewDMh6uSHR0dVl9fwxnFNiynTp1KxR8bhtG3UfbJa6+9to1PLdaWwLKs9c6wIqLRIBuV6jvKtu3g/Pnz8LzKkDbLstpaNgwDSqn4R1L10N9uW0Q0GQ/puq575syZhpMVrqSP2uzRR3btx9ZbaVdWgpZjL4UuXLiAUqkEwzBWJXsrkzmuRh++7y8a0+d5nt+KL40muySiVdsGSqmF/wMss3GsPd8ywQAAAABJRU5ErkJggg==);
+ background-position: 10px -31px;
+}
+.leaflet-styleeditor-button:hover {
+ background-color: #00ad87;
+}
+.leaflet-styleeditor-button:active {
+ background-color: #20e1b6;
+}
+.leaflet-styleeditor-colorpicker {
+ width: 160px;
+ display: inline-block;
+}
+.leaflet-styleeditor-color {
+ height: 35px;
+ width: 30px;
+ float: left;
+ border: 1px solid white;
+ cursor: pointer;
+}
+.leaflet-styleeditor-color:hover {
+ border:1px solid black;
+}
+.leaflet-styleeditor-sizeicon {
+ background-image: url('https://unpkg.com/leaflet@0.7.7/dist/images/marker-icon-2x.png');
+ background-repeat: no-repeat;
+ float: left;
+ margin-right: 15px;
+ border: 1px solid white;
+ cursor: pointer;
+}
+.leaflet-styleeditor-sizeicon:hover {
+ border: 1px solid black;
+}
+.sizeicon-small {
+ background-size: 20px 33px;
+ width: 22px;
+ height: 33px;
+}
+.sizeicon-medium {
+ background-size: 25px 41px;
+ width:28px;
+ height:41px;
+}
+.sizeicon-large {
+ background-size: 30px 49px;
+ width:32px;
+ height: 49px;
+}
+.leaflet-styleeditor-tooltip-wrapper {
+ position: absolute;
+ text-align: center;
+ bottom: 10%;
+ width: 100%;
+ z-index: 1000;
+}
+.leaflet-styleeditor-tooltip {
+ background-color: rgba(68, 68, 68, 0.2);
+ border: 3px solid rgba(68, 68, 68, 0.7);
+ border-radius: 5px;
+ display: inline-block;
+ font: 20px/1"Helvetica Neue", Arial, Helvetica, sans-serif;
+ padding: 10px;
+ position: relative;
+ whitespace: no-wrap;
+}
+.leaflet-styleeditor-stroke {
+ height: 20px;
+ width: 150px;
+ background-repeat: no-repeat;
+ border: 1px solid white;
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAALIAAACmCAYAAAB+zdXlAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAABE/SURBVHic7Z17cFzXXce/v3Pv3adWkiXZQfIjxki1OxAakpC2aehUZWpp0ryalqEpnWEIhPQfSh+0kwaYTtqEUAaGATqE6ZQmQIfppMy0SWrjtQluAqSQQpI2SR0rxLYcybZkW7KkfdznOfyx926uVrurXUXW4/D7zHh89+q353z33s/evbq6Zw8ppRBndHT0GqXUffl8/iNgmE2CqF2hlLoFwIf3799/5zrkYZgVsURkADcAABH92U033dS5xnkYZkVQ/NTiuuuus3p7ey8CyAGAUuqHRHQJQC+AnyKiLxw6dOjv1ycqwzTGDM+JbwFwQ29v77sRSgwARPSLsdpXHMd5Ys0TMkwLmEqpzwH46DJ1U1LKm44ePXqp9gf79++/M5VKPf7EE0+ULk9Ehlkekc/n7wRwJ4DJJnW/deTIkdO1K0dGRu4lon90HOfJW2+9NXPZUjLMMggAyOfz33Jddx+AZxvUXVe7YmRk5F4AD4UP388yM+tJ9arF0aNHCwDmGtT95vDwsAkAw8PDfeG62isaGd/3zdWPyDDLU3v5bVv4/1kiuhnA/QAmAOxIJBK3j46ODiYSiRdGRkYeyufz9wF4IKx/NpVK7T948OD8GuVmmEUsuvw2MjIyCeC87/sffOqppyYBYHh42Ewmk7cppRwADwPYEZb/cT6f/8LIyMhdrus+Fh7RGWZdWCTy6OjoxxzHOVh7dWJ0dHRQKXUUb0oMAK5hGL9w8ODBn6xRVoZpCNXea1FLI4kBfCSfzz95OcMxTKs0/eWsicQfzufz37usyRimDerdawGgscREdAdLzGw06orcQGKHiO44dOjQgbWJxjCts+QcuZHESqk7Dh8+fHBN0zFMiyw5IkspP4saiQF8iCVmNjJLRCYiij10iOj2fD7/z2uYiWHaZslVCyJ6USn1OABIKR8+cuRIfu1jMUx7LHsdmWE2Aw0vvzHMZoJFZrSARWa0gEVmtIBFZrSARWa0gEVmtIBFZrSARWa0gEVmtIBFZrSARWa0gEVmtIBFZrSARWa0gEVmtIBFZrSARWa0gEVmtIBFZrRAENFDhmF8koiS6x2GYVaKMAxjyDTNPxVCnGWhmc2KAICBgQFr7969W3K53B+FQv8uEaXWOxzDtEr1HDmTyWBwcDAbCv1gKPSnWGhmM7Dkl72Y0N0dHR0PhEJ/moVmNjINr1pkMhkMDQ1FQn85FPozRJRey4AM0wrLXn6rEfpLQogzLDSz0Wj5OnIdoc8ahvFZFprZCLT9B5GY0F0xoX+PiHjWU2bdWPFf9kKhM6HQ94dCf46FZtaDt/wn6pjQnalU6ktEdIaI7l6NcAzTKqtyr4Xnebhw4YJj27Yion8A8PhqtMswrfKWJkF3XRfnzp1zLl68KAH8rZTyy0qp6VXKxjAtsyKRWWBmo9GWyDUCf11K+QALzGwEWhLZdV2cPXvWmZmZYYGZDUlTkVlgZrNQV2QWmNlsLBI5FNiemZlRYIGZTYQJVAQeHx+3Z2ZmAOBrUsoHWWBmM2ECCM6ePesREQvMbFoIwAiAF1hgZjPDU/gyWsDfa8FoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBARjs7+9/NpFIeOsdhmHaYW5u7huzs7N/CFS+oMXs7u7emsnw1B/M5mJ+fr76lW8mAFUqlUpSSrmOmRimbeLKUvjvynVLwzArZ1opVQL4m4YYTeCrFowWsMiMFrDIjBawyIwWsMiMFrDIjBawyIwWsMiMFrDIjBawyIwWsMiMFrDIjBawyIwWsMiMFlTvsM/lcv8khLgaAEzTdHzfT8aWU7Zt3+44zksAQERWLpd7gYhSdept27bPFIvF/VHb2Wz2E5ZlfUYpJYjIF0IgCAKTiJRhGG65XH68VCrdF9V3dHQ8YhjGL9W2bRiG53meKBQKNyqlzkf1nZ2dzxFRT73stm1fKhaL74lq0+n0x9Pp9B/E+4/Xu677bwsLC5+IZflzwzBuifqXUgqllEFEAQDMzc3dqpQ6Hm4Xo6ur63mlVLZeFsdxnEKhcE3UtmVZw7lc7uEgCMx69cVi8YBt25+P6pPJ5APpdPpXov6FEEEQBIkwm1sqlT5l2/bh2Hb5TyLqq9e267qqUChcX72fl+iqzs7OxwBYDbbjvxeLxXtivvyFEOKD0c+DILDi+7dcLv9JuVz+Rmw7HjUMY2eDtp1isVjdLkR0ZS6XO9DIL8dxXi4UCh+N1XdVRc5kMu/duXPnVtTBdV0cP3787QBeClcZuVzuZ/v7++uV4+TJk9vjjw3DGB0cHByqWwzg9ddfvxh/nEgkfnnPnj0769VOTU2hUChkAVRFTqfT1+7cubPup8vExESxZtX79+zZs7dRltOnTy8aKWMYxsjQ0NDP1KstFAqYm5vbAeB4uMrq7u6+qq+vj+rVnzt3zo0/JqKrduzYsTeRSNTNcvz48fmaLPsHBwffVq/W8zyMjY1dA6AqcjabvXb79u1mvfrZ2VlZKBQ6AZTCVbt6e3v39fT01M1y4sSJRTeuW5b1gT179tTdLgDw6quv3gigKnImk3nXrl27UvVqJyYmaseLbmnm1+nTp7M1q9ItnVoIIWBZ1mwrtQBgmmbQam1YX2i11rKsdpqGaZptjRwwTdNuoxYAWq63LKutLIZhlFutFUJACNHOPgKAlts3DMNptRYAEonExeWrKqxgny4ZKF19txKR8rzKz4UQ1fFQQgg4jgPP87bEn2gYBjzPW1IrpYTneUa8VgjhNGpbSgnf93M1Qf3atokqBznHWbo9l8my6M0qhCjWyxK177pupqZt1/O86s+VUrVZkrFyCVSOjkQEIlqUx7bt2gPHfJixbvYgCNI12cu1WaJ613WhlOqO1xMRmu1TAGkAc2G5E9U3yBJ/nRBCBM32qeu6vTX1qlHbrusu+QRrtk9d113yEVYd6pTJZD4EYG+4AS5FGyVaLpfLf6OUuhSuo2Qy+UkhRLpebRAEJxzHeSzqJJlMvt0wjNsatQ3gmVKp9GxUn0qlbhZC/Fy8FkABQEpKqRzH+apSqmp0IpG42zTN3pq2Z5VSW3zfP+e67qNRbSaT2Qng12qzxNr/kW3bR6L6dDo9TETvjH6OypvfC/+lyuXyw0qphVj23xFCZAEEqBzxOqK+giAQtm3/dXXjE12RTqd/o9F28X3/qOu6/xV7ne80TXM41n+mZh/9nVLqbKz+LtM0t9VrPwiCtOM4f6mUCsJ1Xclk8h4hhKiXRSn1XLlc/tfYdnkvEd3QKHsQBN91HOfVWJZfN02zv8HrnHFd92ux7ZJJJpN3N/JLSvmKbdtPxuotHrPHaAFffmO0gEVmtIBFZrSARWa0gEVmtIBFZrSARWa0gEVmtIBFZrSARWa0gEVmtIBFZrSARWa0IH4/cg+A6wEYqNyCOK6UOtboiUQ0BCAareAA+O/oNs8G9TcA2BKrT9ZZ9lB5c0UZJCpDb04ppV5p0vYeVG5BFeHznlNKzTSpvxFA1zJZapcTqExTMaGU+lGTtneHWUwAKsxyoUn9uwBE9+620r8frjNRmXrgh03a3gXgqvChB+CYUuqNJvXXAbiiQf8W3ty+Kvb63LBuVin1LBpARNvDLNG+fUEpNdWk/ioAu1rsn6oid3R0/FV/f//HohvHZ2Zm3og1tITu7u4DfX19Q0BlUpLTp0/fC+Arjeq3bNnydG9vbzQ2Db5f2R/xZcMwIKWs3rweju3D1NTUSwB+vlHb2Wz2sYGBgWuVUhBCYGJi4hEAdzWq37Zt2/c7OzuNZlkaLc/Ozp4C8NON2k4mk49ceeWV74tuBp+enj4A4OZG9T09PU/39PQkWu1fiMqHqJQS8/PzFwH0NWq7q6vr21u3br0eqGzb6enpHwC4oUmW7/f09ETjDRf1HwQBou0b9R+vKxQKBQC5+i0D2Wz2kYGBgQ9E+3ZycvI7AO5okv2prVu3bm2l/6mpqTePyEIIr7Ozs9rQpUsND67Vp3R1VQ5qrusCy5ymGIaBqL5dpqenlzsFMuLZiajuOLWIRCKx4iyzs7NN20ZlPGP1wfT0dNN6y7JWnGVhYWG5bR7E2z5//nzTmbuEECvOUiqVmmYhokX7aHJysul2ISJqNUsmk1kk36IgiUTCRYuEQ2+a1huG8Vbu4G/rXJ6Imo6ji4YKrQVCiKZj3S5nFqXUosYtyyo1qg2zrNkoCyFEy34th+/7bx6Rfd9/fXx8/Jxpmp7v+5Zt26eaPdnzvGOTk5MJ3/ctpRQZhnG8WX2xWDw5Pj7eGbUPVAYRtrLseV7D82MAkFL+eHJysjd6jpTyhWb1CwsL4+VyORPvRwgRKKWEUoqISBKRklJGpx/VuiAI/meZLM9PTk7ujup933+xWb1t2/87Pj7e02r/8WXf98eate267mvj4+O7o+c4jtPw/BgAHMf5yfj4+C4iUkQkl+u/ZrucadZ2EAQvTU5ODsX20fPN6j3Pe3liYmIw9lUJDTMUi0WenozRA778xmgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGiBCVS+rbyjo+MrSqlOADAM41IQBN2tLgsh5qWUOVSG4pSllF8vlUo/iDrp6Oj4PICrm7VBREWlVAKVIS0eKvc3Z1eSxzCM2SAItgCAlPLbpVLpO1GWbDb720T0vrXqP74+CILD5XL50ShLLpf7VaXUbavUf90+48sAiqgMmbJ83/+WbdtPRFmSyeRtlmXdsVb9A/CIyFVKZT3PO+g4zjdjvnxcSnlTbbtCiKJSylJKJYjIIyJHStlRLpfvje5Hflt/f/898Tv43wrHjh3LAYiL/Ps7duxYncbb5MSJE1kAVZHT6fQXd+/ePbAeWd54443tAB6NHgshHmw0Y9TlZmxsrANAVWTLsu7ft2/fO9Yjy8mTJ3cAqIqcSqW+uHv37sFWnus4Dl5++eUntT+1qJ1oZz3xfX/R5DZE1HTo0WXO0lGzat1uTA+CILN8VX3CETb+ZRHZMIy5+GPLstqaruxyUm9qq/VCCOEvX7U2rObQo7UkHISaik4txqempv5jamoqmtVTCiG8aEoqwzDK0VRZrSx7nvd0vLOZmZmn5ubmdhmG4QFAEAQWESkhhN1Ou02WbSllIjbzpor6kFI+Es+ysLBweGxs7B1r1X+8DyL6ZjyLbdsHx8bGFlrsw5VSGquxf0zTLPm+/914FsdxDoyNjV22/sM+M/VqHMf5l3iWcrn8vRMnTlwdHamFELaUMgFAEJEPQCmlLAAqfAO+yEOdGC3Q/hyZ+f8Bi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoAYvMaAGLzGgBi8xoQXXqhZ6entdSqdRuADBNU/m+T7XLjWZdqq2zLEt5nrfk+a0sCyEwMzPz1VKp9OlYtjOpVGrramRrZTmeP77cbh9BEATT09PblFLzAEBE+3p7e3+cSCRWtG1W8hrb3RfxWZPCPlQQBFQoFJ6bn59/T7RPuru7n8lkMu+Onh8EATWYdamt19jktSspZbUPIqq+9oWFhWeqIluW1TUwMLDcjEVrwszMzBXxx4lEIrdRsrVDoVAwp6enewDMh6uSHR0dVl9fwxnFNiynTp1KxR8bhtG3UfbJa6+9to1PLdaWwLKs9c6wIqLRIBuV6jvKtu3g/Pnz8LzKkDbLstpaNgwDSqn4R1L10N9uW0Q0GQ/puq575syZhpMVrqSP2uzRR3btx9ZbaVdWgpZjL4UuXLiAUqkEwzBWJXsrkzmuRh++7y8a0+d5nt+KL40muySiVdsGSqmF/wMss3GsPd8ywQAAAABJRU5ErkJggg==);
+ cursor: pointer;
+}
+.leaflet-styleeditor-stroke:hover {
+ border: 1px solid black;
+}
+
+.leaflet-styleeditor-marker-s {
+ padding-top: 3px;
+ font-size: 8px;
+ height: 50px;
+ width: 20px;
+}
+.leaflet-styleeditor-marker-m {
+ padding-top: 5px;
+ font-size: 12px;
+ height: 70px;
+ width: 30px;
+}
+.leaflet-styleeditor-marker-l {
+ padding-top: 9px;
+ font-size: 16px;
+ height: 90px;
+ width: 35px;
+}
+
+.leaflet-styleeditor-marker {
+ text-align: center;
+ color: white;
+ display: flex;
+ background-repeat: no-repeat;
+}
+
+.leaflet-styleeditor-maki-marker-icon {
+ width: 100%;
+ height: 25%;
+}
+.leaflet-styleeditor-marker-selected {
+ background-clip: padding-box;
+ background-color: rgba(254,87,161,0.1);
+ border: 4px dashed rgba(254,87,161,0.6);
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ box-sizing: content-box;
+ left: -4px;
+ top: -4px;
+}
+
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.StyleEditor/img/control.svg b/www/wiki/extensions/Maps/resources/lib/leaflet.StyleEditor/img/control.svg
new file mode 100644
index 00000000..54c69ee2
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.StyleEditor/img/control.svg
@@ -0,0 +1,114 @@
+<?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://creativecommons.org/ns#"
+ 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://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="5.0973964mm"
+ height="5.2094336mm"
+ viewBox="0 0 5.0973964 5.2094336"
+ version="1.1"
+ id="svg3851"
+ inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+ sodipodi:docname="control.svg">
+ <defs
+ id="defs3845" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="10.24"
+ inkscape:cx="4.3567738"
+ inkscape:cy="11.408412"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1366"
+ inkscape:window-height="768"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="0"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0" />
+ <metadata
+ id="metadata3848">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Ebene 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-0.03511714,-291.8183)">
+ <g
+ id="g3875"
+ inkscape:export-filename="D:\Coding\leaflet-editStyle\img\icon.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90"
+ transform="matrix(-0.24830897,0,0,0.24830897,156.68418,164.82136)">
+ <polygon
+ transform="translate(288,498.8979)"
+ style="fill:#464646"
+ points="327,33.529 327,30.699 335.891,21.809 338.719,24.637 329.828,33.529 "
+ id="polygon3006" />
+ <rect
+ style="fill:#464646"
+ x="-813.74628"
+ y="74.533409"
+ transform="rotate(-134.9919)"
+ width="3.9999616"
+ height="3.9999616"
+ id="rect3008" />
+ <path
+ transform="matrix(0.78402363,0,0,0.78402363,222.68069,148.68168)"
+ inkscape:transform-center-y="-0.29924052"
+ inkscape:transform-center-x="0.29924114"
+ d="m 508.57142,476.82648 c -0.78265,0.78265 -4.14781,-1.95042 -5.24102,-1.77728 -1.0932,0.17315 -3.44909,3.81235 -4.43529,3.30986 -0.98619,-0.50249 0.57322,-4.54752 0.0707,-5.53371 -0.50249,-0.9862 -4.69158,-2.10221 -4.51844,-3.19541 0.17315,-1.09321 4.50208,-0.8601 5.28473,-1.64274 0.78265,-0.78265 0.54954,-5.11158 1.64274,-5.28473 1.09321,-0.17315 2.20922,4.01595 3.19541,4.51844 0.9862,0.50249 5.03122,-1.05693 5.53371,-0.0707 0.50249,0.98619 -3.13671,3.34208 -3.30985,4.43529 -0.17315,1.0932 2.55992,4.45836 1.77728,5.24101 z"
+ inkscape:randomized="0"
+ inkscape:rounded="0.2"
+ inkscape:flatsided="false"
+ sodipodi:arg2="1.4137167"
+ sodipodi:arg1="0.78539816"
+ sodipodi:r2="4.1668792"
+ sodipodi:r1="8.3337584"
+ sodipodi:cy="470.93362"
+ sodipodi:cx="502.67856"
+ sodipodi:sides="5"
+ id="path3099"
+ style="fill:#464646;fill-opacity:1;stroke:#000000;stroke-width:0;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ sodipodi:type="star" />
+ </g>
+ <path
+ style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke-width:0.04724703;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 4.2983645,294.67034 c 0,-0.0992 -0.02286,-0.30397 -0.050797,-0.45511 -0.074689,-0.40402 -0.042135,-0.47419 0.2996803,-0.64598 0.3800898,-0.19102 0.554318,-0.31545 0.554318,-0.3959 0,-0.0789 -0.067114,-0.10113 -0.5718837,-0.18963 -0.2322777,-0.0407 -0.418703,-0.0994 -0.4498154,-0.14167 -0.029223,-0.0397 -0.086566,-0.25421 -0.1274313,-0.47676 -0.1219597,-0.6642 -0.1922462,-0.67173 -0.5131461,-0.0549 -0.1074923,0.20662 -0.2279147,0.38813 -0.2676022,0.40336 -0.039688,0.0152 -0.2742592,0.002 -0.5212689,-0.029 -0.3812672,-0.0482 -0.4593616,-0.0465 -0.5170064,0.0112 -0.059036,0.059 -0.023918,0.11437 0.2689516,0.42379 0.1852665,0.19574 0.336849,0.39748 0.336849,0.44831 0,0.0508 -0.087085,0.27392 -0.1935216,0.49574 -0.2748015,0.5727 -0.2265283,0.61179 0.3883078,0.31447 0.2149422,-0.10395 0.419616,-0.18899 0.4548267,-0.18899 0.035213,0 0.2212631,0.14883 0.4134459,0.33073 0.406347,0.38461 0.4960937,0.41182 0.4960937,0.15042 z"
+ id="path3885"
+ inkscape:connector-curvature="0" />
+ <path
+ style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke-width:0.04724703;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 3.9676353,296.68109 v -0.34291 l -1.0992696,-1.09853 -1.0992723,-1.09852 -0.3413972,0.34369 -0.3413971,0.34369 1.098497,1.09775 1.0984971,1.09775 H 3.625463 3.9676353 Z"
+ id="path3887"
+ inkscape:connector-curvature="0" />
+ <path
+ style="opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke-width:0.04724703;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 1.0746309,294.27107 0.3418205,-0.34412 -0.3312371,-0.3287 -0.33123977,-0.3287 -0.3417015,0.3417 -0.3417014,0.3417 0.3311181,0.33112 0.3311181,0.33112 z"
+ id="path3889"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.StyleEditor/img/icon.png b/www/wiki/extensions/Maps/resources/lib/leaflet.StyleEditor/img/icon.png
new file mode 100644
index 00000000..7691615c
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.StyleEditor/img/icon.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.StyleEditor/javascript/Leaflet.StyleEditor.min.js b/www/wiki/extensions/Maps/resources/lib/leaflet.StyleEditor/javascript/Leaflet.StyleEditor.min.js
new file mode 100644
index 00000000..596bd64f
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.StyleEditor/javascript/Leaflet.StyleEditor.min.js
@@ -0,0 +1,2 @@
+// 0.1.14 8054a4ff462ef0126582a3789e5c7688aa2e18ab
+var leafletstyleeditor=function(t){var e={};function o(i){if(e[i])return e[i].exports;var n=e[i]={i:i,l:!1,exports:{}};return t[i].call(n.exports,n,n.exports,o),n.l=!0,n.exports}return o.m=t,o.c=e,o.d=function(t,e,i){o.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},o.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},o.t=function(t,e){if(1&e&&(t=o(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(o.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var n in t)o.d(i,n,function(e){return t[e]}.bind(null,n));return i},o.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return o.d(e,"a",e),e},o.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},o.p="",o(o.s=2)}([function(t,e){t.exports=void 0},function(t,e,o){},function(t,e,o){"use strict";o.r(e);o(0);o(1),L.StyleEditor={marker:{},forms:{},formElements:{}},L.StyleEditor.Util=L.Class.extend({initialize:function(t){t&&L.setOptions(this,t)},fireEvent:function(t,e){this.options.styleEditorOptions.map.fireEvent(this.options.styleEditorOptions.styleEditorEventPrefix+t,e)},fireChangeEvent:function(t){this.fireEvent("changed",t)},hideElement:function(t){t&&L.DomUtil.addClass(t,"leaflet-styleeditor-hidden")},rgbToHex:function(t,e){if(t||0!==(t=this.options.styleEditorOptions.defaultColor).indexOf("#")&&(t="#"+t),0===t.indexOf("#"))return e&&t.replace("#",""),t;if(t.indexOf("(")<0)return"#"+t;var o=t.substring(4).replace(")","").split(","),i=this._componentToHex(parseInt(o[0],10))+this._componentToHex(parseInt(o[1],10))+this._componentToHex(parseInt(o[2],10));return e?i:"#"+i},getCurrentElement:function(){return this.options.styleEditorOptions.currentElement?void 0!==this.options.styleEditorOptions.currentElement.target?this.options.styleEditorOptions.currentElement.target:this.options.styleEditorOptions.currentElement:null},setCurrentElement:function(t){this.options.styleEditorOptions.currentElement.target=t},fillCurrentElement:function(){return this.getCurrentElement().options.fill},getStyle:function(t){var e=this.getCurrentElement().options[t];return e||null},setStyle:function(t,e){var o=this.getCurrentElement();if(o instanceof L.Marker)this.options.styleEditorOptions.markerType.setStyle(t,e);else{var i={};i[t]=e,o.setStyle(i)}this.fireChangeEvent(o)},showElement:function(t){t&&L.DomUtil.removeClass(t,"leaflet-styleeditor-hidden")},_componentToHex:function(t){var e=t.toString(16);return 1===e.length?"0"+e:e},getMarkersForColor:function(t){t=this.rgbToHex(t);var e=this.options.styleEditorOptions.markerType.options.markers,o=this.options.styleEditorOptions.markers;if(Array.isArray(e)||(e=Object.keys(e).includes(t)?e[t]:e.default),null!==o){if(!Array.isArray(o)){var i=Object.keys(o);o=i.includes(t)?o[t]:i.includes("default")?o.default:e}return e.filter((function(t){return o.includes(t)}))}return e},getDefaultMarkerForColor:function(t){t=this.rgbToHex(t);var e=this.getMarkersForColor(t),o=[],i=this.options.styleEditorOptions.defaultMarkerIcon;return null!==i&&("string"==typeof i&&o.push(i),Object.keys(i).includes(t)&&o.push(i[t])),void 0!==(i=this.options.styleEditorOptions.markerType.options.defaultMarkerIcon)&&("string"==typeof i&&o.push(i),Object.keys(i).includes(t)&&o.push(i[t])),o.filter((function(t){return e.includes(t)})),o.length>0?o[0]:e[0]}}),L.StyleEditor.formElements.FormElement=L.Class.extend({initialize:function(t){t&&L.setOptions(this,t),!this.options.title&&this.options.styleOption&&(this.options.title=this.options.styleOption.charAt(0).toUpperCase()+this.options.styleOption.slice(1))},create:function(t){this.options.uiElement=L.DomUtil.create("div","leaflet-styleeditor-uiElement",t),this.createTitle(),this.createContent()},createTitle:function(){L.DomUtil.create("label","leaflet-styleeditor-label",this.options.uiElement).innerHTML=this.options.title+":"},createContent:function(){},show:function(){this.style(),this.showForm()},showForm:function(){this.options.styleEditorOptions.util.showElement(this.options.uiElement)},hide:function(){this.options.styleEditorOptions.util.hideElement(this.options.uiElement)},style:function(){},lostFocus:function(){},setStyle:function(t){var e=this.options.styleEditorOptions.util.getCurrentElement(),o=[e];e instanceof L.LayerGroup&&(o=Object.values(e._layers));for(var i=0;i<o.length;i++){var n=o[i];if(n instanceof L.Marker)this.options.styleEditorOptions.markerType.setStyle(this.options.styleOption,t);else{var s={};s[this.options.styleOption]=t,n.setStyle(s),n.options[this.options.styleOption]=t}this.options.styleEditorOptions.util.fireChangeEvent(n)}this.options.parentForm.style()}}),L.StyleEditor.formElements.ColorElement=L.StyleEditor.formElements.FormElement.extend({createContent:function(){this.options.colorPickerDiv=L.DomUtil.create("div","leaflet-styleeditor-colorpicker",this.options.uiElement),this._getColorRamp().forEach(this._setSelectCallback,this)},_getColorRamp:function(){return this.options.colorRamp||(this.options.parentForm instanceof L.StyleEditor.forms.MarkerForm&&this.options.styleEditorOptions.markerType.options.colorRamp?this.options.colorRamp=this.options.styleEditorOptions.markerType.options.colorRamp:this.options.colorRamp=this.options.styleEditorOptions.colorRamp),this.options.colorRamp},_setSelectCallback:function(t){var e=L.DomUtil.create("div","leaflet-styleeditor-color",this.options.colorPickerDiv);e.style.backgroundColor=t,L.DomEvent.addListener(e,"click",this._selectColor,this)},_selectColor:function(t){t.stopPropagation(),this.setStyle(this.options.styleEditorOptions.util.rgbToHex(t.target.style.backgroundColor)),this.options.styleEditorOptions.currentElement.target instanceof L.Marker&&this.options.styleEditorOptions.markerType.setNewMarker()}}),L.StyleEditor.formElements.DashElement=L.StyleEditor.formElements.FormElement.extend({createContent:function(){var t=this.options.uiElement,e=L.DomUtil.create("div","leaflet-styleeditor-stroke",t);e.style.backgroundPosition="0px -75px",L.DomEvent.addListener(e,"click",(function(){this.setStyle("1")}),this),(e=L.DomUtil.create("div","leaflet-styleeditor-stroke",t)).style.backgroundPosition="0px -95px",L.DomEvent.addListener(e,"click",(function(){this.setStyle("10, 10")}),this),(e=L.DomUtil.create("div","leaflet-styleeditor-stroke",t)).style.backgroundPosition="0px -115px",L.DomEvent.addListener(e,"click",(function(){this.setStyle("15, 10, 1, 10")}),this)}}),L.StyleEditor.formElements.IconElement=L.StyleEditor.formElements.FormElement.extend({_selectOptionWrapperClasses:"leaflet-styleeditor-select-option-wrapper leaflet-styleeditor-hidden",_selectOptionClasses:"leaflet-styleeditor-select-option",createContent:function(){var t=this.options.uiElement,e=L.DomUtil.create("div","leaflet-styleeditor-select",t);this.options.selectBoxImage=this._createSelectInputImage(e),L.DomEvent.addListener(e,"click",this._toggleSelectInput,this)},style:function(){var t=this.options.styleEditorOptions.markerType.getIconOptions();this._styleSelectInputImage(this.options.selectBoxImage,t.icon,t.iconColor),this._createColorSelect(this.options.styleEditorOptions.markerType.options.iconOptions.iconColor),this._hideSelectOptions()},lostFocus:function(){this._hideSelectOptions()},_createSelectInputImage:function(t){var e=L.DomUtil.create("div","leaflet-styleeditor-select-image-wrapper",t);return L.DomUtil.create("div","leaflet-styleeditor-select-image",e)},_styleSelectInputImage:function(t,e,o){if(e||(e=t.getAttribute("value"))){var i=this.options.styleEditorOptions.markerType.getIconOptions();o&&(i.iconColor=o),t.innerHTML="",this.options.styleEditorOptions.markerType.createSelectHTML(t,i,e),t.setAttribute("value",e)}},_createColorSelect:function(t){if(this.options.selectOptions||(this.options.selectOptions={}),!(t in this.options.selectOptions)){var e=this.options.uiElement,o=L.DomUtil.create("ul",this._selectOptionWrapperClasses,e);this.options.styleEditorOptions.util.getMarkersForColor(t).forEach((function(e){var i=L.DomUtil.create("li",this._selectOptionClasses,o),n=this._createSelectInputImage(i);this._styleSelectInputImage(n,e,t)}),this),this.options.selectOptions[t]=o,L.DomEvent.addListener(o,"click",(function(t){t.stopPropagation();var e=t.target;if("UL"!==e.nodeName){if("leaflet-styleeditor-select-image"===e.parentNode.className)e=e.parentNode;else for(;e&&"leaflet-styleeditor-select-image"!==e.className;)e=e.childNodes[0];this._selectMarker({target:e},this)}}),this)}},_toggleSelectInput:function(t){var e=this._getCurrentColorElement(this.options.styleEditorOptions.util.rgbToHex(this.options.styleEditorOptions.markerType.options.iconOptions.iconColor)),o=!1;e&&(o=L.DomUtil.hasClass(e,"leaflet-styleeditor-hidden")),this._hideSelectOptions(),o&&this.options.styleEditorOptions.util.showElement(e)},_selectMarker:function(t){var e=this._getValue(t.target);this.options.selectBoxImage.setAttribute("value",e),this.setStyle(e),this._hideSelectOptions()},_getValue:function(t){return t.getAttribute("value")},_getCurrentColorElement:function(t){return this.options.selectOptions[t]||this._createColorSelect(t),this.options.selectOptions[t]},_hideSelectOptions:function(){for(var t in this.options.selectOptions)this.options.styleEditorOptions.util.hideElement(this.options.selectOptions[t])}}),L.StyleEditor.formElements.OpacityElement=L.StyleEditor.formElements.FormElement.extend({createContent:function(){this.options.label=L.DomUtil.create("span","leaflet-styleeditor-input-span",this.options.uiElement);var t=this.options.slider=L.DomUtil.create("input","leaflet-styleeditor-input",this.options.uiElement);t.type="range",t.max=1,t.min=0,t.step=.01,t.value=.5,L.DomEvent.addListener(t,"change",this._setStyle,this),L.DomEvent.addListener(t,"input",this._setStyle,this),L.DomEvent.addListener(t,"keyup",this._setStyle,this),L.DomEvent.addListener(t,"mouseup",this._setStyle,this)},style:function(){this.options.slider.value=this.options.styleEditorOptions.util.getStyle(this.options.styleOption),this.options.label.innerText=parseInt(100*this.options.slider.value)+"%"},_setStyle:function(){this.setStyle(this.options.slider.value)}}),L.StyleEditor.formElements.PopupContentElement=L.StyleEditor.formElements.FormElement.extend({options:{title:"Description"},createContent:function(){var t=this.options.uiElement,e=this.options.descTextAreaField=L.DomUtil.create("textarea","leaflet-styleeditor-input",t);L.DomEvent.addListener(e,"change",this._setStyle,this)},style:function(){var t=this.options.styleEditorOptions.util.getCurrentElement();t&&t.options&&(this.options.descTextAreaField.value=t.options.popupContent||"")},_setStyle:function(){var t=this.options.styleEditorOptions.util.getCurrentElement(),e=this.options.descTextAreaField.value,o=[t];t instanceof L.LayerGroup&&(o=Object.values(t._layers));for(var i=0;i<o.length;i++){var n=o[i];if(n&&n.getPopup&&n.bindPopup){var s=n.getPopup();s?s.setContent(e):n.bindPopup(e),n.options=n.options||{},n.options.popupContent=e}}this.setStyle(e)}}),L.StyleEditor.formElements.SizeElement=L.StyleEditor.formElements.FormElement.extend({createContent:function(){var t=this.options.uiElement,e=L.DomUtil.create("div","leaflet-styleeditor-sizeicon sizeicon-small",t);L.DomEvent.addListener(e,"click",(function(){this.setStyle(this.options.styleEditorOptions.markerType.options.size.small)}),this),e=L.DomUtil.create("div","leaflet-styleeditor-sizeicon sizeicon-medium",t),L.DomEvent.addListener(e,"click",(function(){this.setStyle(this.options.styleEditorOptions.markerType.options.size.medium)}),this),e=L.DomUtil.create("div","leaflet-styleeditor-sizeicon sizeicon-large",t),L.DomEvent.addListener(e,"click",(function(){this.setStyle(this.options.styleEditorOptions.markerType.options.size.large)}),this)}}),L.StyleEditor.formElements.WeightElement=L.StyleEditor.formElements.FormElement.extend({createContent:function(){this.options.label=L.DomUtil.create("span","leaflet-styleeditor-input-span",this.options.uiElement);var t=this.options.weight=L.DomUtil.create("input","leaflet-styleeditor-input",this.options.uiElement);t.type="range",t.min=0,t.max=20,t.step=1,t.value=4,L.DomEvent.addListener(t,"change",this._setStyle,this),L.DomEvent.addListener(t,"input",this._setStyle,this),L.DomEvent.addListener(t,"keyup",this._setStyle,this),L.DomEvent.addListener(t,"mouseup",this._setStyle,this)},style:function(){this.options.weight.value=this.options.styleEditorOptions.util.getStyle(this.options.styleOption),this.options.label.innerText=this.options.weight.value},_setStyle:function(){this.setStyle(this.options.weight.value)}}),L.StyleEditor.forms.Form=L.Class.extend({initialize:function(t){t&&L.setOptions(this,t),this.options.initializedElements=[]},create:function(t){this.options.parentUiElement=t;for(var e=this.getFormElements(),o=Object.keys(e),i=0;i<o.length;i++){var n=this.getFormElementClass(o[i],e);void 0!==n&&(n.create(t),this.options.initializedElements.push(n))}},hide:function(){this.hideFormElements(),this.hideForm()},hideFormElements:function(){for(var t=0;t<this.options.initializedElements.length;t++)this.options.initializedElements[t].hide()},hideForm:function(){this.options.styleEditorOptions.util.hideElement(this.options.parentUiElement)},show:function(){this.preShow(),this.showFormElements(),this.showForm(),this.style()},preShow:function(){},showFormElements:function(){for(var t=0;t<this.options.initializedElements.length;t++)this.showFormElement(this.options.initializedElements[t])},showForm:function(){this.options.styleEditorOptions.util.showElement(this.options.parentUiElement)},style:function(){for(var t=0;t<this.options.initializedElements.length;t++)this.options.initializedElements[t].style()},lostFocus:function(){for(var t=0;t<this.options.initializedElements.length;t++)this.options.initializedElements[t].lostFocus()},showFormElement:function(t){this.showFormElementForStyleOption(t.options.styleOption)?t.show():t.hide()},getFormElements:function(){return this.options.formOptionKey in this.options.styleEditorOptions.forms?this.options.styleEditorOptions.forms[this.options.formOptionKey]:this.options.formElements},getFormElementClass:function(t){var e=this.getFormElements();if(Object.keys(e).indexOf(t)>=0){var o=e[t];if(o){if("boolean"==typeof o)return this.getFormElementStandardClass(t);"formElement"in o&&"boolean"in o&&(o=o.formElement);try{var i=new o({styleOption:t,parentForm:this,styleEditorOptions:this.options.styleEditorOptions});if(i instanceof L.StyleEditor.formElements.FormElement)return i}catch(t){}}return this.getFormElementStandardClass(t)}},showFormElementForStyleOption:function(t){var e=this.getFormElements();if(t in e){var o=e[t];if("function"==typeof o)try{return o(this.options.styleEditorOptions.util.getCurrentElement())}catch(t){return!0}return"boolean"==typeof o?o:!("boolean"in o)||("function"==typeof o.boolean?o.boolean(this.options.styleEditorOptions.util.getCurrentElement()):o.boolean)}return!1},getFormElementStandardClass:function(t){return new this.options.formElements[t]({styleOption:t,parentForm:this,styleEditorOptions:this.options.styleEditorOptions})}}),L.StyleEditor.forms.GeometryForm=L.StyleEditor.forms.Form.extend({options:{formOptionKey:"geometry",formElements:{color:L.StyleEditor.formElements.ColorElement,opacity:L.StyleEditor.formElements.OpacityElement,weight:L.StyleEditor.formElements.WeightElement,dashArray:L.StyleEditor.formElements.DashElement,fillColor:L.StyleEditor.formElements.ColorElement,fillOpacity:L.StyleEditor.formElements.OpacityElement,popupContent:L.StyleEditor.formElements.PopupContentElement}},showFormElements:function(){for(var t=0;t<this.options.initializedElements.length;t++)0===this.options.initializedElements[t].options.styleOption.indexOf("fill")?this.options.styleEditorOptions.util.fillCurrentElement()?this.showFormElement(this.options.initializedElements[t]):this.options.initializedElements[t].hide():this.showFormElement(this.options.initializedElements[t])}}),L.StyleEditor.forms.MarkerForm=L.StyleEditor.forms.Form.extend({options:{formOptionKey:"marker",formElements:{icon:L.StyleEditor.formElements.IconElement,color:L.StyleEditor.formElements.ColorElement,size:L.StyleEditor.formElements.SizeElement,popupContent:L.StyleEditor.formElements.PopupContentElement}}}),L.StyleEditor.marker.Marker=L.Marker.extend({markerForm:L.StyleEditor.forms.MarkerForm,options:{size:{small:[20,50],medium:[30,70],large:[35,90]},selectIconSize:[],selectIconClass:"",iconOptions:{}},initialize:function(t){L.setOptions(this,t),L.setOptions(this,this.options),""===this.options.selectIconClass||this.options.selectIconClass.startsWith("leaflet-styleeditor-select-image")||(this.options.selectIconClass="leaflet-styleeditor-select-image-"+this.options.selectIconClass)},setNewMarker:function(){var t=this._createMarkerIcon(),e=this.options.styleEditorOptions.currentElement.target;e.setIcon(t),e instanceof L.LayerGroup?e.eachLayer((function(t){L.DomUtil.addClass(t.getElement(),"leaflet-styleeditor-marker-selected")})):L.DomUtil.addClass(e.getElement(),"leaflet-styleeditor-marker-selected")},setStyle:function(t,e){"icon"!==t&&(t="icon"+t.charAt(0).toUpperCase()+t.slice(1)),this.setIconOptions(t,e),this.setNewMarker()},createSelectHTML:function(t,e,o){},getIconOptions:function(){return this.options.styleEditorOptions.currentElement&&(this.options.iconOptions=this.options.styleEditorOptions.currentElement.target.options.icon.options),Object.keys(this.options.iconOptions).length>0?this.options.iconOptions:(this.options.iconOptions.iconColor=this._getDefaultMarkerColor(),this.options.iconOptions.iconSize=this.options.styleEditorOptions.markerType.options.size.small,this.options.iconOptions.icon=this.options.styleEditorOptions.util.getDefaultMarkerForColor(this.options.iconOptions.iconColor),this.options.iconOptions=this._ensureMarkerIcon(this.options.iconOptions),this.options.iconOptions)},resetIconOptions:function(){var t=this;Object.keys(this.getIconOptions()).forEach((function(e){return t.setStyle(e,t.options.iconOptions[e])}))},setIconOptions:function(t,e){this.getIconOptions()[t]=e},_createMarkerIcon:function(){var t=this.getIconOptions();return this.createMarkerIcon(t)},_ensureMarkerIcon:function(t){return this.options.styleEditorOptions.util.getMarkersForColor(t.iconColor).includes(t.icon)?t:(t.icon=this.options.styleEditorOptions.util.getDefaultMarkerForColor(t.iconColor),t)},_getDefaultMarkerColor:function(){var t=this.options.colorRamp,e=this.options.styleEditorOptions.colorRamp,o=[];null!=t?0===(o=t.filter((function(t){return e.includes(t)}))).length&&(o=t):o=e;var i=this.options.styleEditorOptions.defaultMarkerColor;return null===i||o.includes(i)||(i=null),null===i&&(null===(i=this.options.styleEditorOptions.defaultColor)||o.includes(i)||(i=null),null===i&&(i=o[0])),this.options.styleEditorOptions.util.rgbToHex(i)},sizeToName:function(t){var e=Object.keys(this.options.size);if("string"==typeof t){"s"===t?t="small":"m"===t?t="medium":"l"===t&&(t="large");for(var o=0;o<e.length;o++)if(this.options.size[e[o]]===t)return e[o]}for(var i=Object.values(this.options.size),n=0;n<i.length;n++)if(JSON.stringify(t)===JSON.stringify(i[n]))return e[n];return e[0]},sizeToPixel:function(t){return t=this.sizeToName(t),this.options.size[t]}}),L.StyleEditor.marker.DefaultMarker=L.StyleEditor.marker.Marker.extend({createMarkerIcon:function(t,e){e||(e="");var o=t.iconSize;return new L.Icon({iconUrl:this._getMarkerUrlForStyle(t),iconSize:t.iconSize,iconColor:t.iconColor,icon:t.icon,className:e,iconAnchor:[o[0]/2,o[1]/2],popupAnchor:[0,-o[1]/2]})},createSelectHTML:function(t,e,o){var i={};i.iconSize=this.options.size.small,i.icon=o,i.iconColor=e.iconColor,t.innerHTML=this.createMarkerIcon(i,this.options.selectIconClass).createIcon().outerHTML},_getMarkerUrlForStyle:function(t){return this._getMarkerUrl(t.iconSize,t.iconColor,t.icon)},_getMarkerUrl:function(t,e,o){var i="https://api.tiles.mapbox.com/v3/marker/pin-"+(t=this.sizeToName(t)[0]);return o&&(i+="-"+o),i+"+"+(e=0===e.indexOf("#")?e.replace("#",""):this.options.styleEditorOptions.util.rgbToHex(e,!0))+".png"},options:{selectIconClass:"defaultmarker",markers:["circle-stroked","circle","square-stroked","square","triangle-stroked","triangle","star-stroked","star","cross","marker-stroked","marker","religious-jewish","religious-christian","religious-muslim","cemetery","rocket","airport","heliport","rail","rail-metro","rail-light","bus","fuel","parking","parking-garage","airfield","roadblock","ferry","harbor","bicycle","park","park2","museum","lodging","monument","zoo","garden","campsite","theatre","art-gallery","pitch","soccer","america-football","tennis","basketball","baseball","golf","swimming","cricket","skiing","school","college","library","post","fire-station","town-hall","police","prison","embassy","beer","restaurant","cafe","shop","fast-food","bar","bank","grocery","cinema","pharmacy","hospital","danger","industrial","warehouse","commercial","building","place-of-worship","alcohol-shop","logging","oil-well","slaughterhouse","dam","water","wetland","disability","telephone","emergency-telephone","toilets","waste-basket","music","land-use","city","town","village","farm","bakery","dog-park","lighthouse","clothing-store","polling-place","playground","entrance","heart","london-underground","minefield","rail-underground","rail-above","camera","laundry","car","suitcase","hairdresser","chemist","mobilephone","scooter"]}}),L.StyleEditor.marker.GlyphiconMarker=L.StyleEditor.marker.Marker.extend({getMarkerHtml:function(t,e,o){var i=this._getMarkerUrl(t,e);return'<div class="leaflet-styleeditor-marker leaflet-styleeditor-marker-'+this.sizeToName(t)[0]+'" style="background-image: url('+i+');"><div class="leaflet-styleeditor-fill"></div><i class="glyphicon '+o+'"></i><div class="leaflet-styleeditor-fill"></div></div>'},createMarkerIcon:function(t){var e=t.iconSize;return L.divIcon({className:"leaflet-styleeditor-glyphicon-marker-wrapper",html:this.getMarkerHtml(e,t.iconColor,t.icon),icon:t.icon,iconColor:t.iconColor,iconSize:e,iconAnchor:[e[0]/2,e[1]/2],popupAnchor:[0,-e[1]/2]})},setStyle:function(t,e){"icon"!==t&&(t="icon"+t.charAt(0).toUpperCase()+t.slice(1));var o=this.options.iconOptions;o[t]!==e&&(o[t]=e,this.setNewMarker())},createSelectHTML:function(t,e,o){t.innerHTML=this.getMarkerHtml("s",e.iconColor,o)},_getMarkerUrlForStyle:function(t){return this._getMarkerUrl(t.iconSize,t.iconColor,t.icon)},_getMarkerUrl:function(t,e,o){return"https://api.tiles.mapbox.com/v3/marker/pin-"+(t=this.sizeToName(t)[0])+"+"+(e=0===e.indexOf("#")?e.replace("#",""):this.options.styleEditorOptions.util.rgbToHex(e,!0))+".png"},options:{markers:["glyphicon-plus","glyphicon-asterisk","glyphicon-plus","glyphicon-euro","glyphicon-minus","glyphicon-cloud","glyphicon-envelope","glyphicon-pencil","glyphicon-glass","glyphicon-music","glyphicon-search","glyphicon-heart","glyphicon-star","glyphicon-star-empty","glyphicon-user","glyphicon-film","glyphicon-th-large","glyphicon-th","glyphicon-th-list","glyphicon-ok","glyphicon-remove","glyphicon-zoom-in","glyphicon-zoom-out","glyphicon-off","glyphicon-signal","glyphicon-cog","glyphicon-trash","glyphicon-home","glyphicon-file","glyphicon-time","glyphicon-road","glyphicon-download-alt","glyphicon-download","glyphicon-upload","glyphicon-inbox","glyphicon-play-circle","glyphicon-repeat","glyphicon-refresh","glyphicon-list-alt","glyphicon-lock","glyphicon-flag","glyphicon-headphones","glyphicon-volume-off","glyphicon-volume-down","glyphicon-volume-up","glyphicon-qrcode","glyphicon-barcode","glyphicon-tag","glyphicon-tags","glyphicon-book","glyphicon-bookmark","glyphicon-print","glyphicon-camera","glyphicon-font","glyphicon-bold","glyphicon-italic","glyphicon-text-height","glyphicon-text-width","glyphicon-align-left","glyphicon-align-center","glyphicon-align-right","glyphicon-align-justify","glyphicon-list","glyphicon-indent-left","glyphicon-indent-right","glyphicon-facetime-video","glyphicon-picture","glyphicon-map-marker","glyphicon-adjust","glyphicon-tint","glyphicon-edit","glyphicon-share","glyphicon-check","glyphicon-move","glyphicon-chevron-right","glyphicon-plus-sign","glyphicon-minus-sign","glyphicon-remove-sign","glyphicon-ok-sign","glyphicon-question-sign","glyphicon-info-sign","glyphicon-screenshot","glyphicon-remove-circle","glyphicon-ok-circle","glyphicon-ban-circle","glyphicon-arrow-left","glyphicon-arrow-right","glyphicon-arrow-up","glyphicon-arrow-down","glyphicon-share-alt","glyphicon-resize-full","glyphicon-resize-small","glyphicon-exclamation-sign","glyphicon-gift","glyphicon-leaf","glyphicon-fire","glyphicon-eye-open","glyphicon-eye-close","glyphicon-warning-sign","glyphicon-plane","glyphicon-calendar","glyphicon-random","glyphicon-comment","glyphicon-magnet","glyphicon-chevron-up","glyphicon-chevron-down","glyphicon-retweet","glyphicon-shopping-cart","glyphicon-bullhorn","glyphicon-bell","glyphicon-certificate","glyphicon-thumbs-up","glyphicon-thumbs-down","glyphicon-hand-right","glyphicon-hand-left","glyphicon-hand-up","glyphicon-hand-down","glyphicon-circle-arrow-right","glyphicon-circle-arrow-left","glyphicon-circle-arrow-up","glyphicon-circle-arrow-down","glyphicon-globe","glyphicon-wrench","glyphicon-tasks","glyphicon-filter","glyphicon-briefcase","glyphicon-fullscreen","glyphicon-dashboard","glyphicon-paperclip","glyphicon-heart-empty","glyphicon-link","glyphicon-phone","glyphicon-pushpin","glyphicon-usd"]}}),L.StyleForm=L.Class.extend({initialize:function(t){L.setOptions(this,t),this.createMarkerForm(),this.createGeometryForm(),this.addDOMEvents()},addDOMEvents:function(){L.DomEvent.addListener(this.options.styleEditorOptions.map,"click",this.lostFocus,this),L.DomEvent.addListener(this.options.styleEditorDiv,"click",this.lostFocus,this)},clearForm:function(){this.options.styleEditorOptions.markerForm.hide(),this.options.styleEditorOptions.geometryForm.hide()},createMarkerForm:function(){var t=L.DomUtil.create("div","leaflet-styleeditor-interior-marker",this.options.styleEditorInterior);this.options.styleEditorOptions.markerForm.create(t)},createGeometryForm:function(){var t=L.DomUtil.create("div","leaflet-styleeditor-interior-geometry",this.options.styleEditorInterior);this.options.styleEditorOptions.geometryForm.create(t)},showMarkerForm:function(){this.clearForm(),this.options.styleEditorOptions.markerForm.show()},showGeometryForm:function(){this.clearForm(),this.options.styleEditorOptions.geometryForm.show()},fireChangeEvent:function(t){this.options.styleEditorOptions.util.fireChangedEvent(t)},lostFocus:function(t){for(var e=t.target,o=0;o<10&&e;o++){if(e.className&&L.DomUtil.hasClass(e,"leaflet-styleeditor-interior"))return;e=e.parentNode}this.options.styleEditorOptions.markerForm.lostFocus(),this.options.styleEditorOptions.geometryForm.lostFocus()}}),L.Control.StyleEditor=L.Control.extend({options:{position:"topleft",colorRamp:["#1abc9c","#2ecc71","#3498db","#9b59b6","#34495e","#16a085","#27ae60","#2980b9","#8e44ad","#2c3e50","#f1c40f","#e67e22","#e74c3c","#ecf0f1","#95a5a6","#f39c12","#d35400","#c0392b","#bdc3c7","#7f8c8d"],defaultColor:null,markerType:L.StyleEditor.marker.DefaultMarker,markers:null,defaultMarkerIcon:null,defaultMarkerColor:null,geometryForm:L.StyleEditor.forms.GeometryForm,forms:{},openOnLeafletDraw:!0,showTooltip:!0,strings:{cancel:"Cancel",cancelTitle:"Cancel Styling",tooltip:"Click on the element you want to style",tooltipNext:"Choose another element you want to style"},useGrouping:!0,styleEditorEventPrefix:"styleeditor:",currentElement:null,_editLayers:[],_layerGroups:[]},initialize:function(t){t&&L.setOptions(this,t),this.options.util=new L.StyleEditor.Util({styleEditorOptions:this.options}),this.options.markerType=new this.options.markerType({styleEditorOptions:this.options}),this.options.markerForm=new this.options.markerType.markerForm({styleEditorOptions:this.options}),this.options.geometryForm=new this.options.geometryForm({styleEditorOptions:this.options}),this.getDefaultIcon=this.options.markerType._createMarkerIcon.bind(this.options.markerType),this.createIcon=this.options.markerType.createMarkerIcon.bind(this.options.markerType)},onAdd:function(t){return this.options.map=t,this.createUi()},fireEvent:function(t,e){this.options.util.fireEvent(t,e)},createUi:function(){var t=this.options.controlDiv=L.DomUtil.create("div","leaflet-control-styleeditor leaflet-control leaflet-bar");(this.options.controlUI=L.DomUtil.create("a","leaflet-control-styleeditor-interior",t)).title="Style Editor";var e=this.options.cancelUI=L.DomUtil.create("div","leaflet-control-styleeditor-cancel leaflet-styleeditor-hidden",t);e.innerHTML=this.options.strings.cancel,e.title=this.options.strings.cancelTitle;var o=this.options.styleEditorDiv=L.DomUtil.create("div","leaflet-styleeditor",this.options.map._container);this.options.styleEditorHeader=L.DomUtil.create("div","leaflet-styleeditor-header",o);var i=L.DomUtil.create("div","leaflet-styleeditor-interior",o);return this.addDomEvents(),this.addLeafletDrawEvents(),this.addButtons(),this.options.styleForm=new L.StyleForm({styleEditorDiv:o,styleEditorInterior:i,styleEditorOptions:this.options}),t},addDomEvents:function(){L.DomEvent.disableScrollPropagation(this.options.styleEditorDiv),L.DomEvent.disableScrollPropagation(this.options.controlDiv),L.DomEvent.disableScrollPropagation(this.options.cancelUI),L.DomEvent.disableClickPropagation(this.options.styleEditorDiv),L.DomEvent.disableClickPropagation(this.options.controlDiv),L.DomEvent.disableClickPropagation(this.options.cancelUI),L.DomEvent.on(this.options.controlDiv,"click",(function(){this.toggle()}),this)},addLeafletDrawEvents:function(){this.options.openOnLeafletDraw&&L.Control.Draw&&(this.options.map.on("layeradd",this.onLayerAdd,this),this.options.map.on(L.Draw.Event.CREATED,this.onLeafletDrawCreated,this))},onLeafletDrawCreated:function(t){this.removeIndicators(),this.options.currentElement=t.layer},onLayerAdd:function(t){this.options.currentElement&&t.layer===this.options.util.getCurrentElement()&&this.enable(t.layer)},onRemove:function(){this.disable(),this.removeDomEvents(),this.removeLeafletDrawEvents(),L.DomUtil.remove(this.options.styleEditorDiv),L.DomUtil.remove(this.options.cancelUI),delete this.options.styleEditorDiv,delete this.options.cancelUI},removeLeafletDrawEvents:function(){this.options.map.off("layeradd",this.onLayerAdd),L.Draw&&this.options.map.off(L.Draw.Event.CREATED,this.onLeafletDrawCreated)},removeDomEvents:function(){L.DomEvent.off(this.options.controlDiv,"click",(function(){this.toggle()}),this)},addButtons:function(){var t=L.DomUtil.create("button","leaflet-styleeditor-button styleeditor-nextBtn",this.options.styleEditorHeader);t.title=this.options.strings.tooltipNext,L.DomEvent.on(t,"click",(function(t){this.hideEditor(),L.DomUtil.hasClass(this.options.controlUI,"enabled")&&this.createTooltip(),t.stopPropagation()}),this)},toggle:function(){L.DomUtil.hasClass(this.options.controlUI,"enabled")?this.disable():this.enable()},enable:function(t){L.DomUtil.addClass(this.options.controlUI,"enabled"),this.options.map.eachLayer(this.addEditClickEvents,this),this.showCancelButton(),this.createTooltip(),void 0!==t&&(this.isEnabled()&&this.removeIndicators(),this.initChangeStyle({target:t}))},isEnabled:function(){return L.DomUtil.hasClass(this.options.controlUI,"enabled")},disable:function(){this.isEnabled()&&(this.options._editLayers.forEach(this.removeEditClickEvents,this),this.options._editLayers=[],this.options._layerGroups=[],this.hideEditor(),this.hideCancelButton(),this.removeTooltip(),L.DomUtil.removeClass(this.options.controlUI,"enabled"))},addEditClickEvents:function(t){if(this.options.useGrouping&&t instanceof L.LayerGroup)this.options._layerGroups.push(t);else if(t instanceof L.Marker||t instanceof L.Path){var e=t.on("click",this.initChangeStyle,this);this.options._editLayers.push(e)}},removeEditClickEvents:function(t){t.off("click",this.initChangeStyle,this)},addIndicators:function(){if(this.options.currentElement){var t=this.options.currentElement.target;t instanceof L.LayerGroup?t.eachLayer((function(t){t instanceof L.Marker&&t.getElement()&&L.DomUtil.addClass(t.getElement(),"leaflet-styleeditor-marker-selected")})):t instanceof L.Marker&&t.getElement()&&L.DomUtil.addClass(t.getElement(),"leaflet-styleeditor-marker-selected")}},removeIndicators:function(){if(this.options.currentElement){var t=this.options.util.getCurrentElement();t instanceof L.LayerGroup?t.eachLayer((function(t){t.getElement()&&L.DomUtil.removeClass(t.getElement(),"leaflet-styleeditor-marker-selected")})):t.getElement()&&L.DomUtil.removeClass(t.getElement(),"leaflet-styleeditor-marker-selected")}},hideEditor:function(){L.DomUtil.hasClass(this.options.styleEditorDiv,"editor-enabled")&&(this.removeIndicators(),L.DomUtil.removeClass(this.options.styleEditorDiv,"editor-enabled"),this.fireEvent("hidden"))},hideCancelButton:function(){L.DomUtil.addClass(this.options.cancelUI,"leaflet-styleeditor-hidden")},showEditor:function(){var t=this.options.styleEditorDiv;L.DomUtil.hasClass(t,"editor-enabled")||(L.DomUtil.addClass(t,"editor-enabled"),this.fireEvent("visible"))},showCancelButton:function(){L.DomUtil.removeClass(this.options.cancelUI,"leaflet-styleeditor-hidden")},initChangeStyle:function(t){this.removeIndicators(),this.options.currentElement=this.options.useGrouping?this.getMatchingElement(t):t,this.addIndicators(),this.showEditor(),this.removeTooltip();var e=t;e instanceof L.Layer||(e=t.target),this.fireEvent("editing",e),e instanceof L.Marker?(this.options.markerType.resetIconOptions(),this.showMarkerForm(e)):this.showGeometryForm(e)},showGeometryForm:function(t){this.fireEvent("geometry",t),this.options.styleForm.showGeometryForm()},showMarkerForm:function(t){this.fireEvent("marker",t),this.options.styleForm.showMarkerForm()},createTooltip:function(){this.options.showTooltip&&(this.options.tooltipWrapper||(this.options.tooltipWrapper=L.DomUtil.create("div","leaflet-styleeditor-tooltip-wrapper",this.options.map.getContainer())),this.options.tooltip||(this.options.tooltip=L.DomUtil.create("div","leaflet-styleeditor-tooltip",this.options.tooltipWrapper)),this.options.tooltip.innerHTML=this.options.strings.tooltip)},getMatchingElement:function(t){for(var e=null,o=t.target,i=0;i<this.options._layerGroups.length;++i)if((e=this.options._layerGroups[i])&&o!==e&&e.hasLayer(o))return e.options&&e.options.opacity||(e.options=o.options,o.setIcon&&(e.setIcon=function(t){e.eachLayer((function(e){e instanceof L.Marker&&e.setIcon(t)}))})),this.getMatchingElement({target:e});return t},removeTooltip:function(){this.options.tooltip&&this.options.tooltip.parentNode&&(this.options.tooltip.remove(),this.options.tooltip=void 0)}}),L.control.styleEditor=function(t){return t||(t={}),new L.Control.StyleEditor(t)};e.default=L}]);
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.draw/images/spritesheet-2x.png b/www/wiki/extensions/Maps/resources/lib/leaflet.draw/images/spritesheet-2x.png
new file mode 100644
index 00000000..c45231af
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.draw/images/spritesheet-2x.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.draw/images/spritesheet.svg b/www/wiki/extensions/Maps/resources/lib/leaflet.draw/images/spritesheet.svg
new file mode 100644
index 00000000..3c00f303
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.draw/images/spritesheet.svg
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ 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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ viewBox="0 0 600 60"
+ height="60"
+ width="600"
+ id="svg4225"
+ version="1.1"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="spritesheet.svg"
+ inkscape:export-filename="/home/fpuga/development/upstream/icarto.Leaflet.draw/src/images/spritesheet-2x.png"
+ inkscape:export-xdpi="90"
+ inkscape:export-ydpi="90">
+ <metadata
+ id="metadata4258">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs4256" />
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="1920"
+ inkscape:window-height="1056"
+ id="namedview4254"
+ showgrid="false"
+ inkscape:zoom="1.3101852"
+ inkscape:cx="237.56928"
+ inkscape:cy="7.2419621"
+ inkscape:window-x="1920"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="svg4225" />
+ <g
+ id="enabled"
+ style="fill:#464646;fill-opacity:1">
+ <g
+ id="polyline"
+ style="fill:#464646;fill-opacity:1">
+ <path
+ d="m 18,36 0,6 6,0 0,-6 -6,0 z m 4,4 -2,0 0,-2 2,0 0,2 z"
+ id="path4229"
+ inkscape:connector-curvature="0"
+ style="fill:#464646;fill-opacity:1" />
+ <path
+ d="m 36,18 0,6 6,0 0,-6 -6,0 z m 4,4 -2,0 0,-2 2,0 0,2 z"
+ id="path4231"
+ inkscape:connector-curvature="0"
+ style="fill:#464646;fill-opacity:1" />
+ <path
+ d="m 23.142,39.145 -2.285,-2.29 16,-15.998 2.285,2.285 z"
+ id="path4233"
+ inkscape:connector-curvature="0"
+ style="fill:#464646;fill-opacity:1" />
+ </g>
+ <path
+ id="polygon"
+ d="M 100,24.565 97.904,39.395 83.07,42 76,28.773 86.463,18 Z"
+ inkscape:connector-curvature="0"
+ style="fill:#464646;fill-opacity:1" />
+ <path
+ id="rectangle"
+ d="m 140,20 20,0 0,20 -20,0 z"
+ inkscape:connector-curvature="0"
+ style="fill:#464646;fill-opacity:1" />
+ <path
+ id="circle"
+ d="m 221,30 c 0,6.078 -4.926,11 -11,11 -6.074,0 -11,-4.922 -11,-11 0,-6.074 4.926,-11 11,-11 6.074,0 11,4.926 11,11 z"
+ inkscape:connector-curvature="0"
+ style="fill:#464646;fill-opacity:1" />
+ <path
+ id="marker"
+ d="m 270,19 c -4.971,0 -9,4.029 -9,9 0,4.971 5.001,12 9,14 4.001,-2 9,-9.029 9,-14 0,-4.971 -4.029,-9 -9,-9 z m 0,12.5 c -2.484,0 -4.5,-2.014 -4.5,-4.5 0,-2.484 2.016,-4.5 4.5,-4.5 2.485,0 4.5,2.016 4.5,4.5 0,2.486 -2.015,4.5 -4.5,4.5 z"
+ inkscape:connector-curvature="0"
+ style="fill:#464646;fill-opacity:1" />
+ <g
+ id="edit"
+ style="fill:#464646;fill-opacity:1">
+ <path
+ d="m 337,30.156 0,0.407 0,5.604 c 0,1.658 -1.344,3 -3,3 l -10,0 c -1.655,0 -3,-1.342 -3,-3 l 0,-10 c 0,-1.657 1.345,-3 3,-3 l 6.345,0 3.19,-3.17 -9.535,0 c -3.313,0 -6,2.687 -6,6 l 0,10 c 0,3.313 2.687,6 6,6 l 10,0 c 3.314,0 6,-2.687 6,-6 l 0,-8.809 -3,2.968"
+ id="path4240"
+ inkscape:connector-curvature="0"
+ style="fill:#464646;fill-opacity:1" />
+ <path
+ d="m 338.72,24.637 -8.892,8.892 -2.828,0 0,-2.829 8.89,-8.89 z"
+ id="path4242"
+ inkscape:connector-curvature="0"
+ style="fill:#464646;fill-opacity:1" />
+ <path
+ d="m 338.697,17.826 4,0 0,4 -4,0 z"
+ transform="matrix(-0.70698336,-0.70723018,0.70723018,-0.70698336,567.55917,274.78273)"
+ id="path4244"
+ inkscape:connector-curvature="0"
+ style="fill:#464646;fill-opacity:1" />
+ </g>
+ <g
+ id="remove"
+ style="fill:#464646;fill-opacity:1">
+ <path
+ d="m 381,42 18,0 0,-18 -18,0 0,18 z m 14,-16 2,0 0,14 -2,0 0,-14 z m -4,0 2,0 0,14 -2,0 0,-14 z m -4,0 2,0 0,14 -2,0 0,-14 z m -4,0 2,0 0,14 -2,0 0,-14 z"
+ id="path4247"
+ inkscape:connector-curvature="0"
+ style="fill:#464646;fill-opacity:1" />
+ <path
+ d="m 395,20 0,-4 -10,0 0,4 -6,0 0,2 22,0 0,-2 -6,0 z m -2,0 -6,0 0,-2 6,0 0,2 z"
+ id="path4249"
+ inkscape:connector-curvature="0"
+ style="fill:#464646;fill-opacity:1" />
+ </g>
+ </g>
+ <g
+ id="disabled"
+ transform="translate(120,0)"
+ style="fill:#bbbbbb">
+ <use
+ xlink:href="#edit"
+ id="edit-disabled"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ <use
+ xlink:href="#remove"
+ id="remove-disabled"
+ x="0"
+ y="0"
+ width="100%"
+ height="100%" />
+ </g>
+ <path
+ style="fill:none;stroke:#464646;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="circle-3"
+ d="m 581.65725,30 c 0,6.078 -4.926,11 -11,11 -6.074,0 -11,-4.922 -11,-11 0,-6.074 4.926,-11 11,-11 6.074,0 11,4.926 11,11 z"
+ inkscape:connector-curvature="0" />
+</svg>
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw-src.css b/www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw-src.css
new file mode 100644
index 00000000..dffca06b
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw-src.css
@@ -0,0 +1,325 @@
+/* ================================================================== */
+/* Toolbars
+/* ================================================================== */
+
+.leaflet-draw-section {
+ position: relative;
+}
+
+.leaflet-draw-toolbar {
+ margin-top: 12px;
+}
+
+.leaflet-draw-toolbar-top {
+ margin-top: 0;
+}
+
+.leaflet-draw-toolbar-notop a:first-child {
+ border-top-right-radius: 0;
+}
+
+.leaflet-draw-toolbar-nobottom a:last-child {
+ border-bottom-right-radius: 0;
+}
+
+.leaflet-draw-toolbar a {
+ background-image: url('images/spritesheet.png');
+ background-image: linear-gradient(transparent, transparent), url('images/spritesheet.svg');
+ background-repeat: no-repeat;
+ background-size: 300px 30px;
+ background-clip: padding-box;
+}
+
+.leaflet-retina .leaflet-draw-toolbar a {
+ background-image: url('images/spritesheet-2x.png');
+ background-image: linear-gradient(transparent, transparent), url('images/spritesheet.svg');
+}
+
+.leaflet-draw a {
+ display: block;
+ text-align: center;
+ text-decoration: none;
+}
+
+.leaflet-draw a .sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ border: 0;
+}
+
+/* ================================================================== */
+/* Toolbar actions menu
+/* ================================================================== */
+
+.leaflet-draw-actions {
+ display: none;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ position: absolute;
+ left: 26px; /* leaflet-draw-toolbar.left + leaflet-draw-toolbar.width */
+ top: 0;
+ white-space: nowrap;
+}
+
+.leaflet-touch .leaflet-draw-actions {
+ left: 32px;
+}
+
+.leaflet-right .leaflet-draw-actions {
+ right: 26px;
+ left: auto;
+}
+
+.leaflet-touch .leaflet-right .leaflet-draw-actions {
+ right: 32px;
+ left: auto;
+}
+
+.leaflet-draw-actions li {
+ display: inline-block;
+}
+
+.leaflet-draw-actions li:first-child a {
+ border-left: none;
+}
+
+.leaflet-draw-actions li:last-child a {
+ -webkit-border-radius: 0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
+}
+
+.leaflet-right .leaflet-draw-actions li:last-child a {
+ -webkit-border-radius: 0;
+ border-radius: 0;
+}
+
+.leaflet-right .leaflet-draw-actions li:first-child a {
+ -webkit-border-radius: 4px 0 0 4px;
+ border-radius: 4px 0 0 4px;
+}
+
+.leaflet-draw-actions a {
+ background-color: #919187;
+ border-left: 1px solid #AAA;
+ color: #FFF;
+ font: 11px/19px "Helvetica Neue", Arial, Helvetica, sans-serif;
+ line-height: 28px;
+ text-decoration: none;
+ padding-left: 10px;
+ padding-right: 10px;
+ height: 28px;
+}
+
+.leaflet-touch .leaflet-draw-actions a {
+ font-size: 12px;
+ line-height: 30px;
+ height: 30px;
+}
+
+.leaflet-draw-actions-bottom {
+ margin-top: 0;
+}
+
+.leaflet-draw-actions-top {
+ margin-top: 1px;
+}
+
+.leaflet-draw-actions-top a,
+.leaflet-draw-actions-bottom a {
+ height: 27px;
+ line-height: 27px;
+}
+
+.leaflet-draw-actions a:hover {
+ background-color: #A0A098;
+}
+
+.leaflet-draw-actions-top.leaflet-draw-actions-bottom a {
+ height: 26px;
+ line-height: 26px;
+}
+
+/* ================================================================== */
+/* Draw toolbar
+/* ================================================================== */
+
+.leaflet-draw-toolbar .leaflet-draw-draw-polyline {
+ background-position: -2px -2px;
+}
+
+.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-polyline {
+ background-position: 0 -1px;
+}
+
+.leaflet-draw-toolbar .leaflet-draw-draw-polygon {
+ background-position: -31px -2px;
+}
+
+.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-polygon {
+ background-position: -29px -1px;
+}
+
+.leaflet-draw-toolbar .leaflet-draw-draw-rectangle {
+ background-position: -62px -2px;
+}
+
+.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-rectangle {
+ background-position: -60px -1px;
+}
+
+.leaflet-draw-toolbar .leaflet-draw-draw-circle {
+ background-position: -92px -2px;
+}
+
+.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-circle {
+ background-position: -90px -1px;
+}
+
+.leaflet-draw-toolbar .leaflet-draw-draw-marker {
+ background-position: -122px -2px;
+}
+
+.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-marker {
+ background-position: -120px -1px;
+}
+
+.leaflet-draw-toolbar .leaflet-draw-draw-circlemarker {
+ background-position: -273px -2px;
+}
+
+.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-circlemarker {
+ background-position: -271px -1px;
+}
+
+/* ================================================================== */
+/* Edit toolbar
+/* ================================================================== */
+
+.leaflet-draw-toolbar .leaflet-draw-edit-edit {
+ background-position: -152px -2px;
+}
+
+.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-edit {
+ background-position: -150px -1px;
+}
+
+.leaflet-draw-toolbar .leaflet-draw-edit-remove {
+ background-position: -182px -2px;
+}
+
+.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-remove {
+ background-position: -180px -1px;
+}
+
+.leaflet-draw-toolbar .leaflet-draw-edit-edit.leaflet-disabled {
+ background-position: -212px -2px;
+}
+
+.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-edit.leaflet-disabled {
+ background-position: -210px -1px;
+}
+
+.leaflet-draw-toolbar .leaflet-draw-edit-remove.leaflet-disabled {
+ background-position: -242px -2px;
+}
+
+.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-remove.leaflet-disabled {
+ background-position: -240px -2px;
+}
+
+/* ================================================================== */
+/* Drawing styles
+/* ================================================================== */
+
+.leaflet-mouse-marker {
+ background-color: #fff;
+ cursor: crosshair;
+}
+
+.leaflet-draw-tooltip {
+ background: rgb(54, 54, 54);
+ background: rgba(0, 0, 0, 0.5);
+ border: 1px solid transparent;
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ color: #fff;
+ font: 12px/18px "Helvetica Neue", Arial, Helvetica, sans-serif;
+ margin-left: 20px;
+ margin-top: -21px;
+ padding: 4px 8px;
+ position: absolute;
+ visibility: hidden;
+ white-space: nowrap;
+ z-index: 6;
+}
+
+.leaflet-draw-tooltip:before {
+ border-right: 6px solid black;
+ border-right-color: rgba(0, 0, 0, 0.5);
+ border-top: 6px solid transparent;
+ border-bottom: 6px solid transparent;
+ content: "";
+ position: absolute;
+ top: 7px;
+ left: -7px;
+}
+
+.leaflet-error-draw-tooltip {
+ background-color: #F2DEDE;
+ border: 1px solid #E6B6BD;
+ color: #B94A48;
+}
+
+.leaflet-error-draw-tooltip:before {
+ border-right-color: #E6B6BD;
+}
+
+.leaflet-draw-tooltip-single {
+ margin-top: -12px
+}
+
+.leaflet-draw-tooltip-subtext {
+ color: #f8d5e4;
+}
+
+.leaflet-draw-guide-dash {
+ font-size: 1%;
+ opacity: 0.6;
+ position: absolute;
+ width: 5px;
+ height: 5px;
+}
+
+/* ================================================================== */
+/* Edit styles
+/* ================================================================== */
+
+.leaflet-edit-marker-selected {
+ background-color: rgba(254, 87, 161, 0.1);
+ border: 4px dashed rgba(254, 87, 161, 0.6);
+ -webkit-border-radius: 4px;
+ border-radius: 4px;
+ box-sizing: content-box;
+}
+
+.leaflet-edit-move {
+ cursor: move;
+}
+
+.leaflet-edit-resize {
+ cursor: pointer;
+}
+
+/* ================================================================== */
+/* Old IE styles
+/* ================================================================== */
+
+.leaflet-oldie .leaflet-draw-toolbar {
+ border: 1px solid #999;
+}
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw-src.js b/www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw-src.js
new file mode 100644
index 00000000..8f427527
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw-src.js
@@ -0,0 +1,4774 @@
+/*
+ Leaflet.draw 1.0.4, a plugin that adds drawing and editing tools to Leaflet powered maps.
+ (c) 2012-2017, Jacob Toye, Jon West, Smartrak, Leaflet
+
+ https://github.com/Leaflet/Leaflet.draw
+ http://leafletjs.com
+ */
+(function (window, document, undefined) {/**
+ * Leaflet.draw assumes that you have already included the Leaflet library.
+ */
+L.drawVersion = "1.0.4";
+/**
+ * @class L.Draw
+ * @aka Draw
+ *
+ *
+ * To add the draw toolbar set the option drawControl: true in the map options.
+ *
+ * @example
+ * ```js
+ * var map = L.map('map', {drawControl: true}).setView([51.505, -0.09], 13);
+ *
+ * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
+ * attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
+ * }).addTo(map);
+ * ```
+ *
+ * ### Adding the edit toolbar
+ * To use the edit toolbar you must initialise the Leaflet.draw control and manually add it to the map.
+ *
+ * ```js
+ * var map = L.map('map').setView([51.505, -0.09], 13);
+ *
+ * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
+ * attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
+ * }).addTo(map);
+ *
+ * // FeatureGroup is to store editable layers
+ * var drawnItems = new L.FeatureGroup();
+ * map.addLayer(drawnItems);
+ *
+ * var drawControl = new L.Control.Draw({
+ * edit: {
+ * featureGroup: drawnItems
+ * }
+ * });
+ * map.addControl(drawControl);
+ * ```
+ *
+ * The key here is the featureGroup option. This tells the plugin which FeatureGroup contains the layers that
+ * should be editable. The featureGroup can contain 0 or more features with geometry types Point, LineString, and Polygon.
+ * Leaflet.draw does not work with multigeometry features such as MultiPoint, MultiLineString, MultiPolygon,
+ * or GeometryCollection. If you need to add multigeometry features to the draw plugin, convert them to a
+ * FeatureCollection of non-multigeometries (Points, LineStrings, or Polygons).
+ */
+L.Draw = {};
+
+/**
+ * @class L.drawLocal
+ * @aka L.drawLocal
+ *
+ * The core toolbar class of the API — it is used to create the toolbar ui
+ *
+ * @example
+ * ```js
+ * var modifiedDraw = L.drawLocal.extend({
+ * draw: {
+ * toolbar: {
+ * buttons: {
+ * polygon: 'Draw an awesome polygon'
+ * }
+ * }
+ * }
+ * });
+ * ```
+ *
+ * The default state for the control is the draw toolbar just below the zoom control.
+ * This will allow map users to draw vectors and markers.
+ * **Please note the edit toolbar is not enabled by default.**
+ */
+L.drawLocal = {
+ // format: {
+ // numeric: {
+ // delimiters: {
+ // thousands: ',',
+ // decimal: '.'
+ // }
+ // }
+ // },
+ draw: {
+ toolbar: {
+ // #TODO: this should be reorganized where actions are nested in actions
+ // ex: actions.undo or actions.cancel
+ actions: {
+ title: 'Cancel drawing',
+ text: 'Cancel'
+ },
+ finish: {
+ title: 'Finish drawing',
+ text: 'Finish'
+ },
+ undo: {
+ title: 'Delete last point drawn',
+ text: 'Delete last point'
+ },
+ buttons: {
+ polyline: 'Draw a polyline',
+ polygon: 'Draw a polygon',
+ rectangle: 'Draw a rectangle',
+ circle: 'Draw a circle',
+ marker: 'Draw a marker',
+ circlemarker: 'Draw a circlemarker'
+ }
+ },
+ handlers: {
+ circle: {
+ tooltip: {
+ start: 'Click and drag to draw circle.'
+ },
+ radius: 'Radius'
+ },
+ circlemarker: {
+ tooltip: {
+ start: 'Click map to place circle marker.'
+ }
+ },
+ marker: {
+ tooltip: {
+ start: 'Click map to place marker.'
+ }
+ },
+ polygon: {
+ tooltip: {
+ start: 'Click to start drawing shape.',
+ cont: 'Click to continue drawing shape.',
+ end: 'Click first point to close this shape.'
+ }
+ },
+ polyline: {
+ error: '<strong>Error:</strong> shape edges cannot cross!',
+ tooltip: {
+ start: 'Click to start drawing line.',
+ cont: 'Click to continue drawing line.',
+ end: 'Click last point to finish line.'
+ }
+ },
+ rectangle: {
+ tooltip: {
+ start: 'Click and drag to draw rectangle.'
+ }
+ },
+ simpleshape: {
+ tooltip: {
+ end: 'Release mouse to finish drawing.'
+ }
+ }
+ }
+ },
+ edit: {
+ toolbar: {
+ actions: {
+ save: {
+ title: 'Save changes',
+ text: 'Save'
+ },
+ cancel: {
+ title: 'Cancel editing, discards all changes',
+ text: 'Cancel'
+ },
+ clearAll: {
+ title: 'Clear all layers',
+ text: 'Clear All'
+ }
+ },
+ buttons: {
+ edit: 'Edit layers',
+ editDisabled: 'No layers to edit',
+ remove: 'Delete layers',
+ removeDisabled: 'No layers to delete'
+ }
+ },
+ handlers: {
+ edit: {
+ tooltip: {
+ text: 'Drag handles or markers to edit features.',
+ subtext: 'Click cancel to undo changes.'
+ }
+ },
+ remove: {
+ tooltip: {
+ text: 'Click on a feature to remove.'
+ }
+ }
+ }
+ }
+};
+
+
+
+/**
+ * ### Events
+ * Once you have successfully added the Leaflet.draw plugin to your map you will want to respond to the different
+ * actions users can initiate. The following events will be triggered on the map:
+ *
+ * @class L.Draw.Event
+ * @aka Draw.Event
+ *
+ * Use `L.Draw.Event.EVENTNAME` constants to ensure events are correct.
+ *
+ * @example
+ * ```js
+ * map.on(L.Draw.Event.CREATED; function (e) {
+ * var type = e.layerType,
+ * layer = e.layer;
+ *
+ * if (type === 'marker') {
+ * // Do marker specific actions
+ * }
+ *
+ * // Do whatever else you need to. (save to db; add to map etc)
+ * map.addLayer(layer);
+ *});
+ * ```
+ */
+L.Draw.Event = {};
+/**
+ * @event draw:created: PolyLine; Polygon; Rectangle; Circle; Marker | String
+ *
+ * Layer that was just created.
+ * The type of layer this is. One of: `polyline`; `polygon`; `rectangle`; `circle`; `marker`
+ * Triggered when a new vector or marker has been created.
+ *
+ */
+L.Draw.Event.CREATED = 'draw:created';
+
+/**
+ * @event draw:edited: LayerGroup
+ *
+ * List of all layers just edited on the map.
+ *
+ *
+ * Triggered when layers in the FeatureGroup; initialised with the plugin; have been edited and saved.
+ *
+ * @example
+ * ```js
+ * map.on('draw:edited', function (e) {
+ * var layers = e.layers;
+ * layers.eachLayer(function (layer) {
+ * //do whatever you want; most likely save back to db
+ * });
+ * });
+ * ```
+ */
+L.Draw.Event.EDITED = 'draw:edited';
+
+/**
+ * @event draw:deleted: LayerGroup
+ *
+ * List of all layers just removed from the map.
+ *
+ * Triggered when layers have been removed (and saved) from the FeatureGroup.
+ */
+L.Draw.Event.DELETED = 'draw:deleted';
+
+/**
+ * @event draw:drawstart: String
+ *
+ * The type of layer this is. One of:`polyline`; `polygon`; `rectangle`; `circle`; `marker`
+ *
+ * Triggered when the user has chosen to draw a particular vector or marker.
+ */
+L.Draw.Event.DRAWSTART = 'draw:drawstart';
+
+/**
+ * @event draw:drawstop: String
+ *
+ * The type of layer this is. One of: `polyline`; `polygon`; `rectangle`; `circle`; `marker`
+ *
+ * Triggered when the user has finished a particular vector or marker.
+ */
+
+L.Draw.Event.DRAWSTOP = 'draw:drawstop';
+
+/**
+ * @event draw:drawvertex: LayerGroup
+ *
+ * List of all layers just being added from the map.
+ *
+ * Triggered when a vertex is created on a polyline or polygon.
+ */
+L.Draw.Event.DRAWVERTEX = 'draw:drawvertex';
+
+/**
+ * @event draw:editstart: String
+ *
+ * The type of edit this is. One of: `edit`
+ *
+ * Triggered when the user starts edit mode by clicking the edit tool button.
+ */
+
+L.Draw.Event.EDITSTART = 'draw:editstart';
+
+/**
+ * @event draw:editmove: ILayer
+ *
+ * Layer that was just moved.
+ *
+ * Triggered as the user moves a rectangle; circle or marker.
+ */
+L.Draw.Event.EDITMOVE = 'draw:editmove';
+
+/**
+ * @event draw:editresize: ILayer
+ *
+ * Layer that was just moved.
+ *
+ * Triggered as the user resizes a rectangle or circle.
+ */
+L.Draw.Event.EDITRESIZE = 'draw:editresize';
+
+/**
+ * @event draw:editvertex: LayerGroup
+ *
+ * List of all layers just being edited from the map.
+ *
+ * Triggered when a vertex is edited on a polyline or polygon.
+ */
+L.Draw.Event.EDITVERTEX = 'draw:editvertex';
+
+/**
+ * @event draw:editstop: String
+ *
+ * The type of edit this is. One of: `edit`
+ *
+ * Triggered when the user has finshed editing (edit mode) and saves edits.
+ */
+L.Draw.Event.EDITSTOP = 'draw:editstop';
+
+/**
+ * @event draw:deletestart: String
+ *
+ * The type of edit this is. One of: `remove`
+ *
+ * Triggered when the user starts remove mode by clicking the remove tool button.
+ */
+L.Draw.Event.DELETESTART = 'draw:deletestart';
+
+/**
+ * @event draw:deletestop: String
+ *
+ * The type of edit this is. One of: `remove`
+ *
+ * Triggered when the user has finished removing shapes (remove mode) and saves.
+ */
+L.Draw.Event.DELETESTOP = 'draw:deletestop';
+
+/**
+ * @event draw:toolbaropened: String
+ *
+ * Triggered when a toolbar is opened.
+ */
+L.Draw.Event.TOOLBAROPENED = 'draw:toolbaropened';
+
+/**
+ * @event draw:toolbarclosed: String
+ *
+ * Triggered when a toolbar is closed.
+ */
+L.Draw.Event.TOOLBARCLOSED = 'draw:toolbarclosed';
+
+/**
+ * @event draw:markercontext: String
+ *
+ * Triggered when a marker is right clicked.
+ */
+L.Draw.Event.MARKERCONTEXT = 'draw:markercontext';
+
+
+L.Draw = L.Draw || {};
+
+/**
+ * @class L.Draw.Feature
+ * @aka Draw.Feature
+ */
+L.Draw.Feature = L.Handler.extend({
+
+ // @method initialize(): void
+ initialize: function (map, options) {
+ this._map = map;
+ this._container = map._container;
+ this._overlayPane = map._panes.overlayPane;
+ this._popupPane = map._panes.popupPane;
+
+ // Merge default shapeOptions options with custom shapeOptions
+ if (options && options.shapeOptions) {
+ options.shapeOptions = L.Util.extend({}, this.options.shapeOptions, options.shapeOptions);
+ }
+ L.setOptions(this, options);
+
+ var version = L.version.split('.');
+ //If Version is >= 1.2.0
+ if (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) {
+ L.Draw.Feature.include(L.Evented.prototype);
+ } else {
+ L.Draw.Feature.include(L.Mixin.Events);
+ }
+ },
+
+ // @method enable(): void
+ // Enables this handler
+ enable: function () {
+ if (this._enabled) {
+ return;
+ }
+
+ L.Handler.prototype.enable.call(this);
+
+ this.fire('enabled', {handler: this.type});
+
+ this._map.fire(L.Draw.Event.DRAWSTART, {layerType: this.type});
+ },
+
+ // @method disable(): void
+ disable: function () {
+ if (!this._enabled) {
+ return;
+ }
+
+ L.Handler.prototype.disable.call(this);
+
+ this._map.fire(L.Draw.Event.DRAWSTOP, {layerType: this.type});
+
+ this.fire('disabled', {handler: this.type});
+ },
+
+ // @method addHooks(): void
+ // Add's event listeners to this handler
+ addHooks: function () {
+ var map = this._map;
+
+ if (map) {
+ L.DomUtil.disableTextSelection();
+
+ map.getContainer().focus();
+
+ this._tooltip = new L.Draw.Tooltip(this._map);
+
+ L.DomEvent.on(this._container, 'keyup', this._cancelDrawing, this);
+ }
+ },
+
+ // @method removeHooks(): void
+ // Removes event listeners from this handler
+ removeHooks: function () {
+ if (this._map) {
+ L.DomUtil.enableTextSelection();
+
+ this._tooltip.dispose();
+ this._tooltip = null;
+
+ L.DomEvent.off(this._container, 'keyup', this._cancelDrawing, this);
+ }
+ },
+
+ // @method setOptions(object): void
+ // Sets new options to this handler
+ setOptions: function (options) {
+ L.setOptions(this, options);
+ },
+
+ _fireCreatedEvent: function (layer) {
+ this._map.fire(L.Draw.Event.CREATED, {layer: layer, layerType: this.type});
+ },
+
+ // Cancel drawing when the escape key is pressed
+ _cancelDrawing: function (e) {
+ if (e.keyCode === 27) {
+ this._map.fire('draw:canceled', {layerType: this.type});
+ this.disable();
+ }
+ }
+});
+
+
+
+/**
+ * @class L.Draw.Polyline
+ * @aka Draw.Polyline
+ * @inherits L.Draw.Feature
+ */
+L.Draw.Polyline = L.Draw.Feature.extend({
+ statics: {
+ TYPE: 'polyline'
+ },
+
+ Poly: L.Polyline,
+
+ options: {
+ allowIntersection: true,
+ repeatMode: false,
+ drawError: {
+ color: '#b00b00',
+ timeout: 2500
+ },
+ icon: new L.DivIcon({
+ iconSize: new L.Point(8, 8),
+ className: 'leaflet-div-icon leaflet-editing-icon'
+ }),
+ touchIcon: new L.DivIcon({
+ iconSize: new L.Point(20, 20),
+ className: 'leaflet-div-icon leaflet-editing-icon leaflet-touch-icon'
+ }),
+ guidelineDistance: 20,
+ maxGuideLineLength: 4000,
+ shapeOptions: {
+ stroke: true,
+ color: '#3388ff',
+ weight: 4,
+ opacity: 0.5,
+ fill: false,
+ clickable: true
+ },
+ metric: true, // Whether to use the metric measurement system or imperial
+ feet: true, // When not metric, to use feet instead of yards for display.
+ nautic: false, // When not metric, not feet use nautic mile for display
+ showLength: true, // Whether to display distance in the tooltip
+ zIndexOffset: 2000, // This should be > than the highest z-index any map layers
+ factor: 1, // To change distance calculation
+ maxPoints: 0 // Once this number of points are placed, finish shape
+ },
+
+ // @method initialize(): void
+ initialize: function (map, options) {
+ // if touch, switch to touch icon
+ if (L.Browser.touch) {
+ this.options.icon = this.options.touchIcon;
+ }
+
+ // Need to set this here to ensure the correct message is used.
+ this.options.drawError.message = L.drawLocal.draw.handlers.polyline.error;
+
+ // Merge default drawError options with custom options
+ if (options && options.drawError) {
+ options.drawError = L.Util.extend({}, this.options.drawError, options.drawError);
+ }
+
+ // Save the type so super can fire, need to do this as cannot do this.TYPE :(
+ this.type = L.Draw.Polyline.TYPE;
+
+ L.Draw.Feature.prototype.initialize.call(this, map, options);
+ },
+
+ // @method addHooks(): void
+ // Add listener hooks to this handler
+ addHooks: function () {
+ L.Draw.Feature.prototype.addHooks.call(this);
+ if (this._map) {
+ this._markers = [];
+
+ this._markerGroup = new L.LayerGroup();
+ this._map.addLayer(this._markerGroup);
+
+ this._poly = new L.Polyline([], this.options.shapeOptions);
+
+ this._tooltip.updateContent(this._getTooltipText());
+
+ // Make a transparent marker that will used to catch click events. These click
+ // events will create the vertices. We need to do this so we can ensure that
+ // we can create vertices over other map layers (markers, vector layers). We
+ // also do not want to trigger any click handlers of objects we are clicking on
+ // while drawing.
+ if (!this._mouseMarker) {
+ this._mouseMarker = L.marker(this._map.getCenter(), {
+ icon: L.divIcon({
+ className: 'leaflet-mouse-marker',
+ iconAnchor: [20, 20],
+ iconSize: [40, 40]
+ }),
+ opacity: 0,
+ zIndexOffset: this.options.zIndexOffset
+ });
+ }
+
+ this._mouseMarker
+ .on('mouseout', this._onMouseOut, this)
+ .on('mousemove', this._onMouseMove, this) // Necessary to prevent 0.8 stutter
+ .on('mousedown', this._onMouseDown, this)
+ .on('mouseup', this._onMouseUp, this) // Necessary for 0.8 compatibility
+ .addTo(this._map);
+
+ this._map
+ .on('mouseup', this._onMouseUp, this) // Necessary for 0.7 compatibility
+ .on('mousemove', this._onMouseMove, this)
+ .on('zoomlevelschange', this._onZoomEnd, this)
+ .on('touchstart', this._onTouch, this)
+ .on('zoomend', this._onZoomEnd, this);
+
+ }
+ },
+
+ // @method removeHooks(): void
+ // Remove listener hooks from this handler.
+ removeHooks: function () {
+ L.Draw.Feature.prototype.removeHooks.call(this);
+
+ this._clearHideErrorTimeout();
+
+ this._cleanUpShape();
+
+ // remove markers from map
+ this._map.removeLayer(this._markerGroup);
+ delete this._markerGroup;
+ delete this._markers;
+
+ this._map.removeLayer(this._poly);
+ delete this._poly;
+
+ this._mouseMarker
+ .off('mousedown', this._onMouseDown, this)
+ .off('mouseout', this._onMouseOut, this)
+ .off('mouseup', this._onMouseUp, this)
+ .off('mousemove', this._onMouseMove, this);
+ this._map.removeLayer(this._mouseMarker);
+ delete this._mouseMarker;
+
+ // clean up DOM
+ this._clearGuides();
+
+ this._map
+ .off('mouseup', this._onMouseUp, this)
+ .off('mousemove', this._onMouseMove, this)
+ .off('zoomlevelschange', this._onZoomEnd, this)
+ .off('zoomend', this._onZoomEnd, this)
+ .off('touchstart', this._onTouch, this)
+ .off('click', this._onTouch, this);
+ },
+
+ // @method deleteLastVertex(): void
+ // Remove the last vertex from the polyline, removes polyline from map if only one point exists.
+ deleteLastVertex: function () {
+ if (this._markers.length <= 1) {
+ return;
+ }
+
+ var lastMarker = this._markers.pop(),
+ poly = this._poly,
+ // Replaces .spliceLatLngs()
+ latlngs = poly.getLatLngs(),
+ latlng = latlngs.splice(-1, 1)[0];
+ this._poly.setLatLngs(latlngs);
+
+ this._markerGroup.removeLayer(lastMarker);
+
+ if (poly.getLatLngs().length < 2) {
+ this._map.removeLayer(poly);
+ }
+
+ this._vertexChanged(latlng, false);
+ },
+
+ // @method addVertex(): void
+ // Add a vertex to the end of the polyline
+ addVertex: function (latlng) {
+ var markersLength = this._markers.length;
+ // markersLength must be greater than or equal to 2 before intersections can occur
+ if (markersLength >= 2 && !this.options.allowIntersection && this._poly.newLatLngIntersects(latlng)) {
+ this._showErrorTooltip();
+ return;
+ }
+ else if (this._errorShown) {
+ this._hideErrorTooltip();
+ }
+
+ this._markers.push(this._createMarker(latlng));
+
+ this._poly.addLatLng(latlng);
+
+ if (this._poly.getLatLngs().length === 2) {
+ this._map.addLayer(this._poly);
+ }
+
+ this._vertexChanged(latlng, true);
+ },
+
+ // @method completeShape(): void
+ // Closes the polyline between the first and last points
+ completeShape: function () {
+ if (this._markers.length <= 1 || !this._shapeIsValid()) {
+ return;
+ }
+
+ this._fireCreatedEvent();
+ this.disable();
+
+ if (this.options.repeatMode) {
+ this.enable();
+ }
+ },
+
+ _finishShape: function () {
+ var latlngs = this._poly._defaultShape ? this._poly._defaultShape() : this._poly.getLatLngs();
+ var intersects = this._poly.newLatLngIntersects(latlngs[latlngs.length - 1]);
+
+ if ((!this.options.allowIntersection && intersects) || !this._shapeIsValid()) {
+ this._showErrorTooltip();
+ return;
+ }
+
+ this._fireCreatedEvent();
+ this.disable();
+ if (this.options.repeatMode) {
+ this.enable();
+ }
+ },
+
+ // Called to verify the shape is valid when the user tries to finish it
+ // Return false if the shape is not valid
+ _shapeIsValid: function () {
+ return true;
+ },
+
+ _onZoomEnd: function () {
+ if (this._markers !== null) {
+ this._updateGuide();
+ }
+ },
+
+ _onMouseMove: function (e) {
+ var newPos = this._map.mouseEventToLayerPoint(e.originalEvent);
+ var latlng = this._map.layerPointToLatLng(newPos);
+
+ // Save latlng
+ // should this be moved to _updateGuide() ?
+ this._currentLatLng = latlng;
+
+ this._updateTooltip(latlng);
+
+ // Update the guide line
+ this._updateGuide(newPos);
+
+ // Update the mouse marker position
+ this._mouseMarker.setLatLng(latlng);
+
+ L.DomEvent.preventDefault(e.originalEvent);
+ },
+
+ _vertexChanged: function (latlng, added) {
+ this._map.fire(L.Draw.Event.DRAWVERTEX, {layers: this._markerGroup});
+ this._updateFinishHandler();
+
+ this._updateRunningMeasure(latlng, added);
+
+ this._clearGuides();
+
+ this._updateTooltip();
+ },
+
+ _onMouseDown: function (e) {
+ if (!this._clickHandled && !this._touchHandled && !this._disableMarkers) {
+ this._onMouseMove(e);
+ this._clickHandled = true;
+ this._disableNewMarkers();
+ var originalEvent = e.originalEvent;
+ var clientX = originalEvent.clientX;
+ var clientY = originalEvent.clientY;
+ this._startPoint.call(this, clientX, clientY);
+ }
+ },
+
+ _startPoint: function (clientX, clientY) {
+ this._mouseDownOrigin = L.point(clientX, clientY);
+ },
+
+ _onMouseUp: function (e) {
+ var originalEvent = e.originalEvent;
+ var clientX = originalEvent.clientX;
+ var clientY = originalEvent.clientY;
+ this._endPoint.call(this, clientX, clientY, e);
+ this._clickHandled = null;
+ },
+
+ _endPoint: function (clientX, clientY, e) {
+ if (this._mouseDownOrigin) {
+ var dragCheckDistance = L.point(clientX, clientY)
+ .distanceTo(this._mouseDownOrigin);
+ var lastPtDistance = this._calculateFinishDistance(e.latlng);
+ if (this.options.maxPoints > 1 && this.options.maxPoints == this._markers.length + 1) {
+ this.addVertex(e.latlng);
+ this._finishShape();
+ } else if (lastPtDistance < 10 && L.Browser.touch) {
+ this._finishShape();
+ } else if (Math.abs(dragCheckDistance) < 9 * (window.devicePixelRatio || 1)) {
+ this.addVertex(e.latlng);
+ }
+ this._enableNewMarkers(); // after a short pause, enable new markers
+ }
+ this._mouseDownOrigin = null;
+ },
+
+ // ontouch prevented by clickHandled flag because some browsers fire both click/touch events,
+ // causing unwanted behavior
+ _onTouch: function (e) {
+ var originalEvent = e.originalEvent;
+ var clientX;
+ var clientY;
+ if (originalEvent.touches && originalEvent.touches[0] && !this._clickHandled && !this._touchHandled && !this._disableMarkers) {
+ clientX = originalEvent.touches[0].clientX;
+ clientY = originalEvent.touches[0].clientY;
+ this._disableNewMarkers();
+ this._touchHandled = true;
+ this._startPoint.call(this, clientX, clientY);
+ this._endPoint.call(this, clientX, clientY, e);
+ this._touchHandled = null;
+ }
+ this._clickHandled = null;
+ },
+
+ _onMouseOut: function () {
+ if (this._tooltip) {
+ this._tooltip._onMouseOut.call(this._tooltip);
+ }
+ },
+
+ // calculate if we are currently within close enough distance
+ // of the closing point (first point for shapes, last point for lines)
+ // this is semi-ugly code but the only reliable way i found to get the job done
+ // note: calculating point.distanceTo between mouseDownOrigin and last marker did NOT work
+ _calculateFinishDistance: function (potentialLatLng) {
+ var lastPtDistance;
+ if (this._markers.length > 0) {
+ var finishMarker;
+ if (this.type === L.Draw.Polyline.TYPE) {
+ finishMarker = this._markers[this._markers.length - 1];
+ } else if (this.type === L.Draw.Polygon.TYPE) {
+ finishMarker = this._markers[0];
+ } else {
+ return Infinity;
+ }
+ var lastMarkerPoint = this._map.latLngToContainerPoint(finishMarker.getLatLng()),
+ potentialMarker = new L.Marker(potentialLatLng, {
+ icon: this.options.icon,
+ zIndexOffset: this.options.zIndexOffset * 2
+ });
+ var potentialMarkerPint = this._map.latLngToContainerPoint(potentialMarker.getLatLng());
+ lastPtDistance = lastMarkerPoint.distanceTo(potentialMarkerPint);
+ } else {
+ lastPtDistance = Infinity;
+ }
+ return lastPtDistance;
+ },
+
+ _updateFinishHandler: function () {
+ var markerCount = this._markers.length;
+ // The last marker should have a click handler to close the polyline
+ if (markerCount > 1) {
+ this._markers[markerCount - 1].on('click', this._finishShape, this);
+ }
+
+ // Remove the old marker click handler (as only the last point should close the polyline)
+ if (markerCount > 2) {
+ this._markers[markerCount - 2].off('click', this._finishShape, this);
+ }
+ },
+
+ _createMarker: function (latlng) {
+ var marker = new L.Marker(latlng, {
+ icon: this.options.icon,
+ zIndexOffset: this.options.zIndexOffset * 2
+ });
+
+ this._markerGroup.addLayer(marker);
+
+ return marker;
+ },
+
+ _updateGuide: function (newPos) {
+ var markerCount = this._markers ? this._markers.length : 0;
+
+ if (markerCount > 0) {
+ newPos = newPos || this._map.latLngToLayerPoint(this._currentLatLng);
+
+ // draw the guide line
+ this._clearGuides();
+ this._drawGuide(
+ this._map.latLngToLayerPoint(this._markers[markerCount - 1].getLatLng()),
+ newPos
+ );
+ }
+ },
+
+ _updateTooltip: function (latLng) {
+ var text = this._getTooltipText();
+
+ if (latLng) {
+ this._tooltip.updatePosition(latLng);
+ }
+
+ if (!this._errorShown) {
+ this._tooltip.updateContent(text);
+ }
+ },
+
+ _drawGuide: function (pointA, pointB) {
+ var length = Math.floor(Math.sqrt(Math.pow((pointB.x - pointA.x), 2) + Math.pow((pointB.y - pointA.y), 2))),
+ guidelineDistance = this.options.guidelineDistance,
+ maxGuideLineLength = this.options.maxGuideLineLength,
+ // Only draw a guideline with a max length
+ i = length > maxGuideLineLength ? length - maxGuideLineLength : guidelineDistance,
+ fraction,
+ dashPoint,
+ dash;
+
+ //create the guides container if we haven't yet
+ if (!this._guidesContainer) {
+ this._guidesContainer = L.DomUtil.create('div', 'leaflet-draw-guides', this._overlayPane);
+ }
+
+ //draw a dash every GuildeLineDistance
+ for (; i < length; i += this.options.guidelineDistance) {
+ //work out fraction along line we are
+ fraction = i / length;
+
+ //calculate new x,y point
+ dashPoint = {
+ x: Math.floor((pointA.x * (1 - fraction)) + (fraction * pointB.x)),
+ y: Math.floor((pointA.y * (1 - fraction)) + (fraction * pointB.y))
+ };
+
+ //add guide dash to guide container
+ dash = L.DomUtil.create('div', 'leaflet-draw-guide-dash', this._guidesContainer);
+ dash.style.backgroundColor =
+ !this._errorShown ? this.options.shapeOptions.color : this.options.drawError.color;
+
+ L.DomUtil.setPosition(dash, dashPoint);
+ }
+ },
+
+ _updateGuideColor: function (color) {
+ if (this._guidesContainer) {
+ for (var i = 0, l = this._guidesContainer.childNodes.length; i < l; i++) {
+ this._guidesContainer.childNodes[i].style.backgroundColor = color;
+ }
+ }
+ },
+
+ // removes all child elements (guide dashes) from the guides container
+ _clearGuides: function () {
+ if (this._guidesContainer) {
+ while (this._guidesContainer.firstChild) {
+ this._guidesContainer.removeChild(this._guidesContainer.firstChild);
+ }
+ }
+ },
+
+ _getTooltipText: function () {
+ var showLength = this.options.showLength,
+ labelText, distanceStr;
+ if (this._markers.length === 0) {
+ labelText = {
+ text: L.drawLocal.draw.handlers.polyline.tooltip.start
+ };
+ } else {
+ distanceStr = showLength ? this._getMeasurementString() : '';
+
+ if (this._markers.length === 1) {
+ labelText = {
+ text: L.drawLocal.draw.handlers.polyline.tooltip.cont,
+ subtext: distanceStr
+ };
+ } else {
+ labelText = {
+ text: L.drawLocal.draw.handlers.polyline.tooltip.end,
+ subtext: distanceStr
+ };
+ }
+ }
+ return labelText;
+ },
+
+ _updateRunningMeasure: function (latlng, added) {
+ var markersLength = this._markers.length,
+ previousMarkerIndex, distance;
+
+ if (this._markers.length === 1) {
+ this._measurementRunningTotal = 0;
+ } else {
+ previousMarkerIndex = markersLength - (added ? 2 : 1);
+
+ // Calculate the distance based on the version
+ if (L.GeometryUtil.isVersion07x()) {
+ distance = latlng.distanceTo(this._markers[previousMarkerIndex].getLatLng()) * (this.options.factor || 1);
+ } else {
+ distance = this._map.distance(latlng, this._markers[previousMarkerIndex].getLatLng()) * (this.options.factor || 1);
+ }
+
+ this._measurementRunningTotal += distance * (added ? 1 : -1);
+ }
+ },
+
+ _getMeasurementString: function () {
+ var currentLatLng = this._currentLatLng,
+ previousLatLng = this._markers[this._markers.length - 1].getLatLng(),
+ distance;
+
+ // Calculate the distance from the last fixed point to the mouse position based on the version
+ if (L.GeometryUtil.isVersion07x()) {
+ distance = previousLatLng && currentLatLng && currentLatLng.distanceTo ? this._measurementRunningTotal + currentLatLng.distanceTo(previousLatLng) * (this.options.factor || 1) : this._measurementRunningTotal || 0;
+ } else {
+ distance = previousLatLng && currentLatLng ? this._measurementRunningTotal + this._map.distance(currentLatLng, previousLatLng) * (this.options.factor || 1) : this._measurementRunningTotal || 0;
+ }
+
+ return L.GeometryUtil.readableDistance(distance, this.options.metric, this.options.feet, this.options.nautic, this.options.precision);
+ },
+
+ _showErrorTooltip: function () {
+ this._errorShown = true;
+
+ // Update tooltip
+ this._tooltip
+ .showAsError()
+ .updateContent({text: this.options.drawError.message});
+
+ // Update shape
+ this._updateGuideColor(this.options.drawError.color);
+ this._poly.setStyle({color: this.options.drawError.color});
+
+ // Hide the error after 2 seconds
+ this._clearHideErrorTimeout();
+ this._hideErrorTimeout = setTimeout(L.Util.bind(this._hideErrorTooltip, this), this.options.drawError.timeout);
+ },
+
+ _hideErrorTooltip: function () {
+ this._errorShown = false;
+
+ this._clearHideErrorTimeout();
+
+ // Revert tooltip
+ this._tooltip
+ .removeError()
+ .updateContent(this._getTooltipText());
+
+ // Revert shape
+ this._updateGuideColor(this.options.shapeOptions.color);
+ this._poly.setStyle({color: this.options.shapeOptions.color});
+ },
+
+ _clearHideErrorTimeout: function () {
+ if (this._hideErrorTimeout) {
+ clearTimeout(this._hideErrorTimeout);
+ this._hideErrorTimeout = null;
+ }
+ },
+
+ // disable new markers temporarily;
+ // this is to prevent duplicated touch/click events in some browsers
+ _disableNewMarkers: function () {
+ this._disableMarkers = true;
+ },
+
+ // see _disableNewMarkers
+ _enableNewMarkers: function () {
+ setTimeout(function () {
+ this._disableMarkers = false;
+ }.bind(this), 50);
+ },
+
+ _cleanUpShape: function () {
+ if (this._markers.length > 1) {
+ this._markers[this._markers.length - 1].off('click', this._finishShape, this);
+ }
+ },
+
+ _fireCreatedEvent: function () {
+ var poly = new this.Poly(this._poly.getLatLngs(), this.options.shapeOptions);
+ L.Draw.Feature.prototype._fireCreatedEvent.call(this, poly);
+ }
+});
+
+
+
+/**
+ * @class L.Draw.Polygon
+ * @aka Draw.Polygon
+ * @inherits L.Draw.Polyline
+ */
+L.Draw.Polygon = L.Draw.Polyline.extend({
+ statics: {
+ TYPE: 'polygon'
+ },
+
+ Poly: L.Polygon,
+
+ options: {
+ showArea: false,
+ showLength: false,
+ shapeOptions: {
+ stroke: true,
+ color: '#3388ff',
+ weight: 4,
+ opacity: 0.5,
+ fill: true,
+ fillColor: null, //same as color by default
+ fillOpacity: 0.2,
+ clickable: true
+ },
+ // Whether to use the metric measurement system (truthy) or not (falsy).
+ // Also defines the units to use for the metric system as an array of
+ // strings (e.g. `['ha', 'm']`).
+ metric: true,
+ feet: true, // When not metric, to use feet instead of yards for display.
+ nautic: false, // When not metric, not feet use nautic mile for display
+ // Defines the precision for each type of unit (e.g. {km: 2, ft: 0}
+ precision: {}
+ },
+
+ // @method initialize(): void
+ initialize: function (map, options) {
+ L.Draw.Polyline.prototype.initialize.call(this, map, options);
+
+ // Save the type so super can fire, need to do this as cannot do this.TYPE :(
+ this.type = L.Draw.Polygon.TYPE;
+ },
+
+ _updateFinishHandler: function () {
+ var markerCount = this._markers.length;
+
+ // The first marker should have a click handler to close the polygon
+ if (markerCount === 1) {
+ this._markers[0].on('click', this._finishShape, this);
+ }
+
+ // Add and update the double click handler
+ if (markerCount > 2) {
+ this._markers[markerCount - 1].on('dblclick', this._finishShape, this);
+ // Only need to remove handler if has been added before
+ if (markerCount > 3) {
+ this._markers[markerCount - 2].off('dblclick', this._finishShape, this);
+ }
+ }
+ },
+
+ _getTooltipText: function () {
+ var text, subtext;
+
+ if (this._markers.length === 0) {
+ text = L.drawLocal.draw.handlers.polygon.tooltip.start;
+ } else if (this._markers.length < 3) {
+ text = L.drawLocal.draw.handlers.polygon.tooltip.cont;
+ subtext = this._getMeasurementString();
+ } else {
+ text = L.drawLocal.draw.handlers.polygon.tooltip.end;
+ subtext = this._getMeasurementString();
+ }
+
+ return {
+ text: text,
+ subtext: subtext
+ };
+ },
+
+ _getMeasurementString: function () {
+ var area = this._area,
+ measurementString = '';
+
+
+ if (!area && !this.options.showLength) {
+ return null;
+ }
+
+ if (this.options.showLength) {
+ measurementString = L.Draw.Polyline.prototype._getMeasurementString.call(this);
+ }
+
+ if (area) {
+ measurementString += '<br>' + L.GeometryUtil.readableArea(area, this.options.metric, this.options.precision);
+ }
+
+ return measurementString;
+ },
+
+ _shapeIsValid: function () {
+ return this._markers.length >= 3;
+ },
+
+ _vertexChanged: function (latlng, added) {
+ var latLngs;
+
+ // Check to see if we should show the area
+ if (!this.options.allowIntersection && this.options.showArea) {
+ latLngs = this._poly.getLatLngs();
+
+ this._area = L.GeometryUtil.geodesicArea(latLngs);
+ }
+
+ L.Draw.Polyline.prototype._vertexChanged.call(this, latlng, added);
+ },
+
+ _cleanUpShape: function () {
+ var markerCount = this._markers.length;
+
+ if (markerCount > 0) {
+ this._markers[0].off('click', this._finishShape, this);
+
+ if (markerCount > 2) {
+ this._markers[markerCount - 1].off('dblclick', this._finishShape, this);
+ }
+ }
+ }
+});
+
+
+
+L.SimpleShape = {};
+/**
+ * @class L.Draw.SimpleShape
+ * @aka Draw.SimpleShape
+ * @inherits L.Draw.Feature
+ */
+L.Draw.SimpleShape = L.Draw.Feature.extend({
+ options: {
+ repeatMode: false
+ },
+
+ // @method initialize(): void
+ initialize: function (map, options) {
+ this._endLabelText = L.drawLocal.draw.handlers.simpleshape.tooltip.end;
+
+ L.Draw.Feature.prototype.initialize.call(this, map, options);
+ },
+
+ // @method addHooks(): void
+ // Add listener hooks to this handler.
+ addHooks: function () {
+ L.Draw.Feature.prototype.addHooks.call(this);
+ if (this._map) {
+ this._mapDraggable = this._map.dragging.enabled();
+
+ if (this._mapDraggable) {
+ this._map.dragging.disable();
+ }
+
+ //TODO refactor: move cursor to styles
+ this._container.style.cursor = 'crosshair';
+
+ this._tooltip.updateContent({text: this._initialLabelText});
+
+ this._map
+ .on('mousedown', this._onMouseDown, this)
+ .on('mousemove', this._onMouseMove, this)
+ .on('touchstart', this._onMouseDown, this)
+ .on('touchmove', this._onMouseMove, this);
+
+ // we should prevent default, otherwise default behavior (scrolling) will fire,
+ // and that will cause document.touchend to fire and will stop the drawing
+ // (circle, rectangle) in touch mode.
+ // (update): we have to send passive now to prevent scroll, because by default it is {passive: true} now, which means,
+ // handler can't event.preventDefault
+ // check the news https://developers.google.com/web/updates/2016/06/passive-event-listeners
+ document.addEventListener('touchstart', L.DomEvent.preventDefault, {passive: false});
+ }
+ },
+
+ // @method removeHooks(): void
+ // Remove listener hooks from this handler.
+ removeHooks: function () {
+ L.Draw.Feature.prototype.removeHooks.call(this);
+ if (this._map) {
+ if (this._mapDraggable) {
+ this._map.dragging.enable();
+ }
+
+ //TODO refactor: move cursor to styles
+ this._container.style.cursor = '';
+
+ this._map
+ .off('mousedown', this._onMouseDown, this)
+ .off('mousemove', this._onMouseMove, this)
+ .off('touchstart', this._onMouseDown, this)
+ .off('touchmove', this._onMouseMove, this);
+
+ L.DomEvent.off(document, 'mouseup', this._onMouseUp, this);
+ L.DomEvent.off(document, 'touchend', this._onMouseUp, this);
+
+ document.removeEventListener('touchstart', L.DomEvent.preventDefault);
+
+ // If the box element doesn't exist they must not have moved the mouse, so don't need to destroy/return
+ if (this._shape) {
+ this._map.removeLayer(this._shape);
+ delete this._shape;
+ }
+ }
+ this._isDrawing = false;
+ },
+
+ _getTooltipText: function () {
+ return {
+ text: this._endLabelText
+ };
+ },
+
+ _onMouseDown: function (e) {
+ this._isDrawing = true;
+ this._startLatLng = e.latlng;
+
+ L.DomEvent
+ .on(document, 'mouseup', this._onMouseUp, this)
+ .on(document, 'touchend', this._onMouseUp, this)
+ .preventDefault(e.originalEvent);
+ },
+
+ _onMouseMove: function (e) {
+ var latlng = e.latlng;
+
+ this._tooltip.updatePosition(latlng);
+ if (this._isDrawing) {
+ this._tooltip.updateContent(this._getTooltipText());
+ this._drawShape(latlng);
+ }
+ },
+
+ _onMouseUp: function () {
+ if (this._shape) {
+ this._fireCreatedEvent();
+ }
+
+ this.disable();
+ if (this.options.repeatMode) {
+ this.enable();
+ }
+ }
+});
+
+
+
+/**
+ * @class L.Draw.Rectangle
+ * @aka Draw.Rectangle
+ * @inherits L.Draw.SimpleShape
+ */
+L.Draw.Rectangle = L.Draw.SimpleShape.extend({
+ statics: {
+ TYPE: 'rectangle'
+ },
+
+ options: {
+ shapeOptions: {
+ stroke: true,
+ color: '#3388ff',
+ weight: 4,
+ opacity: 0.5,
+ fill: true,
+ fillColor: null, //same as color by default
+ fillOpacity: 0.2,
+ clickable: true
+ },
+ showArea: true, //Whether to show the area in the tooltip
+ metric: true // Whether to use the metric measurement system or imperial
+ },
+
+ // @method initialize(): void
+ initialize: function (map, options) {
+ // Save the type so super can fire, need to do this as cannot do this.TYPE :(
+ this.type = L.Draw.Rectangle.TYPE;
+
+ this._initialLabelText = L.drawLocal.draw.handlers.rectangle.tooltip.start;
+
+ L.Draw.SimpleShape.prototype.initialize.call(this, map, options);
+ },
+
+ // @method disable(): void
+ disable: function () {
+ if (!this._enabled) {
+ return;
+ }
+
+ this._isCurrentlyTwoClickDrawing = false;
+ L.Draw.SimpleShape.prototype.disable.call(this);
+ },
+
+ _onMouseUp: function (e) {
+ if (!this._shape && !this._isCurrentlyTwoClickDrawing) {
+ this._isCurrentlyTwoClickDrawing = true;
+ return;
+ }
+
+ // Make sure closing click is on map
+ if (this._isCurrentlyTwoClickDrawing && !_hasAncestor(e.target, 'leaflet-pane')) {
+ return;
+ }
+
+ L.Draw.SimpleShape.prototype._onMouseUp.call(this);
+ },
+
+ _drawShape: function (latlng) {
+ if (!this._shape) {
+ this._shape = new L.Rectangle(new L.LatLngBounds(this._startLatLng, latlng), this.options.shapeOptions);
+ this._map.addLayer(this._shape);
+ } else {
+ this._shape.setBounds(new L.LatLngBounds(this._startLatLng, latlng));
+ }
+ },
+
+ _fireCreatedEvent: function () {
+ var rectangle = new L.Rectangle(this._shape.getBounds(), this.options.shapeOptions);
+ L.Draw.SimpleShape.prototype._fireCreatedEvent.call(this, rectangle);
+ },
+
+ _getTooltipText: function () {
+ var tooltipText = L.Draw.SimpleShape.prototype._getTooltipText.call(this),
+ shape = this._shape,
+ showArea = this.options.showArea,
+ latLngs, area, subtext;
+
+ if (shape) {
+ latLngs = this._shape._defaultShape ? this._shape._defaultShape() : this._shape.getLatLngs();
+ area = L.GeometryUtil.geodesicArea(latLngs);
+ subtext = showArea ? L.GeometryUtil.readableArea(area, this.options.metric) : '';
+ }
+
+ return {
+ text: tooltipText.text,
+ subtext: subtext
+ };
+ }
+});
+
+function _hasAncestor(el, cls) {
+ while ((el = el.parentElement) && !el.classList.contains(cls)) {
+ ;
+ }
+ return el;
+}
+
+
+
+/**
+ * @class L.Draw.Marker
+ * @aka Draw.Marker
+ * @inherits L.Draw.Feature
+ */
+L.Draw.Marker = L.Draw.Feature.extend({
+ statics: {
+ TYPE: 'marker'
+ },
+
+ options: {
+ icon: new L.Icon.Default(),
+ repeatMode: false,
+ zIndexOffset: 2000 // This should be > than the highest z-index any markers
+ },
+
+ // @method initialize(): void
+ initialize: function (map, options) {
+ // Save the type so super can fire, need to do this as cannot do this.TYPE :(
+ this.type = L.Draw.Marker.TYPE;
+
+ this._initialLabelText = L.drawLocal.draw.handlers.marker.tooltip.start;
+
+ L.Draw.Feature.prototype.initialize.call(this, map, options);
+ },
+
+ // @method addHooks(): void
+ // Add listener hooks to this handler.
+ addHooks: function () {
+ L.Draw.Feature.prototype.addHooks.call(this);
+
+ if (this._map) {
+ this._tooltip.updateContent({text: this._initialLabelText});
+
+ // Same mouseMarker as in Draw.Polyline
+ if (!this._mouseMarker) {
+ this._mouseMarker = L.marker(this._map.getCenter(), {
+ icon: L.divIcon({
+ className: 'leaflet-mouse-marker',
+ iconAnchor: [20, 20],
+ iconSize: [40, 40]
+ }),
+ opacity: 0,
+ zIndexOffset: this.options.zIndexOffset
+ });
+ }
+
+ this._mouseMarker
+ .on('click', this._onClick, this)
+ .addTo(this._map);
+
+ this._map.on('mousemove', this._onMouseMove, this);
+ this._map.on('click', this._onTouch, this);
+ }
+ },
+
+ // @method removeHooks(): void
+ // Remove listener hooks from this handler.
+ removeHooks: function () {
+ L.Draw.Feature.prototype.removeHooks.call(this);
+
+ if (this._map) {
+ this._map
+ .off('click', this._onClick, this)
+ .off('click', this._onTouch, this);
+ if (this._marker) {
+ this._marker.off('click', this._onClick, this);
+ this._map
+ .removeLayer(this._marker);
+ delete this._marker;
+ }
+
+ this._mouseMarker.off('click', this._onClick, this);
+ this._map.removeLayer(this._mouseMarker);
+ delete this._mouseMarker;
+
+ this._map.off('mousemove', this._onMouseMove, this);
+ }
+ },
+
+ _onMouseMove: function (e) {
+ var latlng = e.latlng;
+
+ this._tooltip.updatePosition(latlng);
+ this._mouseMarker.setLatLng(latlng);
+
+ if (!this._marker) {
+ this._marker = this._createMarker(latlng);
+ // Bind to both marker and map to make sure we get the click event.
+ this._marker.on('click', this._onClick, this);
+ this._map
+ .on('click', this._onClick, this)
+ .addLayer(this._marker);
+ }
+ else {
+ latlng = this._mouseMarker.getLatLng();
+ this._marker.setLatLng(latlng);
+ }
+ },
+
+ _createMarker: function (latlng) {
+ return new L.Marker(latlng, {
+ icon: this.options.icon,
+ zIndexOffset: this.options.zIndexOffset
+ });
+ },
+
+ _onClick: function () {
+ this._fireCreatedEvent();
+
+ this.disable();
+ if (this.options.repeatMode) {
+ this.enable();
+ }
+ },
+
+ _onTouch: function (e) {
+ // called on click & tap, only really does any thing on tap
+ this._onMouseMove(e); // creates & places marker
+ this._onClick(); // permanently places marker & ends interaction
+ },
+
+ _fireCreatedEvent: function () {
+ var marker = new L.Marker.Touch(this._marker.getLatLng(), {icon: this.options.icon});
+ L.Draw.Feature.prototype._fireCreatedEvent.call(this, marker);
+ }
+});
+
+
+
+/**
+ * @class L.Draw.CircleMarker
+ * @aka Draw.CircleMarker
+ * @inherits L.Draw.Marker
+ */
+L.Draw.CircleMarker = L.Draw.Marker.extend({
+ statics: {
+ TYPE: 'circlemarker'
+ },
+
+ options: {
+ stroke: true,
+ color: '#3388ff',
+ weight: 4,
+ opacity: 0.5,
+ fill: true,
+ fillColor: null, //same as color by default
+ fillOpacity: 0.2,
+ clickable: true,
+ zIndexOffset: 2000 // This should be > than the highest z-index any markers
+ },
+
+ // @method initialize(): void
+ initialize: function (map, options) {
+ // Save the type so super can fire, need to do this as cannot do this.TYPE :(
+ this.type = L.Draw.CircleMarker.TYPE;
+
+ this._initialLabelText = L.drawLocal.draw.handlers.circlemarker.tooltip.start;
+
+ L.Draw.Feature.prototype.initialize.call(this, map, options);
+ },
+
+
+ _fireCreatedEvent: function () {
+ var circleMarker = new L.CircleMarker(this._marker.getLatLng(), this.options);
+ L.Draw.Feature.prototype._fireCreatedEvent.call(this, circleMarker);
+ },
+
+ _createMarker: function (latlng) {
+ return new L.CircleMarker(latlng, this.options);
+ }
+});
+
+
+
+/**
+ * @class L.Draw.Circle
+ * @aka Draw.Circle
+ * @inherits L.Draw.SimpleShape
+ */
+L.Draw.Circle = L.Draw.SimpleShape.extend({
+ statics: {
+ TYPE: 'circle'
+ },
+
+ options: {
+ shapeOptions: {
+ stroke: true,
+ color: '#3388ff',
+ weight: 4,
+ opacity: 0.5,
+ fill: true,
+ fillColor: null, //same as color by default
+ fillOpacity: 0.2,
+ clickable: true
+ },
+ showRadius: true,
+ metric: true, // Whether to use the metric measurement system or imperial
+ feet: true, // When not metric, use feet instead of yards for display
+ nautic: false // When not metric, not feet use nautic mile for display
+ },
+
+ // @method initialize(): void
+ initialize: function (map, options) {
+ // Save the type so super can fire, need to do this as cannot do this.TYPE :(
+ this.type = L.Draw.Circle.TYPE;
+
+ this._initialLabelText = L.drawLocal.draw.handlers.circle.tooltip.start;
+
+ L.Draw.SimpleShape.prototype.initialize.call(this, map, options);
+ },
+
+ _drawShape: function (latlng) {
+ // Calculate the distance based on the version
+ if (L.GeometryUtil.isVersion07x()) {
+ var distance = this._startLatLng.distanceTo(latlng);
+ } else {
+ var distance = this._map.distance(this._startLatLng, latlng);
+ }
+
+ if (!this._shape) {
+ this._shape = new L.Circle(this._startLatLng, distance, this.options.shapeOptions);
+ this._map.addLayer(this._shape);
+ } else {
+ this._shape.setRadius(distance);
+ }
+ },
+
+ _fireCreatedEvent: function () {
+ var circle = new L.Circle(this._startLatLng, this._shape.getRadius(), this.options.shapeOptions);
+ L.Draw.SimpleShape.prototype._fireCreatedEvent.call(this, circle);
+ },
+
+ _onMouseMove: function (e) {
+ var latlng = e.latlng,
+ showRadius = this.options.showRadius,
+ useMetric = this.options.metric,
+ radius;
+
+ this._tooltip.updatePosition(latlng);
+ if (this._isDrawing) {
+ this._drawShape(latlng);
+
+ // Get the new radius (rounded to 1 dp)
+ radius = this._shape.getRadius().toFixed(1);
+
+ var subtext = '';
+ if (showRadius) {
+ subtext = L.drawLocal.draw.handlers.circle.radius + ': ' +
+ L.GeometryUtil.readableDistance(radius, useMetric, this.options.feet, this.options.nautic);
+ }
+ this._tooltip.updateContent({
+ text: this._endLabelText,
+ subtext: subtext
+ });
+ }
+ }
+});
+
+
+
+L.Edit = L.Edit || {};
+
+/**
+ * @class L.Edit.Marker
+ * @aka Edit.Marker
+ */
+L.Edit.Marker = L.Handler.extend({
+ // @method initialize(): void
+ initialize: function (marker, options) {
+ this._marker = marker;
+ L.setOptions(this, options);
+ },
+
+ // @method addHooks(): void
+ // Add listener hooks to this handler
+ addHooks: function () {
+ var marker = this._marker;
+
+ marker.dragging.enable();
+ marker.on('dragend', this._onDragEnd, marker);
+ this._toggleMarkerHighlight();
+ },
+
+ // @method removeHooks(): void
+ // Remove listener hooks from this handler
+ removeHooks: function () {
+ var marker = this._marker;
+
+ marker.dragging.disable();
+ marker.off('dragend', this._onDragEnd, marker);
+ this._toggleMarkerHighlight();
+ },
+
+ _onDragEnd: function (e) {
+ var layer = e.target;
+ layer.edited = true;
+ this._map.fire(L.Draw.Event.EDITMOVE, {layer: layer});
+ },
+
+ _toggleMarkerHighlight: function () {
+ var icon = this._marker._icon;
+
+ // Don't do anything if this layer is a marker but doesn't have an icon. Markers
+ // should usually have icons. If using Leaflet.draw with Leaflet.markercluster there
+ // is a chance that a marker doesn't.
+ if (!icon) {
+ return;
+ }
+
+ // This is quite naughty, but I don't see another way of doing it. (short of setting a new icon)
+ icon.style.display = 'none';
+
+ if (L.DomUtil.hasClass(icon, 'leaflet-edit-marker-selected')) {
+ L.DomUtil.removeClass(icon, 'leaflet-edit-marker-selected');
+ // Offset as the border will make the icon move.
+ this._offsetMarker(icon, -4);
+
+ } else {
+ L.DomUtil.addClass(icon, 'leaflet-edit-marker-selected');
+ // Offset as the border will make the icon move.
+ this._offsetMarker(icon, 4);
+ }
+
+ icon.style.display = '';
+ },
+
+ _offsetMarker: function (icon, offset) {
+ var iconMarginTop = parseInt(icon.style.marginTop, 10) - offset,
+ iconMarginLeft = parseInt(icon.style.marginLeft, 10) - offset;
+
+ icon.style.marginTop = iconMarginTop + 'px';
+ icon.style.marginLeft = iconMarginLeft + 'px';
+ }
+});
+
+L.Marker.addInitHook(function () {
+ if (L.Edit.Marker) {
+ this.editing = new L.Edit.Marker(this);
+
+ if (this.options.editable) {
+ this.editing.enable();
+ }
+ }
+});
+
+
+
+L.Edit = L.Edit || {};
+
+/**
+ * @class L.Edit.Polyline
+ * @aka L.Edit.Poly
+ * @aka Edit.Poly
+ */
+L.Edit.Poly = L.Handler.extend({
+ // @method initialize(): void
+ initialize: function (poly) {
+
+ this.latlngs = [poly._latlngs];
+ if (poly._holes) {
+ this.latlngs = this.latlngs.concat(poly._holes);
+ }
+
+ this._poly = poly;
+
+ this._poly.on('revert-edited', this._updateLatLngs, this);
+ },
+
+ // Compatibility method to normalize Poly* objects
+ // between 0.7.x and 1.0+
+ _defaultShape: function () {
+ if (!L.Polyline._flat) {
+ return this._poly._latlngs;
+ }
+ return L.Polyline._flat(this._poly._latlngs) ? this._poly._latlngs : this._poly._latlngs[0];
+ },
+
+ _eachVertexHandler: function (callback) {
+ for (var i = 0; i < this._verticesHandlers.length; i++) {
+ callback(this._verticesHandlers[i]);
+ }
+ },
+
+ // @method addHooks(): void
+ // Add listener hooks to this handler
+ addHooks: function () {
+ this._initHandlers();
+ this._eachVertexHandler(function (handler) {
+ handler.addHooks();
+ });
+ },
+
+ // @method removeHooks(): void
+ // Remove listener hooks from this handler
+ removeHooks: function () {
+ this._eachVertexHandler(function (handler) {
+ handler.removeHooks();
+ });
+ },
+
+ // @method updateMarkers(): void
+ // Fire an update for each vertex handler
+ updateMarkers: function () {
+ this._eachVertexHandler(function (handler) {
+ handler.updateMarkers();
+ });
+ },
+
+ _initHandlers: function () {
+ this._verticesHandlers = [];
+ for (var i = 0; i < this.latlngs.length; i++) {
+ this._verticesHandlers.push(new L.Edit.PolyVerticesEdit(this._poly, this.latlngs[i], this._poly.options.poly));
+ }
+ },
+
+ _updateLatLngs: function (e) {
+ this.latlngs = [e.layer._latlngs];
+ if (e.layer._holes) {
+ this.latlngs = this.latlngs.concat(e.layer._holes);
+ }
+ }
+
+});
+
+/**
+ * @class L.Edit.PolyVerticesEdit
+ * @aka Edit.PolyVerticesEdit
+ */
+L.Edit.PolyVerticesEdit = L.Handler.extend({
+ options: {
+ icon: new L.DivIcon({
+ iconSize: new L.Point(8, 8),
+ className: 'leaflet-div-icon leaflet-editing-icon'
+ }),
+ touchIcon: new L.DivIcon({
+ iconSize: new L.Point(20, 20),
+ className: 'leaflet-div-icon leaflet-editing-icon leaflet-touch-icon'
+ }),
+ drawError: {
+ color: '#b00b00',
+ timeout: 1000
+ }
+
+
+ },
+
+ // @method intialize(): void
+ initialize: function (poly, latlngs, options) {
+ // if touch, switch to touch icon
+ if (L.Browser.touch) {
+ this.options.icon = this.options.touchIcon;
+ }
+ this._poly = poly;
+
+ if (options && options.drawError) {
+ options.drawError = L.Util.extend({}, this.options.drawError, options.drawError);
+ }
+
+ this._latlngs = latlngs;
+
+ L.setOptions(this, options);
+ },
+
+ // Compatibility method to normalize Poly* objects
+ // between 0.7.x and 1.0+
+ _defaultShape: function () {
+ if (!L.Polyline._flat) {
+ return this._latlngs;
+ }
+ return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
+ },
+
+ // @method addHooks(): void
+ // Add listener hooks to this handler.
+ addHooks: function () {
+ var poly = this._poly;
+ var path = poly._path;
+
+ if (!(poly instanceof L.Polygon)) {
+ poly.options.fill = false;
+ if (poly.options.editing) {
+ poly.options.editing.fill = false;
+ }
+ }
+
+ if (path) {
+ if (poly.options.editing && poly.options.editing.className) {
+ if (poly.options.original.className) {
+ poly.options.original.className.split(' ').forEach(function (className) {
+ L.DomUtil.removeClass(path, className);
+ });
+ }
+ poly.options.editing.className.split(' ').forEach(function (className) {
+ L.DomUtil.addClass(path, className);
+ });
+ }
+ }
+
+ poly.setStyle(poly.options.editing);
+
+ if (this._poly._map) {
+
+ this._map = this._poly._map; // Set map
+
+ if (!this._markerGroup) {
+ this._initMarkers();
+ }
+ this._poly._map.addLayer(this._markerGroup);
+ }
+ },
+
+ // @method removeHooks(): void
+ // Remove listener hooks from this handler.
+ removeHooks: function () {
+ var poly = this._poly;
+ var path = poly._path;
+
+ if (path) {
+ if (poly.options.editing && poly.options.editing.className) {
+ poly.options.editing.className.split(' ').forEach(function (className) {
+ L.DomUtil.removeClass(path, className);
+ });
+ if (poly.options.original.className) {
+ poly.options.original.className.split(' ').forEach(function (className) {
+ L.DomUtil.addClass(path, className);
+ });
+ }
+ }
+ }
+
+ poly.setStyle(poly.options.original);
+
+ if (poly._map) {
+ poly._map.removeLayer(this._markerGroup);
+ delete this._markerGroup;
+ delete this._markers;
+ }
+ },
+
+ // @method updateMarkers(): void
+ // Clear markers and update their location
+ updateMarkers: function () {
+ this._markerGroup.clearLayers();
+ this._initMarkers();
+ },
+
+ _initMarkers: function () {
+ if (!this._markerGroup) {
+ this._markerGroup = new L.LayerGroup();
+ }
+ this._markers = [];
+
+ var latlngs = this._defaultShape(),
+ i, j, len, marker;
+
+ for (i = 0, len = latlngs.length; i < len; i++) {
+
+ marker = this._createMarker(latlngs[i], i);
+ marker.on('click', this._onMarkerClick, this);
+ marker.on('contextmenu', this._onContextMenu, this);
+ this._markers.push(marker);
+ }
+
+ var markerLeft, markerRight;
+
+ for (i = 0, j = len - 1; i < len; j = i++) {
+ if (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) {
+ continue;
+ }
+
+ markerLeft = this._markers[j];
+ markerRight = this._markers[i];
+
+ this._createMiddleMarker(markerLeft, markerRight);
+ this._updatePrevNext(markerLeft, markerRight);
+ }
+ },
+
+ _createMarker: function (latlng, index) {
+ // Extending L.Marker in TouchEvents.js to include touch.
+ var marker = new L.Marker.Touch(latlng, {
+ draggable: true,
+ icon: this.options.icon,
+ });
+
+ marker._origLatLng = latlng;
+ marker._index = index;
+
+ marker
+ .on('dragstart', this._onMarkerDragStart, this)
+ .on('drag', this._onMarkerDrag, this)
+ .on('dragend', this._fireEdit, this)
+ .on('touchmove', this._onTouchMove, this)
+ .on('touchend', this._fireEdit, this)
+ .on('MSPointerMove', this._onTouchMove, this)
+ .on('MSPointerUp', this._fireEdit, this);
+
+ this._markerGroup.addLayer(marker);
+
+ return marker;
+ },
+
+ _onMarkerDragStart: function () {
+ this._poly.fire('editstart');
+ },
+
+ _spliceLatLngs: function () {
+ var latlngs = this._defaultShape();
+ var removed = [].splice.apply(latlngs, arguments);
+ this._poly._convertLatLngs(latlngs, true);
+ this._poly.redraw();
+ return removed;
+ },
+
+ _removeMarker: function (marker) {
+ var i = marker._index;
+
+ this._markerGroup.removeLayer(marker);
+ this._markers.splice(i, 1);
+ this._spliceLatLngs(i, 1);
+ this._updateIndexes(i, -1);
+
+ marker
+ .off('dragstart', this._onMarkerDragStart, this)
+ .off('drag', this._onMarkerDrag, this)
+ .off('dragend', this._fireEdit, this)
+ .off('touchmove', this._onMarkerDrag, this)
+ .off('touchend', this._fireEdit, this)
+ .off('click', this._onMarkerClick, this)
+ .off('MSPointerMove', this._onTouchMove, this)
+ .off('MSPointerUp', this._fireEdit, this);
+ },
+
+ _fireEdit: function () {
+ this._poly.edited = true;
+ this._poly.fire('edit');
+ this._poly._map.fire(L.Draw.Event.EDITVERTEX, {layers: this._markerGroup, poly: this._poly});
+ },
+
+ _onMarkerDrag: function (e) {
+ var marker = e.target;
+ var poly = this._poly;
+
+ var oldOrigLatLng = L.LatLngUtil.cloneLatLng(marker._origLatLng);
+ L.extend(marker._origLatLng, marker._latlng);
+ if (poly.options.poly) {
+ var tooltip = poly._map._editTooltip; // Access the tooltip
+
+ // If we don't allow intersections and the polygon intersects
+ if (!poly.options.poly.allowIntersection && poly.intersects()) {
+ L.extend(marker._origLatLng, oldOrigLatLng);
+ marker.setLatLng(oldOrigLatLng);
+ var originalColor = poly.options.color;
+ poly.setStyle({color: this.options.drawError.color});
+ if (tooltip) {
+ tooltip.updateContent({
+ text: L.drawLocal.draw.handlers.polyline.error
+ });
+ }
+
+ // Reset everything back to normal after a second
+ setTimeout(function () {
+ poly.setStyle({color: originalColor});
+ if (tooltip) {
+ tooltip.updateContent({
+ text: L.drawLocal.edit.handlers.edit.tooltip.text,
+ subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext
+ });
+ }
+ }, 1000);
+ }
+ }
+
+ if (marker._middleLeft) {
+ marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));
+ }
+ if (marker._middleRight) {
+ marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));
+ }
+
+ //refresh the bounds when draging
+ this._poly._bounds._southWest = L.latLng(Infinity, Infinity);
+ this._poly._bounds._northEast = L.latLng(-Infinity, -Infinity);
+ var latlngs = this._poly.getLatLngs();
+ this._poly._convertLatLngs(latlngs, true);
+ this._poly.redraw();
+ this._poly.fire('editdrag');
+ },
+
+ _onMarkerClick: function (e) {
+
+ var minPoints = L.Polygon && (this._poly instanceof L.Polygon) ? 4 : 3,
+ marker = e.target;
+
+ // If removing this point would create an invalid polyline/polygon don't remove
+ if (this._defaultShape().length < minPoints) {
+ return;
+ }
+
+ // remove the marker
+ this._removeMarker(marker);
+
+ // update prev/next links of adjacent markers
+ this._updatePrevNext(marker._prev, marker._next);
+
+ // remove ghost markers near the removed marker
+ if (marker._middleLeft) {
+ this._markerGroup.removeLayer(marker._middleLeft);
+ }
+ if (marker._middleRight) {
+ this._markerGroup.removeLayer(marker._middleRight);
+ }
+
+ // create a ghost marker in place of the removed one
+ if (marker._prev && marker._next) {
+ this._createMiddleMarker(marker._prev, marker._next);
+
+ } else if (!marker._prev) {
+ marker._next._middleLeft = null;
+
+ } else if (!marker._next) {
+ marker._prev._middleRight = null;
+ }
+
+ this._fireEdit();
+ },
+
+ _onContextMenu: function (e) {
+ var marker = e.target;
+ var poly = this._poly;
+ this._poly._map.fire(L.Draw.Event.MARKERCONTEXT, {marker: marker, layers: this._markerGroup, poly: this._poly});
+ L.DomEvent.stopPropagation;
+ },
+
+ _onTouchMove: function (e) {
+
+ var layerPoint = this._map.mouseEventToLayerPoint(e.originalEvent.touches[0]),
+ latlng = this._map.layerPointToLatLng(layerPoint),
+ marker = e.target;
+
+ L.extend(marker._origLatLng, latlng);
+
+ if (marker._middleLeft) {
+ marker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));
+ }
+ if (marker._middleRight) {
+ marker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));
+ }
+
+ this._poly.redraw();
+ this.updateMarkers();
+ },
+
+ _updateIndexes: function (index, delta) {
+ this._markerGroup.eachLayer(function (marker) {
+ if (marker._index > index) {
+ marker._index += delta;
+ }
+ });
+ },
+
+ _createMiddleMarker: function (marker1, marker2) {
+ var latlng = this._getMiddleLatLng(marker1, marker2),
+ marker = this._createMarker(latlng),
+ onClick,
+ onDragStart,
+ onDragEnd;
+
+ marker.setOpacity(0.6);
+
+ marker1._middleRight = marker2._middleLeft = marker;
+
+ onDragStart = function () {
+ marker.off('touchmove', onDragStart, this);
+ var i = marker2._index;
+
+ marker._index = i;
+
+ marker
+ .off('click', onClick, this)
+ .on('click', this._onMarkerClick, this);
+
+ latlng.lat = marker.getLatLng().lat;
+ latlng.lng = marker.getLatLng().lng;
+ this._spliceLatLngs(i, 0, latlng);
+ this._markers.splice(i, 0, marker);
+
+ marker.setOpacity(1);
+
+ this._updateIndexes(i, 1);
+ marker2._index++;
+ this._updatePrevNext(marker1, marker);
+ this._updatePrevNext(marker, marker2);
+
+ this._poly.fire('editstart');
+ };
+
+ onDragEnd = function () {
+ marker.off('dragstart', onDragStart, this);
+ marker.off('dragend', onDragEnd, this);
+ marker.off('touchmove', onDragStart, this);
+
+ this._createMiddleMarker(marker1, marker);
+ this._createMiddleMarker(marker, marker2);
+ };
+
+ onClick = function () {
+ onDragStart.call(this);
+ onDragEnd.call(this);
+ this._fireEdit();
+ };
+
+ marker
+ .on('click', onClick, this)
+ .on('dragstart', onDragStart, this)
+ .on('dragend', onDragEnd, this)
+ .on('touchmove', onDragStart, this);
+
+ this._markerGroup.addLayer(marker);
+ },
+
+ _updatePrevNext: function (marker1, marker2) {
+ if (marker1) {
+ marker1._next = marker2;
+ }
+ if (marker2) {
+ marker2._prev = marker1;
+ }
+ },
+
+ _getMiddleLatLng: function (marker1, marker2) {
+ var map = this._poly._map,
+ p1 = map.project(marker1.getLatLng()),
+ p2 = map.project(marker2.getLatLng());
+
+ return map.unproject(p1._add(p2)._divideBy(2));
+ }
+});
+
+L.Polyline.addInitHook(function () {
+
+ // Check to see if handler has already been initialized. This is to support versions of Leaflet that still have L.Handler.PolyEdit
+ if (this.editing) {
+ return;
+ }
+
+ if (L.Edit.Poly) {
+
+ this.editing = new L.Edit.Poly(this);
+
+ if (this.options.editable) {
+ this.editing.enable();
+ }
+ }
+
+ this.on('add', function () {
+ if (this.editing && this.editing.enabled()) {
+ this.editing.addHooks();
+ }
+ });
+
+ this.on('remove', function () {
+ if (this.editing && this.editing.enabled()) {
+ this.editing.removeHooks();
+ }
+ });
+});
+
+
+
+L.Edit = L.Edit || {};
+/**
+ * @class L.Edit.SimpleShape
+ * @aka Edit.SimpleShape
+ */
+L.Edit.SimpleShape = L.Handler.extend({
+ options: {
+ moveIcon: new L.DivIcon({
+ iconSize: new L.Point(8, 8),
+ className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-move'
+ }),
+ resizeIcon: new L.DivIcon({
+ iconSize: new L.Point(8, 8),
+ className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-resize'
+ }),
+ touchMoveIcon: new L.DivIcon({
+ iconSize: new L.Point(20, 20),
+ className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-move leaflet-touch-icon'
+ }),
+ touchResizeIcon: new L.DivIcon({
+ iconSize: new L.Point(20, 20),
+ className: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-resize leaflet-touch-icon'
+ }),
+ },
+
+ // @method intialize(): void
+ initialize: function (shape, options) {
+ // if touch, switch to touch icon
+ if (L.Browser.touch) {
+ this.options.moveIcon = this.options.touchMoveIcon;
+ this.options.resizeIcon = this.options.touchResizeIcon;
+ }
+
+ this._shape = shape;
+ L.Util.setOptions(this, options);
+ },
+
+ // @method addHooks(): void
+ // Add listener hooks to this handler
+ addHooks: function () {
+ var shape = this._shape;
+ if (this._shape._map) {
+ this._map = this._shape._map;
+ shape.setStyle(shape.options.editing);
+
+ if (shape._map) {
+ this._map = shape._map;
+ if (!this._markerGroup) {
+ this._initMarkers();
+ }
+ this._map.addLayer(this._markerGroup);
+ }
+ }
+ },
+
+ // @method removeHooks(): void
+ // Remove listener hooks from this handler
+ removeHooks: function () {
+ var shape = this._shape;
+
+ shape.setStyle(shape.options.original);
+
+ if (shape._map) {
+ this._unbindMarker(this._moveMarker);
+
+ for (var i = 0, l = this._resizeMarkers.length; i < l; i++) {
+ this._unbindMarker(this._resizeMarkers[i]);
+ }
+ this._resizeMarkers = null;
+
+ this._map.removeLayer(this._markerGroup);
+ delete this._markerGroup;
+ }
+
+ this._map = null;
+ },
+
+ // @method updateMarkers(): void
+ // Remove the edit markers from this layer
+ updateMarkers: function () {
+ this._markerGroup.clearLayers();
+ this._initMarkers();
+ },
+
+ _initMarkers: function () {
+ if (!this._markerGroup) {
+ this._markerGroup = new L.LayerGroup();
+ }
+
+ // Create center marker
+ this._createMoveMarker();
+
+ // Create edge marker
+ this._createResizeMarker();
+ },
+
+ _createMoveMarker: function () {
+ // Children override
+ },
+
+ _createResizeMarker: function () {
+ // Children override
+ },
+
+ _createMarker: function (latlng, icon) {
+ // Extending L.Marker in TouchEvents.js to include touch.
+ var marker = new L.Marker.Touch(latlng, {
+ draggable: true,
+ icon: icon,
+ zIndexOffset: 10
+ });
+
+ this._bindMarker(marker);
+
+ this._markerGroup.addLayer(marker);
+
+ return marker;
+ },
+
+ _bindMarker: function (marker) {
+ marker
+ .on('dragstart', this._onMarkerDragStart, this)
+ .on('drag', this._onMarkerDrag, this)
+ .on('dragend', this._onMarkerDragEnd, this)
+ .on('touchstart', this._onTouchStart, this)
+ .on('touchmove', this._onTouchMove, this)
+ .on('MSPointerMove', this._onTouchMove, this)
+ .on('touchend', this._onTouchEnd, this)
+ .on('MSPointerUp', this._onTouchEnd, this);
+ },
+
+ _unbindMarker: function (marker) {
+ marker
+ .off('dragstart', this._onMarkerDragStart, this)
+ .off('drag', this._onMarkerDrag, this)
+ .off('dragend', this._onMarkerDragEnd, this)
+ .off('touchstart', this._onTouchStart, this)
+ .off('touchmove', this._onTouchMove, this)
+ .off('MSPointerMove', this._onTouchMove, this)
+ .off('touchend', this._onTouchEnd, this)
+ .off('MSPointerUp', this._onTouchEnd, this);
+ },
+
+ _onMarkerDragStart: function (e) {
+ var marker = e.target;
+ marker.setOpacity(0);
+
+ this._shape.fire('editstart');
+ },
+
+ _fireEdit: function () {
+ this._shape.edited = true;
+ this._shape.fire('edit');
+ },
+
+ _onMarkerDrag: function (e) {
+ var marker = e.target,
+ latlng = marker.getLatLng();
+
+ if (marker === this._moveMarker) {
+ this._move(latlng);
+ } else {
+ this._resize(latlng);
+ }
+
+ this._shape.redraw();
+ this._shape.fire('editdrag');
+ },
+
+ _onMarkerDragEnd: function (e) {
+ var marker = e.target;
+ marker.setOpacity(1);
+
+ this._fireEdit();
+ },
+
+ _onTouchStart: function (e) {
+ L.Edit.SimpleShape.prototype._onMarkerDragStart.call(this, e);
+
+ if (typeof(this._getCorners) === 'function') {
+ // Save a reference to the opposite point
+ var corners = this._getCorners(),
+ marker = e.target,
+ currentCornerIndex = marker._cornerIndex;
+
+ marker.setOpacity(0);
+
+ // Copyed from Edit.Rectangle.js line 23 _onMarkerDragStart()
+ // Latlng is null otherwise.
+ this._oppositeCorner = corners[(currentCornerIndex + 2) % 4];
+ this._toggleCornerMarkers(0, currentCornerIndex);
+ }
+
+ this._shape.fire('editstart');
+ },
+
+ _onTouchMove: function (e) {
+ var layerPoint = this._map.mouseEventToLayerPoint(e.originalEvent.touches[0]),
+ latlng = this._map.layerPointToLatLng(layerPoint),
+ marker = e.target;
+
+ if (marker === this._moveMarker) {
+ this._move(latlng);
+ } else {
+ this._resize(latlng);
+ }
+
+ this._shape.redraw();
+
+ // prevent touchcancel in IOS
+ // e.preventDefault();
+ return false;
+ },
+
+ _onTouchEnd: function (e) {
+ var marker = e.target;
+ marker.setOpacity(1);
+ this.updateMarkers();
+ this._fireEdit();
+ },
+
+ _move: function () {
+ // Children override
+ },
+
+ _resize: function () {
+ // Children override
+ }
+});
+
+
+
+L.Edit = L.Edit || {};
+/**
+ * @class L.Edit.Rectangle
+ * @aka Edit.Rectangle
+ * @inherits L.Edit.SimpleShape
+ */
+L.Edit.Rectangle = L.Edit.SimpleShape.extend({
+ _createMoveMarker: function () {
+ var bounds = this._shape.getBounds(),
+ center = bounds.getCenter();
+
+ this._moveMarker = this._createMarker(center, this.options.moveIcon);
+ },
+
+ _createResizeMarker: function () {
+ var corners = this._getCorners();
+
+ this._resizeMarkers = [];
+
+ for (var i = 0, l = corners.length; i < l; i++) {
+ this._resizeMarkers.push(this._createMarker(corners[i], this.options.resizeIcon));
+ // Monkey in the corner index as we will need to know this for dragging
+ this._resizeMarkers[i]._cornerIndex = i;
+ }
+ },
+
+ _onMarkerDragStart: function (e) {
+ L.Edit.SimpleShape.prototype._onMarkerDragStart.call(this, e);
+
+ // Save a reference to the opposite point
+ var corners = this._getCorners(),
+ marker = e.target,
+ currentCornerIndex = marker._cornerIndex;
+
+ this._oppositeCorner = corners[(currentCornerIndex + 2) % 4];
+
+ this._toggleCornerMarkers(0, currentCornerIndex);
+ },
+
+ _onMarkerDragEnd: function (e) {
+ var marker = e.target,
+ bounds, center;
+
+ // Reset move marker position to the center
+ if (marker === this._moveMarker) {
+ bounds = this._shape.getBounds();
+ center = bounds.getCenter();
+
+ marker.setLatLng(center);
+ }
+
+ this._toggleCornerMarkers(1);
+
+ this._repositionCornerMarkers();
+
+ L.Edit.SimpleShape.prototype._onMarkerDragEnd.call(this, e);
+ },
+
+ _move: function (newCenter) {
+ var latlngs = this._shape._defaultShape ? this._shape._defaultShape() : this._shape.getLatLngs(),
+ bounds = this._shape.getBounds(),
+ center = bounds.getCenter(),
+ offset, newLatLngs = [];
+
+ // Offset the latlngs to the new center
+ for (var i = 0, l = latlngs.length; i < l; i++) {
+ offset = [latlngs[i].lat - center.lat, latlngs[i].lng - center.lng];
+ newLatLngs.push([newCenter.lat + offset[0], newCenter.lng + offset[1]]);
+ }
+
+ this._shape.setLatLngs(newLatLngs);
+
+ // Reposition the resize markers
+ this._repositionCornerMarkers();
+
+ this._map.fire(L.Draw.Event.EDITMOVE, {layer: this._shape});
+ },
+
+ _resize: function (latlng) {
+ var bounds;
+
+ // Update the shape based on the current position of this corner and the opposite point
+ this._shape.setBounds(L.latLngBounds(latlng, this._oppositeCorner));
+
+ // Reposition the move marker
+ bounds = this._shape.getBounds();
+ this._moveMarker.setLatLng(bounds.getCenter());
+
+ this._map.fire(L.Draw.Event.EDITRESIZE, {layer: this._shape});
+ },
+
+ _getCorners: function () {
+ var bounds = this._shape.getBounds(),
+ nw = bounds.getNorthWest(),
+ ne = bounds.getNorthEast(),
+ se = bounds.getSouthEast(),
+ sw = bounds.getSouthWest();
+
+ return [nw, ne, se, sw];
+ },
+
+ _toggleCornerMarkers: function (opacity) {
+ for (var i = 0, l = this._resizeMarkers.length; i < l; i++) {
+ this._resizeMarkers[i].setOpacity(opacity);
+ }
+ },
+
+ _repositionCornerMarkers: function () {
+ var corners = this._getCorners();
+
+ for (var i = 0, l = this._resizeMarkers.length; i < l; i++) {
+ this._resizeMarkers[i].setLatLng(corners[i]);
+ }
+ }
+});
+
+L.Rectangle.addInitHook(function () {
+ if (L.Edit.Rectangle) {
+ this.editing = new L.Edit.Rectangle(this);
+
+ if (this.options.editable) {
+ this.editing.enable();
+ }
+ }
+});
+
+
+
+L.Edit = L.Edit || {};
+/**
+ * @class L.Edit.CircleMarker
+ * @aka Edit.Circle
+ * @inherits L.Edit.SimpleShape
+ */
+L.Edit.CircleMarker = L.Edit.SimpleShape.extend({
+ _createMoveMarker: function () {
+ var center = this._shape.getLatLng();
+
+ this._moveMarker = this._createMarker(center, this.options.moveIcon);
+ },
+
+ _createResizeMarker: function () {
+ // To avoid an undefined check in L.Edit.SimpleShape.removeHooks
+ this._resizeMarkers = [];
+ },
+
+ _move: function (latlng) {
+ if (this._resizeMarkers.length) {
+ var resizemarkerPoint = this._getResizeMarkerPoint(latlng);
+ // Move the resize marker
+ this._resizeMarkers[0].setLatLng(resizemarkerPoint);
+ }
+
+ // Move the circle
+ this._shape.setLatLng(latlng);
+
+ this._map.fire(L.Draw.Event.EDITMOVE, {layer: this._shape});
+ },
+});
+
+L.CircleMarker.addInitHook(function () {
+ if (L.Edit.CircleMarker) {
+ this.editing = new L.Edit.CircleMarker(this);
+
+ if (this.options.editable) {
+ this.editing.enable();
+ }
+ }
+
+ this.on('add', function () {
+ if (this.editing && this.editing.enabled()) {
+ this.editing.addHooks();
+ }
+ });
+
+ this.on('remove', function () {
+ if (this.editing && this.editing.enabled()) {
+ this.editing.removeHooks();
+ }
+ });
+});
+
+
+
+L.Edit = L.Edit || {};
+/**
+ * @class L.Edit.Circle
+ * @aka Edit.Circle
+ * @inherits L.Edit.CircleMarker
+ */
+L.Edit.Circle = L.Edit.CircleMarker.extend({
+
+ _createResizeMarker: function () {
+ var center = this._shape.getLatLng(),
+ resizemarkerPoint = this._getResizeMarkerPoint(center);
+
+ this._resizeMarkers = [];
+ this._resizeMarkers.push(this._createMarker(resizemarkerPoint, this.options.resizeIcon));
+ },
+
+ _getResizeMarkerPoint: function (latlng) {
+ // From L.shape.getBounds()
+ var delta = this._shape._radius * Math.cos(Math.PI / 4),
+ point = this._map.project(latlng);
+ return this._map.unproject([point.x + delta, point.y - delta]);
+ },
+
+ _resize: function (latlng) {
+ var moveLatLng = this._moveMarker.getLatLng();
+
+ // Calculate the radius based on the version
+ if (L.GeometryUtil.isVersion07x()) {
+ radius = moveLatLng.distanceTo(latlng);
+ } else {
+ radius = this._map.distance(moveLatLng, latlng);
+ }
+ this._shape.setRadius(radius);
+
+ if (this._map.editTooltip) {
+ this._map._editTooltip.updateContent({
+ text: L.drawLocal.edit.handlers.edit.tooltip.subtext + '<br />' + L.drawLocal.edit.handlers.edit.tooltip.text,
+ subtext: L.drawLocal.draw.handlers.circle.radius + ': ' +
+ L.GeometryUtil.readableDistance(radius, true, this.options.feet, this.options.nautic)
+ });
+ }
+
+ this._shape.setRadius(radius);
+
+ this._map.fire(L.Draw.Event.EDITRESIZE, {layer: this._shape});
+ }
+});
+
+L.Circle.addInitHook(function () {
+ if (L.Edit.Circle) {
+ this.editing = new L.Edit.Circle(this);
+
+ if (this.options.editable) {
+ this.editing.enable();
+ }
+ }
+});
+
+
+
+L.Map.mergeOptions({
+ touchExtend: true
+});
+
+/**
+ * @class L.Map.TouchExtend
+ * @aka TouchExtend
+ */
+L.Map.TouchExtend = L.Handler.extend({
+
+ // @method initialize(): void
+ // Sets TouchExtend private accessor variables
+ initialize: function (map) {
+ this._map = map;
+ this._container = map._container;
+ this._pane = map._panes.overlayPane;
+ },
+
+ // @method addHooks(): void
+ // Adds dom listener events to the map container
+ addHooks: function () {
+ L.DomEvent.on(this._container, 'touchstart', this._onTouchStart, this);
+ L.DomEvent.on(this._container, 'touchend', this._onTouchEnd, this);
+ L.DomEvent.on(this._container, 'touchmove', this._onTouchMove, this);
+ if (this._detectIE()) {
+ L.DomEvent.on(this._container, 'MSPointerDown', this._onTouchStart, this);
+ L.DomEvent.on(this._container, 'MSPointerUp', this._onTouchEnd, this);
+ L.DomEvent.on(this._container, 'MSPointerMove', this._onTouchMove, this);
+ L.DomEvent.on(this._container, 'MSPointerCancel', this._onTouchCancel, this);
+
+ } else {
+ L.DomEvent.on(this._container, 'touchcancel', this._onTouchCancel, this);
+ L.DomEvent.on(this._container, 'touchleave', this._onTouchLeave, this);
+ }
+ },
+
+ // @method removeHooks(): void
+ // Removes dom listener events from the map container
+ removeHooks: function () {
+ L.DomEvent.off(this._container, 'touchstart', this._onTouchStart, this);
+ L.DomEvent.off(this._container, 'touchend', this._onTouchEnd, this);
+ L.DomEvent.off(this._container, 'touchmove', this._onTouchMove, this);
+ if (this._detectIE()) {
+ L.DomEvent.off(this._container, 'MSPointerDown', this._onTouchStart, this);
+ L.DomEvent.off(this._container, 'MSPointerUp', this._onTouchEnd, this);
+ L.DomEvent.off(this._container, 'MSPointerMove', this._onTouchMove, this);
+ L.DomEvent.off(this._container, 'MSPointerCancel', this._onTouchCancel, this);
+ } else {
+ L.DomEvent.off(this._container, 'touchcancel', this._onTouchCancel, this);
+ L.DomEvent.off(this._container, 'touchleave', this._onTouchLeave, this);
+ }
+ },
+
+ _touchEvent: function (e, type) {
+ // #TODO: fix the pageX error that is do a bug in Android where a single touch triggers two click events
+ // _filterClick is what leaflet uses as a workaround.
+ // This is a problem with more things than just android. Another problem is touchEnd has no touches in
+ // its touch list.
+ var touchEvent = {};
+ if (typeof e.touches !== 'undefined') {
+ if (!e.touches.length) {
+ return;
+ }
+ touchEvent = e.touches[0];
+ } else if (e.pointerType === 'touch') {
+ touchEvent = e;
+ if (!this._filterClick(e)) {
+ return;
+ }
+ } else {
+ return;
+ }
+
+ var containerPoint = this._map.mouseEventToContainerPoint(touchEvent),
+ layerPoint = this._map.mouseEventToLayerPoint(touchEvent),
+ latlng = this._map.layerPointToLatLng(layerPoint);
+
+ this._map.fire(type, {
+ latlng: latlng,
+ layerPoint: layerPoint,
+ containerPoint: containerPoint,
+ pageX: touchEvent.pageX,
+ pageY: touchEvent.pageY,
+ originalEvent: e
+ });
+ },
+
+ /** Borrowed from Leaflet and modified for bool ops **/
+ _filterClick: function (e) {
+ var timeStamp = (e.timeStamp || e.originalEvent.timeStamp),
+ elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
+
+ // are they closer together than 500ms yet more than 100ms?
+ // Android typically triggers them ~300ms apart while multiple listeners
+ // on the same event should be triggered far faster;
+ // or check if click is simulated on the element, and if it is, reject any non-simulated events
+ if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
+ L.DomEvent.stop(e);
+ return false;
+ }
+ L.DomEvent._lastClick = timeStamp;
+ return true;
+ },
+
+ _onTouchStart: function (e) {
+ if (!this._map._loaded) {
+ return;
+ }
+
+ var type = 'touchstart';
+ this._touchEvent(e, type);
+
+ },
+
+ _onTouchEnd: function (e) {
+ if (!this._map._loaded) {
+ return;
+ }
+
+ var type = 'touchend';
+ this._touchEvent(e, type);
+ },
+
+ _onTouchCancel: function (e) {
+ if (!this._map._loaded) {
+ return;
+ }
+
+ var type = 'touchcancel';
+ if (this._detectIE()) {
+ type = 'pointercancel';
+ }
+ this._touchEvent(e, type);
+ },
+
+ _onTouchLeave: function (e) {
+ if (!this._map._loaded) {
+ return;
+ }
+
+ var type = 'touchleave';
+ this._touchEvent(e, type);
+ },
+
+ _onTouchMove: function (e) {
+ if (!this._map._loaded) {
+ return;
+ }
+
+ var type = 'touchmove';
+ this._touchEvent(e, type);
+ },
+
+ _detectIE: function () {
+ var ua = window.navigator.userAgent;
+
+ var msie = ua.indexOf('MSIE ');
+ if (msie > 0) {
+ // IE 10 or older => return version number
+ return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
+ }
+
+ var trident = ua.indexOf('Trident/');
+ if (trident > 0) {
+ // IE 11 => return version number
+ var rv = ua.indexOf('rv:');
+ return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
+ }
+
+ var edge = ua.indexOf('Edge/');
+ if (edge > 0) {
+ // IE 12 => return version number
+ return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
+ }
+
+ // other browser
+ return false;
+ }
+});
+
+L.Map.addInitHook('addHandler', 'touchExtend', L.Map.TouchExtend);
+
+
+/**
+ * @class L.Marker.Touch
+ * @aka Marker.Touch
+ *
+ * This isn't full Touch support. This is just to get markers to also support dom touch events after creation
+ * #TODO: find a better way of getting markers to support touch.
+ */
+L.Marker.Touch = L.Marker.extend({
+
+ _initInteraction: function () {
+ if (!this.addInteractiveTarget) {
+ // 0.7.x support
+ return this._initInteractionLegacy();
+ }
+ // TODO this may need be updated to re-add touch events for 1.0+
+ return L.Marker.prototype._initInteraction.apply(this);
+ },
+
+ // This is an exact copy of https://github.com/Leaflet/Leaflet/blob/v0.7/src/layer/marker/Marker.js
+ // with the addition of the touch events
+ _initInteractionLegacy: function () {
+
+ if (!this.options.clickable) {
+ return;
+ }
+
+ // TODO refactor into something shared with Map/Path/etc. to DRY it up
+
+ var icon = this._icon,
+ events = ['dblclick',
+ 'mousedown',
+ 'mouseover',
+ 'mouseout',
+ 'contextmenu',
+ 'touchstart',
+ 'touchend',
+ 'touchmove'];
+ if (this._detectIE) {
+ events.concat(['MSPointerDown',
+ 'MSPointerUp',
+ 'MSPointerMove',
+ 'MSPointerCancel']);
+ } else {
+ events.concat(['touchcancel']);
+ }
+
+ L.DomUtil.addClass(icon, 'leaflet-clickable');
+ L.DomEvent.on(icon, 'click', this._onMouseClick, this);
+ L.DomEvent.on(icon, 'keypress', this._onKeyPress, this);
+
+ for (var i = 0; i < events.length; i++) {
+ L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
+ }
+
+ if (L.Handler.MarkerDrag) {
+ this.dragging = new L.Handler.MarkerDrag(this);
+
+ if (this.options.draggable) {
+ this.dragging.enable();
+ }
+ }
+ },
+
+ _detectIE: function () {
+ var ua = window.navigator.userAgent;
+
+ var msie = ua.indexOf('MSIE ');
+ if (msie > 0) {
+ // IE 10 or older => return version number
+ return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
+ }
+
+ var trident = ua.indexOf('Trident/');
+ if (trident > 0) {
+ // IE 11 => return version number
+ var rv = ua.indexOf('rv:');
+ return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
+ }
+
+ var edge = ua.indexOf('Edge/');
+ if (edge > 0) {
+ // IE 12 => return version number
+ return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
+ }
+
+ // other browser
+ return false;
+ }
+});
+
+
+
+/**
+ * @class L.LatLngUtil
+ * @aka LatLngUtil
+ */
+L.LatLngUtil = {
+ // Clones a LatLngs[], returns [][]
+
+ // @method cloneLatLngs(LatLngs[]): L.LatLngs[]
+ // Clone the latLng point or points or nested points and return an array with those points
+ cloneLatLngs: function (latlngs) {
+ var clone = [];
+ for (var i = 0, l = latlngs.length; i < l; i++) {
+ // Check for nested array (Polyline/Polygon)
+ if (Array.isArray(latlngs[i])) {
+ clone.push(L.LatLngUtil.cloneLatLngs(latlngs[i]));
+ } else {
+ clone.push(this.cloneLatLng(latlngs[i]));
+ }
+ }
+ return clone;
+ },
+
+ // @method cloneLatLng(LatLng): L.LatLng
+ // Clone the latLng and return a new LatLng object.
+ cloneLatLng: function (latlng) {
+ return L.latLng(latlng.lat, latlng.lng);
+ }
+};
+
+
+
+(function () {
+
+ var defaultPrecision = {
+ km: 2,
+ ha: 2,
+ m: 0,
+ mi: 2,
+ ac: 2,
+ yd: 0,
+ ft: 0,
+ nm: 2
+ };
+
+
+ /**
+ * @class L.GeometryUtil
+ * @aka GeometryUtil
+ */
+ L.GeometryUtil = L.extend(L.GeometryUtil || {}, {
+ // Ported from the OpenLayers implementation. See https://github.com/openlayers/openlayers/blob/master/lib/OpenLayers/Geometry/LinearRing.js#L270
+
+ // @method geodesicArea(): number
+ geodesicArea: function (latLngs) {
+ var pointsCount = latLngs.length,
+ area = 0.0,
+ d2r = Math.PI / 180,
+ p1, p2;
+
+ if (pointsCount > 2) {
+ for (var i = 0; i < pointsCount; i++) {
+ p1 = latLngs[i];
+ p2 = latLngs[(i + 1) % pointsCount];
+ area += ((p2.lng - p1.lng) * d2r) *
+ (2 + Math.sin(p1.lat * d2r) + Math.sin(p2.lat * d2r));
+ }
+ area = area * 6378137.0 * 6378137.0 / 2.0;
+ }
+
+ return Math.abs(area);
+ },
+
+ // @method formattedNumber(n, precision): string
+ // Returns n in specified number format (if defined) and precision
+ formattedNumber: function (n, precision) {
+ var formatted = parseFloat(n).toFixed(precision),
+ format = L.drawLocal.format && L.drawLocal.format.numeric,
+ delimiters = format && format.delimiters,
+ thousands = delimiters && delimiters.thousands,
+ decimal = delimiters && delimiters.decimal;
+
+ if (thousands || decimal) {
+ var splitValue = formatted.split('.');
+ formatted = thousands ? splitValue[0].replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1' + thousands) : splitValue[0];
+ decimal = decimal || '.';
+ if (splitValue.length > 1) {
+ formatted = formatted + decimal + splitValue[1];
+ }
+ }
+
+ return formatted;
+ },
+
+ // @method readableArea(area, isMetric, precision): string
+ // Returns a readable area string in yards or metric.
+ // The value will be rounded as defined by the precision option object.
+ readableArea: function (area, isMetric, precision) {
+ var areaStr,
+ units,
+ precision = L.Util.extend({}, defaultPrecision, precision);
+
+ if (isMetric) {
+ units = ['ha', 'm'];
+ type = typeof isMetric;
+ if (type === 'string') {
+ units = [isMetric];
+ } else if (type !== 'boolean') {
+ units = isMetric;
+ }
+
+ if (area >= 1000000 && units.indexOf('km') !== -1) {
+ areaStr = L.GeometryUtil.formattedNumber(area * 0.000001, precision['km']) + ' km²';
+ } else if (area >= 10000 && units.indexOf('ha') !== -1) {
+ areaStr = L.GeometryUtil.formattedNumber(area * 0.0001, precision['ha']) + ' ha';
+ } else {
+ areaStr = L.GeometryUtil.formattedNumber(area, precision['m']) + ' m²';
+ }
+ } else {
+ area /= 0.836127; // Square yards in 1 meter
+
+ if (area >= 3097600) { //3097600 square yards in 1 square mile
+ areaStr = L.GeometryUtil.formattedNumber(area / 3097600, precision['mi']) + ' mi²';
+ } else if (area >= 4840) { //4840 square yards in 1 acre
+ areaStr = L.GeometryUtil.formattedNumber(area / 4840, precision['ac']) + ' acres';
+ } else {
+ areaStr = L.GeometryUtil.formattedNumber(area, precision['yd']) + ' yd²';
+ }
+ }
+
+ return areaStr;
+ },
+
+ // @method readableDistance(distance, units): string
+ // Converts a metric distance to one of [ feet, nauticalMile, metric or yards ] string
+ //
+ // @alternative
+ // @method readableDistance(distance, isMetric, useFeet, isNauticalMile, precision): string
+ // Converts metric distance to distance string.
+ // The value will be rounded as defined by the precision option object.
+ readableDistance: function (distance, isMetric, isFeet, isNauticalMile, precision) {
+ var distanceStr,
+ units,
+ precision = L.Util.extend({}, defaultPrecision, precision);
+
+ if (isMetric) {
+ units = typeof isMetric == 'string' ? isMetric : 'metric';
+ } else if (isFeet) {
+ units = 'feet';
+ } else if (isNauticalMile) {
+ units = 'nauticalMile';
+ } else {
+ units = 'yards';
+ }
+
+ switch (units) {
+ case 'metric':
+ // show metres when distance is < 1km, then show km
+ if (distance > 1000) {
+ distanceStr = L.GeometryUtil.formattedNumber(distance / 1000, precision['km']) + ' km';
+ } else {
+ distanceStr = L.GeometryUtil.formattedNumber(distance, precision['m']) + ' m';
+ }
+ break;
+ case 'feet':
+ distance *= 1.09361 * 3;
+ distanceStr = L.GeometryUtil.formattedNumber(distance, precision['ft']) + ' ft';
+
+ break;
+ case 'nauticalMile':
+ distance *= 0.53996;
+ distanceStr = L.GeometryUtil.formattedNumber(distance / 1000, precision['nm']) + ' nm';
+ break;
+ case 'yards':
+ default:
+ distance *= 1.09361;
+
+ if (distance > 1760) {
+ distanceStr = L.GeometryUtil.formattedNumber(distance / 1760, precision['mi']) + ' miles';
+ } else {
+ distanceStr = L.GeometryUtil.formattedNumber(distance, precision['yd']) + ' yd';
+ }
+ break;
+ }
+ return distanceStr;
+ },
+
+ // @method isVersion07x(): boolean
+ // Returns true if the Leaflet version is 0.7.x, false otherwise.
+ isVersion07x: function () {
+ var version = L.version.split('.');
+ //If Version is == 0.7.*
+ return parseInt(version[0], 10) === 0 && parseInt(version[1], 10) === 7;
+ },
+ });
+
+})();
+
+
+
+/**
+ * @class L.LineUtil
+ * @aka Util
+ * @aka L.Utils
+ */
+L.Util.extend(L.LineUtil, {
+
+ // @method segmentsIntersect(): boolean
+ // Checks to see if two line segments intersect. Does not handle degenerate cases.
+ // http://compgeom.cs.uiuc.edu/~jeffe/teaching/373/notes/x06-sweepline.pdf
+ segmentsIntersect: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2, /*Point*/ p3) {
+ return this._checkCounterclockwise(p, p2, p3) !==
+ this._checkCounterclockwise(p1, p2, p3) &&
+ this._checkCounterclockwise(p, p1, p2) !==
+ this._checkCounterclockwise(p, p1, p3);
+ },
+
+ // check to see if points are in counterclockwise order
+ _checkCounterclockwise: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
+ return (p2.y - p.y) * (p1.x - p.x) > (p1.y - p.y) * (p2.x - p.x);
+ }
+});
+
+
+
+/**
+ * @class L.Polyline
+ * @aka Polyline
+ */
+L.Polyline.include({
+
+ // @method intersects(): boolean
+ // Check to see if this polyline has any linesegments that intersect.
+ // NOTE: does not support detecting intersection for degenerate cases.
+ intersects: function () {
+ var points = this._getProjectedPoints(),
+ len = points ? points.length : 0,
+ i, p, p1;
+
+ if (this._tooFewPointsForIntersection()) {
+ return false;
+ }
+
+ for (i = len - 1; i >= 3; i--) {
+ p = points[i - 1];
+ p1 = points[i];
+
+
+ if (this._lineSegmentsIntersectsRange(p, p1, i - 2)) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ // @method newLatLngIntersects(): boolean
+ // Check for intersection if new latlng was added to this polyline.
+ // NOTE: does not support detecting intersection for degenerate cases.
+ newLatLngIntersects: function (latlng, skipFirst) {
+ // Cannot check a polyline for intersecting lats/lngs when not added to the map
+ if (!this._map) {
+ return false;
+ }
+
+ return this.newPointIntersects(this._map.latLngToLayerPoint(latlng), skipFirst);
+ },
+
+ // @method newPointIntersects(): boolean
+ // Check for intersection if new point was added to this polyline.
+ // newPoint must be a layer point.
+ // NOTE: does not support detecting intersection for degenerate cases.
+ newPointIntersects: function (newPoint, skipFirst) {
+ var points = this._getProjectedPoints(),
+ len = points ? points.length : 0,
+ lastPoint = points ? points[len - 1] : null,
+ // The previous previous line segment. Previous line segment doesn't need testing.
+ maxIndex = len - 2;
+
+ if (this._tooFewPointsForIntersection(1)) {
+ return false;
+ }
+
+ return this._lineSegmentsIntersectsRange(lastPoint, newPoint, maxIndex, skipFirst ? 1 : 0);
+ },
+
+ // Polylines with 2 sides can only intersect in cases where points are collinear (we don't support detecting these).
+ // Cannot have intersection when < 3 line segments (< 4 points)
+ _tooFewPointsForIntersection: function (extraPoints) {
+ var points = this._getProjectedPoints(),
+ len = points ? points.length : 0;
+ // Increment length by extraPoints if present
+ len += extraPoints || 0;
+
+ return !points || len <= 3;
+ },
+
+ // Checks a line segment intersections with any line segments before its predecessor.
+ // Don't need to check the predecessor as will never intersect.
+ _lineSegmentsIntersectsRange: function (p, p1, maxIndex, minIndex) {
+ var points = this._getProjectedPoints(),
+ p2, p3;
+
+ minIndex = minIndex || 0;
+
+ // Check all previous line segments (beside the immediately previous) for intersections
+ for (var j = maxIndex; j > minIndex; j--) {
+ p2 = points[j - 1];
+ p3 = points[j];
+
+ if (L.LineUtil.segmentsIntersect(p, p1, p2, p3)) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ _getProjectedPoints: function () {
+ if (!this._defaultShape) {
+ return this._originalPoints;
+ }
+ var points = [],
+ _shape = this._defaultShape();
+
+ for (var i = 0; i < _shape.length; i++) {
+ points.push(this._map.latLngToLayerPoint(_shape[i]));
+ }
+ return points;
+ }
+});
+
+
+
+/**
+ * @class L.Polygon
+ * @aka Polygon
+ */
+L.Polygon.include({
+
+ // @method intersects(): boolean
+ // Checks a polygon for any intersecting line segments. Ignores holes.
+ intersects: function () {
+ var polylineIntersects,
+ points = this._getProjectedPoints(),
+ len, firstPoint, lastPoint, maxIndex;
+
+ if (this._tooFewPointsForIntersection()) {
+ return false;
+ }
+
+ polylineIntersects = L.Polyline.prototype.intersects.call(this);
+
+ // If already found an intersection don't need to check for any more.
+ if (polylineIntersects) {
+ return true;
+ }
+
+ len = points.length;
+ firstPoint = points[0];
+ lastPoint = points[len - 1];
+ maxIndex = len - 2;
+
+ // Check the line segment between last and first point. Don't need to check the first line segment (minIndex = 1)
+ return this._lineSegmentsIntersectsRange(lastPoint, firstPoint, maxIndex, 1);
+ }
+});
+
+
+
+/**
+ * @class L.Control.Draw
+ * @aka L.Draw
+ */
+L.Control.Draw = L.Control.extend({
+
+ // Options
+ options: {
+ position: 'topleft',
+ draw: {},
+ edit: false
+ },
+
+ // @method initialize(): void
+ // Initializes draw control, toolbars from the options
+ initialize: function (options) {
+ if (L.version < '0.7') {
+ throw new Error('Leaflet.draw 0.2.3+ requires Leaflet 0.7.0+. Download latest from https://github.com/Leaflet/Leaflet/');
+ }
+
+ L.Control.prototype.initialize.call(this, options);
+
+ var toolbar;
+
+ this._toolbars = {};
+
+ // Initialize toolbars
+ if (L.DrawToolbar && this.options.draw) {
+ toolbar = new L.DrawToolbar(this.options.draw);
+
+ this._toolbars[L.DrawToolbar.TYPE] = toolbar;
+
+ // Listen for when toolbar is enabled
+ this._toolbars[L.DrawToolbar.TYPE].on('enable', this._toolbarEnabled, this);
+ }
+
+ if (L.EditToolbar && this.options.edit) {
+ toolbar = new L.EditToolbar(this.options.edit);
+
+ this._toolbars[L.EditToolbar.TYPE] = toolbar;
+
+ // Listen for when toolbar is enabled
+ this._toolbars[L.EditToolbar.TYPE].on('enable', this._toolbarEnabled, this);
+ }
+ L.toolbar = this; //set global var for editing the toolbar
+ },
+
+ // @method onAdd(): container
+ // Adds the toolbar container to the map
+ onAdd: function (map) {
+ var container = L.DomUtil.create('div', 'leaflet-draw'),
+ addedTopClass = false,
+ topClassName = 'leaflet-draw-toolbar-top',
+ toolbarContainer;
+
+ for (var toolbarId in this._toolbars) {
+ if (this._toolbars.hasOwnProperty(toolbarId)) {
+ toolbarContainer = this._toolbars[toolbarId].addToolbar(map);
+
+ if (toolbarContainer) {
+ // Add class to the first toolbar to remove the margin
+ if (!addedTopClass) {
+ if (!L.DomUtil.hasClass(toolbarContainer, topClassName)) {
+ L.DomUtil.addClass(toolbarContainer.childNodes[0], topClassName);
+ }
+ addedTopClass = true;
+ }
+
+ container.appendChild(toolbarContainer);
+ }
+ }
+ }
+
+ return container;
+ },
+
+ // @method onRemove(): void
+ // Removes the toolbars from the map toolbar container
+ onRemove: function () {
+ for (var toolbarId in this._toolbars) {
+ if (this._toolbars.hasOwnProperty(toolbarId)) {
+ this._toolbars[toolbarId].removeToolbar();
+ }
+ }
+ },
+
+ // @method setDrawingOptions(options): void
+ // Sets options to all toolbar instances
+ setDrawingOptions: function (options) {
+ for (var toolbarId in this._toolbars) {
+ if (this._toolbars[toolbarId] instanceof L.DrawToolbar) {
+ this._toolbars[toolbarId].setOptions(options);
+ }
+ }
+ },
+
+ _toolbarEnabled: function (e) {
+ var enabledToolbar = e.target;
+
+ for (var toolbarId in this._toolbars) {
+ if (this._toolbars[toolbarId] !== enabledToolbar) {
+ this._toolbars[toolbarId].disable();
+ }
+ }
+ }
+});
+
+L.Map.mergeOptions({
+ drawControlTooltips: true,
+ drawControl: false
+});
+
+L.Map.addInitHook(function () {
+ if (this.options.drawControl) {
+ this.drawControl = new L.Control.Draw();
+ this.addControl(this.drawControl);
+ }
+});
+
+
+
+/**
+ * @class L.Draw.Toolbar
+ * @aka Toolbar
+ *
+ * The toolbar class of the API — it is used to create the ui
+ * This will be depreciated
+ *
+ * @example
+ *
+ * ```js
+ * var toolbar = L.Toolbar();
+ * toolbar.addToolbar(map);
+ * ```
+ *
+ * ### Disabling a toolbar
+ *
+ * If you do not want a particular toolbar in your app you can turn it off by setting the toolbar to false.
+ *
+ * ```js
+ * var drawControl = new L.Control.Draw({
+ * draw: false,
+ * edit: {
+ * featureGroup: editableLayers
+ * }
+ * });
+ * ```
+ *
+ * ### Disabling a toolbar item
+ *
+ * If you want to turn off a particular toolbar item, set it to false. The following disables drawing polygons and
+ * markers. It also turns off the ability to edit layers.
+ *
+ * ```js
+ * var drawControl = new L.Control.Draw({
+ * draw: {
+ * polygon: false,
+ * marker: false
+ * },
+ * edit: {
+ * featureGroup: editableLayers,
+ * edit: false
+ * }
+ * });
+ * ```
+ */
+L.Toolbar = L.Class.extend({
+ // @section Methods for modifying the toolbar
+
+ // @method initialize(options): void
+ // Toolbar constructor
+ initialize: function (options) {
+ L.setOptions(this, options);
+
+ this._modes = {};
+ this._actionButtons = [];
+ this._activeMode = null;
+
+ var version = L.version.split('.');
+ //If Version is >= 1.2.0
+ if (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) {
+ L.Toolbar.include(L.Evented.prototype);
+ } else {
+ L.Toolbar.include(L.Mixin.Events);
+ }
+ },
+
+ // @method enabled(): boolean
+ // Gets a true/false of whether the toolbar is enabled
+ enabled: function () {
+ return this._activeMode !== null;
+ },
+
+ // @method disable(): void
+ // Disables the toolbar
+ disable: function () {
+ if (!this.enabled()) {
+ return;
+ }
+
+ this._activeMode.handler.disable();
+ },
+
+ // @method addToolbar(map): L.DomUtil
+ // Adds the toolbar to the map and returns the toolbar dom element
+ addToolbar: function (map) {
+ var container = L.DomUtil.create('div', 'leaflet-draw-section'),
+ buttonIndex = 0,
+ buttonClassPrefix = this._toolbarClass || '',
+ modeHandlers = this.getModeHandlers(map),
+ i;
+
+ this._toolbarContainer = L.DomUtil.create('div', 'leaflet-draw-toolbar leaflet-bar');
+ this._map = map;
+
+ for (i = 0; i < modeHandlers.length; i++) {
+ if (modeHandlers[i].enabled) {
+ this._initModeHandler(
+ modeHandlers[i].handler,
+ this._toolbarContainer,
+ buttonIndex++,
+ buttonClassPrefix,
+ modeHandlers[i].title
+ );
+ }
+ }
+
+ // if no buttons were added, do not add the toolbar
+ if (!buttonIndex) {
+ return;
+ }
+
+ // Save button index of the last button, -1 as we would have ++ after the last button
+ this._lastButtonIndex = --buttonIndex;
+
+ // Create empty actions part of the toolbar
+ this._actionsContainer = L.DomUtil.create('ul', 'leaflet-draw-actions');
+
+ // Add draw and cancel containers to the control container
+ container.appendChild(this._toolbarContainer);
+ container.appendChild(this._actionsContainer);
+
+ return container;
+ },
+
+ // @method removeToolbar(): void
+ // Removes the toolbar and drops the handler event listeners
+ removeToolbar: function () {
+ // Dispose each handler
+ for (var handlerId in this._modes) {
+ if (this._modes.hasOwnProperty(handlerId)) {
+ // Unbind handler button
+ this._disposeButton(
+ this._modes[handlerId].button,
+ this._modes[handlerId].handler.enable,
+ this._modes[handlerId].handler
+ );
+
+ // Make sure is disabled
+ this._modes[handlerId].handler.disable();
+
+ // Unbind handler
+ this._modes[handlerId].handler
+ .off('enabled', this._handlerActivated, this)
+ .off('disabled', this._handlerDeactivated, this);
+ }
+ }
+ this._modes = {};
+
+ // Dispose the actions toolbar
+ for (var i = 0, l = this._actionButtons.length; i < l; i++) {
+ this._disposeButton(
+ this._actionButtons[i].button,
+ this._actionButtons[i].callback,
+ this
+ );
+ }
+ this._actionButtons = [];
+ this._actionsContainer = null;
+ },
+
+ _initModeHandler: function (handler, container, buttonIndex, classNamePredix, buttonTitle) {
+ var type = handler.type;
+
+ this._modes[type] = {};
+
+ this._modes[type].handler = handler;
+
+ this._modes[type].button = this._createButton({
+ type: type,
+ title: buttonTitle,
+ className: classNamePredix + '-' + type,
+ container: container,
+ callback: this._modes[type].handler.enable,
+ context: this._modes[type].handler
+ });
+
+ this._modes[type].buttonIndex = buttonIndex;
+
+ this._modes[type].handler
+ .on('enabled', this._handlerActivated, this)
+ .on('disabled', this._handlerDeactivated, this);
+ },
+
+ /* Detect iOS based on browser User Agent, based on:
+ * http://stackoverflow.com/a/9039885 */
+ _detectIOS: function () {
+ var iOS = (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream);
+ return iOS;
+ },
+
+ _createButton: function (options) {
+
+ var link = L.DomUtil.create('a', options.className || '', options.container);
+ // Screen reader tag
+ var sr = L.DomUtil.create('span', 'sr-only', options.container);
+
+ link.href = '#';
+ link.appendChild(sr);
+
+ if (options.title) {
+ link.title = options.title;
+ sr.innerHTML = options.title;
+ }
+
+ if (options.text) {
+ link.innerHTML = options.text;
+ sr.innerHTML = options.text;
+ }
+
+ /* iOS does not use click events */
+ var buttonEvent = this._detectIOS() ? 'touchstart' : 'click';
+
+ L.DomEvent
+ .on(link, 'click', L.DomEvent.stopPropagation)
+ .on(link, 'mousedown', L.DomEvent.stopPropagation)
+ .on(link, 'dblclick', L.DomEvent.stopPropagation)
+ .on(link, 'touchstart', L.DomEvent.stopPropagation)
+ .on(link, 'click', L.DomEvent.preventDefault)
+ .on(link, buttonEvent, options.callback, options.context);
+
+ return link;
+ },
+
+ _disposeButton: function (button, callback) {
+ /* iOS does not use click events */
+ var buttonEvent = this._detectIOS() ? 'touchstart' : 'click';
+
+ L.DomEvent
+ .off(button, 'click', L.DomEvent.stopPropagation)
+ .off(button, 'mousedown', L.DomEvent.stopPropagation)
+ .off(button, 'dblclick', L.DomEvent.stopPropagation)
+ .off(button, 'touchstart', L.DomEvent.stopPropagation)
+ .off(button, 'click', L.DomEvent.preventDefault)
+ .off(button, buttonEvent, callback);
+ },
+
+ _handlerActivated: function (e) {
+ // Disable active mode (if present)
+ this.disable();
+
+ // Cache new active feature
+ this._activeMode = this._modes[e.handler];
+
+ L.DomUtil.addClass(this._activeMode.button, 'leaflet-draw-toolbar-button-enabled');
+
+ this._showActionsToolbar();
+
+ this.fire('enable');
+ },
+
+ _handlerDeactivated: function () {
+ this._hideActionsToolbar();
+
+ L.DomUtil.removeClass(this._activeMode.button, 'leaflet-draw-toolbar-button-enabled');
+
+ this._activeMode = null;
+
+ this.fire('disable');
+ },
+
+ _createActions: function (handler) {
+ var container = this._actionsContainer,
+ buttons = this.getActions(handler),
+ l = buttons.length,
+ li, di, dl, button;
+
+ // Dispose the actions toolbar (todo: dispose only not used buttons)
+ for (di = 0, dl = this._actionButtons.length; di < dl; di++) {
+ this._disposeButton(this._actionButtons[di].button, this._actionButtons[di].callback);
+ }
+ this._actionButtons = [];
+
+ // Remove all old buttons
+ while (container.firstChild) {
+ container.removeChild(container.firstChild);
+ }
+
+ for (var i = 0; i < l; i++) {
+ if ('enabled' in buttons[i] && !buttons[i].enabled) {
+ continue;
+ }
+
+ li = L.DomUtil.create('li', '', container);
+
+ button = this._createButton({
+ title: buttons[i].title,
+ text: buttons[i].text,
+ container: li,
+ callback: buttons[i].callback,
+ context: buttons[i].context
+ });
+
+ this._actionButtons.push({
+ button: button,
+ callback: buttons[i].callback
+ });
+ }
+ },
+
+ _showActionsToolbar: function () {
+ var buttonIndex = this._activeMode.buttonIndex,
+ lastButtonIndex = this._lastButtonIndex,
+ toolbarPosition = this._activeMode.button.offsetTop - 1;
+
+ // Recreate action buttons on every click
+ this._createActions(this._activeMode.handler);
+
+ // Correctly position the cancel button
+ this._actionsContainer.style.top = toolbarPosition + 'px';
+
+ if (buttonIndex === 0) {
+ L.DomUtil.addClass(this._toolbarContainer, 'leaflet-draw-toolbar-notop');
+ L.DomUtil.addClass(this._actionsContainer, 'leaflet-draw-actions-top');
+ }
+
+ if (buttonIndex === lastButtonIndex) {
+ L.DomUtil.addClass(this._toolbarContainer, 'leaflet-draw-toolbar-nobottom');
+ L.DomUtil.addClass(this._actionsContainer, 'leaflet-draw-actions-bottom');
+ }
+
+ this._actionsContainer.style.display = 'block';
+ this._map.fire(L.Draw.Event.TOOLBAROPENED);
+ },
+
+ _hideActionsToolbar: function () {
+ this._actionsContainer.style.display = 'none';
+
+ L.DomUtil.removeClass(this._toolbarContainer, 'leaflet-draw-toolbar-notop');
+ L.DomUtil.removeClass(this._toolbarContainer, 'leaflet-draw-toolbar-nobottom');
+ L.DomUtil.removeClass(this._actionsContainer, 'leaflet-draw-actions-top');
+ L.DomUtil.removeClass(this._actionsContainer, 'leaflet-draw-actions-bottom');
+ this._map.fire(L.Draw.Event.TOOLBARCLOSED);
+ }
+});
+
+
+
+L.Draw = L.Draw || {};
+/**
+ * @class L.Draw.Tooltip
+ * @aka Tooltip
+ *
+ * The tooltip class — it is used to display the tooltip while drawing
+ * This will be depreciated
+ *
+ * @example
+ *
+ * ```js
+ * var tooltip = L.Draw.Tooltip();
+ * ```
+ *
+ */
+L.Draw.Tooltip = L.Class.extend({
+
+ // @section Methods for modifying draw state
+
+ // @method initialize(map): void
+ // Tooltip constructor
+ initialize: function (map) {
+ this._map = map;
+ this._popupPane = map._panes.popupPane;
+ this._visible = false;
+
+ this._container = map.options.drawControlTooltips ?
+ L.DomUtil.create('div', 'leaflet-draw-tooltip', this._popupPane) : null;
+ this._singleLineLabel = false;
+
+ this._map.on('mouseout', this._onMouseOut, this);
+ },
+
+ // @method dispose(): void
+ // Remove Tooltip DOM and unbind events
+ dispose: function () {
+ this._map.off('mouseout', this._onMouseOut, this);
+
+ if (this._container) {
+ this._popupPane.removeChild(this._container);
+ this._container = null;
+ }
+ },
+
+ // @method updateContent(labelText): this
+ // Changes the tooltip text to string in function call
+ updateContent: function (labelText) {
+ if (!this._container) {
+ return this;
+ }
+ labelText.subtext = labelText.subtext || '';
+
+ // update the vertical position (only if changed)
+ if (labelText.subtext.length === 0 && !this._singleLineLabel) {
+ L.DomUtil.addClass(this._container, 'leaflet-draw-tooltip-single');
+ this._singleLineLabel = true;
+ }
+ else if (labelText.subtext.length > 0 && this._singleLineLabel) {
+ L.DomUtil.removeClass(this._container, 'leaflet-draw-tooltip-single');
+ this._singleLineLabel = false;
+ }
+
+ this._container.innerHTML =
+ (labelText.subtext.length > 0 ?
+ '<span class="leaflet-draw-tooltip-subtext">' + labelText.subtext + '</span>' + '<br />' : '') +
+ '<span>' + labelText.text + '</span>';
+
+ if (!labelText.text && !labelText.subtext) {
+ this._visible = false;
+ this._container.style.visibility = 'hidden';
+ } else {
+ this._visible = true;
+ this._container.style.visibility = 'inherit';
+ }
+
+ return this;
+ },
+
+ // @method updatePosition(latlng): this
+ // Changes the location of the tooltip
+ updatePosition: function (latlng) {
+ var pos = this._map.latLngToLayerPoint(latlng),
+ tooltipContainer = this._container;
+
+ if (this._container) {
+ if (this._visible) {
+ tooltipContainer.style.visibility = 'inherit';
+ }
+ L.DomUtil.setPosition(tooltipContainer, pos);
+ }
+
+ return this;
+ },
+
+ // @method showAsError(): this
+ // Applies error class to tooltip
+ showAsError: function () {
+ if (this._container) {
+ L.DomUtil.addClass(this._container, 'leaflet-error-draw-tooltip');
+ }
+ return this;
+ },
+
+ // @method removeError(): this
+ // Removes the error class from the tooltip
+ removeError: function () {
+ if (this._container) {
+ L.DomUtil.removeClass(this._container, 'leaflet-error-draw-tooltip');
+ }
+ return this;
+ },
+
+ _onMouseOut: function () {
+ if (this._container) {
+ this._container.style.visibility = 'hidden';
+ }
+ }
+});
+
+
+
+/**
+ * @class L.DrawToolbar
+ * @aka Toolbar
+ */
+L.DrawToolbar = L.Toolbar.extend({
+
+ statics: {
+ TYPE: 'draw'
+ },
+
+ options: {
+ polyline: {},
+ polygon: {},
+ rectangle: {},
+ circle: {},
+ marker: {},
+ circlemarker: {}
+ },
+
+ // @method initialize(): void
+ initialize: function (options) {
+ // Ensure that the options are merged correctly since L.extend is only shallow
+ for (var type in this.options) {
+ if (this.options.hasOwnProperty(type)) {
+ if (options[type]) {
+ options[type] = L.extend({}, this.options[type], options[type]);
+ }
+ }
+ }
+
+ this._toolbarClass = 'leaflet-draw-draw';
+ L.Toolbar.prototype.initialize.call(this, options);
+ },
+
+ // @method getModeHandlers(): object
+ // Get mode handlers information
+ getModeHandlers: function (map) {
+ return [
+ {
+ enabled: this.options.polyline,
+ handler: new L.Draw.Polyline(map, this.options.polyline),
+ title: L.drawLocal.draw.toolbar.buttons.polyline
+ },
+ {
+ enabled: this.options.polygon,
+ handler: new L.Draw.Polygon(map, this.options.polygon),
+ title: L.drawLocal.draw.toolbar.buttons.polygon
+ },
+ {
+ enabled: this.options.rectangle,
+ handler: new L.Draw.Rectangle(map, this.options.rectangle),
+ title: L.drawLocal.draw.toolbar.buttons.rectangle
+ },
+ {
+ enabled: this.options.circle,
+ handler: new L.Draw.Circle(map, this.options.circle),
+ title: L.drawLocal.draw.toolbar.buttons.circle
+ },
+ {
+ enabled: this.options.marker,
+ handler: new L.Draw.Marker(map, this.options.marker),
+ title: L.drawLocal.draw.toolbar.buttons.marker
+ },
+ {
+ enabled: this.options.circlemarker,
+ handler: new L.Draw.CircleMarker(map, this.options.circlemarker),
+ title: L.drawLocal.draw.toolbar.buttons.circlemarker
+ }
+ ];
+ },
+
+ // @method getActions(): object
+ // Get action information
+ getActions: function (handler) {
+ return [
+ {
+ enabled: handler.completeShape,
+ title: L.drawLocal.draw.toolbar.finish.title,
+ text: L.drawLocal.draw.toolbar.finish.text,
+ callback: handler.completeShape,
+ context: handler
+ },
+ {
+ enabled: handler.deleteLastVertex,
+ title: L.drawLocal.draw.toolbar.undo.title,
+ text: L.drawLocal.draw.toolbar.undo.text,
+ callback: handler.deleteLastVertex,
+ context: handler
+ },
+ {
+ title: L.drawLocal.draw.toolbar.actions.title,
+ text: L.drawLocal.draw.toolbar.actions.text,
+ callback: this.disable,
+ context: this
+ }
+ ];
+ },
+
+ // @method setOptions(): void
+ // Sets the options to the toolbar
+ setOptions: function (options) {
+ L.setOptions(this, options);
+
+ for (var type in this._modes) {
+ if (this._modes.hasOwnProperty(type) && options.hasOwnProperty(type)) {
+ this._modes[type].handler.setOptions(options[type]);
+ }
+ }
+ }
+});
+
+
+
+/*L.Map.mergeOptions({
+ editControl: true
+ });*/
+/**
+ * @class L.EditToolbar
+ * @aka EditToolbar
+ */
+L.EditToolbar = L.Toolbar.extend({
+ statics: {
+ TYPE: 'edit'
+ },
+
+ options: {
+ edit: {
+ selectedPathOptions: {
+ dashArray: '10, 10',
+
+ fill: true,
+ fillColor: '#fe57a1',
+ fillOpacity: 0.1,
+
+ // Whether to user the existing layers color
+ maintainColor: false
+ }
+ },
+ remove: {},
+ poly: null,
+ featureGroup: null /* REQUIRED! TODO: perhaps if not set then all layers on the map are selectable? */
+ },
+
+ // @method intialize(): void
+ initialize: function (options) {
+ // Need to set this manually since null is an acceptable value here
+ if (options.edit) {
+ if (typeof options.edit.selectedPathOptions === 'undefined') {
+ options.edit.selectedPathOptions = this.options.edit.selectedPathOptions;
+ }
+ options.edit.selectedPathOptions = L.extend({}, this.options.edit.selectedPathOptions, options.edit.selectedPathOptions);
+ }
+
+ if (options.remove) {
+ options.remove = L.extend({}, this.options.remove, options.remove);
+ }
+
+ if (options.poly) {
+ options.poly = L.extend({}, this.options.poly, options.poly);
+ }
+
+ this._toolbarClass = 'leaflet-draw-edit';
+ L.Toolbar.prototype.initialize.call(this, options);
+
+ this._selectedFeatureCount = 0;
+ },
+
+ // @method getModeHandlers(): object
+ // Get mode handlers information
+ getModeHandlers: function (map) {
+ var featureGroup = this.options.featureGroup;
+ return [
+ {
+ enabled: this.options.edit,
+ handler: new L.EditToolbar.Edit(map, {
+ featureGroup: featureGroup,
+ selectedPathOptions: this.options.edit.selectedPathOptions,
+ poly: this.options.poly
+ }),
+ title: L.drawLocal.edit.toolbar.buttons.edit
+ },
+ {
+ enabled: this.options.remove,
+ handler: new L.EditToolbar.Delete(map, {
+ featureGroup: featureGroup
+ }),
+ title: L.drawLocal.edit.toolbar.buttons.remove
+ }
+ ];
+ },
+
+ // @method getActions(): object
+ // Get actions information
+ getActions: function (handler) {
+ var actions = [
+ {
+ title: L.drawLocal.edit.toolbar.actions.save.title,
+ text: L.drawLocal.edit.toolbar.actions.save.text,
+ callback: this._save,
+ context: this
+ },
+ {
+ title: L.drawLocal.edit.toolbar.actions.cancel.title,
+ text: L.drawLocal.edit.toolbar.actions.cancel.text,
+ callback: this.disable,
+ context: this
+ }
+ ];
+
+ if (handler.removeAllLayers) {
+ actions.push({
+ title: L.drawLocal.edit.toolbar.actions.clearAll.title,
+ text: L.drawLocal.edit.toolbar.actions.clearAll.text,
+ callback: this._clearAllLayers,
+ context: this
+ });
+ }
+
+ return actions;
+ },
+
+ // @method addToolbar(map): L.DomUtil
+ // Adds the toolbar to the map
+ addToolbar: function (map) {
+ var container = L.Toolbar.prototype.addToolbar.call(this, map);
+
+ this._checkDisabled();
+
+ this.options.featureGroup.on('layeradd layerremove', this._checkDisabled, this);
+
+ return container;
+ },
+
+ // @method removeToolbar(): void
+ // Removes the toolbar from the map
+ removeToolbar: function () {
+ this.options.featureGroup.off('layeradd layerremove', this._checkDisabled, this);
+
+ L.Toolbar.prototype.removeToolbar.call(this);
+ },
+
+ // @method disable(): void
+ // Disables the toolbar
+ disable: function () {
+ if (!this.enabled()) {
+ return;
+ }
+
+ this._activeMode.handler.revertLayers();
+
+ L.Toolbar.prototype.disable.call(this);
+ },
+
+ _save: function () {
+ this._activeMode.handler.save();
+ if (this._activeMode) {
+ this._activeMode.handler.disable();
+ }
+ },
+
+ _clearAllLayers: function () {
+ this._activeMode.handler.removeAllLayers();
+ if (this._activeMode) {
+ this._activeMode.handler.disable();
+ }
+ },
+
+ _checkDisabled: function () {
+ var featureGroup = this.options.featureGroup,
+ hasLayers = featureGroup.getLayers().length !== 0,
+ button;
+
+ if (this.options.edit) {
+ button = this._modes[L.EditToolbar.Edit.TYPE].button;
+
+ if (hasLayers) {
+ L.DomUtil.removeClass(button, 'leaflet-disabled');
+ } else {
+ L.DomUtil.addClass(button, 'leaflet-disabled');
+ }
+
+ button.setAttribute(
+ 'title',
+ hasLayers ?
+ L.drawLocal.edit.toolbar.buttons.edit
+ : L.drawLocal.edit.toolbar.buttons.editDisabled
+ );
+ }
+
+ if (this.options.remove) {
+ button = this._modes[L.EditToolbar.Delete.TYPE].button;
+
+ if (hasLayers) {
+ L.DomUtil.removeClass(button, 'leaflet-disabled');
+ } else {
+ L.DomUtil.addClass(button, 'leaflet-disabled');
+ }
+
+ button.setAttribute(
+ 'title',
+ hasLayers ?
+ L.drawLocal.edit.toolbar.buttons.remove
+ : L.drawLocal.edit.toolbar.buttons.removeDisabled
+ );
+ }
+ }
+});
+
+
+
+/**
+ * @class L.EditToolbar.Edit
+ * @aka EditToolbar.Edit
+ */
+L.EditToolbar.Edit = L.Handler.extend({
+ statics: {
+ TYPE: 'edit'
+ },
+
+ // @method intialize(): void
+ initialize: function (map, options) {
+ L.Handler.prototype.initialize.call(this, map);
+
+ L.setOptions(this, options);
+
+ // Store the selectable layer group for ease of access
+ this._featureGroup = options.featureGroup;
+
+ if (!(this._featureGroup instanceof L.FeatureGroup)) {
+ throw new Error('options.featureGroup must be a L.FeatureGroup');
+ }
+
+ this._uneditedLayerProps = {};
+
+ // Save the type so super can fire, need to do this as cannot do this.TYPE :(
+ this.type = L.EditToolbar.Edit.TYPE;
+
+ var version = L.version.split('.');
+ //If Version is >= 1.2.0
+ if (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) {
+ L.EditToolbar.Edit.include(L.Evented.prototype);
+ } else {
+ L.EditToolbar.Edit.include(L.Mixin.Events);
+ }
+ },
+
+ // @method enable(): void
+ // Enable the edit toolbar
+ enable: function () {
+ if (this._enabled || !this._hasAvailableLayers()) {
+ return;
+ }
+ this.fire('enabled', {handler: this.type});
+ //this disable other handlers
+
+ this._map.fire(L.Draw.Event.EDITSTART, {handler: this.type});
+ //allow drawLayer to be updated before beginning edition.
+
+ L.Handler.prototype.enable.call(this);
+ this._featureGroup
+ .on('layeradd', this._enableLayerEdit, this)
+ .on('layerremove', this._disableLayerEdit, this);
+ },
+
+ // @method disable(): void
+ // Disable the edit toolbar
+ disable: function () {
+ if (!this._enabled) {
+ return;
+ }
+ this._featureGroup
+ .off('layeradd', this._enableLayerEdit, this)
+ .off('layerremove', this._disableLayerEdit, this);
+ L.Handler.prototype.disable.call(this);
+ this._map.fire(L.Draw.Event.EDITSTOP, {handler: this.type});
+ this.fire('disabled', {handler: this.type});
+ },
+
+ // @method addHooks(): void
+ // Add listener hooks for this handler
+ addHooks: function () {
+ var map = this._map;
+
+ if (map) {
+ map.getContainer().focus();
+
+ this._featureGroup.eachLayer(this._enableLayerEdit, this);
+
+ this._tooltip = new L.Draw.Tooltip(this._map);
+ this._tooltip.updateContent({
+ text: L.drawLocal.edit.handlers.edit.tooltip.text,
+ subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext
+ });
+
+ // Quickly access the tooltip to update for intersection checking
+ map._editTooltip = this._tooltip;
+
+ this._updateTooltip();
+
+ this._map
+ .on('mousemove', this._onMouseMove, this)
+ .on('touchmove', this._onMouseMove, this)
+ .on('MSPointerMove', this._onMouseMove, this)
+ .on(L.Draw.Event.EDITVERTEX, this._updateTooltip, this);
+ }
+ },
+
+ // @method removeHooks(): void
+ // Remove listener hooks for this handler
+ removeHooks: function () {
+ if (this._map) {
+ // Clean up selected layers.
+ this._featureGroup.eachLayer(this._disableLayerEdit, this);
+
+ // Clear the backups of the original layers
+ this._uneditedLayerProps = {};
+
+ this._tooltip.dispose();
+ this._tooltip = null;
+
+ this._map
+ .off('mousemove', this._onMouseMove, this)
+ .off('touchmove', this._onMouseMove, this)
+ .off('MSPointerMove', this._onMouseMove, this)
+ .off(L.Draw.Event.EDITVERTEX, this._updateTooltip, this);
+ }
+ },
+
+ // @method revertLayers(): void
+ // Revert each layer's geometry changes
+ revertLayers: function () {
+ this._featureGroup.eachLayer(function (layer) {
+ this._revertLayer(layer);
+ }, this);
+ },
+
+ // @method save(): void
+ // Save the layer geometries
+ save: function () {
+ var editedLayers = new L.LayerGroup();
+ this._featureGroup.eachLayer(function (layer) {
+ if (layer.edited) {
+ editedLayers.addLayer(layer);
+ layer.edited = false;
+ }
+ });
+ this._map.fire(L.Draw.Event.EDITED, {layers: editedLayers});
+ },
+
+ _backupLayer: function (layer) {
+ var id = L.Util.stamp(layer);
+
+ if (!this._uneditedLayerProps[id]) {
+ // Polyline, Polygon or Rectangle
+ if (layer instanceof L.Polyline || layer instanceof L.Polygon || layer instanceof L.Rectangle) {
+ this._uneditedLayerProps[id] = {
+ latlngs: L.LatLngUtil.cloneLatLngs(layer.getLatLngs())
+ };
+ } else if (layer instanceof L.Circle) {
+ this._uneditedLayerProps[id] = {
+ latlng: L.LatLngUtil.cloneLatLng(layer.getLatLng()),
+ radius: layer.getRadius()
+ };
+ } else if (layer instanceof L.Marker || layer instanceof L.CircleMarker) { // Marker
+ this._uneditedLayerProps[id] = {
+ latlng: L.LatLngUtil.cloneLatLng(layer.getLatLng())
+ };
+ }
+ }
+ },
+
+ _getTooltipText: function () {
+ return ({
+ text: L.drawLocal.edit.handlers.edit.tooltip.text,
+ subtext: L.drawLocal.edit.handlers.edit.tooltip.subtext
+ });
+ },
+
+ _updateTooltip: function () {
+ this._tooltip.updateContent(this._getTooltipText());
+ },
+
+ _revertLayer: function (layer) {
+ var id = L.Util.stamp(layer);
+ layer.edited = false;
+ if (this._uneditedLayerProps.hasOwnProperty(id)) {
+ // Polyline, Polygon or Rectangle
+ if (layer instanceof L.Polyline || layer instanceof L.Polygon || layer instanceof L.Rectangle) {
+ layer.setLatLngs(this._uneditedLayerProps[id].latlngs);
+ } else if (layer instanceof L.Circle) {
+ layer.setLatLng(this._uneditedLayerProps[id].latlng);
+ layer.setRadius(this._uneditedLayerProps[id].radius);
+ } else if (layer instanceof L.Marker || layer instanceof L.CircleMarker) { // Marker or CircleMarker
+ layer.setLatLng(this._uneditedLayerProps[id].latlng);
+ }
+
+ layer.fire('revert-edited', {layer: layer});
+ }
+ },
+
+ _enableLayerEdit: function (e) {
+ var layer = e.layer || e.target || e,
+ pathOptions, poly;
+
+ // Back up this layer (if haven't before)
+ this._backupLayer(layer);
+
+ if (this.options.poly) {
+ poly = L.Util.extend({}, this.options.poly);
+ layer.options.poly = poly;
+ }
+
+ // Set different style for editing mode
+ if (this.options.selectedPathOptions) {
+ pathOptions = L.Util.extend({}, this.options.selectedPathOptions);
+
+ // Use the existing color of the layer
+ if (pathOptions.maintainColor) {
+ pathOptions.color = layer.options.color;
+ pathOptions.fillColor = layer.options.fillColor;
+ }
+
+ layer.options.original = L.extend({}, layer.options);
+ layer.options.editing = pathOptions;
+
+ }
+
+ if (layer instanceof L.Marker) {
+ if (layer.editing) {
+ layer.editing.enable();
+ }
+ layer.dragging.enable();
+ layer
+ .on('dragend', this._onMarkerDragEnd)
+ // #TODO: remove when leaflet finally fixes their draggable so it's touch friendly again.
+ .on('touchmove', this._onTouchMove, this)
+ .on('MSPointerMove', this._onTouchMove, this)
+ .on('touchend', this._onMarkerDragEnd, this)
+ .on('MSPointerUp', this._onMarkerDragEnd, this);
+ } else {
+ layer.editing.enable();
+ }
+ },
+
+ _disableLayerEdit: function (e) {
+ var layer = e.layer || e.target || e;
+
+ layer.edited = false;
+ if (layer.editing) {
+ layer.editing.disable();
+ }
+
+ delete layer.options.editing;
+ delete layer.options.original;
+ // Reset layer styles to that of before select
+ if (this._selectedPathOptions) {
+ if (layer instanceof L.Marker) {
+ this._toggleMarkerHighlight(layer);
+ } else {
+ // reset the layer style to what is was before being selected
+ layer.setStyle(layer.options.previousOptions);
+ // remove the cached options for the layer object
+ delete layer.options.previousOptions;
+ }
+ }
+
+ if (layer instanceof L.Marker) {
+ layer.dragging.disable();
+ layer
+ .off('dragend', this._onMarkerDragEnd, this)
+ .off('touchmove', this._onTouchMove, this)
+ .off('MSPointerMove', this._onTouchMove, this)
+ .off('touchend', this._onMarkerDragEnd, this)
+ .off('MSPointerUp', this._onMarkerDragEnd, this);
+ } else {
+ layer.editing.disable();
+ }
+ },
+
+ _onMouseMove: function (e) {
+ this._tooltip.updatePosition(e.latlng);
+ },
+
+ _onMarkerDragEnd: function (e) {
+ var layer = e.target;
+ layer.edited = true;
+ this._map.fire(L.Draw.Event.EDITMOVE, {layer: layer});
+ },
+
+ _onTouchMove: function (e) {
+ var touchEvent = e.originalEvent.changedTouches[0],
+ layerPoint = this._map.mouseEventToLayerPoint(touchEvent),
+ latlng = this._map.layerPointToLatLng(layerPoint);
+ e.target.setLatLng(latlng);
+ },
+
+ _hasAvailableLayers: function () {
+ return this._featureGroup.getLayers().length !== 0;
+ }
+});
+
+
+
+/**
+ * @class L.EditToolbar.Delete
+ * @aka EditToolbar.Delete
+ */
+L.EditToolbar.Delete = L.Handler.extend({
+ statics: {
+ TYPE: 'remove' // not delete as delete is reserved in js
+ },
+
+ // @method intialize(): void
+ initialize: function (map, options) {
+ L.Handler.prototype.initialize.call(this, map);
+
+ L.Util.setOptions(this, options);
+
+ // Store the selectable layer group for ease of access
+ this._deletableLayers = this.options.featureGroup;
+
+ if (!(this._deletableLayers instanceof L.FeatureGroup)) {
+ throw new Error('options.featureGroup must be a L.FeatureGroup');
+ }
+
+ // Save the type so super can fire, need to do this as cannot do this.TYPE :(
+ this.type = L.EditToolbar.Delete.TYPE;
+
+ var version = L.version.split('.');
+ //If Version is >= 1.2.0
+ if (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) {
+ L.EditToolbar.Delete.include(L.Evented.prototype);
+ } else {
+ L.EditToolbar.Delete.include(L.Mixin.Events);
+ }
+
+ },
+
+ // @method enable(): void
+ // Enable the delete toolbar
+ enable: function () {
+ if (this._enabled || !this._hasAvailableLayers()) {
+ return;
+ }
+ this.fire('enabled', {handler: this.type});
+
+ this._map.fire(L.Draw.Event.DELETESTART, {handler: this.type});
+
+ L.Handler.prototype.enable.call(this);
+
+ this._deletableLayers
+ .on('layeradd', this._enableLayerDelete, this)
+ .on('layerremove', this._disableLayerDelete, this);
+ },
+
+ // @method disable(): void
+ // Disable the delete toolbar
+ disable: function () {
+ if (!this._enabled) {
+ return;
+ }
+
+ this._deletableLayers
+ .off('layeradd', this._enableLayerDelete, this)
+ .off('layerremove', this._disableLayerDelete, this);
+
+ L.Handler.prototype.disable.call(this);
+
+ this._map.fire(L.Draw.Event.DELETESTOP, {handler: this.type});
+
+ this.fire('disabled', {handler: this.type});
+ },
+
+ // @method addHooks(): void
+ // Add listener hooks to this handler
+ addHooks: function () {
+ var map = this._map;
+
+ if (map) {
+ map.getContainer().focus();
+
+ this._deletableLayers.eachLayer(this._enableLayerDelete, this);
+ this._deletedLayers = new L.LayerGroup();
+
+ this._tooltip = new L.Draw.Tooltip(this._map);
+ this._tooltip.updateContent({text: L.drawLocal.edit.handlers.remove.tooltip.text});
+
+ this._map.on('mousemove', this._onMouseMove, this);
+ }
+ },
+
+ // @method removeHooks(): void
+ // Remove listener hooks from this handler
+ removeHooks: function () {
+ if (this._map) {
+ this._deletableLayers.eachLayer(this._disableLayerDelete, this);
+ this._deletedLayers = null;
+
+ this._tooltip.dispose();
+ this._tooltip = null;
+
+ this._map.off('mousemove', this._onMouseMove, this);
+ }
+ },
+
+ // @method revertLayers(): void
+ // Revert the deleted layers back to their prior state.
+ revertLayers: function () {
+ // Iterate of the deleted layers and add them back into the featureGroup
+ this._deletedLayers.eachLayer(function (layer) {
+ this._deletableLayers.addLayer(layer);
+ layer.fire('revert-deleted', {layer: layer});
+ }, this);
+ },
+
+ // @method save(): void
+ // Save deleted layers
+ save: function () {
+ this._map.fire(L.Draw.Event.DELETED, {layers: this._deletedLayers});
+ },
+
+ // @method removeAllLayers(): void
+ // Remove all delateable layers
+ removeAllLayers: function () {
+ // Iterate of the delateable layers and add remove them
+ this._deletableLayers.eachLayer(function (layer) {
+ this._removeLayer({layer: layer});
+ }, this);
+ this.save();
+ },
+
+ _enableLayerDelete: function (e) {
+ var layer = e.layer || e.target || e;
+
+ layer.on('click', this._removeLayer, this);
+ },
+
+ _disableLayerDelete: function (e) {
+ var layer = e.layer || e.target || e;
+
+ layer.off('click', this._removeLayer, this);
+
+ // Remove from the deleted layers so we can't accidentally revert if the user presses cancel
+ this._deletedLayers.removeLayer(layer);
+ },
+
+ _removeLayer: function (e) {
+ var layer = e.layer || e.target || e;
+
+ this._deletableLayers.removeLayer(layer);
+
+ this._deletedLayers.addLayer(layer);
+
+ layer.fire('deleted');
+ },
+
+ _onMouseMove: function (e) {
+ this._tooltip.updatePosition(e.latlng);
+ },
+
+ _hasAvailableLayers: function () {
+ return this._deletableLayers.getLayers().length !== 0;
+ }
+});
+
+
+
+}(window, document));
+//# sourceMappingURL=leaflet.draw-src.map \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw-src.map b/www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw-src.map
new file mode 100644
index 00000000..53d78238
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw-src.map
@@ -0,0 +1 @@
+{"version":3,"sources":["src/Leaflet.draw.js","src/Leaflet.Draw.Event.js","src/draw/handler/Draw.Feature.js","src/draw/handler/Draw.Polyline.js","src/draw/handler/Draw.Polygon.js","src/draw/handler/Draw.SimpleShape.js","src/draw/handler/Draw.Rectangle.js","src/draw/handler/Draw.Marker.js","src/draw/handler/Draw.CircleMarker.js","src/draw/handler/Draw.Circle.js","src/edit/handler/Edit.Marker.js","src/edit/handler/Edit.Poly.js","src/edit/handler/Edit.SimpleShape.js","src/edit/handler/Edit.Rectangle.js","src/edit/handler/Edit.CircleMarker.js","src/edit/handler/Edit.Circle.js","src/ext/TouchEvents.js","src/ext/LatLngUtil.js","src/ext/GeometryUtil.js","src/ext/LineUtil.Intersect.js","src/ext/Polyline.Intersect.js","src/ext/Polygon.Intersect.js","src/Control.Draw.js","src/Toolbar.js","src/Tooltip.js","src/draw/DrawToolbar.js","src/edit/EditToolbar.js","src/edit/handler/EditToolbar.Edit.js","src/edit/handler/EditToolbar.Delete.js"],"names":[],"mappings":";;;;;;;yCAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AC7LA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AChLA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACxGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AChlBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACjIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACvHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AClGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AC/HA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AC1CA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACnFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACpFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACxgBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACrOA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AC7HA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACrDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACzDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AChRA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AC5BA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACrKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACtBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AC1GA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACjCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACtHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AC9UA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;ACtHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AC9GA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AClMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;AClSA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sourcesContent":["/**\n * Leaflet.draw assumes that you have already included the Leaflet library.\n */\nL.drawVersion = \"1.0.4\";\n/**\n * @class L.Draw\n * @aka Draw\n *\n *\n * To add the draw toolbar set the option drawControl: true in the map options.\n *\n * @example\n * ```js\n * var map = L.map('map', {drawControl: true}).setView([51.505, -0.09], 13);\n *\n * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {\n * attribution: '&copy; <a href=\"http://osm.org/copyright\">OpenStreetMap</a> contributors'\n * }).addTo(map);\n * ```\n *\n * ### Adding the edit toolbar\n * To use the edit toolbar you must initialise the Leaflet.draw control and manually add it to the map.\n *\n * ```js\n * var map = L.map('map').setView([51.505, -0.09], 13);\n *\n * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {\n * attribution: '&copy; <a href=\"http://osm.org/copyright\">OpenStreetMap</a> contributors'\n * }).addTo(map);\n *\n * // FeatureGroup is to store editable layers\n * var drawnItems = new L.FeatureGroup();\n * map.addLayer(drawnItems);\n *\n * var drawControl = new L.Control.Draw({\n * edit: {\n * featureGroup: drawnItems\n * }\n * });\n * map.addControl(drawControl);\n * ```\n *\n * The key here is the featureGroup option. This tells the plugin which FeatureGroup contains the layers that\n * should be editable. The featureGroup can contain 0 or more features with geometry types Point, LineString, and Polygon.\n * Leaflet.draw does not work with multigeometry features such as MultiPoint, MultiLineString, MultiPolygon,\n * or GeometryCollection. If you need to add multigeometry features to the draw plugin, convert them to a\n * FeatureCollection of non-multigeometries (Points, LineStrings, or Polygons).\n */\nL.Draw = {};\n\n/**\n * @class L.drawLocal\n * @aka L.drawLocal\n *\n * The core toolbar class of the API — it is used to create the toolbar ui\n *\n * @example\n * ```js\n * var modifiedDraw = L.drawLocal.extend({\n * draw: {\n * toolbar: {\n * buttons: {\n * polygon: 'Draw an awesome polygon'\n * }\n * }\n * }\n * });\n * ```\n *\n * The default state for the control is the draw toolbar just below the zoom control.\n * This will allow map users to draw vectors and markers.\n * **Please note the edit toolbar is not enabled by default.**\n */\nL.drawLocal = {\n\t// format: {\n\t// \tnumeric: {\n\t// \t\tdelimiters: {\n\t// \t\t\tthousands: ',',\n\t// \t\t\tdecimal: '.'\n\t// \t\t}\n\t// \t}\n\t// },\n\tdraw: {\n\t\ttoolbar: {\n\t\t\t// #TODO: this should be reorganized where actions are nested in actions\n\t\t\t// ex: actions.undo or actions.cancel\n\t\t\tactions: {\n\t\t\t\ttitle: 'Cancel drawing',\n\t\t\t\ttext: 'Cancel'\n\t\t\t},\n\t\t\tfinish: {\n\t\t\t\ttitle: 'Finish drawing',\n\t\t\t\ttext: 'Finish'\n\t\t\t},\n\t\t\tundo: {\n\t\t\t\ttitle: 'Delete last point drawn',\n\t\t\t\ttext: 'Delete last point'\n\t\t\t},\n\t\t\tbuttons: {\n\t\t\t\tpolyline: 'Draw a polyline',\n\t\t\t\tpolygon: 'Draw a polygon',\n\t\t\t\trectangle: 'Draw a rectangle',\n\t\t\t\tcircle: 'Draw a circle',\n\t\t\t\tmarker: 'Draw a marker',\n\t\t\t\tcirclemarker: 'Draw a circlemarker'\n\t\t\t}\n\t\t},\n\t\thandlers: {\n\t\t\tcircle: {\n\t\t\t\ttooltip: {\n\t\t\t\t\tstart: 'Click and drag to draw circle.'\n\t\t\t\t},\n\t\t\t\tradius: 'Radius'\n\t\t\t},\n\t\t\tcirclemarker: {\n\t\t\t\ttooltip: {\n\t\t\t\t\tstart: 'Click map to place circle marker.'\n\t\t\t\t}\n\t\t\t},\n\t\t\tmarker: {\n\t\t\t\ttooltip: {\n\t\t\t\t\tstart: 'Click map to place marker.'\n\t\t\t\t}\n\t\t\t},\n\t\t\tpolygon: {\n\t\t\t\ttooltip: {\n\t\t\t\t\tstart: 'Click to start drawing shape.',\n\t\t\t\t\tcont: 'Click to continue drawing shape.',\n\t\t\t\t\tend: 'Click first point to close this shape.'\n\t\t\t\t}\n\t\t\t},\n\t\t\tpolyline: {\n\t\t\t\terror: '<strong>Error:</strong> shape edges cannot cross!',\n\t\t\t\ttooltip: {\n\t\t\t\t\tstart: 'Click to start drawing line.',\n\t\t\t\t\tcont: 'Click to continue drawing line.',\n\t\t\t\t\tend: 'Click last point to finish line.'\n\t\t\t\t}\n\t\t\t},\n\t\t\trectangle: {\n\t\t\t\ttooltip: {\n\t\t\t\t\tstart: 'Click and drag to draw rectangle.'\n\t\t\t\t}\n\t\t\t},\n\t\t\tsimpleshape: {\n\t\t\t\ttooltip: {\n\t\t\t\t\tend: 'Release mouse to finish drawing.'\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\tedit: {\n\t\ttoolbar: {\n\t\t\tactions: {\n\t\t\t\tsave: {\n\t\t\t\t\ttitle: 'Save changes',\n\t\t\t\t\ttext: 'Save'\n\t\t\t\t},\n\t\t\t\tcancel: {\n\t\t\t\t\ttitle: 'Cancel editing, discards all changes',\n\t\t\t\t\ttext: 'Cancel'\n\t\t\t\t},\n\t\t\t\tclearAll: {\n\t\t\t\t\ttitle: 'Clear all layers',\n\t\t\t\t\ttext: 'Clear All'\n\t\t\t\t}\n\t\t\t},\n\t\t\tbuttons: {\n\t\t\t\tedit: 'Edit layers',\n\t\t\t\teditDisabled: 'No layers to edit',\n\t\t\t\tremove: 'Delete layers',\n\t\t\t\tremoveDisabled: 'No layers to delete'\n\t\t\t}\n\t\t},\n\t\thandlers: {\n\t\t\tedit: {\n\t\t\t\ttooltip: {\n\t\t\t\t\ttext: 'Drag handles or markers to edit features.',\n\t\t\t\t\tsubtext: 'Click cancel to undo changes.'\n\t\t\t\t}\n\t\t\t},\n\t\t\tremove: {\n\t\t\t\ttooltip: {\n\t\t\t\t\ttext: 'Click on a feature to remove.'\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n};\n","/**\n * ### Events\n * Once you have successfully added the Leaflet.draw plugin to your map you will want to respond to the different\n * actions users can initiate. The following events will be triggered on the map:\n *\n * @class L.Draw.Event\n * @aka Draw.Event\n *\n * Use `L.Draw.Event.EVENTNAME` constants to ensure events are correct.\n *\n * @example\n * ```js\n * map.on(L.Draw.Event.CREATED; function (e) {\n * var type = e.layerType,\n * layer = e.layer;\n *\n * if (type === 'marker') {\n * // Do marker specific actions\n * }\n *\n * // Do whatever else you need to. (save to db; add to map etc)\n * map.addLayer(layer);\n *});\n * ```\n */\nL.Draw.Event = {};\n/**\n * @event draw:created: PolyLine; Polygon; Rectangle; Circle; Marker | String\n *\n * Layer that was just created.\n * The type of layer this is. One of: `polyline`; `polygon`; `rectangle`; `circle`; `marker`\n * Triggered when a new vector or marker has been created.\n *\n */\nL.Draw.Event.CREATED = 'draw:created';\n\n/**\n * @event draw:edited: LayerGroup\n *\n * List of all layers just edited on the map.\n *\n *\n * Triggered when layers in the FeatureGroup; initialised with the plugin; have been edited and saved.\n *\n * @example\n * ```js\n * map.on('draw:edited', function (e) {\n * var layers = e.layers;\n * layers.eachLayer(function (layer) {\n * //do whatever you want; most likely save back to db\n * });\n * });\n * ```\n */\nL.Draw.Event.EDITED = 'draw:edited';\n\n/**\n * @event draw:deleted: LayerGroup\n *\n * List of all layers just removed from the map.\n *\n * Triggered when layers have been removed (and saved) from the FeatureGroup.\n */\nL.Draw.Event.DELETED = 'draw:deleted';\n\n/**\n * @event draw:drawstart: String\n *\n * The type of layer this is. One of:`polyline`; `polygon`; `rectangle`; `circle`; `marker`\n *\n * Triggered when the user has chosen to draw a particular vector or marker.\n */\nL.Draw.Event.DRAWSTART = 'draw:drawstart';\n\n/**\n * @event draw:drawstop: String\n *\n * The type of layer this is. One of: `polyline`; `polygon`; `rectangle`; `circle`; `marker`\n *\n * Triggered when the user has finished a particular vector or marker.\n */\n\nL.Draw.Event.DRAWSTOP = 'draw:drawstop';\n\n/**\n * @event draw:drawvertex: LayerGroup\n *\n * List of all layers just being added from the map.\n *\n * Triggered when a vertex is created on a polyline or polygon.\n */\nL.Draw.Event.DRAWVERTEX = 'draw:drawvertex';\n\n/**\n * @event draw:editstart: String\n *\n * The type of edit this is. One of: `edit`\n *\n * Triggered when the user starts edit mode by clicking the edit tool button.\n */\n\nL.Draw.Event.EDITSTART = 'draw:editstart';\n\n/**\n * @event draw:editmove: ILayer\n *\n * Layer that was just moved.\n *\n * Triggered as the user moves a rectangle; circle or marker.\n */\nL.Draw.Event.EDITMOVE = 'draw:editmove';\n\n/**\n * @event draw:editresize: ILayer\n *\n * Layer that was just moved.\n *\n * Triggered as the user resizes a rectangle or circle.\n */\nL.Draw.Event.EDITRESIZE = 'draw:editresize';\n\n/**\n * @event draw:editvertex: LayerGroup\n *\n * List of all layers just being edited from the map.\n *\n * Triggered when a vertex is edited on a polyline or polygon.\n */\nL.Draw.Event.EDITVERTEX = 'draw:editvertex';\n\n/**\n * @event draw:editstop: String\n *\n * The type of edit this is. One of: `edit`\n *\n * Triggered when the user has finshed editing (edit mode) and saves edits.\n */\nL.Draw.Event.EDITSTOP = 'draw:editstop';\n\n/**\n * @event draw:deletestart: String\n *\n * The type of edit this is. One of: `remove`\n *\n * Triggered when the user starts remove mode by clicking the remove tool button.\n */\nL.Draw.Event.DELETESTART = 'draw:deletestart';\n\n/**\n * @event draw:deletestop: String\n *\n * The type of edit this is. One of: `remove`\n *\n * Triggered when the user has finished removing shapes (remove mode) and saves.\n */\nL.Draw.Event.DELETESTOP = 'draw:deletestop';\n\n/**\n * @event draw:toolbaropened: String\n *\n * Triggered when a toolbar is opened.\n */\nL.Draw.Event.TOOLBAROPENED = 'draw:toolbaropened';\n\n/**\n * @event draw:toolbarclosed: String\n *\n * Triggered when a toolbar is closed.\n */\nL.Draw.Event.TOOLBARCLOSED = 'draw:toolbarclosed';\n\n/**\n * @event draw:markercontext: String\n *\n * Triggered when a marker is right clicked.\n */\nL.Draw.Event.MARKERCONTEXT = 'draw:markercontext';","L.Draw = L.Draw || {};\n\n/**\n * @class L.Draw.Feature\n * @aka Draw.Feature\n */\nL.Draw.Feature = L.Handler.extend({\n\n\t// @method initialize(): void\n\tinitialize: function (map, options) {\n\t\tthis._map = map;\n\t\tthis._container = map._container;\n\t\tthis._overlayPane = map._panes.overlayPane;\n\t\tthis._popupPane = map._panes.popupPane;\n\n\t\t// Merge default shapeOptions options with custom shapeOptions\n\t\tif (options && options.shapeOptions) {\n\t\t\toptions.shapeOptions = L.Util.extend({}, this.options.shapeOptions, options.shapeOptions);\n\t\t}\n\t\tL.setOptions(this, options);\n\n\t\tvar version = L.version.split('.');\n\t\t//If Version is >= 1.2.0\n\t\tif (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) {\n\t\t\tL.Draw.Feature.include(L.Evented.prototype);\n\t\t} else {\n\t\t\tL.Draw.Feature.include(L.Mixin.Events);\n\t\t}\n\t},\n\n\t// @method enable(): void\n\t// Enables this handler\n\tenable: function () {\n\t\tif (this._enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tL.Handler.prototype.enable.call(this);\n\n\t\tthis.fire('enabled', {handler: this.type});\n\n\t\tthis._map.fire(L.Draw.Event.DRAWSTART, {layerType: this.type});\n\t},\n\n\t// @method disable(): void\n\tdisable: function () {\n\t\tif (!this._enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tL.Handler.prototype.disable.call(this);\n\n\t\tthis._map.fire(L.Draw.Event.DRAWSTOP, {layerType: this.type});\n\n\t\tthis.fire('disabled', {handler: this.type});\n\t},\n\n\t// @method addHooks(): void\n\t// Add's event listeners to this handler\n\taddHooks: function () {\n\t\tvar map = this._map;\n\n\t\tif (map) {\n\t\t\tL.DomUtil.disableTextSelection();\n\n\t\t\tmap.getContainer().focus();\n\n\t\t\tthis._tooltip = new L.Draw.Tooltip(this._map);\n\n\t\t\tL.DomEvent.on(this._container, 'keyup', this._cancelDrawing, this);\n\t\t}\n\t},\n\n\t// @method removeHooks(): void\n\t// Removes event listeners from this handler\n\tremoveHooks: function () {\n\t\tif (this._map) {\n\t\t\tL.DomUtil.enableTextSelection();\n\n\t\t\tthis._tooltip.dispose();\n\t\t\tthis._tooltip = null;\n\n\t\t\tL.DomEvent.off(this._container, 'keyup', this._cancelDrawing, this);\n\t\t}\n\t},\n\n\t// @method setOptions(object): void\n\t// Sets new options to this handler\n\tsetOptions: function (options) {\n\t\tL.setOptions(this, options);\n\t},\n\n\t_fireCreatedEvent: function (layer) {\n\t\tthis._map.fire(L.Draw.Event.CREATED, {layer: layer, layerType: this.type});\n\t},\n\n\t// Cancel drawing when the escape key is pressed\n\t_cancelDrawing: function (e) {\n\t\tif (e.keyCode === 27) {\n\t\t\tthis._map.fire('draw:canceled', {layerType: this.type});\n\t\t\tthis.disable();\n\t\t}\n\t}\n});\n","/**\n * @class L.Draw.Polyline\n * @aka Draw.Polyline\n * @inherits L.Draw.Feature\n */\nL.Draw.Polyline = L.Draw.Feature.extend({\n\tstatics: {\n\t\tTYPE: 'polyline'\n\t},\n\n\tPoly: L.Polyline,\n\n\toptions: {\n\t\tallowIntersection: true,\n\t\trepeatMode: false,\n\t\tdrawError: {\n\t\t\tcolor: '#b00b00',\n\t\t\ttimeout: 2500\n\t\t},\n\t\ticon: new L.DivIcon({\n\t\t\ticonSize: new L.Point(8, 8),\n\t\t\tclassName: 'leaflet-div-icon leaflet-editing-icon'\n\t\t}),\n\t\ttouchIcon: new L.DivIcon({\n\t\t\ticonSize: new L.Point(20, 20),\n\t\t\tclassName: 'leaflet-div-icon leaflet-editing-icon leaflet-touch-icon'\n\t\t}),\n\t\tguidelineDistance: 20,\n\t\tmaxGuideLineLength: 4000,\n\t\tshapeOptions: {\n\t\t\tstroke: true,\n\t\t\tcolor: '#3388ff',\n\t\t\tweight: 4,\n\t\t\topacity: 0.5,\n\t\t\tfill: false,\n\t\t\tclickable: true\n\t\t},\n\t\tmetric: true, // Whether to use the metric measurement system or imperial\n\t\tfeet: true, // When not metric, to use feet instead of yards for display.\n\t\tnautic: false, // When not metric, not feet use nautic mile for display\n\t\tshowLength: true, // Whether to display distance in the tooltip\n\t\tzIndexOffset: 2000, // This should be > than the highest z-index any map layers\n\t\tfactor: 1, // To change distance calculation\n\t\tmaxPoints: 0 // Once this number of points are placed, finish shape\n\t},\n\n\t// @method initialize(): void\n\tinitialize: function (map, options) {\n\t\t// if touch, switch to touch icon\n\t\tif (L.Browser.touch) {\n\t\t\tthis.options.icon = this.options.touchIcon;\n\t\t}\n\n\t\t// Need to set this here to ensure the correct message is used.\n\t\tthis.options.drawError.message = L.drawLocal.draw.handlers.polyline.error;\n\n\t\t// Merge default drawError options with custom options\n\t\tif (options && options.drawError) {\n\t\t\toptions.drawError = L.Util.extend({}, this.options.drawError, options.drawError);\n\t\t}\n\n\t\t// Save the type so super can fire, need to do this as cannot do this.TYPE :(\n\t\tthis.type = L.Draw.Polyline.TYPE;\n\n\t\tL.Draw.Feature.prototype.initialize.call(this, map, options);\n\t},\n\n\t// @method addHooks(): void\n\t// Add listener hooks to this handler\n\taddHooks: function () {\n\t\tL.Draw.Feature.prototype.addHooks.call(this);\n\t\tif (this._map) {\n\t\t\tthis._markers = [];\n\n\t\t\tthis._markerGroup = new L.LayerGroup();\n\t\t\tthis._map.addLayer(this._markerGroup);\n\n\t\t\tthis._poly = new L.Polyline([], this.options.shapeOptions);\n\n\t\t\tthis._tooltip.updateContent(this._getTooltipText());\n\n\t\t\t// Make a transparent marker that will used to catch click events. These click\n\t\t\t// events will create the vertices. We need to do this so we can ensure that\n\t\t\t// we can create vertices over other map layers (markers, vector layers). We\n\t\t\t// also do not want to trigger any click handlers of objects we are clicking on\n\t\t\t// while drawing.\n\t\t\tif (!this._mouseMarker) {\n\t\t\t\tthis._mouseMarker = L.marker(this._map.getCenter(), {\n\t\t\t\t\ticon: L.divIcon({\n\t\t\t\t\t\tclassName: 'leaflet-mouse-marker',\n\t\t\t\t\t\ticonAnchor: [20, 20],\n\t\t\t\t\t\ticonSize: [40, 40]\n\t\t\t\t\t}),\n\t\t\t\t\topacity: 0,\n\t\t\t\t\tzIndexOffset: this.options.zIndexOffset\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tthis._mouseMarker\n\t\t\t\t.on('mouseout', this._onMouseOut, this)\n\t\t\t\t.on('mousemove', this._onMouseMove, this) // Necessary to prevent 0.8 stutter\n\t\t\t\t.on('mousedown', this._onMouseDown, this)\n\t\t\t\t.on('mouseup', this._onMouseUp, this) // Necessary for 0.8 compatibility\n\t\t\t\t.addTo(this._map);\n\n\t\t\tthis._map\n\t\t\t\t.on('mouseup', this._onMouseUp, this) // Necessary for 0.7 compatibility\n\t\t\t\t.on('mousemove', this._onMouseMove, this)\n\t\t\t\t.on('zoomlevelschange', this._onZoomEnd, this)\n\t\t\t\t.on('touchstart', this._onTouch, this)\n\t\t\t\t.on('zoomend', this._onZoomEnd, this);\n\n\t\t}\n\t},\n\n\t// @method removeHooks(): void\n\t// Remove listener hooks from this handler.\n\tremoveHooks: function () {\n\t\tL.Draw.Feature.prototype.removeHooks.call(this);\n\n\t\tthis._clearHideErrorTimeout();\n\n\t\tthis._cleanUpShape();\n\n\t\t// remove markers from map\n\t\tthis._map.removeLayer(this._markerGroup);\n\t\tdelete this._markerGroup;\n\t\tdelete this._markers;\n\n\t\tthis._map.removeLayer(this._poly);\n\t\tdelete this._poly;\n\n\t\tthis._mouseMarker\n\t\t\t.off('mousedown', this._onMouseDown, this)\n\t\t\t.off('mouseout', this._onMouseOut, this)\n\t\t\t.off('mouseup', this._onMouseUp, this)\n\t\t\t.off('mousemove', this._onMouseMove, this);\n\t\tthis._map.removeLayer(this._mouseMarker);\n\t\tdelete this._mouseMarker;\n\n\t\t// clean up DOM\n\t\tthis._clearGuides();\n\n\t\tthis._map\n\t\t\t.off('mouseup', this._onMouseUp, this)\n\t\t\t.off('mousemove', this._onMouseMove, this)\n\t\t\t.off('zoomlevelschange', this._onZoomEnd, this)\n\t\t\t.off('zoomend', this._onZoomEnd, this)\n\t\t\t.off('touchstart', this._onTouch, this)\n\t\t\t.off('click', this._onTouch, this);\n\t},\n\n\t// @method deleteLastVertex(): void\n\t// Remove the last vertex from the polyline, removes polyline from map if only one point exists.\n\tdeleteLastVertex: function () {\n\t\tif (this._markers.length <= 1) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar lastMarker = this._markers.pop(),\n\t\t\tpoly = this._poly,\n\t\t\t// Replaces .spliceLatLngs()\n\t\t\tlatlngs = poly.getLatLngs(),\n\t\t\tlatlng = latlngs.splice(-1, 1)[0];\n\t\tthis._poly.setLatLngs(latlngs);\n\n\t\tthis._markerGroup.removeLayer(lastMarker);\n\n\t\tif (poly.getLatLngs().length < 2) {\n\t\t\tthis._map.removeLayer(poly);\n\t\t}\n\n\t\tthis._vertexChanged(latlng, false);\n\t},\n\n\t// @method addVertex(): void\n\t// Add a vertex to the end of the polyline\n\taddVertex: function (latlng) {\n\t\tvar markersLength = this._markers.length;\n\t\t// markersLength must be greater than or equal to 2 before intersections can occur\n\t\tif (markersLength >= 2 && !this.options.allowIntersection && this._poly.newLatLngIntersects(latlng)) {\n\t\t\tthis._showErrorTooltip();\n\t\t\treturn;\n\t\t}\n\t\telse if (this._errorShown) {\n\t\t\tthis._hideErrorTooltip();\n\t\t}\n\n\t\tthis._markers.push(this._createMarker(latlng));\n\n\t\tthis._poly.addLatLng(latlng);\n\n\t\tif (this._poly.getLatLngs().length === 2) {\n\t\t\tthis._map.addLayer(this._poly);\n\t\t}\n\n\t\tthis._vertexChanged(latlng, true);\n\t},\n\n\t// @method completeShape(): void\n\t// Closes the polyline between the first and last points\n\tcompleteShape: function () {\n\t\tif (this._markers.length <= 1 || !this._shapeIsValid()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._fireCreatedEvent();\n\t\tthis.disable();\n\n\t\tif (this.options.repeatMode) {\n\t\t\tthis.enable();\n\t\t}\n\t},\n\n\t_finishShape: function () {\n\t\tvar latlngs = this._poly._defaultShape ? this._poly._defaultShape() : this._poly.getLatLngs();\n\t\tvar intersects = this._poly.newLatLngIntersects(latlngs[latlngs.length - 1]);\n\n\t\tif ((!this.options.allowIntersection && intersects) || !this._shapeIsValid()) {\n\t\t\tthis._showErrorTooltip();\n\t\t\treturn;\n\t\t}\n\n\t\tthis._fireCreatedEvent();\n\t\tthis.disable();\n\t\tif (this.options.repeatMode) {\n\t\t\tthis.enable();\n\t\t}\n\t},\n\n\t// Called to verify the shape is valid when the user tries to finish it\n\t// Return false if the shape is not valid\n\t_shapeIsValid: function () {\n\t\treturn true;\n\t},\n\n\t_onZoomEnd: function () {\n\t\tif (this._markers !== null) {\n\t\t\tthis._updateGuide();\n\t\t}\n\t},\n\n\t_onMouseMove: function (e) {\n\t\tvar newPos = this._map.mouseEventToLayerPoint(e.originalEvent);\n\t\tvar latlng = this._map.layerPointToLatLng(newPos);\n\n\t\t// Save latlng\n\t\t// should this be moved to _updateGuide() ?\n\t\tthis._currentLatLng = latlng;\n\n\t\tthis._updateTooltip(latlng);\n\n\t\t// Update the guide line\n\t\tthis._updateGuide(newPos);\n\n\t\t// Update the mouse marker position\n\t\tthis._mouseMarker.setLatLng(latlng);\n\n\t\tL.DomEvent.preventDefault(e.originalEvent);\n\t},\n\n\t_vertexChanged: function (latlng, added) {\n\t\tthis._map.fire(L.Draw.Event.DRAWVERTEX, {layers: this._markerGroup});\n\t\tthis._updateFinishHandler();\n\n\t\tthis._updateRunningMeasure(latlng, added);\n\n\t\tthis._clearGuides();\n\n\t\tthis._updateTooltip();\n\t},\n\n\t_onMouseDown: function (e) {\n\t\tif (!this._clickHandled && !this._touchHandled && !this._disableMarkers) {\n\t\t\tthis._onMouseMove(e);\n\t\t\tthis._clickHandled = true;\n\t\t\tthis._disableNewMarkers();\n\t\t\tvar originalEvent = e.originalEvent;\n\t\t\tvar clientX = originalEvent.clientX;\n\t\t\tvar clientY = originalEvent.clientY;\n\t\t\tthis._startPoint.call(this, clientX, clientY);\n\t\t}\n\t},\n\n\t_startPoint: function (clientX, clientY) {\n\t\tthis._mouseDownOrigin = L.point(clientX, clientY);\n\t},\n\n\t_onMouseUp: function (e) {\n\t\tvar originalEvent = e.originalEvent;\n\t\tvar clientX = originalEvent.clientX;\n\t\tvar clientY = originalEvent.clientY;\n\t\tthis._endPoint.call(this, clientX, clientY, e);\n\t\tthis._clickHandled = null;\n\t},\n\n\t_endPoint: function (clientX, clientY, e) {\n\t\tif (this._mouseDownOrigin) {\n\t\t\tvar dragCheckDistance = L.point(clientX, clientY)\n\t\t\t\t.distanceTo(this._mouseDownOrigin);\n\t\t\tvar lastPtDistance = this._calculateFinishDistance(e.latlng);\n\t\t\tif (this.options.maxPoints > 1 && this.options.maxPoints == this._markers.length + 1) {\n\t\t\t\tthis.addVertex(e.latlng);\n\t\t\t\tthis._finishShape();\n\t\t\t} else if (lastPtDistance < 10 && L.Browser.touch) {\n\t\t\t\tthis._finishShape();\n\t\t\t} else if (Math.abs(dragCheckDistance) < 9 * (window.devicePixelRatio || 1)) {\n\t\t\t\tthis.addVertex(e.latlng);\n\t\t\t}\n\t\t\tthis._enableNewMarkers(); // after a short pause, enable new markers\n\t\t}\n\t\tthis._mouseDownOrigin = null;\n\t},\n\n\t// ontouch prevented by clickHandled flag because some browsers fire both click/touch events,\n\t// causing unwanted behavior\n\t_onTouch: function (e) {\n\t\tvar originalEvent = e.originalEvent;\n\t\tvar clientX;\n\t\tvar clientY;\n\t\tif (originalEvent.touches && originalEvent.touches[0] && !this._clickHandled && !this._touchHandled && !this._disableMarkers) {\n\t\t\tclientX = originalEvent.touches[0].clientX;\n\t\t\tclientY = originalEvent.touches[0].clientY;\n\t\t\tthis._disableNewMarkers();\n\t\t\tthis._touchHandled = true;\n\t\t\tthis._startPoint.call(this, clientX, clientY);\n\t\t\tthis._endPoint.call(this, clientX, clientY, e);\n\t\t\tthis._touchHandled = null;\n\t\t}\n\t\tthis._clickHandled = null;\n\t},\n\n\t_onMouseOut: function () {\n\t\tif (this._tooltip) {\n\t\t\tthis._tooltip._onMouseOut.call(this._tooltip);\n\t\t}\n\t},\n\n\t// calculate if we are currently within close enough distance\n\t// of the closing point (first point for shapes, last point for lines)\n\t// this is semi-ugly code but the only reliable way i found to get the job done\n\t// note: calculating point.distanceTo between mouseDownOrigin and last marker did NOT work\n\t_calculateFinishDistance: function (potentialLatLng) {\n\t\tvar lastPtDistance;\n\t\tif (this._markers.length > 0) {\n\t\t\tvar finishMarker;\n\t\t\tif (this.type === L.Draw.Polyline.TYPE) {\n\t\t\t\tfinishMarker = this._markers[this._markers.length - 1];\n\t\t\t} else if (this.type === L.Draw.Polygon.TYPE) {\n\t\t\t\tfinishMarker = this._markers[0];\n\t\t\t} else {\n\t\t\t\treturn Infinity;\n\t\t\t}\n\t\t\tvar lastMarkerPoint = this._map.latLngToContainerPoint(finishMarker.getLatLng()),\n\t\t\t\tpotentialMarker = new L.Marker(potentialLatLng, {\n\t\t\t\t\ticon: this.options.icon,\n\t\t\t\t\tzIndexOffset: this.options.zIndexOffset * 2\n\t\t\t\t});\n\t\t\tvar potentialMarkerPint = this._map.latLngToContainerPoint(potentialMarker.getLatLng());\n\t\t\tlastPtDistance = lastMarkerPoint.distanceTo(potentialMarkerPint);\n\t\t} else {\n\t\t\tlastPtDistance = Infinity;\n\t\t}\n\t\treturn lastPtDistance;\n\t},\n\n\t_updateFinishHandler: function () {\n\t\tvar markerCount = this._markers.length;\n\t\t// The last marker should have a click handler to close the polyline\n\t\tif (markerCount > 1) {\n\t\t\tthis._markers[markerCount - 1].on('click', this._finishShape, this);\n\t\t}\n\n\t\t// Remove the old marker click handler (as only the last point should close the polyline)\n\t\tif (markerCount > 2) {\n\t\t\tthis._markers[markerCount - 2].off('click', this._finishShape, this);\n\t\t}\n\t},\n\n\t_createMarker: function (latlng) {\n\t\tvar marker = new L.Marker(latlng, {\n\t\t\ticon: this.options.icon,\n\t\t\tzIndexOffset: this.options.zIndexOffset * 2\n\t\t});\n\n\t\tthis._markerGroup.addLayer(marker);\n\n\t\treturn marker;\n\t},\n\n\t_updateGuide: function (newPos) {\n\t\tvar markerCount = this._markers ? this._markers.length : 0;\n\n\t\tif (markerCount > 0) {\n\t\t\tnewPos = newPos || this._map.latLngToLayerPoint(this._currentLatLng);\n\n\t\t\t// draw the guide line\n\t\t\tthis._clearGuides();\n\t\t\tthis._drawGuide(\n\t\t\t\tthis._map.latLngToLayerPoint(this._markers[markerCount - 1].getLatLng()),\n\t\t\t\tnewPos\n\t\t\t);\n\t\t}\n\t},\n\n\t_updateTooltip: function (latLng) {\n\t\tvar text = this._getTooltipText();\n\n\t\tif (latLng) {\n\t\t\tthis._tooltip.updatePosition(latLng);\n\t\t}\n\n\t\tif (!this._errorShown) {\n\t\t\tthis._tooltip.updateContent(text);\n\t\t}\n\t},\n\n\t_drawGuide: function (pointA, pointB) {\n\t\tvar length = Math.floor(Math.sqrt(Math.pow((pointB.x - pointA.x), 2) + Math.pow((pointB.y - pointA.y), 2))),\n\t\t\tguidelineDistance = this.options.guidelineDistance,\n\t\t\tmaxGuideLineLength = this.options.maxGuideLineLength,\n\t\t\t// Only draw a guideline with a max length\n\t\t\ti = length > maxGuideLineLength ? length - maxGuideLineLength : guidelineDistance,\n\t\t\tfraction,\n\t\t\tdashPoint,\n\t\t\tdash;\n\n\t\t//create the guides container if we haven't yet\n\t\tif (!this._guidesContainer) {\n\t\t\tthis._guidesContainer = L.DomUtil.create('div', 'leaflet-draw-guides', this._overlayPane);\n\t\t}\n\n\t\t//draw a dash every GuildeLineDistance\n\t\tfor (; i < length; i += this.options.guidelineDistance) {\n\t\t\t//work out fraction along line we are\n\t\t\tfraction = i / length;\n\n\t\t\t//calculate new x,y point\n\t\t\tdashPoint = {\n\t\t\t\tx: Math.floor((pointA.x * (1 - fraction)) + (fraction * pointB.x)),\n\t\t\t\ty: Math.floor((pointA.y * (1 - fraction)) + (fraction * pointB.y))\n\t\t\t};\n\n\t\t\t//add guide dash to guide container\n\t\t\tdash = L.DomUtil.create('div', 'leaflet-draw-guide-dash', this._guidesContainer);\n\t\t\tdash.style.backgroundColor =\n\t\t\t\t!this._errorShown ? this.options.shapeOptions.color : this.options.drawError.color;\n\n\t\t\tL.DomUtil.setPosition(dash, dashPoint);\n\t\t}\n\t},\n\n\t_updateGuideColor: function (color) {\n\t\tif (this._guidesContainer) {\n\t\t\tfor (var i = 0, l = this._guidesContainer.childNodes.length; i < l; i++) {\n\t\t\t\tthis._guidesContainer.childNodes[i].style.backgroundColor = color;\n\t\t\t}\n\t\t}\n\t},\n\n\t// removes all child elements (guide dashes) from the guides container\n\t_clearGuides: function () {\n\t\tif (this._guidesContainer) {\n\t\t\twhile (this._guidesContainer.firstChild) {\n\t\t\t\tthis._guidesContainer.removeChild(this._guidesContainer.firstChild);\n\t\t\t}\n\t\t}\n\t},\n\n\t_getTooltipText: function () {\n\t\tvar showLength = this.options.showLength,\n\t\t\tlabelText, distanceStr;\n\t\tif (this._markers.length === 0) {\n\t\t\tlabelText = {\n\t\t\t\ttext: L.drawLocal.draw.handlers.polyline.tooltip.start\n\t\t\t};\n\t\t} else {\n\t\t\tdistanceStr = showLength ? this._getMeasurementString() : '';\n\n\t\t\tif (this._markers.length === 1) {\n\t\t\t\tlabelText = {\n\t\t\t\t\ttext: L.drawLocal.draw.handlers.polyline.tooltip.cont,\n\t\t\t\t\tsubtext: distanceStr\n\t\t\t\t};\n\t\t\t} else {\n\t\t\t\tlabelText = {\n\t\t\t\t\ttext: L.drawLocal.draw.handlers.polyline.tooltip.end,\n\t\t\t\t\tsubtext: distanceStr\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\treturn labelText;\n\t},\n\n\t_updateRunningMeasure: function (latlng, added) {\n\t\tvar markersLength = this._markers.length,\n\t\t\tpreviousMarkerIndex, distance;\n\n\t\tif (this._markers.length === 1) {\n\t\t\tthis._measurementRunningTotal = 0;\n\t\t} else {\n\t\t\tpreviousMarkerIndex = markersLength - (added ? 2 : 1);\n\n\t\t\t// Calculate the distance based on the version\n\t\t\tif (L.GeometryUtil.isVersion07x()) {\n\t\t\t\tdistance = latlng.distanceTo(this._markers[previousMarkerIndex].getLatLng()) * (this.options.factor || 1);\n\t\t\t} else {\n\t\t\t\tdistance = this._map.distance(latlng, this._markers[previousMarkerIndex].getLatLng()) * (this.options.factor || 1);\n\t\t\t}\n\n\t\t\tthis._measurementRunningTotal += distance * (added ? 1 : -1);\n\t\t}\n\t},\n\n\t_getMeasurementString: function () {\n\t\tvar currentLatLng = this._currentLatLng,\n\t\t\tpreviousLatLng = this._markers[this._markers.length - 1].getLatLng(),\n\t\t\tdistance;\n\n\t\t// Calculate the distance from the last fixed point to the mouse position based on the version\n\t\tif (L.GeometryUtil.isVersion07x()) {\n\t\t\tdistance = previousLatLng && currentLatLng && currentLatLng.distanceTo ? this._measurementRunningTotal + currentLatLng.distanceTo(previousLatLng) * (this.options.factor || 1) : this._measurementRunningTotal || 0;\n\t\t} else {\n\t\t\tdistance = previousLatLng && currentLatLng ? this._measurementRunningTotal + this._map.distance(currentLatLng, previousLatLng) * (this.options.factor || 1) : this._measurementRunningTotal || 0;\n\t\t}\n\n\t\treturn L.GeometryUtil.readableDistance(distance, this.options.metric, this.options.feet, this.options.nautic, this.options.precision);\n\t},\n\n\t_showErrorTooltip: function () {\n\t\tthis._errorShown = true;\n\n\t\t// Update tooltip\n\t\tthis._tooltip\n\t\t\t.showAsError()\n\t\t\t.updateContent({text: this.options.drawError.message});\n\n\t\t// Update shape\n\t\tthis._updateGuideColor(this.options.drawError.color);\n\t\tthis._poly.setStyle({color: this.options.drawError.color});\n\n\t\t// Hide the error after 2 seconds\n\t\tthis._clearHideErrorTimeout();\n\t\tthis._hideErrorTimeout = setTimeout(L.Util.bind(this._hideErrorTooltip, this), this.options.drawError.timeout);\n\t},\n\n\t_hideErrorTooltip: function () {\n\t\tthis._errorShown = false;\n\n\t\tthis._clearHideErrorTimeout();\n\n\t\t// Revert tooltip\n\t\tthis._tooltip\n\t\t\t.removeError()\n\t\t\t.updateContent(this._getTooltipText());\n\n\t\t// Revert shape\n\t\tthis._updateGuideColor(this.options.shapeOptions.color);\n\t\tthis._poly.setStyle({color: this.options.shapeOptions.color});\n\t},\n\n\t_clearHideErrorTimeout: function () {\n\t\tif (this._hideErrorTimeout) {\n\t\t\tclearTimeout(this._hideErrorTimeout);\n\t\t\tthis._hideErrorTimeout = null;\n\t\t}\n\t},\n\n\t// disable new markers temporarily;\n\t// this is to prevent duplicated touch/click events in some browsers\n\t_disableNewMarkers: function () {\n\t\tthis._disableMarkers = true;\n\t},\n\n\t// see _disableNewMarkers\n\t_enableNewMarkers: function () {\n\t\tsetTimeout(function () {\n\t\t\tthis._disableMarkers = false;\n\t\t}.bind(this), 50);\n\t},\n\n\t_cleanUpShape: function () {\n\t\tif (this._markers.length > 1) {\n\t\t\tthis._markers[this._markers.length - 1].off('click', this._finishShape, this);\n\t\t}\n\t},\n\n\t_fireCreatedEvent: function () {\n\t\tvar poly = new this.Poly(this._poly.getLatLngs(), this.options.shapeOptions);\n\t\tL.Draw.Feature.prototype._fireCreatedEvent.call(this, poly);\n\t}\n});\n","/**\n * @class L.Draw.Polygon\n * @aka Draw.Polygon\n * @inherits L.Draw.Polyline\n */\nL.Draw.Polygon = L.Draw.Polyline.extend({\n\tstatics: {\n\t\tTYPE: 'polygon'\n\t},\n\n\tPoly: L.Polygon,\n\n\toptions: {\n\t\tshowArea: false,\n\t\tshowLength: false,\n\t\tshapeOptions: {\n\t\t\tstroke: true,\n\t\t\tcolor: '#3388ff',\n\t\t\tweight: 4,\n\t\t\topacity: 0.5,\n\t\t\tfill: true,\n\t\t\tfillColor: null, //same as color by default\n\t\t\tfillOpacity: 0.2,\n\t\t\tclickable: true\n\t\t},\n\t\t// Whether to use the metric measurement system (truthy) or not (falsy).\n\t\t// Also defines the units to use for the metric system as an array of\n\t\t// strings (e.g. `['ha', 'm']`).\n\t\tmetric: true,\n\t\tfeet: true, // When not metric, to use feet instead of yards for display.\n\t\tnautic: false, // When not metric, not feet use nautic mile for display\n\t\t// Defines the precision for each type of unit (e.g. {km: 2, ft: 0}\n\t\tprecision: {}\n\t},\n\n\t// @method initialize(): void\n\tinitialize: function (map, options) {\n\t\tL.Draw.Polyline.prototype.initialize.call(this, map, options);\n\n\t\t// Save the type so super can fire, need to do this as cannot do this.TYPE :(\n\t\tthis.type = L.Draw.Polygon.TYPE;\n\t},\n\n\t_updateFinishHandler: function () {\n\t\tvar markerCount = this._markers.length;\n\n\t\t// The first marker should have a click handler to close the polygon\n\t\tif (markerCount === 1) {\n\t\t\tthis._markers[0].on('click', this._finishShape, this);\n\t\t}\n\n\t\t// Add and update the double click handler\n\t\tif (markerCount > 2) {\n\t\t\tthis._markers[markerCount - 1].on('dblclick', this._finishShape, this);\n\t\t\t// Only need to remove handler if has been added before\n\t\t\tif (markerCount > 3) {\n\t\t\t\tthis._markers[markerCount - 2].off('dblclick', this._finishShape, this);\n\t\t\t}\n\t\t}\n\t},\n\n\t_getTooltipText: function () {\n\t\tvar text, subtext;\n\n\t\tif (this._markers.length === 0) {\n\t\t\ttext = L.drawLocal.draw.handlers.polygon.tooltip.start;\n\t\t} else if (this._markers.length < 3) {\n\t\t\ttext = L.drawLocal.draw.handlers.polygon.tooltip.cont;\n\t\t\tsubtext = this._getMeasurementString();\n\t\t} else {\n\t\t\ttext = L.drawLocal.draw.handlers.polygon.tooltip.end;\n\t\t\tsubtext = this._getMeasurementString();\n\t\t}\n\n\t\treturn {\n\t\t\ttext: text,\n\t\t\tsubtext: subtext\n\t\t};\n\t},\n\n\t_getMeasurementString: function () {\n\t\tvar area = this._area,\n\t\t\tmeasurementString = '';\n\n\n\t\tif (!area && !this.options.showLength) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.options.showLength) {\n\t\t\tmeasurementString = L.Draw.Polyline.prototype._getMeasurementString.call(this);\n\t\t}\n\n\t\tif (area) {\n\t\t\tmeasurementString += '<br>' + L.GeometryUtil.readableArea(area, this.options.metric, this.options.precision);\n\t\t}\n\n\t\treturn measurementString;\n\t},\n\n\t_shapeIsValid: function () {\n\t\treturn this._markers.length >= 3;\n\t},\n\n\t_vertexChanged: function (latlng, added) {\n\t\tvar latLngs;\n\n\t\t// Check to see if we should show the area\n\t\tif (!this.options.allowIntersection && this.options.showArea) {\n\t\t\tlatLngs = this._poly.getLatLngs();\n\n\t\t\tthis._area = L.GeometryUtil.geodesicArea(latLngs);\n\t\t}\n\n\t\tL.Draw.Polyline.prototype._vertexChanged.call(this, latlng, added);\n\t},\n\n\t_cleanUpShape: function () {\n\t\tvar markerCount = this._markers.length;\n\n\t\tif (markerCount > 0) {\n\t\t\tthis._markers[0].off('click', this._finishShape, this);\n\n\t\t\tif (markerCount > 2) {\n\t\t\t\tthis._markers[markerCount - 1].off('dblclick', this._finishShape, this);\n\t\t\t}\n\t\t}\n\t}\n});\n","L.SimpleShape = {};\n/**\n * @class L.Draw.SimpleShape\n * @aka Draw.SimpleShape\n * @inherits L.Draw.Feature\n */\nL.Draw.SimpleShape = L.Draw.Feature.extend({\n\toptions: {\n\t\trepeatMode: false\n\t},\n\n\t// @method initialize(): void\n\tinitialize: function (map, options) {\n\t\tthis._endLabelText = L.drawLocal.draw.handlers.simpleshape.tooltip.end;\n\n\t\tL.Draw.Feature.prototype.initialize.call(this, map, options);\n\t},\n\n\t// @method addHooks(): void\n\t// Add listener hooks to this handler.\n\taddHooks: function () {\n\t\tL.Draw.Feature.prototype.addHooks.call(this);\n\t\tif (this._map) {\n\t\t\tthis._mapDraggable = this._map.dragging.enabled();\n\n\t\t\tif (this._mapDraggable) {\n\t\t\t\tthis._map.dragging.disable();\n\t\t\t}\n\n\t\t\t//TODO refactor: move cursor to styles\n\t\t\tthis._container.style.cursor = 'crosshair';\n\n\t\t\tthis._tooltip.updateContent({text: this._initialLabelText});\n\n\t\t\tthis._map\n\t\t\t\t.on('mousedown', this._onMouseDown, this)\n\t\t\t\t.on('mousemove', this._onMouseMove, this)\n\t\t\t\t.on('touchstart', this._onMouseDown, this)\n\t\t\t\t.on('touchmove', this._onMouseMove, this);\n\n\t\t\t// we should prevent default, otherwise default behavior (scrolling) will fire,\n\t\t\t// and that will cause document.touchend to fire and will stop the drawing\n\t\t\t// (circle, rectangle) in touch mode.\n\t\t\t// (update): we have to send passive now to prevent scroll, because by default it is {passive: true} now, which means,\n\t\t\t// handler can't event.preventDefault\n\t\t\t// check the news https://developers.google.com/web/updates/2016/06/passive-event-listeners\n\t\t\tdocument.addEventListener('touchstart', L.DomEvent.preventDefault, {passive: false});\n\t\t}\n\t},\n\n\t// @method removeHooks(): void\n\t// Remove listener hooks from this handler.\n\tremoveHooks: function () {\n\t\tL.Draw.Feature.prototype.removeHooks.call(this);\n\t\tif (this._map) {\n\t\t\tif (this._mapDraggable) {\n\t\t\t\tthis._map.dragging.enable();\n\t\t\t}\n\n\t\t\t//TODO refactor: move cursor to styles\n\t\t\tthis._container.style.cursor = '';\n\n\t\t\tthis._map\n\t\t\t\t.off('mousedown', this._onMouseDown, this)\n\t\t\t\t.off('mousemove', this._onMouseMove, this)\n\t\t\t\t.off('touchstart', this._onMouseDown, this)\n\t\t\t\t.off('touchmove', this._onMouseMove, this);\n\n\t\t\tL.DomEvent.off(document, 'mouseup', this._onMouseUp, this);\n\t\t\tL.DomEvent.off(document, 'touchend', this._onMouseUp, this);\n\n\t\t\tdocument.removeEventListener('touchstart', L.DomEvent.preventDefault);\n\n\t\t\t// If the box element doesn't exist they must not have moved the mouse, so don't need to destroy/return\n\t\t\tif (this._shape) {\n\t\t\t\tthis._map.removeLayer(this._shape);\n\t\t\t\tdelete this._shape;\n\t\t\t}\n\t\t}\n\t\tthis._isDrawing = false;\n\t},\n\n\t_getTooltipText: function () {\n\t\treturn {\n\t\t\ttext: this._endLabelText\n\t\t};\n\t},\n\n\t_onMouseDown: function (e) {\n\t\tthis._isDrawing = true;\n\t\tthis._startLatLng = e.latlng;\n\n\t\tL.DomEvent\n\t\t\t.on(document, 'mouseup', this._onMouseUp, this)\n\t\t\t.on(document, 'touchend', this._onMouseUp, this)\n\t\t\t.preventDefault(e.originalEvent);\n\t},\n\n\t_onMouseMove: function (e) {\n\t\tvar latlng = e.latlng;\n\n\t\tthis._tooltip.updatePosition(latlng);\n\t\tif (this._isDrawing) {\n\t\t\tthis._tooltip.updateContent(this._getTooltipText());\n\t\t\tthis._drawShape(latlng);\n\t\t}\n\t},\n\n\t_onMouseUp: function () {\n\t\tif (this._shape) {\n\t\t\tthis._fireCreatedEvent();\n\t\t}\n\n\t\tthis.disable();\n\t\tif (this.options.repeatMode) {\n\t\t\tthis.enable();\n\t\t}\n\t}\n});\n","/**\n * @class L.Draw.Rectangle\n * @aka Draw.Rectangle\n * @inherits L.Draw.SimpleShape\n */\nL.Draw.Rectangle = L.Draw.SimpleShape.extend({\n\tstatics: {\n\t\tTYPE: 'rectangle'\n\t},\n\n\toptions: {\n\t\tshapeOptions: {\n\t\t\tstroke: true,\n\t\t\tcolor: '#3388ff',\n\t\t\tweight: 4,\n\t\t\topacity: 0.5,\n\t\t\tfill: true,\n\t\t\tfillColor: null, //same as color by default\n\t\t\tfillOpacity: 0.2,\n\t\t\tclickable: true\n\t\t},\n\t\tshowArea: true, //Whether to show the area in the tooltip\n\t\tmetric: true // Whether to use the metric measurement system or imperial\n\t},\n\n\t// @method initialize(): void\n\tinitialize: function (map, options) {\n\t\t// Save the type so super can fire, need to do this as cannot do this.TYPE :(\n\t\tthis.type = L.Draw.Rectangle.TYPE;\n\n\t\tthis._initialLabelText = L.drawLocal.draw.handlers.rectangle.tooltip.start;\n\n\t\tL.Draw.SimpleShape.prototype.initialize.call(this, map, options);\n\t},\n\n\t// @method disable(): void\n\tdisable: function () {\n\t\tif (!this._enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._isCurrentlyTwoClickDrawing = false;\n\t\tL.Draw.SimpleShape.prototype.disable.call(this);\n\t},\n\n\t_onMouseUp: function (e) {\n\t\tif (!this._shape && !this._isCurrentlyTwoClickDrawing) {\n\t\t\tthis._isCurrentlyTwoClickDrawing = true;\n\t\t\treturn;\n\t\t}\n\n\t\t// Make sure closing click is on map\n\t\tif (this._isCurrentlyTwoClickDrawing && !_hasAncestor(e.target, 'leaflet-pane')) {\n\t\t\treturn;\n\t\t}\n\n\t\tL.Draw.SimpleShape.prototype._onMouseUp.call(this);\n\t},\n\n\t_drawShape: function (latlng) {\n\t\tif (!this._shape) {\n\t\t\tthis._shape = new L.Rectangle(new L.LatLngBounds(this._startLatLng, latlng), this.options.shapeOptions);\n\t\t\tthis._map.addLayer(this._shape);\n\t\t} else {\n\t\t\tthis._shape.setBounds(new L.LatLngBounds(this._startLatLng, latlng));\n\t\t}\n\t},\n\n\t_fireCreatedEvent: function () {\n\t\tvar rectangle = new L.Rectangle(this._shape.getBounds(), this.options.shapeOptions);\n\t\tL.Draw.SimpleShape.prototype._fireCreatedEvent.call(this, rectangle);\n\t},\n\n\t_getTooltipText: function () {\n\t\tvar tooltipText = L.Draw.SimpleShape.prototype._getTooltipText.call(this),\n\t\t\tshape = this._shape,\n\t\t\tshowArea = this.options.showArea,\n\t\t\tlatLngs, area, subtext;\n\n\t\tif (shape) {\n\t\t\tlatLngs = this._shape._defaultShape ? this._shape._defaultShape() : this._shape.getLatLngs();\n\t\t\tarea = L.GeometryUtil.geodesicArea(latLngs);\n\t\t\tsubtext = showArea ? L.GeometryUtil.readableArea(area, this.options.metric) : '';\n\t\t}\n\n\t\treturn {\n\t\t\ttext: tooltipText.text,\n\t\t\tsubtext: subtext\n\t\t};\n\t}\n});\n\nfunction _hasAncestor(el, cls) {\n\twhile ((el = el.parentElement) && !el.classList.contains(cls)) {\n\t\t;\n\t}\n\treturn el;\n}\n","/**\n * @class L.Draw.Marker\n * @aka Draw.Marker\n * @inherits L.Draw.Feature\n */\nL.Draw.Marker = L.Draw.Feature.extend({\n\tstatics: {\n\t\tTYPE: 'marker'\n\t},\n\n\toptions: {\n\t\ticon: new L.Icon.Default(),\n\t\trepeatMode: false,\n\t\tzIndexOffset: 2000 // This should be > than the highest z-index any markers\n\t},\n\n\t// @method initialize(): void\n\tinitialize: function (map, options) {\n\t\t// Save the type so super can fire, need to do this as cannot do this.TYPE :(\n\t\tthis.type = L.Draw.Marker.TYPE;\n\n\t\tthis._initialLabelText = L.drawLocal.draw.handlers.marker.tooltip.start;\n\n\t\tL.Draw.Feature.prototype.initialize.call(this, map, options);\n\t},\n\n\t// @method addHooks(): void\n\t// Add listener hooks to this handler.\n\taddHooks: function () {\n\t\tL.Draw.Feature.prototype.addHooks.call(this);\n\n\t\tif (this._map) {\n\t\t\tthis._tooltip.updateContent({text: this._initialLabelText});\n\n\t\t\t// Same mouseMarker as in Draw.Polyline\n\t\t\tif (!this._mouseMarker) {\n\t\t\t\tthis._mouseMarker = L.marker(this._map.getCenter(), {\n\t\t\t\t\ticon: L.divIcon({\n\t\t\t\t\t\tclassName: 'leaflet-mouse-marker',\n\t\t\t\t\t\ticonAnchor: [20, 20],\n\t\t\t\t\t\ticonSize: [40, 40]\n\t\t\t\t\t}),\n\t\t\t\t\topacity: 0,\n\t\t\t\t\tzIndexOffset: this.options.zIndexOffset\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tthis._mouseMarker\n\t\t\t\t.on('click', this._onClick, this)\n\t\t\t\t.addTo(this._map);\n\n\t\t\tthis._map.on('mousemove', this._onMouseMove, this);\n\t\t\tthis._map.on('click', this._onTouch, this);\n\t\t}\n\t},\n\n\t// @method removeHooks(): void\n\t// Remove listener hooks from this handler.\n\tremoveHooks: function () {\n\t\tL.Draw.Feature.prototype.removeHooks.call(this);\n\n\t\tif (this._map) {\n\t\t\tthis._map\n\t\t\t\t.off('click', this._onClick, this)\n\t\t\t\t.off('click', this._onTouch, this);\n\t\t\tif (this._marker) {\n\t\t\t\tthis._marker.off('click', this._onClick, this);\n\t\t\t\tthis._map\n\t\t\t\t\t.removeLayer(this._marker);\n\t\t\t\tdelete this._marker;\n\t\t\t}\n\n\t\t\tthis._mouseMarker.off('click', this._onClick, this);\n\t\t\tthis._map.removeLayer(this._mouseMarker);\n\t\t\tdelete this._mouseMarker;\n\n\t\t\tthis._map.off('mousemove', this._onMouseMove, this);\n\t\t}\n\t},\n\n\t_onMouseMove: function (e) {\n\t\tvar latlng = e.latlng;\n\n\t\tthis._tooltip.updatePosition(latlng);\n\t\tthis._mouseMarker.setLatLng(latlng);\n\n\t\tif (!this._marker) {\n\t\t\tthis._marker = this._createMarker(latlng);\n\t\t\t// Bind to both marker and map to make sure we get the click event.\n\t\t\tthis._marker.on('click', this._onClick, this);\n\t\t\tthis._map\n\t\t\t\t.on('click', this._onClick, this)\n\t\t\t\t.addLayer(this._marker);\n\t\t}\n\t\telse {\n\t\t\tlatlng = this._mouseMarker.getLatLng();\n\t\t\tthis._marker.setLatLng(latlng);\n\t\t}\n\t},\n\n\t_createMarker: function (latlng) {\n\t\treturn new L.Marker(latlng, {\n\t\t\ticon: this.options.icon,\n\t\t\tzIndexOffset: this.options.zIndexOffset\n\t\t});\n\t},\n\n\t_onClick: function () {\n\t\tthis._fireCreatedEvent();\n\n\t\tthis.disable();\n\t\tif (this.options.repeatMode) {\n\t\t\tthis.enable();\n\t\t}\n\t},\n\n\t_onTouch: function (e) {\n\t\t// called on click & tap, only really does any thing on tap\n\t\tthis._onMouseMove(e); // creates & places marker\n\t\tthis._onClick(); // permanently places marker & ends interaction\n\t},\n\n\t_fireCreatedEvent: function () {\n\t\tvar marker = new L.Marker.Touch(this._marker.getLatLng(), {icon: this.options.icon});\n\t\tL.Draw.Feature.prototype._fireCreatedEvent.call(this, marker);\n\t}\n});\n","/**\n * @class L.Draw.CircleMarker\n * @aka Draw.CircleMarker\n * @inherits L.Draw.Marker\n */\nL.Draw.CircleMarker = L.Draw.Marker.extend({\n\tstatics: {\n\t\tTYPE: 'circlemarker'\n\t},\n\n\toptions: {\n\t\tstroke: true,\n\t\tcolor: '#3388ff',\n\t\tweight: 4,\n\t\topacity: 0.5,\n\t\tfill: true,\n\t\tfillColor: null, //same as color by default\n\t\tfillOpacity: 0.2,\n\t\tclickable: true,\n\t\tzIndexOffset: 2000 // This should be > than the highest z-index any markers\n\t},\n\n\t// @method initialize(): void\n\tinitialize: function (map, options) {\n\t\t// Save the type so super can fire, need to do this as cannot do this.TYPE :(\n\t\tthis.type = L.Draw.CircleMarker.TYPE;\n\n\t\tthis._initialLabelText = L.drawLocal.draw.handlers.circlemarker.tooltip.start;\n\n\t\tL.Draw.Feature.prototype.initialize.call(this, map, options);\n\t},\n\n\n\t_fireCreatedEvent: function () {\n\t\tvar circleMarker = new L.CircleMarker(this._marker.getLatLng(), this.options);\n\t\tL.Draw.Feature.prototype._fireCreatedEvent.call(this, circleMarker);\n\t},\n\n\t_createMarker: function (latlng) {\n\t\treturn new L.CircleMarker(latlng, this.options);\n\t}\n});\n","/**\n * @class L.Draw.Circle\n * @aka Draw.Circle\n * @inherits L.Draw.SimpleShape\n */\nL.Draw.Circle = L.Draw.SimpleShape.extend({\n\tstatics: {\n\t\tTYPE: 'circle'\n\t},\n\n\toptions: {\n\t\tshapeOptions: {\n\t\t\tstroke: true,\n\t\t\tcolor: '#3388ff',\n\t\t\tweight: 4,\n\t\t\topacity: 0.5,\n\t\t\tfill: true,\n\t\t\tfillColor: null, //same as color by default\n\t\t\tfillOpacity: 0.2,\n\t\t\tclickable: true\n\t\t},\n\t\tshowRadius: true,\n\t\tmetric: true, // Whether to use the metric measurement system or imperial\n\t\tfeet: true, // When not metric, use feet instead of yards for display\n\t\tnautic: false // When not metric, not feet use nautic mile for display\n\t},\n\n\t// @method initialize(): void\n\tinitialize: function (map, options) {\n\t\t// Save the type so super can fire, need to do this as cannot do this.TYPE :(\n\t\tthis.type = L.Draw.Circle.TYPE;\n\n\t\tthis._initialLabelText = L.drawLocal.draw.handlers.circle.tooltip.start;\n\n\t\tL.Draw.SimpleShape.prototype.initialize.call(this, map, options);\n\t},\n\n\t_drawShape: function (latlng) {\n\t\t// Calculate the distance based on the version\n\t\tif (L.GeometryUtil.isVersion07x()) {\n\t\t\tvar distance = this._startLatLng.distanceTo(latlng);\n\t\t} else {\n\t\t\tvar distance = this._map.distance(this._startLatLng, latlng);\n\t\t}\n\n\t\tif (!this._shape) {\n\t\t\tthis._shape = new L.Circle(this._startLatLng, distance, this.options.shapeOptions);\n\t\t\tthis._map.addLayer(this._shape);\n\t\t} else {\n\t\t\tthis._shape.setRadius(distance);\n\t\t}\n\t},\n\n\t_fireCreatedEvent: function () {\n\t\tvar circle = new L.Circle(this._startLatLng, this._shape.getRadius(), this.options.shapeOptions);\n\t\tL.Draw.SimpleShape.prototype._fireCreatedEvent.call(this, circle);\n\t},\n\n\t_onMouseMove: function (e) {\n\t\tvar latlng = e.latlng,\n\t\t\tshowRadius = this.options.showRadius,\n\t\t\tuseMetric = this.options.metric,\n\t\t\tradius;\n\n\t\tthis._tooltip.updatePosition(latlng);\n\t\tif (this._isDrawing) {\n\t\t\tthis._drawShape(latlng);\n\n\t\t\t// Get the new radius (rounded to 1 dp)\n\t\t\tradius = this._shape.getRadius().toFixed(1);\n\n\t\t\tvar subtext = '';\n\t\t\tif (showRadius) {\n\t\t\t\tsubtext = L.drawLocal.draw.handlers.circle.radius + ': ' +\n\t\t\t\t\tL.GeometryUtil.readableDistance(radius, useMetric, this.options.feet, this.options.nautic);\n\t\t\t}\n\t\t\tthis._tooltip.updateContent({\n\t\t\t\ttext: this._endLabelText,\n\t\t\t\tsubtext: subtext\n\t\t\t});\n\t\t}\n\t}\n});\n","L.Edit = L.Edit || {};\n\n/**\n * @class L.Edit.Marker\n * @aka Edit.Marker\n */\nL.Edit.Marker = L.Handler.extend({\n\t// @method initialize(): void\n\tinitialize: function (marker, options) {\n\t\tthis._marker = marker;\n\t\tL.setOptions(this, options);\n\t},\n\n\t// @method addHooks(): void\n\t// Add listener hooks to this handler\n\taddHooks: function () {\n\t\tvar marker = this._marker;\n\n\t\tmarker.dragging.enable();\n\t\tmarker.on('dragend', this._onDragEnd, marker);\n\t\tthis._toggleMarkerHighlight();\n\t},\n\n\t// @method removeHooks(): void\n\t// Remove listener hooks from this handler\n\tremoveHooks: function () {\n\t\tvar marker = this._marker;\n\n\t\tmarker.dragging.disable();\n\t\tmarker.off('dragend', this._onDragEnd, marker);\n\t\tthis._toggleMarkerHighlight();\n\t},\n\n\t_onDragEnd: function (e) {\n\t\tvar layer = e.target;\n\t\tlayer.edited = true;\n\t\tthis._map.fire(L.Draw.Event.EDITMOVE, {layer: layer});\n\t},\n\n\t_toggleMarkerHighlight: function () {\n\t\tvar icon = this._marker._icon;\n\n\t\t// Don't do anything if this layer is a marker but doesn't have an icon. Markers\n\t\t// should usually have icons. If using Leaflet.draw with Leaflet.markercluster there\n\t\t// is a chance that a marker doesn't.\n\t\tif (!icon) {\n\t\t\treturn;\n\t\t}\n\n\t\t// This is quite naughty, but I don't see another way of doing it. (short of setting a new icon)\n\t\ticon.style.display = 'none';\n\n\t\tif (L.DomUtil.hasClass(icon, 'leaflet-edit-marker-selected')) {\n\t\t\tL.DomUtil.removeClass(icon, 'leaflet-edit-marker-selected');\n\t\t\t// Offset as the border will make the icon move.\n\t\t\tthis._offsetMarker(icon, -4);\n\n\t\t} else {\n\t\t\tL.DomUtil.addClass(icon, 'leaflet-edit-marker-selected');\n\t\t\t// Offset as the border will make the icon move.\n\t\t\tthis._offsetMarker(icon, 4);\n\t\t}\n\n\t\ticon.style.display = '';\n\t},\n\n\t_offsetMarker: function (icon, offset) {\n\t\tvar iconMarginTop = parseInt(icon.style.marginTop, 10) - offset,\n\t\t\ticonMarginLeft = parseInt(icon.style.marginLeft, 10) - offset;\n\n\t\ticon.style.marginTop = iconMarginTop + 'px';\n\t\ticon.style.marginLeft = iconMarginLeft + 'px';\n\t}\n});\n\nL.Marker.addInitHook(function () {\n\tif (L.Edit.Marker) {\n\t\tthis.editing = new L.Edit.Marker(this);\n\n\t\tif (this.options.editable) {\n\t\t\tthis.editing.enable();\n\t\t}\n\t}\n});\n","L.Edit = L.Edit || {};\n\n/**\n * @class L.Edit.Polyline\n * @aka L.Edit.Poly\n * @aka Edit.Poly\n */\nL.Edit.Poly = L.Handler.extend({\n\t// @method initialize(): void\n\tinitialize: function (poly) {\n\n\t\tthis.latlngs = [poly._latlngs];\n\t\tif (poly._holes) {\n\t\t\tthis.latlngs = this.latlngs.concat(poly._holes);\n\t\t}\n\n\t\tthis._poly = poly;\n\n\t\tthis._poly.on('revert-edited', this._updateLatLngs, this);\n\t},\n\n\t// Compatibility method to normalize Poly* objects\n\t// between 0.7.x and 1.0+\n\t_defaultShape: function () {\n\t\tif (!L.Polyline._flat) {\n\t\t\treturn this._poly._latlngs;\n\t\t}\n\t\treturn L.Polyline._flat(this._poly._latlngs) ? this._poly._latlngs : this._poly._latlngs[0];\n\t},\n\n\t_eachVertexHandler: function (callback) {\n\t\tfor (var i = 0; i < this._verticesHandlers.length; i++) {\n\t\t\tcallback(this._verticesHandlers[i]);\n\t\t}\n\t},\n\n\t// @method addHooks(): void\n\t// Add listener hooks to this handler\n\taddHooks: function () {\n\t\tthis._initHandlers();\n\t\tthis._eachVertexHandler(function (handler) {\n\t\t\thandler.addHooks();\n\t\t});\n\t},\n\n\t// @method removeHooks(): void\n\t// Remove listener hooks from this handler\n\tremoveHooks: function () {\n\t\tthis._eachVertexHandler(function (handler) {\n\t\t\thandler.removeHooks();\n\t\t});\n\t},\n\n\t// @method updateMarkers(): void\n\t// Fire an update for each vertex handler\n\tupdateMarkers: function () {\n\t\tthis._eachVertexHandler(function (handler) {\n\t\t\thandler.updateMarkers();\n\t\t});\n\t},\n\n\t_initHandlers: function () {\n\t\tthis._verticesHandlers = [];\n\t\tfor (var i = 0; i < this.latlngs.length; i++) {\n\t\t\tthis._verticesHandlers.push(new L.Edit.PolyVerticesEdit(this._poly, this.latlngs[i], this._poly.options.poly));\n\t\t}\n\t},\n\n\t_updateLatLngs: function (e) {\n\t\tthis.latlngs = [e.layer._latlngs];\n\t\tif (e.layer._holes) {\n\t\t\tthis.latlngs = this.latlngs.concat(e.layer._holes);\n\t\t}\n\t}\n\n});\n\n/**\n * @class L.Edit.PolyVerticesEdit\n * @aka Edit.PolyVerticesEdit\n */\nL.Edit.PolyVerticesEdit = L.Handler.extend({\n\toptions: {\n\t\ticon: new L.DivIcon({\n\t\t\ticonSize: new L.Point(8, 8),\n\t\t\tclassName: 'leaflet-div-icon leaflet-editing-icon'\n\t\t}),\n\t\ttouchIcon: new L.DivIcon({\n\t\t\ticonSize: new L.Point(20, 20),\n\t\t\tclassName: 'leaflet-div-icon leaflet-editing-icon leaflet-touch-icon'\n\t\t}),\n\t\tdrawError: {\n\t\t\tcolor: '#b00b00',\n\t\t\ttimeout: 1000\n\t\t}\n\n\n\t},\n\n\t// @method intialize(): void\n\tinitialize: function (poly, latlngs, options) {\n\t\t// if touch, switch to touch icon\n\t\tif (L.Browser.touch) {\n\t\t\tthis.options.icon = this.options.touchIcon;\n\t\t}\n\t\tthis._poly = poly;\n\n\t\tif (options && options.drawError) {\n\t\t\toptions.drawError = L.Util.extend({}, this.options.drawError, options.drawError);\n\t\t}\n\n\t\tthis._latlngs = latlngs;\n\n\t\tL.setOptions(this, options);\n\t},\n\n\t// Compatibility method to normalize Poly* objects\n\t// between 0.7.x and 1.0+\n\t_defaultShape: function () {\n\t\tif (!L.Polyline._flat) {\n\t\t\treturn this._latlngs;\n\t\t}\n\t\treturn L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];\n\t},\n\n\t// @method addHooks(): void\n\t// Add listener hooks to this handler.\n\taddHooks: function () {\n\t\tvar poly = this._poly;\n\t\tvar path = poly._path;\n\n\t\tif (!(poly instanceof L.Polygon)) {\n\t\t\tpoly.options.fill = false;\n\t\t\tif (poly.options.editing) {\n\t\t\t\tpoly.options.editing.fill = false;\n\t\t\t}\n\t\t}\n\n\t\tif (path) {\n\t\t\tif (poly.options.editing && poly.options.editing.className) {\n\t\t\t\tif (poly.options.original.className) {\n\t\t\t\t\tpoly.options.original.className.split(' ').forEach(function (className) {\n\t\t\t\t\t\tL.DomUtil.removeClass(path, className);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tpoly.options.editing.className.split(' ').forEach(function (className) {\n\t\t\t\t\tL.DomUtil.addClass(path, className);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tpoly.setStyle(poly.options.editing);\n\n\t\tif (this._poly._map) {\n\n\t\t\tthis._map = this._poly._map; // Set map\n\n\t\t\tif (!this._markerGroup) {\n\t\t\t\tthis._initMarkers();\n\t\t\t}\n\t\t\tthis._poly._map.addLayer(this._markerGroup);\n\t\t}\n\t},\n\n\t// @method removeHooks(): void\n\t// Remove listener hooks from this handler.\n\tremoveHooks: function () {\n\t\tvar poly = this._poly;\n\t\tvar path = poly._path;\n\n\t\tif (path) {\n\t\t\tif (poly.options.editing && poly.options.editing.className) {\n\t\t\t\tpoly.options.editing.className.split(' ').forEach(function (className) {\n\t\t\t\t\tL.DomUtil.removeClass(path, className);\n\t\t\t\t});\n\t\t\t\tif (poly.options.original.className) {\n\t\t\t\t\tpoly.options.original.className.split(' ').forEach(function (className) {\n\t\t\t\t\t\tL.DomUtil.addClass(path, className);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tpoly.setStyle(poly.options.original);\n\n\t\tif (poly._map) {\n\t\t\tpoly._map.removeLayer(this._markerGroup);\n\t\t\tdelete this._markerGroup;\n\t\t\tdelete this._markers;\n\t\t}\n\t},\n\n\t// @method updateMarkers(): void\n\t// Clear markers and update their location\n\tupdateMarkers: function () {\n\t\tthis._markerGroup.clearLayers();\n\t\tthis._initMarkers();\n\t},\n\n\t_initMarkers: function () {\n\t\tif (!this._markerGroup) {\n\t\t\tthis._markerGroup = new L.LayerGroup();\n\t\t}\n\t\tthis._markers = [];\n\n\t\tvar latlngs = this._defaultShape(),\n\t\t\ti, j, len, marker;\n\n\t\tfor (i = 0, len = latlngs.length; i < len; i++) {\n\n\t\t\tmarker = this._createMarker(latlngs[i], i);\n\t\t\tmarker.on('click', this._onMarkerClick, this);\n\t\t\tmarker.on('contextmenu', this._onContextMenu, this);\n\t\t\tthis._markers.push(marker);\n\t\t}\n\n\t\tvar markerLeft, markerRight;\n\n\t\tfor (i = 0, j = len - 1; i < len; j = i++) {\n\t\t\tif (i === 0 && !(L.Polygon && (this._poly instanceof L.Polygon))) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tmarkerLeft = this._markers[j];\n\t\t\tmarkerRight = this._markers[i];\n\n\t\t\tthis._createMiddleMarker(markerLeft, markerRight);\n\t\t\tthis._updatePrevNext(markerLeft, markerRight);\n\t\t}\n\t},\n\n\t_createMarker: function (latlng, index) {\n\t\t// Extending L.Marker in TouchEvents.js to include touch.\n\t\tvar marker = new L.Marker.Touch(latlng, {\n\t\t\tdraggable: true,\n\t\t\ticon: this.options.icon,\n\t\t});\n\n\t\tmarker._origLatLng = latlng;\n\t\tmarker._index = index;\n\n\t\tmarker\n\t\t\t.on('dragstart', this._onMarkerDragStart, this)\n\t\t\t.on('drag', this._onMarkerDrag, this)\n\t\t\t.on('dragend', this._fireEdit, this)\n\t\t\t.on('touchmove', this._onTouchMove, this)\n\t\t\t.on('touchend', this._fireEdit, this)\n\t\t\t.on('MSPointerMove', this._onTouchMove, this)\n\t\t\t.on('MSPointerUp', this._fireEdit, this);\n\n\t\tthis._markerGroup.addLayer(marker);\n\n\t\treturn marker;\n\t},\n\n\t_onMarkerDragStart: function () {\n\t\tthis._poly.fire('editstart');\n\t},\n\n\t_spliceLatLngs: function () {\n\t\tvar latlngs = this._defaultShape();\n\t\tvar removed = [].splice.apply(latlngs, arguments);\n\t\tthis._poly._convertLatLngs(latlngs, true);\n\t\tthis._poly.redraw();\n\t\treturn removed;\n\t},\n\n\t_removeMarker: function (marker) {\n\t\tvar i = marker._index;\n\n\t\tthis._markerGroup.removeLayer(marker);\n\t\tthis._markers.splice(i, 1);\n\t\tthis._spliceLatLngs(i, 1);\n\t\tthis._updateIndexes(i, -1);\n\n\t\tmarker\n\t\t\t.off('dragstart', this._onMarkerDragStart, this)\n\t\t\t.off('drag', this._onMarkerDrag, this)\n\t\t\t.off('dragend', this._fireEdit, this)\n\t\t\t.off('touchmove', this._onMarkerDrag, this)\n\t\t\t.off('touchend', this._fireEdit, this)\n\t\t\t.off('click', this._onMarkerClick, this)\n\t\t\t.off('MSPointerMove', this._onTouchMove, this)\n\t\t\t.off('MSPointerUp', this._fireEdit, this);\n\t},\n\n\t_fireEdit: function () {\n\t\tthis._poly.edited = true;\n\t\tthis._poly.fire('edit');\n\t\tthis._poly._map.fire(L.Draw.Event.EDITVERTEX, {layers: this._markerGroup, poly: this._poly});\n\t},\n\n\t_onMarkerDrag: function (e) {\n\t\tvar marker = e.target;\n\t\tvar poly = this._poly;\n\n\t\tvar oldOrigLatLng = L.LatLngUtil.cloneLatLng(marker._origLatLng);\n\t\tL.extend(marker._origLatLng, marker._latlng);\n\t\tif (poly.options.poly) {\n\t\t\tvar tooltip = poly._map._editTooltip; // Access the tooltip\n\n\t\t\t// If we don't allow intersections and the polygon intersects\n\t\t\tif (!poly.options.poly.allowIntersection && poly.intersects()) {\n\t\t\t\tL.extend(marker._origLatLng, oldOrigLatLng);\n\t\t\t\tmarker.setLatLng(oldOrigLatLng);\n\t\t\t\tvar originalColor = poly.options.color;\n\t\t\t\tpoly.setStyle({color: this.options.drawError.color});\n\t\t\t\tif (tooltip) {\n\t\t\t\t\ttooltip.updateContent({\n\t\t\t\t\t\ttext: L.drawLocal.draw.handlers.polyline.error\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// Reset everything back to normal after a second\n\t\t\t\tsetTimeout(function () {\n\t\t\t\t\tpoly.setStyle({color: originalColor});\n\t\t\t\t\tif (tooltip) {\n\t\t\t\t\t\ttooltip.updateContent({\n\t\t\t\t\t\t\ttext: L.drawLocal.edit.handlers.edit.tooltip.text,\n\t\t\t\t\t\t\tsubtext: L.drawLocal.edit.handlers.edit.tooltip.subtext\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}, 1000);\n\t\t\t}\n\t\t}\n\n\t\tif (marker._middleLeft) {\n\t\t\tmarker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));\n\t\t}\n\t\tif (marker._middleRight) {\n\t\t\tmarker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));\n\t\t}\n\n\t\t//refresh the bounds when draging\n\t\tthis._poly._bounds._southWest = L.latLng(Infinity, Infinity);\n\t\tthis._poly._bounds._northEast = L.latLng(-Infinity, -Infinity);\n\t\tvar latlngs = this._poly.getLatLngs();\n\t\tthis._poly._convertLatLngs(latlngs, true);\n\t\tthis._poly.redraw();\n\t\tthis._poly.fire('editdrag');\n\t},\n\n\t_onMarkerClick: function (e) {\n\n\t\tvar minPoints = L.Polygon && (this._poly instanceof L.Polygon) ? 4 : 3,\n\t\t\tmarker = e.target;\n\n\t\t// If removing this point would create an invalid polyline/polygon don't remove\n\t\tif (this._defaultShape().length < minPoints) {\n\t\t\treturn;\n\t\t}\n\n\t\t// remove the marker\n\t\tthis._removeMarker(marker);\n\n\t\t// update prev/next links of adjacent markers\n\t\tthis._updatePrevNext(marker._prev, marker._next);\n\n\t\t// remove ghost markers near the removed marker\n\t\tif (marker._middleLeft) {\n\t\t\tthis._markerGroup.removeLayer(marker._middleLeft);\n\t\t}\n\t\tif (marker._middleRight) {\n\t\t\tthis._markerGroup.removeLayer(marker._middleRight);\n\t\t}\n\n\t\t// create a ghost marker in place of the removed one\n\t\tif (marker._prev && marker._next) {\n\t\t\tthis._createMiddleMarker(marker._prev, marker._next);\n\n\t\t} else if (!marker._prev) {\n\t\t\tmarker._next._middleLeft = null;\n\n\t\t} else if (!marker._next) {\n\t\t\tmarker._prev._middleRight = null;\n\t\t}\n\n\t\tthis._fireEdit();\n\t},\n\n\t_onContextMenu: function (e) {\n\t\tvar marker = e.target;\n\t\tvar poly = this._poly;\n\t\tthis._poly._map.fire(L.Draw.Event.MARKERCONTEXT, {marker: marker, layers: this._markerGroup, poly: this._poly});\n\t\tL.DomEvent.stopPropagation;\n\t},\n\n\t_onTouchMove: function (e) {\n\n\t\tvar layerPoint = this._map.mouseEventToLayerPoint(e.originalEvent.touches[0]),\n\t\t\tlatlng = this._map.layerPointToLatLng(layerPoint),\n\t\t\tmarker = e.target;\n\n\t\tL.extend(marker._origLatLng, latlng);\n\n\t\tif (marker._middleLeft) {\n\t\t\tmarker._middleLeft.setLatLng(this._getMiddleLatLng(marker._prev, marker));\n\t\t}\n\t\tif (marker._middleRight) {\n\t\t\tmarker._middleRight.setLatLng(this._getMiddleLatLng(marker, marker._next));\n\t\t}\n\n\t\tthis._poly.redraw();\n\t\tthis.updateMarkers();\n\t},\n\n\t_updateIndexes: function (index, delta) {\n\t\tthis._markerGroup.eachLayer(function (marker) {\n\t\t\tif (marker._index > index) {\n\t\t\t\tmarker._index += delta;\n\t\t\t}\n\t\t});\n\t},\n\n\t_createMiddleMarker: function (marker1, marker2) {\n\t\tvar latlng = this._getMiddleLatLng(marker1, marker2),\n\t\t\tmarker = this._createMarker(latlng),\n\t\t\tonClick,\n\t\t\tonDragStart,\n\t\t\tonDragEnd;\n\n\t\tmarker.setOpacity(0.6);\n\n\t\tmarker1._middleRight = marker2._middleLeft = marker;\n\n\t\tonDragStart = function () {\n\t\t\tmarker.off('touchmove', onDragStart, this);\n\t\t\tvar i = marker2._index;\n\n\t\t\tmarker._index = i;\n\n\t\t\tmarker\n\t\t\t\t.off('click', onClick, this)\n\t\t\t\t.on('click', this._onMarkerClick, this);\n\n\t\t\tlatlng.lat = marker.getLatLng().lat;\n\t\t\tlatlng.lng = marker.getLatLng().lng;\n\t\t\tthis._spliceLatLngs(i, 0, latlng);\n\t\t\tthis._markers.splice(i, 0, marker);\n\n\t\t\tmarker.setOpacity(1);\n\n\t\t\tthis._updateIndexes(i, 1);\n\t\t\tmarker2._index++;\n\t\t\tthis._updatePrevNext(marker1, marker);\n\t\t\tthis._updatePrevNext(marker, marker2);\n\n\t\t\tthis._poly.fire('editstart');\n\t\t};\n\n\t\tonDragEnd = function () {\n\t\t\tmarker.off('dragstart', onDragStart, this);\n\t\t\tmarker.off('dragend', onDragEnd, this);\n\t\t\tmarker.off('touchmove', onDragStart, this);\n\n\t\t\tthis._createMiddleMarker(marker1, marker);\n\t\t\tthis._createMiddleMarker(marker, marker2);\n\t\t};\n\n\t\tonClick = function () {\n\t\t\tonDragStart.call(this);\n\t\t\tonDragEnd.call(this);\n\t\t\tthis._fireEdit();\n\t\t};\n\n\t\tmarker\n\t\t\t.on('click', onClick, this)\n\t\t\t.on('dragstart', onDragStart, this)\n\t\t\t.on('dragend', onDragEnd, this)\n\t\t\t.on('touchmove', onDragStart, this);\n\n\t\tthis._markerGroup.addLayer(marker);\n\t},\n\n\t_updatePrevNext: function (marker1, marker2) {\n\t\tif (marker1) {\n\t\t\tmarker1._next = marker2;\n\t\t}\n\t\tif (marker2) {\n\t\t\tmarker2._prev = marker1;\n\t\t}\n\t},\n\n\t_getMiddleLatLng: function (marker1, marker2) {\n\t\tvar map = this._poly._map,\n\t\t\tp1 = map.project(marker1.getLatLng()),\n\t\t\tp2 = map.project(marker2.getLatLng());\n\n\t\treturn map.unproject(p1._add(p2)._divideBy(2));\n\t}\n});\n\nL.Polyline.addInitHook(function () {\n\n\t// Check to see if handler has already been initialized. This is to support versions of Leaflet that still have L.Handler.PolyEdit\n\tif (this.editing) {\n\t\treturn;\n\t}\n\n\tif (L.Edit.Poly) {\n\n\t\tthis.editing = new L.Edit.Poly(this);\n\n\t\tif (this.options.editable) {\n\t\t\tthis.editing.enable();\n\t\t}\n\t}\n\n\tthis.on('add', function () {\n\t\tif (this.editing && this.editing.enabled()) {\n\t\t\tthis.editing.addHooks();\n\t\t}\n\t});\n\n\tthis.on('remove', function () {\n\t\tif (this.editing && this.editing.enabled()) {\n\t\t\tthis.editing.removeHooks();\n\t\t}\n\t});\n});\n","L.Edit = L.Edit || {};\n/**\n * @class L.Edit.SimpleShape\n * @aka Edit.SimpleShape\n */\nL.Edit.SimpleShape = L.Handler.extend({\n\toptions: {\n\t\tmoveIcon: new L.DivIcon({\n\t\t\ticonSize: new L.Point(8, 8),\n\t\t\tclassName: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-move'\n\t\t}),\n\t\tresizeIcon: new L.DivIcon({\n\t\t\ticonSize: new L.Point(8, 8),\n\t\t\tclassName: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-resize'\n\t\t}),\n\t\ttouchMoveIcon: new L.DivIcon({\n\t\t\ticonSize: new L.Point(20, 20),\n\t\t\tclassName: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-move leaflet-touch-icon'\n\t\t}),\n\t\ttouchResizeIcon: new L.DivIcon({\n\t\t\ticonSize: new L.Point(20, 20),\n\t\t\tclassName: 'leaflet-div-icon leaflet-editing-icon leaflet-edit-resize leaflet-touch-icon'\n\t\t}),\n\t},\n\n\t// @method intialize(): void\n\tinitialize: function (shape, options) {\n\t\t// if touch, switch to touch icon\n\t\tif (L.Browser.touch) {\n\t\t\tthis.options.moveIcon = this.options.touchMoveIcon;\n\t\t\tthis.options.resizeIcon = this.options.touchResizeIcon;\n\t\t}\n\n\t\tthis._shape = shape;\n\t\tL.Util.setOptions(this, options);\n\t},\n\n\t// @method addHooks(): void\n\t// Add listener hooks to this handler\n\taddHooks: function () {\n\t\tvar shape = this._shape;\n\t\tif (this._shape._map) {\n\t\t\tthis._map = this._shape._map;\n\t\t\tshape.setStyle(shape.options.editing);\n\n\t\t\tif (shape._map) {\n\t\t\t\tthis._map = shape._map;\n\t\t\t\tif (!this._markerGroup) {\n\t\t\t\t\tthis._initMarkers();\n\t\t\t\t}\n\t\t\t\tthis._map.addLayer(this._markerGroup);\n\t\t\t}\n\t\t}\n\t},\n\n\t// @method removeHooks(): void\n\t// Remove listener hooks from this handler\n\tremoveHooks: function () {\n\t\tvar shape = this._shape;\n\n\t\tshape.setStyle(shape.options.original);\n\n\t\tif (shape._map) {\n\t\t\tthis._unbindMarker(this._moveMarker);\n\n\t\t\tfor (var i = 0, l = this._resizeMarkers.length; i < l; i++) {\n\t\t\t\tthis._unbindMarker(this._resizeMarkers[i]);\n\t\t\t}\n\t\t\tthis._resizeMarkers = null;\n\n\t\t\tthis._map.removeLayer(this._markerGroup);\n\t\t\tdelete this._markerGroup;\n\t\t}\n\n\t\tthis._map = null;\n\t},\n\n\t// @method updateMarkers(): void\n\t// Remove the edit markers from this layer\n\tupdateMarkers: function () {\n\t\tthis._markerGroup.clearLayers();\n\t\tthis._initMarkers();\n\t},\n\n\t_initMarkers: function () {\n\t\tif (!this._markerGroup) {\n\t\t\tthis._markerGroup = new L.LayerGroup();\n\t\t}\n\n\t\t// Create center marker\n\t\tthis._createMoveMarker();\n\n\t\t// Create edge marker\n\t\tthis._createResizeMarker();\n\t},\n\n\t_createMoveMarker: function () {\n\t\t// Children override\n\t},\n\n\t_createResizeMarker: function () {\n\t\t// Children override\n\t},\n\n\t_createMarker: function (latlng, icon) {\n\t\t// Extending L.Marker in TouchEvents.js to include touch.\n\t\tvar marker = new L.Marker.Touch(latlng, {\n\t\t\tdraggable: true,\n\t\t\ticon: icon,\n\t\t\tzIndexOffset: 10\n\t\t});\n\n\t\tthis._bindMarker(marker);\n\n\t\tthis._markerGroup.addLayer(marker);\n\n\t\treturn marker;\n\t},\n\n\t_bindMarker: function (marker) {\n\t\tmarker\n\t\t\t.on('dragstart', this._onMarkerDragStart, this)\n\t\t\t.on('drag', this._onMarkerDrag, this)\n\t\t\t.on('dragend', this._onMarkerDragEnd, this)\n\t\t\t.on('touchstart', this._onTouchStart, this)\n\t\t\t.on('touchmove', this._onTouchMove, this)\n\t\t\t.on('MSPointerMove', this._onTouchMove, this)\n\t\t\t.on('touchend', this._onTouchEnd, this)\n\t\t\t.on('MSPointerUp', this._onTouchEnd, this);\n\t},\n\n\t_unbindMarker: function (marker) {\n\t\tmarker\n\t\t\t.off('dragstart', this._onMarkerDragStart, this)\n\t\t\t.off('drag', this._onMarkerDrag, this)\n\t\t\t.off('dragend', this._onMarkerDragEnd, this)\n\t\t\t.off('touchstart', this._onTouchStart, this)\n\t\t\t.off('touchmove', this._onTouchMove, this)\n\t\t\t.off('MSPointerMove', this._onTouchMove, this)\n\t\t\t.off('touchend', this._onTouchEnd, this)\n\t\t\t.off('MSPointerUp', this._onTouchEnd, this);\n\t},\n\n\t_onMarkerDragStart: function (e) {\n\t\tvar marker = e.target;\n\t\tmarker.setOpacity(0);\n\n\t\tthis._shape.fire('editstart');\n\t},\n\n\t_fireEdit: function () {\n\t\tthis._shape.edited = true;\n\t\tthis._shape.fire('edit');\n\t},\n\n\t_onMarkerDrag: function (e) {\n\t\tvar marker = e.target,\n\t\t\tlatlng = marker.getLatLng();\n\n\t\tif (marker === this._moveMarker) {\n\t\t\tthis._move(latlng);\n\t\t} else {\n\t\t\tthis._resize(latlng);\n\t\t}\n\n\t\tthis._shape.redraw();\n\t\tthis._shape.fire('editdrag');\n\t},\n\n\t_onMarkerDragEnd: function (e) {\n\t\tvar marker = e.target;\n\t\tmarker.setOpacity(1);\n\n\t\tthis._fireEdit();\n\t},\n\n\t_onTouchStart: function (e) {\n\t\tL.Edit.SimpleShape.prototype._onMarkerDragStart.call(this, e);\n\n\t\tif (typeof(this._getCorners) === 'function') {\n\t\t\t// Save a reference to the opposite point\n\t\t\tvar corners = this._getCorners(),\n\t\t\t\tmarker = e.target,\n\t\t\t\tcurrentCornerIndex = marker._cornerIndex;\n\n\t\t\tmarker.setOpacity(0);\n\n\t\t\t// Copyed from Edit.Rectangle.js line 23 _onMarkerDragStart()\n\t\t\t// Latlng is null otherwise.\n\t\t\tthis._oppositeCorner = corners[(currentCornerIndex + 2) % 4];\n\t\t\tthis._toggleCornerMarkers(0, currentCornerIndex);\n\t\t}\n\n\t\tthis._shape.fire('editstart');\n\t},\n\n\t_onTouchMove: function (e) {\n\t\tvar layerPoint = this._map.mouseEventToLayerPoint(e.originalEvent.touches[0]),\n\t\t\tlatlng = this._map.layerPointToLatLng(layerPoint),\n\t\t\tmarker = e.target;\n\n\t\tif (marker === this._moveMarker) {\n\t\t\tthis._move(latlng);\n\t\t} else {\n\t\t\tthis._resize(latlng);\n\t\t}\n\n\t\tthis._shape.redraw();\n\n\t\t// prevent touchcancel in IOS\n\t\t// e.preventDefault();\n\t\treturn false;\n\t},\n\n\t_onTouchEnd: function (e) {\n\t\tvar marker = e.target;\n\t\tmarker.setOpacity(1);\n\t\tthis.updateMarkers();\n\t\tthis._fireEdit();\n\t},\n\n\t_move: function () {\n\t\t// Children override\n\t},\n\n\t_resize: function () {\n\t\t// Children override\n\t}\n});\n","L.Edit = L.Edit || {};\n/**\n * @class L.Edit.Rectangle\n * @aka Edit.Rectangle\n * @inherits L.Edit.SimpleShape\n */\nL.Edit.Rectangle = L.Edit.SimpleShape.extend({\n\t_createMoveMarker: function () {\n\t\tvar bounds = this._shape.getBounds(),\n\t\t\tcenter = bounds.getCenter();\n\n\t\tthis._moveMarker = this._createMarker(center, this.options.moveIcon);\n\t},\n\n\t_createResizeMarker: function () {\n\t\tvar corners = this._getCorners();\n\n\t\tthis._resizeMarkers = [];\n\n\t\tfor (var i = 0, l = corners.length; i < l; i++) {\n\t\t\tthis._resizeMarkers.push(this._createMarker(corners[i], this.options.resizeIcon));\n\t\t\t// Monkey in the corner index as we will need to know this for dragging\n\t\t\tthis._resizeMarkers[i]._cornerIndex = i;\n\t\t}\n\t},\n\n\t_onMarkerDragStart: function (e) {\n\t\tL.Edit.SimpleShape.prototype._onMarkerDragStart.call(this, e);\n\n\t\t// Save a reference to the opposite point\n\t\tvar corners = this._getCorners(),\n\t\t\tmarker = e.target,\n\t\t\tcurrentCornerIndex = marker._cornerIndex;\n\n\t\tthis._oppositeCorner = corners[(currentCornerIndex + 2) % 4];\n\n\t\tthis._toggleCornerMarkers(0, currentCornerIndex);\n\t},\n\n\t_onMarkerDragEnd: function (e) {\n\t\tvar marker = e.target,\n\t\t\tbounds, center;\n\n\t\t// Reset move marker position to the center\n\t\tif (marker === this._moveMarker) {\n\t\t\tbounds = this._shape.getBounds();\n\t\t\tcenter = bounds.getCenter();\n\n\t\t\tmarker.setLatLng(center);\n\t\t}\n\n\t\tthis._toggleCornerMarkers(1);\n\n\t\tthis._repositionCornerMarkers();\n\n\t\tL.Edit.SimpleShape.prototype._onMarkerDragEnd.call(this, e);\n\t},\n\n\t_move: function (newCenter) {\n\t\tvar latlngs = this._shape._defaultShape ? this._shape._defaultShape() : this._shape.getLatLngs(),\n\t\t\tbounds = this._shape.getBounds(),\n\t\t\tcenter = bounds.getCenter(),\n\t\t\toffset, newLatLngs = [];\n\n\t\t// Offset the latlngs to the new center\n\t\tfor (var i = 0, l = latlngs.length; i < l; i++) {\n\t\t\toffset = [latlngs[i].lat - center.lat, latlngs[i].lng - center.lng];\n\t\t\tnewLatLngs.push([newCenter.lat + offset[0], newCenter.lng + offset[1]]);\n\t\t}\n\n\t\tthis._shape.setLatLngs(newLatLngs);\n\n\t\t// Reposition the resize markers\n\t\tthis._repositionCornerMarkers();\n\n\t\tthis._map.fire(L.Draw.Event.EDITMOVE, {layer: this._shape});\n\t},\n\n\t_resize: function (latlng) {\n\t\tvar bounds;\n\n\t\t// Update the shape based on the current position of this corner and the opposite point\n\t\tthis._shape.setBounds(L.latLngBounds(latlng, this._oppositeCorner));\n\n\t\t// Reposition the move marker\n\t\tbounds = this._shape.getBounds();\n\t\tthis._moveMarker.setLatLng(bounds.getCenter());\n\n\t\tthis._map.fire(L.Draw.Event.EDITRESIZE, {layer: this._shape});\n\t},\n\n\t_getCorners: function () {\n\t\tvar bounds = this._shape.getBounds(),\n\t\t\tnw = bounds.getNorthWest(),\n\t\t\tne = bounds.getNorthEast(),\n\t\t\tse = bounds.getSouthEast(),\n\t\t\tsw = bounds.getSouthWest();\n\n\t\treturn [nw, ne, se, sw];\n\t},\n\n\t_toggleCornerMarkers: function (opacity) {\n\t\tfor (var i = 0, l = this._resizeMarkers.length; i < l; i++) {\n\t\t\tthis._resizeMarkers[i].setOpacity(opacity);\n\t\t}\n\t},\n\n\t_repositionCornerMarkers: function () {\n\t\tvar corners = this._getCorners();\n\n\t\tfor (var i = 0, l = this._resizeMarkers.length; i < l; i++) {\n\t\t\tthis._resizeMarkers[i].setLatLng(corners[i]);\n\t\t}\n\t}\n});\n\nL.Rectangle.addInitHook(function () {\n\tif (L.Edit.Rectangle) {\n\t\tthis.editing = new L.Edit.Rectangle(this);\n\n\t\tif (this.options.editable) {\n\t\t\tthis.editing.enable();\n\t\t}\n\t}\n});\n","L.Edit = L.Edit || {};\n/**\n * @class L.Edit.CircleMarker\n * @aka Edit.Circle\n * @inherits L.Edit.SimpleShape\n */\nL.Edit.CircleMarker = L.Edit.SimpleShape.extend({\n\t_createMoveMarker: function () {\n\t\tvar center = this._shape.getLatLng();\n\n\t\tthis._moveMarker = this._createMarker(center, this.options.moveIcon);\n\t},\n\n\t_createResizeMarker: function () {\n\t\t// To avoid an undefined check in L.Edit.SimpleShape.removeHooks\n\t\tthis._resizeMarkers = [];\n\t},\n\n\t_move: function (latlng) {\n\t\tif (this._resizeMarkers.length) {\n\t\t\tvar resizemarkerPoint = this._getResizeMarkerPoint(latlng);\n\t\t\t// Move the resize marker\n\t\t\tthis._resizeMarkers[0].setLatLng(resizemarkerPoint);\n\t\t}\n\n\t\t// Move the circle\n\t\tthis._shape.setLatLng(latlng);\n\n\t\tthis._map.fire(L.Draw.Event.EDITMOVE, {layer: this._shape});\n\t},\n});\n\nL.CircleMarker.addInitHook(function () {\n\tif (L.Edit.CircleMarker) {\n\t\tthis.editing = new L.Edit.CircleMarker(this);\n\n\t\tif (this.options.editable) {\n\t\t\tthis.editing.enable();\n\t\t}\n\t}\n\n\tthis.on('add', function () {\n\t\tif (this.editing && this.editing.enabled()) {\n\t\t\tthis.editing.addHooks();\n\t\t}\n\t});\n\n\tthis.on('remove', function () {\n\t\tif (this.editing && this.editing.enabled()) {\n\t\t\tthis.editing.removeHooks();\n\t\t}\n\t});\n});\n","L.Edit = L.Edit || {};\n/**\n * @class L.Edit.Circle\n * @aka Edit.Circle\n * @inherits L.Edit.CircleMarker\n */\nL.Edit.Circle = L.Edit.CircleMarker.extend({\n\n\t_createResizeMarker: function () {\n\t\tvar center = this._shape.getLatLng(),\n\t\t\tresizemarkerPoint = this._getResizeMarkerPoint(center);\n\n\t\tthis._resizeMarkers = [];\n\t\tthis._resizeMarkers.push(this._createMarker(resizemarkerPoint, this.options.resizeIcon));\n\t},\n\n\t_getResizeMarkerPoint: function (latlng) {\n\t\t// From L.shape.getBounds()\n\t\tvar delta = this._shape._radius * Math.cos(Math.PI / 4),\n\t\t\tpoint = this._map.project(latlng);\n\t\treturn this._map.unproject([point.x + delta, point.y - delta]);\n\t},\n\n\t_resize: function (latlng) {\n\t\tvar moveLatLng = this._moveMarker.getLatLng();\n\n\t\t// Calculate the radius based on the version\n\t\tif (L.GeometryUtil.isVersion07x()) {\n\t\t\tradius = moveLatLng.distanceTo(latlng);\n\t\t} else {\n\t\t\tradius = this._map.distance(moveLatLng, latlng);\n\t\t}\n\t\tthis._shape.setRadius(radius);\n\n\t\tif (this._map.editTooltip) {\n\t\t\tthis._map._editTooltip.updateContent({\n\t\t\t\ttext: L.drawLocal.edit.handlers.edit.tooltip.subtext + '<br />' + L.drawLocal.edit.handlers.edit.tooltip.text,\n\t\t\t\tsubtext: L.drawLocal.draw.handlers.circle.radius + ': ' +\n\t\t\t\tL.GeometryUtil.readableDistance(radius, true, this.options.feet, this.options.nautic)\n\t\t\t});\n\t\t}\n\n\t\tthis._shape.setRadius(radius);\n\n\t\tthis._map.fire(L.Draw.Event.EDITRESIZE, {layer: this._shape});\n\t}\n});\n\nL.Circle.addInitHook(function () {\n\tif (L.Edit.Circle) {\n\t\tthis.editing = new L.Edit.Circle(this);\n\n\t\tif (this.options.editable) {\n\t\t\tthis.editing.enable();\n\t\t}\n\t}\n});\n","L.Map.mergeOptions({\n\ttouchExtend: true\n});\n\n/**\n * @class L.Map.TouchExtend\n * @aka TouchExtend\n */\nL.Map.TouchExtend = L.Handler.extend({\n\n\t// @method initialize(): void\n\t// Sets TouchExtend private accessor variables\n\tinitialize: function (map) {\n\t\tthis._map = map;\n\t\tthis._container = map._container;\n\t\tthis._pane = map._panes.overlayPane;\n\t},\n\n\t// @method addHooks(): void\n\t// Adds dom listener events to the map container\n\taddHooks: function () {\n\t\tL.DomEvent.on(this._container, 'touchstart', this._onTouchStart, this);\n\t\tL.DomEvent.on(this._container, 'touchend', this._onTouchEnd, this);\n\t\tL.DomEvent.on(this._container, 'touchmove', this._onTouchMove, this);\n\t\tif (this._detectIE()) {\n\t\t\tL.DomEvent.on(this._container, 'MSPointerDown', this._onTouchStart, this);\n\t\t\tL.DomEvent.on(this._container, 'MSPointerUp', this._onTouchEnd, this);\n\t\t\tL.DomEvent.on(this._container, 'MSPointerMove', this._onTouchMove, this);\n\t\t\tL.DomEvent.on(this._container, 'MSPointerCancel', this._onTouchCancel, this);\n\n\t\t} else {\n\t\t\tL.DomEvent.on(this._container, 'touchcancel', this._onTouchCancel, this);\n\t\t\tL.DomEvent.on(this._container, 'touchleave', this._onTouchLeave, this);\n\t\t}\n\t},\n\n\t// @method removeHooks(): void\n\t// Removes dom listener events from the map container\n\tremoveHooks: function () {\n\t\tL.DomEvent.off(this._container, 'touchstart', this._onTouchStart, this);\n\t\tL.DomEvent.off(this._container, 'touchend', this._onTouchEnd, this);\n\t\tL.DomEvent.off(this._container, 'touchmove', this._onTouchMove, this);\n\t\tif (this._detectIE()) {\n\t\t\tL.DomEvent.off(this._container, 'MSPointerDown', this._onTouchStart, this);\n\t\t\tL.DomEvent.off(this._container, 'MSPointerUp', this._onTouchEnd, this);\n\t\t\tL.DomEvent.off(this._container, 'MSPointerMove', this._onTouchMove, this);\n\t\t\tL.DomEvent.off(this._container, 'MSPointerCancel', this._onTouchCancel, this);\n\t\t} else {\n\t\t\tL.DomEvent.off(this._container, 'touchcancel', this._onTouchCancel, this);\n\t\t\tL.DomEvent.off(this._container, 'touchleave', this._onTouchLeave, this);\n\t\t}\n\t},\n\n\t_touchEvent: function (e, type) {\n\t\t// #TODO: fix the pageX error that is do a bug in Android where a single touch triggers two click events\n\t\t// _filterClick is what leaflet uses as a workaround.\n\t\t// This is a problem with more things than just android. Another problem is touchEnd has no touches in\n\t\t// its touch list.\n\t\tvar touchEvent = {};\n\t\tif (typeof e.touches !== 'undefined') {\n\t\t\tif (!e.touches.length) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttouchEvent = e.touches[0];\n\t\t} else if (e.pointerType === 'touch') {\n\t\t\ttouchEvent = e;\n\t\t\tif (!this._filterClick(e)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t} else {\n\t\t\treturn;\n\t\t}\n\n\t\tvar containerPoint = this._map.mouseEventToContainerPoint(touchEvent),\n\t\t\tlayerPoint = this._map.mouseEventToLayerPoint(touchEvent),\n\t\t\tlatlng = this._map.layerPointToLatLng(layerPoint);\n\n\t\tthis._map.fire(type, {\n\t\t\tlatlng: latlng,\n\t\t\tlayerPoint: layerPoint,\n\t\t\tcontainerPoint: containerPoint,\n\t\t\tpageX: touchEvent.pageX,\n\t\t\tpageY: touchEvent.pageY,\n\t\t\toriginalEvent: e\n\t\t});\n\t},\n\n\t/** Borrowed from Leaflet and modified for bool ops **/\n\t_filterClick: function (e) {\n\t\tvar timeStamp = (e.timeStamp || e.originalEvent.timeStamp),\n\t\t\telapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);\n\n\t\t// are they closer together than 500ms yet more than 100ms?\n\t\t// Android typically triggers them ~300ms apart while multiple listeners\n\t\t// on the same event should be triggered far faster;\n\t\t// or check if click is simulated on the element, and if it is, reject any non-simulated events\n\t\tif ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {\n\t\t\tL.DomEvent.stop(e);\n\t\t\treturn false;\n\t\t}\n\t\tL.DomEvent._lastClick = timeStamp;\n\t\treturn true;\n\t},\n\n\t_onTouchStart: function (e) {\n\t\tif (!this._map._loaded) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar type = 'touchstart';\n\t\tthis._touchEvent(e, type);\n\n\t},\n\n\t_onTouchEnd: function (e) {\n\t\tif (!this._map._loaded) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar type = 'touchend';\n\t\tthis._touchEvent(e, type);\n\t},\n\n\t_onTouchCancel: function (e) {\n\t\tif (!this._map._loaded) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar type = 'touchcancel';\n\t\tif (this._detectIE()) {\n\t\t\ttype = 'pointercancel';\n\t\t}\n\t\tthis._touchEvent(e, type);\n\t},\n\n\t_onTouchLeave: function (e) {\n\t\tif (!this._map._loaded) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar type = 'touchleave';\n\t\tthis._touchEvent(e, type);\n\t},\n\n\t_onTouchMove: function (e) {\n\t\tif (!this._map._loaded) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar type = 'touchmove';\n\t\tthis._touchEvent(e, type);\n\t},\n\n\t_detectIE: function () {\n\t\tvar ua = window.navigator.userAgent;\n\n\t\tvar msie = ua.indexOf('MSIE ');\n\t\tif (msie > 0) {\n\t\t\t// IE 10 or older => return version number\n\t\t\treturn parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);\n\t\t}\n\n\t\tvar trident = ua.indexOf('Trident/');\n\t\tif (trident > 0) {\n\t\t\t// IE 11 => return version number\n\t\t\tvar rv = ua.indexOf('rv:');\n\t\t\treturn parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);\n\t\t}\n\n\t\tvar edge = ua.indexOf('Edge/');\n\t\tif (edge > 0) {\n\t\t\t// IE 12 => return version number\n\t\t\treturn parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);\n\t\t}\n\n\t\t// other browser\n\t\treturn false;\n\t}\n});\n\nL.Map.addInitHook('addHandler', 'touchExtend', L.Map.TouchExtend);\n\n\n/**\n * @class L.Marker.Touch\n * @aka Marker.Touch\n *\n * This isn't full Touch support. This is just to get markers to also support dom touch events after creation\n * #TODO: find a better way of getting markers to support touch.\n */\nL.Marker.Touch = L.Marker.extend({\n\n\t_initInteraction: function () {\n\t\tif (!this.addInteractiveTarget) {\n\t\t\t// 0.7.x support\n\t\t\treturn this._initInteractionLegacy();\n\t\t}\n\t\t// TODO this may need be updated to re-add touch events for 1.0+\n\t\treturn L.Marker.prototype._initInteraction.apply(this);\n\t},\n\n\t// This is an exact copy of https://github.com/Leaflet/Leaflet/blob/v0.7/src/layer/marker/Marker.js\n\t// with the addition of the touch events\n\t_initInteractionLegacy: function () {\n\n\t\tif (!this.options.clickable) {\n\t\t\treturn;\n\t\t}\n\n\t\t// TODO refactor into something shared with Map/Path/etc. to DRY it up\n\n\t\tvar icon = this._icon,\n\t\t\tevents = ['dblclick',\n\t\t\t\t'mousedown',\n\t\t\t\t'mouseover',\n\t\t\t\t'mouseout',\n\t\t\t\t'contextmenu',\n\t\t\t\t'touchstart',\n\t\t\t\t'touchend',\n\t\t\t\t'touchmove'];\n\t\tif (this._detectIE) {\n\t\t\tevents.concat(['MSPointerDown',\n\t\t\t\t'MSPointerUp',\n\t\t\t\t'MSPointerMove',\n\t\t\t\t'MSPointerCancel']);\n\t\t} else {\n\t\t\tevents.concat(['touchcancel']);\n\t\t}\n\n\t\tL.DomUtil.addClass(icon, 'leaflet-clickable');\n\t\tL.DomEvent.on(icon, 'click', this._onMouseClick, this);\n\t\tL.DomEvent.on(icon, 'keypress', this._onKeyPress, this);\n\n\t\tfor (var i = 0; i < events.length; i++) {\n\t\t\tL.DomEvent.on(icon, events[i], this._fireMouseEvent, this);\n\t\t}\n\n\t\tif (L.Handler.MarkerDrag) {\n\t\t\tthis.dragging = new L.Handler.MarkerDrag(this);\n\n\t\t\tif (this.options.draggable) {\n\t\t\t\tthis.dragging.enable();\n\t\t\t}\n\t\t}\n\t},\n\n\t_detectIE: function () {\n\t\tvar ua = window.navigator.userAgent;\n\n\t\tvar msie = ua.indexOf('MSIE ');\n\t\tif (msie > 0) {\n\t\t\t// IE 10 or older => return version number\n\t\t\treturn parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);\n\t\t}\n\n\t\tvar trident = ua.indexOf('Trident/');\n\t\tif (trident > 0) {\n\t\t\t// IE 11 => return version number\n\t\t\tvar rv = ua.indexOf('rv:');\n\t\t\treturn parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);\n\t\t}\n\n\t\tvar edge = ua.indexOf('Edge/');\n\t\tif (edge > 0) {\n\t\t\t// IE 12 => return version number\n\t\t\treturn parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);\n\t\t}\n\n\t\t// other browser\n\t\treturn false;\n\t}\n});\n","/**\n * @class L.LatLngUtil\n * @aka LatLngUtil\n */\nL.LatLngUtil = {\n\t// Clones a LatLngs[], returns [][]\n\n\t// @method cloneLatLngs(LatLngs[]): L.LatLngs[]\n\t// Clone the latLng point or points or nested points and return an array with those points\n\tcloneLatLngs: function (latlngs) {\n\t\tvar clone = [];\n\t\tfor (var i = 0, l = latlngs.length; i < l; i++) {\n\t\t\t// Check for nested array (Polyline/Polygon)\n\t\t\tif (Array.isArray(latlngs[i])) {\n\t\t\t\tclone.push(L.LatLngUtil.cloneLatLngs(latlngs[i]));\n\t\t\t} else {\n\t\t\t\tclone.push(this.cloneLatLng(latlngs[i]));\n\t\t\t}\n\t\t}\n\t\treturn clone;\n\t},\n\n\t// @method cloneLatLng(LatLng): L.LatLng\n\t// Clone the latLng and return a new LatLng object.\n\tcloneLatLng: function (latlng) {\n\t\treturn L.latLng(latlng.lat, latlng.lng);\n\t}\n};\n","(function () {\n\n\tvar defaultPrecision = {\n\t\tkm: 2,\n\t\tha: 2,\n\t\tm: 0,\n\t\tmi: 2,\n\t\tac: 2,\n\t\tyd: 0,\n\t\tft: 0,\n\t\tnm: 2\n\t};\n\n\n\t/**\n\t * @class L.GeometryUtil\n\t * @aka GeometryUtil\n\t */\n\tL.GeometryUtil = L.extend(L.GeometryUtil || {}, {\n\t\t// Ported from the OpenLayers implementation. See https://github.com/openlayers/openlayers/blob/master/lib/OpenLayers/Geometry/LinearRing.js#L270\n\n\t\t// @method geodesicArea(): number\n\t\tgeodesicArea: function (latLngs) {\n\t\t\tvar pointsCount = latLngs.length,\n\t\t\t\tarea = 0.0,\n\t\t\t\td2r = Math.PI / 180,\n\t\t\t\tp1, p2;\n\n\t\t\tif (pointsCount > 2) {\n\t\t\t\tfor (var i = 0; i < pointsCount; i++) {\n\t\t\t\t\tp1 = latLngs[i];\n\t\t\t\t\tp2 = latLngs[(i + 1) % pointsCount];\n\t\t\t\t\tarea += ((p2.lng - p1.lng) * d2r) *\n\t\t\t\t\t\t(2 + Math.sin(p1.lat * d2r) + Math.sin(p2.lat * d2r));\n\t\t\t\t}\n\t\t\t\tarea = area * 6378137.0 * 6378137.0 / 2.0;\n\t\t\t}\n\n\t\t\treturn Math.abs(area);\n\t\t},\n\n\t\t// @method formattedNumber(n, precision): string\n\t\t// Returns n in specified number format (if defined) and precision\n\t\tformattedNumber: function (n, precision) {\n\t\t\tvar formatted = parseFloat(n).toFixed(precision),\n\t\t\t\tformat = L.drawLocal.format && L.drawLocal.format.numeric,\n\t\t\t\tdelimiters = format && format.delimiters,\n\t\t\t\tthousands = delimiters && delimiters.thousands,\n\t\t\t\tdecimal = delimiters && delimiters.decimal;\n\n\t\t\tif (thousands || decimal) {\n\t\t\t\tvar splitValue = formatted.split('.');\n\t\t\t\tformatted = thousands ? splitValue[0].replace(/(\\d)(?=(\\d{3})+(?!\\d))/g, '$1' + thousands) : splitValue[0];\n\t\t\t\tdecimal = decimal || '.';\n\t\t\t\tif (splitValue.length > 1) {\n\t\t\t\t\tformatted = formatted + decimal + splitValue[1];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn formatted;\n\t\t},\n\n\t\t// @method readableArea(area, isMetric, precision): string\n\t\t// Returns a readable area string in yards or metric.\n\t\t// The value will be rounded as defined by the precision option object.\n\t\treadableArea: function (area, isMetric, precision) {\n\t\t\tvar areaStr,\n\t\t\t\tunits,\n\t\t\t\tprecision = L.Util.extend({}, defaultPrecision, precision);\n\n\t\t\tif (isMetric) {\n\t\t\t\tunits = ['ha', 'm'];\n\t\t\t\ttype = typeof isMetric;\n\t\t\t\tif (type === 'string') {\n\t\t\t\t\tunits = [isMetric];\n\t\t\t\t} else if (type !== 'boolean') {\n\t\t\t\t\tunits = isMetric;\n\t\t\t\t}\n\n\t\t\t\tif (area >= 1000000 && units.indexOf('km') !== -1) {\n\t\t\t\t\tareaStr = L.GeometryUtil.formattedNumber(area * 0.000001, precision['km']) + ' km²';\n\t\t\t\t} else if (area >= 10000 && units.indexOf('ha') !== -1) {\n\t\t\t\t\tareaStr = L.GeometryUtil.formattedNumber(area * 0.0001, precision['ha']) + ' ha';\n\t\t\t\t} else {\n\t\t\t\t\tareaStr = L.GeometryUtil.formattedNumber(area, precision['m']) + ' m²';\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tarea /= 0.836127; // Square yards in 1 meter\n\n\t\t\t\tif (area >= 3097600) { //3097600 square yards in 1 square mile\n\t\t\t\t\tareaStr = L.GeometryUtil.formattedNumber(area / 3097600, precision['mi']) + ' mi²';\n\t\t\t\t} else if (area >= 4840) { //4840 square yards in 1 acre\n\t\t\t\t\tareaStr = L.GeometryUtil.formattedNumber(area / 4840, precision['ac']) + ' acres';\n\t\t\t\t} else {\n\t\t\t\t\tareaStr = L.GeometryUtil.formattedNumber(area, precision['yd']) + ' yd²';\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn areaStr;\n\t\t},\n\n\t\t// @method readableDistance(distance, units): string\n\t\t// Converts a metric distance to one of [ feet, nauticalMile, metric or yards ] string\n\t\t//\n\t\t// @alternative\n\t\t// @method readableDistance(distance, isMetric, useFeet, isNauticalMile, precision): string\n\t\t// Converts metric distance to distance string.\n\t\t// The value will be rounded as defined by the precision option object.\n\t\treadableDistance: function (distance, isMetric, isFeet, isNauticalMile, precision) {\n\t\t\tvar distanceStr,\n\t\t\t\tunits,\n\t\t\t\tprecision = L.Util.extend({}, defaultPrecision, precision);\n\n\t\t\tif (isMetric) {\n\t\t\t\tunits = typeof isMetric == 'string' ? isMetric : 'metric';\n\t\t\t} else if (isFeet) {\n\t\t\t\tunits = 'feet';\n\t\t\t} else if (isNauticalMile) {\n\t\t\t\tunits = 'nauticalMile';\n\t\t\t} else {\n\t\t\t\tunits = 'yards';\n\t\t\t}\n\n\t\t\tswitch (units) {\n\t\t\t\tcase 'metric':\n\t\t\t\t\t// show metres when distance is < 1km, then show km\n\t\t\t\t\tif (distance > 1000) {\n\t\t\t\t\t\tdistanceStr = L.GeometryUtil.formattedNumber(distance / 1000, precision['km']) + ' km';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdistanceStr = L.GeometryUtil.formattedNumber(distance, precision['m']) + ' m';\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'feet':\n\t\t\t\t\tdistance *= 1.09361 * 3;\n\t\t\t\t\tdistanceStr = L.GeometryUtil.formattedNumber(distance, precision['ft']) + ' ft';\n\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'nauticalMile':\n\t\t\t\t\tdistance *= 0.53996;\n\t\t\t\t\tdistanceStr = L.GeometryUtil.formattedNumber(distance / 1000, precision['nm']) + ' nm';\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'yards':\n\t\t\t\tdefault:\n\t\t\t\t\tdistance *= 1.09361;\n\n\t\t\t\t\tif (distance > 1760) {\n\t\t\t\t\t\tdistanceStr = L.GeometryUtil.formattedNumber(distance / 1760, precision['mi']) + ' miles';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdistanceStr = L.GeometryUtil.formattedNumber(distance, precision['yd']) + ' yd';\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\treturn distanceStr;\n\t\t},\n\n\t\t// @method isVersion07x(): boolean\n\t\t// Returns true if the Leaflet version is 0.7.x, false otherwise.\n\t\tisVersion07x: function () {\n\t\t\tvar version = L.version.split('.');\n\t\t\t//If Version is == 0.7.*\n\t\t\treturn parseInt(version[0], 10) === 0 && parseInt(version[1], 10) === 7;\n\t\t},\n\t});\n\n})();\n","/**\n * @class L.LineUtil\n * @aka Util\n * @aka L.Utils\n */\nL.Util.extend(L.LineUtil, {\n\n\t// @method segmentsIntersect(): boolean\n\t// Checks to see if two line segments intersect. Does not handle degenerate cases.\n\t// http://compgeom.cs.uiuc.edu/~jeffe/teaching/373/notes/x06-sweepline.pdf\n\tsegmentsIntersect: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2, /*Point*/ p3) {\n\t\treturn this._checkCounterclockwise(p, p2, p3) !==\n\t\t\tthis._checkCounterclockwise(p1, p2, p3) &&\n\t\t\tthis._checkCounterclockwise(p, p1, p2) !==\n\t\t\tthis._checkCounterclockwise(p, p1, p3);\n\t},\n\n\t// check to see if points are in counterclockwise order\n\t_checkCounterclockwise: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {\n\t\treturn (p2.y - p.y) * (p1.x - p.x) > (p1.y - p.y) * (p2.x - p.x);\n\t}\n});\n","/**\n * @class L.Polyline\n * @aka Polyline\n */\nL.Polyline.include({\n\n\t// @method intersects(): boolean\n\t// Check to see if this polyline has any linesegments that intersect.\n\t// NOTE: does not support detecting intersection for degenerate cases.\n\tintersects: function () {\n\t\tvar points = this._getProjectedPoints(),\n\t\t\tlen = points ? points.length : 0,\n\t\t\ti, p, p1;\n\n\t\tif (this._tooFewPointsForIntersection()) {\n\t\t\treturn false;\n\t\t}\n\n\t\tfor (i = len - 1; i >= 3; i--) {\n\t\t\tp = points[i - 1];\n\t\t\tp1 = points[i];\n\n\n\t\t\tif (this._lineSegmentsIntersectsRange(p, p1, i - 2)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t},\n\n\t// @method newLatLngIntersects(): boolean\n\t// Check for intersection if new latlng was added to this polyline.\n\t// NOTE: does not support detecting intersection for degenerate cases.\n\tnewLatLngIntersects: function (latlng, skipFirst) {\n\t\t// Cannot check a polyline for intersecting lats/lngs when not added to the map\n\t\tif (!this._map) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn this.newPointIntersects(this._map.latLngToLayerPoint(latlng), skipFirst);\n\t},\n\n\t// @method newPointIntersects(): boolean\n\t// Check for intersection if new point was added to this polyline.\n\t// newPoint must be a layer point.\n\t// NOTE: does not support detecting intersection for degenerate cases.\n\tnewPointIntersects: function (newPoint, skipFirst) {\n\t\tvar points = this._getProjectedPoints(),\n\t\t\tlen = points ? points.length : 0,\n\t\t\tlastPoint = points ? points[len - 1] : null,\n\t\t\t// The previous previous line segment. Previous line segment doesn't need testing.\n\t\t\tmaxIndex = len - 2;\n\n\t\tif (this._tooFewPointsForIntersection(1)) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn this._lineSegmentsIntersectsRange(lastPoint, newPoint, maxIndex, skipFirst ? 1 : 0);\n\t},\n\n\t// Polylines with 2 sides can only intersect in cases where points are collinear (we don't support detecting these).\n\t// Cannot have intersection when < 3 line segments (< 4 points)\n\t_tooFewPointsForIntersection: function (extraPoints) {\n\t\tvar points = this._getProjectedPoints(),\n\t\t\tlen = points ? points.length : 0;\n\t\t// Increment length by extraPoints if present\n\t\tlen += extraPoints || 0;\n\n\t\treturn !points || len <= 3;\n\t},\n\n\t// Checks a line segment intersections with any line segments before its predecessor.\n\t// Don't need to check the predecessor as will never intersect.\n\t_lineSegmentsIntersectsRange: function (p, p1, maxIndex, minIndex) {\n\t\tvar points = this._getProjectedPoints(),\n\t\t\tp2, p3;\n\n\t\tminIndex = minIndex || 0;\n\n\t\t// Check all previous line segments (beside the immediately previous) for intersections\n\t\tfor (var j = maxIndex; j > minIndex; j--) {\n\t\t\tp2 = points[j - 1];\n\t\t\tp3 = points[j];\n\n\t\t\tif (L.LineUtil.segmentsIntersect(p, p1, p2, p3)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t},\n\n\t_getProjectedPoints: function () {\n\t\tif (!this._defaultShape) {\n\t\t\treturn this._originalPoints;\n\t\t}\n\t\tvar points = [],\n\t\t\t_shape = this._defaultShape();\n\n\t\tfor (var i = 0; i < _shape.length; i++) {\n\t\t\tpoints.push(this._map.latLngToLayerPoint(_shape[i]));\n\t\t}\n\t\treturn points;\n\t}\n});\n","/**\n * @class L.Polygon\n * @aka Polygon\n */\nL.Polygon.include({\n\n\t// @method intersects(): boolean\n\t// Checks a polygon for any intersecting line segments. Ignores holes.\n\tintersects: function () {\n\t\tvar polylineIntersects,\n\t\t\tpoints = this._getProjectedPoints(),\n\t\t\tlen, firstPoint, lastPoint, maxIndex;\n\n\t\tif (this._tooFewPointsForIntersection()) {\n\t\t\treturn false;\n\t\t}\n\n\t\tpolylineIntersects = L.Polyline.prototype.intersects.call(this);\n\n\t\t// If already found an intersection don't need to check for any more.\n\t\tif (polylineIntersects) {\n\t\t\treturn true;\n\t\t}\n\n\t\tlen = points.length;\n\t\tfirstPoint = points[0];\n\t\tlastPoint = points[len - 1];\n\t\tmaxIndex = len - 2;\n\n\t\t// Check the line segment between last and first point. Don't need to check the first line segment (minIndex = 1)\n\t\treturn this._lineSegmentsIntersectsRange(lastPoint, firstPoint, maxIndex, 1);\n\t}\n});\n","/**\n * @class L.Control.Draw\n * @aka L.Draw\n */\nL.Control.Draw = L.Control.extend({\n\n\t// Options\n\toptions: {\n\t\tposition: 'topleft',\n\t\tdraw: {},\n\t\tedit: false\n\t},\n\n\t// @method initialize(): void\n\t// Initializes draw control, toolbars from the options\n\tinitialize: function (options) {\n\t\tif (L.version < '0.7') {\n\t\t\tthrow new Error('Leaflet.draw 0.2.3+ requires Leaflet 0.7.0+. Download latest from https://github.com/Leaflet/Leaflet/');\n\t\t}\n\n\t\tL.Control.prototype.initialize.call(this, options);\n\n\t\tvar toolbar;\n\n\t\tthis._toolbars = {};\n\n\t\t// Initialize toolbars\n\t\tif (L.DrawToolbar && this.options.draw) {\n\t\t\ttoolbar = new L.DrawToolbar(this.options.draw);\n\n\t\t\tthis._toolbars[L.DrawToolbar.TYPE] = toolbar;\n\n\t\t\t// Listen for when toolbar is enabled\n\t\t\tthis._toolbars[L.DrawToolbar.TYPE].on('enable', this._toolbarEnabled, this);\n\t\t}\n\n\t\tif (L.EditToolbar && this.options.edit) {\n\t\t\ttoolbar = new L.EditToolbar(this.options.edit);\n\n\t\t\tthis._toolbars[L.EditToolbar.TYPE] = toolbar;\n\n\t\t\t// Listen for when toolbar is enabled\n\t\t\tthis._toolbars[L.EditToolbar.TYPE].on('enable', this._toolbarEnabled, this);\n\t\t}\n\t\tL.toolbar = this; //set global var for editing the toolbar\n\t},\n\n\t// @method onAdd(): container\n\t// Adds the toolbar container to the map\n\tonAdd: function (map) {\n\t\tvar container = L.DomUtil.create('div', 'leaflet-draw'),\n\t\t\taddedTopClass = false,\n\t\t\ttopClassName = 'leaflet-draw-toolbar-top',\n\t\t\ttoolbarContainer;\n\n\t\tfor (var toolbarId in this._toolbars) {\n\t\t\tif (this._toolbars.hasOwnProperty(toolbarId)) {\n\t\t\t\ttoolbarContainer = this._toolbars[toolbarId].addToolbar(map);\n\n\t\t\t\tif (toolbarContainer) {\n\t\t\t\t\t// Add class to the first toolbar to remove the margin\n\t\t\t\t\tif (!addedTopClass) {\n\t\t\t\t\t\tif (!L.DomUtil.hasClass(toolbarContainer, topClassName)) {\n\t\t\t\t\t\t\tL.DomUtil.addClass(toolbarContainer.childNodes[0], topClassName);\n\t\t\t\t\t\t}\n\t\t\t\t\t\taddedTopClass = true;\n\t\t\t\t\t}\n\n\t\t\t\t\tcontainer.appendChild(toolbarContainer);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn container;\n\t},\n\n\t// @method onRemove(): void\n\t// Removes the toolbars from the map toolbar container\n\tonRemove: function () {\n\t\tfor (var toolbarId in this._toolbars) {\n\t\t\tif (this._toolbars.hasOwnProperty(toolbarId)) {\n\t\t\t\tthis._toolbars[toolbarId].removeToolbar();\n\t\t\t}\n\t\t}\n\t},\n\n\t// @method setDrawingOptions(options): void\n\t// Sets options to all toolbar instances\n\tsetDrawingOptions: function (options) {\n\t\tfor (var toolbarId in this._toolbars) {\n\t\t\tif (this._toolbars[toolbarId] instanceof L.DrawToolbar) {\n\t\t\t\tthis._toolbars[toolbarId].setOptions(options);\n\t\t\t}\n\t\t}\n\t},\n\n\t_toolbarEnabled: function (e) {\n\t\tvar enabledToolbar = e.target;\n\n\t\tfor (var toolbarId in this._toolbars) {\n\t\t\tif (this._toolbars[toolbarId] !== enabledToolbar) {\n\t\t\t\tthis._toolbars[toolbarId].disable();\n\t\t\t}\n\t\t}\n\t}\n});\n\nL.Map.mergeOptions({\n\tdrawControlTooltips: true,\n\tdrawControl: false\n});\n\nL.Map.addInitHook(function () {\n\tif (this.options.drawControl) {\n\t\tthis.drawControl = new L.Control.Draw();\n\t\tthis.addControl(this.drawControl);\n\t}\n});\n","/**\n * @class L.Draw.Toolbar\n * @aka Toolbar\n *\n * The toolbar class of the API — it is used to create the ui\n * This will be depreciated\n *\n * @example\n *\n * ```js\n * var toolbar = L.Toolbar();\n * toolbar.addToolbar(map);\n * ```\n *\n * ### Disabling a toolbar\n *\n * If you do not want a particular toolbar in your app you can turn it off by setting the toolbar to false.\n *\n * ```js\n * var drawControl = new L.Control.Draw({\n * draw: false,\n * edit: {\n * featureGroup: editableLayers\n * }\n * });\n * ```\n *\n * ### Disabling a toolbar item\n *\n * If you want to turn off a particular toolbar item, set it to false. The following disables drawing polygons and\n * markers. It also turns off the ability to edit layers.\n *\n * ```js\n * var drawControl = new L.Control.Draw({\n * draw: {\n * polygon: false,\n * marker: false\n * },\n * edit: {\n * featureGroup: editableLayers,\n * edit: false\n * }\n * });\n * ```\n */\nL.Toolbar = L.Class.extend({\n\t// @section Methods for modifying the toolbar\n\n\t// @method initialize(options): void\n\t// Toolbar constructor\n\tinitialize: function (options) {\n\t\tL.setOptions(this, options);\n\n\t\tthis._modes = {};\n\t\tthis._actionButtons = [];\n\t\tthis._activeMode = null;\n\n\t\tvar version = L.version.split('.');\n\t\t//If Version is >= 1.2.0\n\t\tif (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) {\n\t\t\tL.Toolbar.include(L.Evented.prototype);\n\t\t} else {\n\t\t\tL.Toolbar.include(L.Mixin.Events);\n\t\t}\n\t},\n\n\t// @method enabled(): boolean\n\t// Gets a true/false of whether the toolbar is enabled\n\tenabled: function () {\n\t\treturn this._activeMode !== null;\n\t},\n\n\t// @method disable(): void\n\t// Disables the toolbar\n\tdisable: function () {\n\t\tif (!this.enabled()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._activeMode.handler.disable();\n\t},\n\n\t// @method addToolbar(map): L.DomUtil\n\t// Adds the toolbar to the map and returns the toolbar dom element\n\taddToolbar: function (map) {\n\t\tvar container = L.DomUtil.create('div', 'leaflet-draw-section'),\n\t\t\tbuttonIndex = 0,\n\t\t\tbuttonClassPrefix = this._toolbarClass || '',\n\t\t\tmodeHandlers = this.getModeHandlers(map),\n\t\t\ti;\n\n\t\tthis._toolbarContainer = L.DomUtil.create('div', 'leaflet-draw-toolbar leaflet-bar');\n\t\tthis._map = map;\n\n\t\tfor (i = 0; i < modeHandlers.length; i++) {\n\t\t\tif (modeHandlers[i].enabled) {\n\t\t\t\tthis._initModeHandler(\n\t\t\t\t\tmodeHandlers[i].handler,\n\t\t\t\t\tthis._toolbarContainer,\n\t\t\t\t\tbuttonIndex++,\n\t\t\t\t\tbuttonClassPrefix,\n\t\t\t\t\tmodeHandlers[i].title\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// if no buttons were added, do not add the toolbar\n\t\tif (!buttonIndex) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Save button index of the last button, -1 as we would have ++ after the last button\n\t\tthis._lastButtonIndex = --buttonIndex;\n\n\t\t// Create empty actions part of the toolbar\n\t\tthis._actionsContainer = L.DomUtil.create('ul', 'leaflet-draw-actions');\n\n\t\t// Add draw and cancel containers to the control container\n\t\tcontainer.appendChild(this._toolbarContainer);\n\t\tcontainer.appendChild(this._actionsContainer);\n\n\t\treturn container;\n\t},\n\n\t// @method removeToolbar(): void\n\t// Removes the toolbar and drops the handler event listeners\n\tremoveToolbar: function () {\n\t\t// Dispose each handler\n\t\tfor (var handlerId in this._modes) {\n\t\t\tif (this._modes.hasOwnProperty(handlerId)) {\n\t\t\t\t// Unbind handler button\n\t\t\t\tthis._disposeButton(\n\t\t\t\t\tthis._modes[handlerId].button,\n\t\t\t\t\tthis._modes[handlerId].handler.enable,\n\t\t\t\t\tthis._modes[handlerId].handler\n\t\t\t\t);\n\n\t\t\t\t// Make sure is disabled\n\t\t\t\tthis._modes[handlerId].handler.disable();\n\n\t\t\t\t// Unbind handler\n\t\t\t\tthis._modes[handlerId].handler\n\t\t\t\t\t.off('enabled', this._handlerActivated, this)\n\t\t\t\t\t.off('disabled', this._handlerDeactivated, this);\n\t\t\t}\n\t\t}\n\t\tthis._modes = {};\n\n\t\t// Dispose the actions toolbar\n\t\tfor (var i = 0, l = this._actionButtons.length; i < l; i++) {\n\t\t\tthis._disposeButton(\n\t\t\t\tthis._actionButtons[i].button,\n\t\t\t\tthis._actionButtons[i].callback,\n\t\t\t\tthis\n\t\t\t);\n\t\t}\n\t\tthis._actionButtons = [];\n\t\tthis._actionsContainer = null;\n\t},\n\n\t_initModeHandler: function (handler, container, buttonIndex, classNamePredix, buttonTitle) {\n\t\tvar type = handler.type;\n\n\t\tthis._modes[type] = {};\n\n\t\tthis._modes[type].handler = handler;\n\n\t\tthis._modes[type].button = this._createButton({\n\t\t\ttype: type,\n\t\t\ttitle: buttonTitle,\n\t\t\tclassName: classNamePredix + '-' + type,\n\t\t\tcontainer: container,\n\t\t\tcallback: this._modes[type].handler.enable,\n\t\t\tcontext: this._modes[type].handler\n\t\t});\n\n\t\tthis._modes[type].buttonIndex = buttonIndex;\n\n\t\tthis._modes[type].handler\n\t\t\t.on('enabled', this._handlerActivated, this)\n\t\t\t.on('disabled', this._handlerDeactivated, this);\n\t},\n\n\t/* Detect iOS based on browser User Agent, based on:\n\t * http://stackoverflow.com/a/9039885 */\n\t_detectIOS: function () {\n\t\tvar iOS = (/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream);\n\t\treturn iOS;\n\t},\n\n\t_createButton: function (options) {\n\n\t\tvar link = L.DomUtil.create('a', options.className || '', options.container);\n\t\t// Screen reader tag\n\t\tvar sr = L.DomUtil.create('span', 'sr-only', options.container);\n\n\t\tlink.href = '#';\n\t\tlink.appendChild(sr);\n\n\t\tif (options.title) {\n\t\t\tlink.title = options.title;\n\t\t\tsr.innerHTML = options.title;\n\t\t}\n\n\t\tif (options.text) {\n\t\t\tlink.innerHTML = options.text;\n\t\t\tsr.innerHTML = options.text;\n\t\t}\n\n\t\t/* iOS does not use click events */\n\t\tvar buttonEvent = this._detectIOS() ? 'touchstart' : 'click';\n\n\t\tL.DomEvent\n\t\t\t.on(link, 'click', L.DomEvent.stopPropagation)\n\t\t\t.on(link, 'mousedown', L.DomEvent.stopPropagation)\n\t\t\t.on(link, 'dblclick', L.DomEvent.stopPropagation)\n\t\t\t.on(link, 'touchstart', L.DomEvent.stopPropagation)\n\t\t\t.on(link, 'click', L.DomEvent.preventDefault)\n\t\t\t.on(link, buttonEvent, options.callback, options.context);\n\n\t\treturn link;\n\t},\n\n\t_disposeButton: function (button, callback) {\n\t\t/* iOS does not use click events */\n\t\tvar buttonEvent = this._detectIOS() ? 'touchstart' : 'click';\n\n\t\tL.DomEvent\n\t\t\t.off(button, 'click', L.DomEvent.stopPropagation)\n\t\t\t.off(button, 'mousedown', L.DomEvent.stopPropagation)\n\t\t\t.off(button, 'dblclick', L.DomEvent.stopPropagation)\n\t\t\t.off(button, 'touchstart', L.DomEvent.stopPropagation)\n\t\t\t.off(button, 'click', L.DomEvent.preventDefault)\n\t\t\t.off(button, buttonEvent, callback);\n\t},\n\n\t_handlerActivated: function (e) {\n\t\t// Disable active mode (if present)\n\t\tthis.disable();\n\n\t\t// Cache new active feature\n\t\tthis._activeMode = this._modes[e.handler];\n\n\t\tL.DomUtil.addClass(this._activeMode.button, 'leaflet-draw-toolbar-button-enabled');\n\n\t\tthis._showActionsToolbar();\n\n\t\tthis.fire('enable');\n\t},\n\n\t_handlerDeactivated: function () {\n\t\tthis._hideActionsToolbar();\n\n\t\tL.DomUtil.removeClass(this._activeMode.button, 'leaflet-draw-toolbar-button-enabled');\n\n\t\tthis._activeMode = null;\n\n\t\tthis.fire('disable');\n\t},\n\n\t_createActions: function (handler) {\n\t\tvar container = this._actionsContainer,\n\t\t\tbuttons = this.getActions(handler),\n\t\t\tl = buttons.length,\n\t\t\tli, di, dl, button;\n\n\t\t// Dispose the actions toolbar (todo: dispose only not used buttons)\n\t\tfor (di = 0, dl = this._actionButtons.length; di < dl; di++) {\n\t\t\tthis._disposeButton(this._actionButtons[di].button, this._actionButtons[di].callback);\n\t\t}\n\t\tthis._actionButtons = [];\n\n\t\t// Remove all old buttons\n\t\twhile (container.firstChild) {\n\t\t\tcontainer.removeChild(container.firstChild);\n\t\t}\n\n\t\tfor (var i = 0; i < l; i++) {\n\t\t\tif ('enabled' in buttons[i] && !buttons[i].enabled) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tli = L.DomUtil.create('li', '', container);\n\n\t\t\tbutton = this._createButton({\n\t\t\t\ttitle: buttons[i].title,\n\t\t\t\ttext: buttons[i].text,\n\t\t\t\tcontainer: li,\n\t\t\t\tcallback: buttons[i].callback,\n\t\t\t\tcontext: buttons[i].context\n\t\t\t});\n\n\t\t\tthis._actionButtons.push({\n\t\t\t\tbutton: button,\n\t\t\t\tcallback: buttons[i].callback\n\t\t\t});\n\t\t}\n\t},\n\n\t_showActionsToolbar: function () {\n\t\tvar buttonIndex = this._activeMode.buttonIndex,\n\t\t\tlastButtonIndex = this._lastButtonIndex,\n\t\t\ttoolbarPosition = this._activeMode.button.offsetTop - 1;\n\n\t\t// Recreate action buttons on every click\n\t\tthis._createActions(this._activeMode.handler);\n\n\t\t// Correctly position the cancel button\n\t\tthis._actionsContainer.style.top = toolbarPosition + 'px';\n\n\t\tif (buttonIndex === 0) {\n\t\t\tL.DomUtil.addClass(this._toolbarContainer, 'leaflet-draw-toolbar-notop');\n\t\t\tL.DomUtil.addClass(this._actionsContainer, 'leaflet-draw-actions-top');\n\t\t}\n\n\t\tif (buttonIndex === lastButtonIndex) {\n\t\t\tL.DomUtil.addClass(this._toolbarContainer, 'leaflet-draw-toolbar-nobottom');\n\t\t\tL.DomUtil.addClass(this._actionsContainer, 'leaflet-draw-actions-bottom');\n\t\t}\n\n\t\tthis._actionsContainer.style.display = 'block';\n\t\tthis._map.fire(L.Draw.Event.TOOLBAROPENED);\n\t},\n\n\t_hideActionsToolbar: function () {\n\t\tthis._actionsContainer.style.display = 'none';\n\n\t\tL.DomUtil.removeClass(this._toolbarContainer, 'leaflet-draw-toolbar-notop');\n\t\tL.DomUtil.removeClass(this._toolbarContainer, 'leaflet-draw-toolbar-nobottom');\n\t\tL.DomUtil.removeClass(this._actionsContainer, 'leaflet-draw-actions-top');\n\t\tL.DomUtil.removeClass(this._actionsContainer, 'leaflet-draw-actions-bottom');\n\t\tthis._map.fire(L.Draw.Event.TOOLBARCLOSED);\n\t}\n});\n","L.Draw = L.Draw || {};\n/**\n * @class L.Draw.Tooltip\n * @aka Tooltip\n *\n * The tooltip class — it is used to display the tooltip while drawing\n * This will be depreciated\n *\n * @example\n *\n * ```js\n * var tooltip = L.Draw.Tooltip();\n * ```\n *\n */\nL.Draw.Tooltip = L.Class.extend({\n\n\t// @section Methods for modifying draw state\n\n\t// @method initialize(map): void\n\t// Tooltip constructor\n\tinitialize: function (map) {\n\t\tthis._map = map;\n\t\tthis._popupPane = map._panes.popupPane;\n\t\tthis._visible = false;\n\n\t\tthis._container = map.options.drawControlTooltips ?\n\t\t\tL.DomUtil.create('div', 'leaflet-draw-tooltip', this._popupPane) : null;\n\t\tthis._singleLineLabel = false;\n\n\t\tthis._map.on('mouseout', this._onMouseOut, this);\n\t},\n\n\t// @method dispose(): void\n\t// Remove Tooltip DOM and unbind events\n\tdispose: function () {\n\t\tthis._map.off('mouseout', this._onMouseOut, this);\n\n\t\tif (this._container) {\n\t\t\tthis._popupPane.removeChild(this._container);\n\t\t\tthis._container = null;\n\t\t}\n\t},\n\n\t// @method updateContent(labelText): this\n\t// Changes the tooltip text to string in function call\n\tupdateContent: function (labelText) {\n\t\tif (!this._container) {\n\t\t\treturn this;\n\t\t}\n\t\tlabelText.subtext = labelText.subtext || '';\n\n\t\t// update the vertical position (only if changed)\n\t\tif (labelText.subtext.length === 0 && !this._singleLineLabel) {\n\t\t\tL.DomUtil.addClass(this._container, 'leaflet-draw-tooltip-single');\n\t\t\tthis._singleLineLabel = true;\n\t\t}\n\t\telse if (labelText.subtext.length > 0 && this._singleLineLabel) {\n\t\t\tL.DomUtil.removeClass(this._container, 'leaflet-draw-tooltip-single');\n\t\t\tthis._singleLineLabel = false;\n\t\t}\n\n\t\tthis._container.innerHTML =\n\t\t\t(labelText.subtext.length > 0 ?\n\t\t\t\t'<span class=\"leaflet-draw-tooltip-subtext\">' + labelText.subtext + '</span>' + '<br />' : '') +\n\t\t\t'<span>' + labelText.text + '</span>';\n\n\t\tif (!labelText.text && !labelText.subtext) {\n\t\t\tthis._visible = false;\n\t\t\tthis._container.style.visibility = 'hidden';\n\t\t} else {\n\t\t\tthis._visible = true;\n\t\t\tthis._container.style.visibility = 'inherit';\n\t\t}\n\n\t\treturn this;\n\t},\n\n\t// @method updatePosition(latlng): this\n\t// Changes the location of the tooltip\n\tupdatePosition: function (latlng) {\n\t\tvar pos = this._map.latLngToLayerPoint(latlng),\n\t\t\ttooltipContainer = this._container;\n\n\t\tif (this._container) {\n\t\t\tif (this._visible) {\n\t\t\t\ttooltipContainer.style.visibility = 'inherit';\n\t\t\t}\n\t\t\tL.DomUtil.setPosition(tooltipContainer, pos);\n\t\t}\n\n\t\treturn this;\n\t},\n\n\t// @method showAsError(): this\n\t// Applies error class to tooltip\n\tshowAsError: function () {\n\t\tif (this._container) {\n\t\t\tL.DomUtil.addClass(this._container, 'leaflet-error-draw-tooltip');\n\t\t}\n\t\treturn this;\n\t},\n\n\t// @method removeError(): this\n\t// Removes the error class from the tooltip\n\tremoveError: function () {\n\t\tif (this._container) {\n\t\t\tL.DomUtil.removeClass(this._container, 'leaflet-error-draw-tooltip');\n\t\t}\n\t\treturn this;\n\t},\n\n\t_onMouseOut: function () {\n\t\tif (this._container) {\n\t\t\tthis._container.style.visibility = 'hidden';\n\t\t}\n\t}\n});\n","/**\n * @class L.DrawToolbar\n * @aka Toolbar\n */\nL.DrawToolbar = L.Toolbar.extend({\n\n\tstatics: {\n\t\tTYPE: 'draw'\n\t},\n\n\toptions: {\n\t\tpolyline: {},\n\t\tpolygon: {},\n\t\trectangle: {},\n\t\tcircle: {},\n\t\tmarker: {},\n\t\tcirclemarker: {}\n\t},\n\n\t// @method initialize(): void\n\tinitialize: function (options) {\n\t\t// Ensure that the options are merged correctly since L.extend is only shallow\n\t\tfor (var type in this.options) {\n\t\t\tif (this.options.hasOwnProperty(type)) {\n\t\t\t\tif (options[type]) {\n\t\t\t\t\toptions[type] = L.extend({}, this.options[type], options[type]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis._toolbarClass = 'leaflet-draw-draw';\n\t\tL.Toolbar.prototype.initialize.call(this, options);\n\t},\n\n\t// @method getModeHandlers(): object\n\t// Get mode handlers information\n\tgetModeHandlers: function (map) {\n\t\treturn [\n\t\t\t{\n\t\t\t\tenabled: this.options.polyline,\n\t\t\t\thandler: new L.Draw.Polyline(map, this.options.polyline),\n\t\t\t\ttitle: L.drawLocal.draw.toolbar.buttons.polyline\n\t\t\t},\n\t\t\t{\n\t\t\t\tenabled: this.options.polygon,\n\t\t\t\thandler: new L.Draw.Polygon(map, this.options.polygon),\n\t\t\t\ttitle: L.drawLocal.draw.toolbar.buttons.polygon\n\t\t\t},\n\t\t\t{\n\t\t\t\tenabled: this.options.rectangle,\n\t\t\t\thandler: new L.Draw.Rectangle(map, this.options.rectangle),\n\t\t\t\ttitle: L.drawLocal.draw.toolbar.buttons.rectangle\n\t\t\t},\n\t\t\t{\n\t\t\t\tenabled: this.options.circle,\n\t\t\t\thandler: new L.Draw.Circle(map, this.options.circle),\n\t\t\t\ttitle: L.drawLocal.draw.toolbar.buttons.circle\n\t\t\t},\n\t\t\t{\n\t\t\t\tenabled: this.options.marker,\n\t\t\t\thandler: new L.Draw.Marker(map, this.options.marker),\n\t\t\t\ttitle: L.drawLocal.draw.toolbar.buttons.marker\n\t\t\t},\n\t\t\t{\n\t\t\t\tenabled: this.options.circlemarker,\n\t\t\t\thandler: new L.Draw.CircleMarker(map, this.options.circlemarker),\n\t\t\t\ttitle: L.drawLocal.draw.toolbar.buttons.circlemarker\n\t\t\t}\n\t\t];\n\t},\n\n\t// @method getActions(): object\n\t// Get action information\n\tgetActions: function (handler) {\n\t\treturn [\n\t\t\t{\n\t\t\t\tenabled: handler.completeShape,\n\t\t\t\ttitle: L.drawLocal.draw.toolbar.finish.title,\n\t\t\t\ttext: L.drawLocal.draw.toolbar.finish.text,\n\t\t\t\tcallback: handler.completeShape,\n\t\t\t\tcontext: handler\n\t\t\t},\n\t\t\t{\n\t\t\t\tenabled: handler.deleteLastVertex,\n\t\t\t\ttitle: L.drawLocal.draw.toolbar.undo.title,\n\t\t\t\ttext: L.drawLocal.draw.toolbar.undo.text,\n\t\t\t\tcallback: handler.deleteLastVertex,\n\t\t\t\tcontext: handler\n\t\t\t},\n\t\t\t{\n\t\t\t\ttitle: L.drawLocal.draw.toolbar.actions.title,\n\t\t\t\ttext: L.drawLocal.draw.toolbar.actions.text,\n\t\t\t\tcallback: this.disable,\n\t\t\t\tcontext: this\n\t\t\t}\n\t\t];\n\t},\n\n\t// @method setOptions(): void\n\t// Sets the options to the toolbar\n\tsetOptions: function (options) {\n\t\tL.setOptions(this, options);\n\n\t\tfor (var type in this._modes) {\n\t\t\tif (this._modes.hasOwnProperty(type) && options.hasOwnProperty(type)) {\n\t\t\t\tthis._modes[type].handler.setOptions(options[type]);\n\t\t\t}\n\t\t}\n\t}\n});\n","/*L.Map.mergeOptions({\n editControl: true\n });*/\n/**\n * @class L.EditToolbar\n * @aka EditToolbar\n */\nL.EditToolbar = L.Toolbar.extend({\n\tstatics: {\n\t\tTYPE: 'edit'\n\t},\n\n\toptions: {\n\t\tedit: {\n\t\t\tselectedPathOptions: {\n\t\t\t\tdashArray: '10, 10',\n\n\t\t\t\tfill: true,\n\t\t\t\tfillColor: '#fe57a1',\n\t\t\t\tfillOpacity: 0.1,\n\n\t\t\t\t// Whether to user the existing layers color\n\t\t\t\tmaintainColor: false\n\t\t\t}\n\t\t},\n\t\tremove: {},\n\t\tpoly: null,\n\t\tfeatureGroup: null /* REQUIRED! TODO: perhaps if not set then all layers on the map are selectable? */\n\t},\n\n\t// @method intialize(): void\n\tinitialize: function (options) {\n\t\t// Need to set this manually since null is an acceptable value here\n\t\tif (options.edit) {\n\t\t\tif (typeof options.edit.selectedPathOptions === 'undefined') {\n\t\t\t\toptions.edit.selectedPathOptions = this.options.edit.selectedPathOptions;\n\t\t\t}\n\t\t\toptions.edit.selectedPathOptions = L.extend({}, this.options.edit.selectedPathOptions, options.edit.selectedPathOptions);\n\t\t}\n\n\t\tif (options.remove) {\n\t\t\toptions.remove = L.extend({}, this.options.remove, options.remove);\n\t\t}\n\n\t\tif (options.poly) {\n\t\t\toptions.poly = L.extend({}, this.options.poly, options.poly);\n\t\t}\n\n\t\tthis._toolbarClass = 'leaflet-draw-edit';\n\t\tL.Toolbar.prototype.initialize.call(this, options);\n\n\t\tthis._selectedFeatureCount = 0;\n\t},\n\n\t// @method getModeHandlers(): object\n\t// Get mode handlers information\n\tgetModeHandlers: function (map) {\n\t\tvar featureGroup = this.options.featureGroup;\n\t\treturn [\n\t\t\t{\n\t\t\t\tenabled: this.options.edit,\n\t\t\t\thandler: new L.EditToolbar.Edit(map, {\n\t\t\t\t\tfeatureGroup: featureGroup,\n\t\t\t\t\tselectedPathOptions: this.options.edit.selectedPathOptions,\n\t\t\t\t\tpoly: this.options.poly\n\t\t\t\t}),\n\t\t\t\ttitle: L.drawLocal.edit.toolbar.buttons.edit\n\t\t\t},\n\t\t\t{\n\t\t\t\tenabled: this.options.remove,\n\t\t\t\thandler: new L.EditToolbar.Delete(map, {\n\t\t\t\t\tfeatureGroup: featureGroup\n\t\t\t\t}),\n\t\t\t\ttitle: L.drawLocal.edit.toolbar.buttons.remove\n\t\t\t}\n\t\t];\n\t},\n\n\t// @method getActions(): object\n\t// Get actions information\n\tgetActions: function (handler) {\n\t\tvar actions = [\n\t\t\t{\n\t\t\t\ttitle: L.drawLocal.edit.toolbar.actions.save.title,\n\t\t\t\ttext: L.drawLocal.edit.toolbar.actions.save.text,\n\t\t\t\tcallback: this._save,\n\t\t\t\tcontext: this\n\t\t\t},\n\t\t\t{\n\t\t\t\ttitle: L.drawLocal.edit.toolbar.actions.cancel.title,\n\t\t\t\ttext: L.drawLocal.edit.toolbar.actions.cancel.text,\n\t\t\t\tcallback: this.disable,\n\t\t\t\tcontext: this\n\t\t\t}\n\t\t];\n\n\t\tif (handler.removeAllLayers) {\n\t\t\tactions.push({\n\t\t\t\ttitle: L.drawLocal.edit.toolbar.actions.clearAll.title,\n\t\t\t\ttext: L.drawLocal.edit.toolbar.actions.clearAll.text,\n\t\t\t\tcallback: this._clearAllLayers,\n\t\t\t\tcontext: this\n\t\t\t});\n\t\t}\n\n\t\treturn actions;\n\t},\n\n\t// @method addToolbar(map): L.DomUtil\n\t// Adds the toolbar to the map\n\taddToolbar: function (map) {\n\t\tvar container = L.Toolbar.prototype.addToolbar.call(this, map);\n\n\t\tthis._checkDisabled();\n\n\t\tthis.options.featureGroup.on('layeradd layerremove', this._checkDisabled, this);\n\n\t\treturn container;\n\t},\n\n\t// @method removeToolbar(): void\n\t// Removes the toolbar from the map\n\tremoveToolbar: function () {\n\t\tthis.options.featureGroup.off('layeradd layerremove', this._checkDisabled, this);\n\n\t\tL.Toolbar.prototype.removeToolbar.call(this);\n\t},\n\n\t// @method disable(): void\n\t// Disables the toolbar\n\tdisable: function () {\n\t\tif (!this.enabled()) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._activeMode.handler.revertLayers();\n\n\t\tL.Toolbar.prototype.disable.call(this);\n\t},\n\n\t_save: function () {\n\t\tthis._activeMode.handler.save();\n\t\tif (this._activeMode) {\n\t\t\tthis._activeMode.handler.disable();\n\t\t}\n\t},\n\n\t_clearAllLayers: function () {\n\t\tthis._activeMode.handler.removeAllLayers();\n\t\tif (this._activeMode) {\n\t\t\tthis._activeMode.handler.disable();\n\t\t}\n\t},\n\n\t_checkDisabled: function () {\n\t\tvar featureGroup = this.options.featureGroup,\n\t\t\thasLayers = featureGroup.getLayers().length !== 0,\n\t\t\tbutton;\n\n\t\tif (this.options.edit) {\n\t\t\tbutton = this._modes[L.EditToolbar.Edit.TYPE].button;\n\n\t\t\tif (hasLayers) {\n\t\t\t\tL.DomUtil.removeClass(button, 'leaflet-disabled');\n\t\t\t} else {\n\t\t\t\tL.DomUtil.addClass(button, 'leaflet-disabled');\n\t\t\t}\n\n\t\t\tbutton.setAttribute(\n\t\t\t\t'title',\n\t\t\t\thasLayers ?\n\t\t\t\t\tL.drawLocal.edit.toolbar.buttons.edit\n\t\t\t\t\t: L.drawLocal.edit.toolbar.buttons.editDisabled\n\t\t\t);\n\t\t}\n\n\t\tif (this.options.remove) {\n\t\t\tbutton = this._modes[L.EditToolbar.Delete.TYPE].button;\n\n\t\t\tif (hasLayers) {\n\t\t\t\tL.DomUtil.removeClass(button, 'leaflet-disabled');\n\t\t\t} else {\n\t\t\t\tL.DomUtil.addClass(button, 'leaflet-disabled');\n\t\t\t}\n\n\t\t\tbutton.setAttribute(\n\t\t\t\t'title',\n\t\t\t\thasLayers ?\n\t\t\t\t\tL.drawLocal.edit.toolbar.buttons.remove\n\t\t\t\t\t: L.drawLocal.edit.toolbar.buttons.removeDisabled\n\t\t\t);\n\t\t}\n\t}\n});\n","/**\n * @class L.EditToolbar.Edit\n * @aka EditToolbar.Edit\n */\nL.EditToolbar.Edit = L.Handler.extend({\n\tstatics: {\n\t\tTYPE: 'edit'\n\t},\n\n\t// @method intialize(): void\n\tinitialize: function (map, options) {\n\t\tL.Handler.prototype.initialize.call(this, map);\n\n\t\tL.setOptions(this, options);\n\n\t\t// Store the selectable layer group for ease of access\n\t\tthis._featureGroup = options.featureGroup;\n\n\t\tif (!(this._featureGroup instanceof L.FeatureGroup)) {\n\t\t\tthrow new Error('options.featureGroup must be a L.FeatureGroup');\n\t\t}\n\n\t\tthis._uneditedLayerProps = {};\n\n\t\t// Save the type so super can fire, need to do this as cannot do this.TYPE :(\n\t\tthis.type = L.EditToolbar.Edit.TYPE;\n\n\t\tvar version = L.version.split('.');\n\t\t//If Version is >= 1.2.0\n\t\tif (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) {\n\t\t\tL.EditToolbar.Edit.include(L.Evented.prototype);\n\t\t} else {\n\t\t\tL.EditToolbar.Edit.include(L.Mixin.Events);\n\t\t}\n\t},\n\n\t// @method enable(): void\n\t// Enable the edit toolbar\n\tenable: function () {\n\t\tif (this._enabled || !this._hasAvailableLayers()) {\n\t\t\treturn;\n\t\t}\n\t\tthis.fire('enabled', {handler: this.type});\n\t\t//this disable other handlers\n\n\t\tthis._map.fire(L.Draw.Event.EDITSTART, {handler: this.type});\n\t\t//allow drawLayer to be updated before beginning edition.\n\n\t\tL.Handler.prototype.enable.call(this);\n\t\tthis._featureGroup\n\t\t\t.on('layeradd', this._enableLayerEdit, this)\n\t\t\t.on('layerremove', this._disableLayerEdit, this);\n\t},\n\n\t// @method disable(): void\n\t// Disable the edit toolbar\n\tdisable: function () {\n\t\tif (!this._enabled) {\n\t\t\treturn;\n\t\t}\n\t\tthis._featureGroup\n\t\t\t.off('layeradd', this._enableLayerEdit, this)\n\t\t\t.off('layerremove', this._disableLayerEdit, this);\n\t\tL.Handler.prototype.disable.call(this);\n\t\tthis._map.fire(L.Draw.Event.EDITSTOP, {handler: this.type});\n\t\tthis.fire('disabled', {handler: this.type});\n\t},\n\n\t// @method addHooks(): void\n\t// Add listener hooks for this handler\n\taddHooks: function () {\n\t\tvar map = this._map;\n\n\t\tif (map) {\n\t\t\tmap.getContainer().focus();\n\n\t\t\tthis._featureGroup.eachLayer(this._enableLayerEdit, this);\n\n\t\t\tthis._tooltip = new L.Draw.Tooltip(this._map);\n\t\t\tthis._tooltip.updateContent({\n\t\t\t\ttext: L.drawLocal.edit.handlers.edit.tooltip.text,\n\t\t\t\tsubtext: L.drawLocal.edit.handlers.edit.tooltip.subtext\n\t\t\t});\n\n\t\t\t// Quickly access the tooltip to update for intersection checking\n\t\t\tmap._editTooltip = this._tooltip;\n\n\t\t\tthis._updateTooltip();\n\n\t\t\tthis._map\n\t\t\t\t.on('mousemove', this._onMouseMove, this)\n\t\t\t\t.on('touchmove', this._onMouseMove, this)\n\t\t\t\t.on('MSPointerMove', this._onMouseMove, this)\n\t\t\t\t.on(L.Draw.Event.EDITVERTEX, this._updateTooltip, this);\n\t\t}\n\t},\n\n\t// @method removeHooks(): void\n\t// Remove listener hooks for this handler\n\tremoveHooks: function () {\n\t\tif (this._map) {\n\t\t\t// Clean up selected layers.\n\t\t\tthis._featureGroup.eachLayer(this._disableLayerEdit, this);\n\n\t\t\t// Clear the backups of the original layers\n\t\t\tthis._uneditedLayerProps = {};\n\n\t\t\tthis._tooltip.dispose();\n\t\t\tthis._tooltip = null;\n\n\t\t\tthis._map\n\t\t\t\t.off('mousemove', this._onMouseMove, this)\n\t\t\t\t.off('touchmove', this._onMouseMove, this)\n\t\t\t\t.off('MSPointerMove', this._onMouseMove, this)\n\t\t\t\t.off(L.Draw.Event.EDITVERTEX, this._updateTooltip, this);\n\t\t}\n\t},\n\n\t// @method revertLayers(): void\n\t// Revert each layer's geometry changes\n\trevertLayers: function () {\n\t\tthis._featureGroup.eachLayer(function (layer) {\n\t\t\tthis._revertLayer(layer);\n\t\t}, this);\n\t},\n\n\t// @method save(): void\n\t// Save the layer geometries\n\tsave: function () {\n\t\tvar editedLayers = new L.LayerGroup();\n\t\tthis._featureGroup.eachLayer(function (layer) {\n\t\t\tif (layer.edited) {\n\t\t\t\teditedLayers.addLayer(layer);\n\t\t\t\tlayer.edited = false;\n\t\t\t}\n\t\t});\n\t\tthis._map.fire(L.Draw.Event.EDITED, {layers: editedLayers});\n\t},\n\n\t_backupLayer: function (layer) {\n\t\tvar id = L.Util.stamp(layer);\n\n\t\tif (!this._uneditedLayerProps[id]) {\n\t\t\t// Polyline, Polygon or Rectangle\n\t\t\tif (layer instanceof L.Polyline || layer instanceof L.Polygon || layer instanceof L.Rectangle) {\n\t\t\t\tthis._uneditedLayerProps[id] = {\n\t\t\t\t\tlatlngs: L.LatLngUtil.cloneLatLngs(layer.getLatLngs())\n\t\t\t\t};\n\t\t\t} else if (layer instanceof L.Circle) {\n\t\t\t\tthis._uneditedLayerProps[id] = {\n\t\t\t\t\tlatlng: L.LatLngUtil.cloneLatLng(layer.getLatLng()),\n\t\t\t\t\tradius: layer.getRadius()\n\t\t\t\t};\n\t\t\t} else if (layer instanceof L.Marker || layer instanceof L.CircleMarker) { // Marker\n\t\t\t\tthis._uneditedLayerProps[id] = {\n\t\t\t\t\tlatlng: L.LatLngUtil.cloneLatLng(layer.getLatLng())\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t},\n\n\t_getTooltipText: function () {\n\t\treturn ({\n\t\t\ttext: L.drawLocal.edit.handlers.edit.tooltip.text,\n\t\t\tsubtext: L.drawLocal.edit.handlers.edit.tooltip.subtext\n\t\t});\n\t},\n\n\t_updateTooltip: function () {\n\t\tthis._tooltip.updateContent(this._getTooltipText());\n\t},\n\n\t_revertLayer: function (layer) {\n\t\tvar id = L.Util.stamp(layer);\n\t\tlayer.edited = false;\n\t\tif (this._uneditedLayerProps.hasOwnProperty(id)) {\n\t\t\t// Polyline, Polygon or Rectangle\n\t\t\tif (layer instanceof L.Polyline || layer instanceof L.Polygon || layer instanceof L.Rectangle) {\n\t\t\t\tlayer.setLatLngs(this._uneditedLayerProps[id].latlngs);\n\t\t\t} else if (layer instanceof L.Circle) {\n\t\t\t\tlayer.setLatLng(this._uneditedLayerProps[id].latlng);\n\t\t\t\tlayer.setRadius(this._uneditedLayerProps[id].radius);\n\t\t\t} else if (layer instanceof L.Marker || layer instanceof L.CircleMarker) { // Marker or CircleMarker\n\t\t\t\tlayer.setLatLng(this._uneditedLayerProps[id].latlng);\n\t\t\t}\n\n\t\t\tlayer.fire('revert-edited', {layer: layer});\n\t\t}\n\t},\n\n\t_enableLayerEdit: function (e) {\n\t\tvar layer = e.layer || e.target || e,\n\t\t\tpathOptions, poly;\n\n\t\t// Back up this layer (if haven't before)\n\t\tthis._backupLayer(layer);\n\n\t\tif (this.options.poly) {\n\t\t\tpoly = L.Util.extend({}, this.options.poly);\n\t\t\tlayer.options.poly = poly;\n\t\t}\n\n\t\t// Set different style for editing mode\n\t\tif (this.options.selectedPathOptions) {\n\t\t\tpathOptions = L.Util.extend({}, this.options.selectedPathOptions);\n\n\t\t\t// Use the existing color of the layer\n\t\t\tif (pathOptions.maintainColor) {\n\t\t\t\tpathOptions.color = layer.options.color;\n\t\t\t\tpathOptions.fillColor = layer.options.fillColor;\n\t\t\t}\n\n\t\t\tlayer.options.original = L.extend({}, layer.options);\n\t\t\tlayer.options.editing = pathOptions;\n\n\t\t}\n\n\t\tif (layer instanceof L.Marker) {\n\t\t\tif (layer.editing) {\n\t\t\t\tlayer.editing.enable();\n\t\t\t}\n\t\t\tlayer.dragging.enable();\n\t\t\tlayer\n\t\t\t\t.on('dragend', this._onMarkerDragEnd)\n\t\t\t\t// #TODO: remove when leaflet finally fixes their draggable so it's touch friendly again.\n\t\t\t\t.on('touchmove', this._onTouchMove, this)\n\t\t\t\t.on('MSPointerMove', this._onTouchMove, this)\n\t\t\t\t.on('touchend', this._onMarkerDragEnd, this)\n\t\t\t\t.on('MSPointerUp', this._onMarkerDragEnd, this);\n\t\t} else {\n\t\t\tlayer.editing.enable();\n\t\t}\n\t},\n\n\t_disableLayerEdit: function (e) {\n\t\tvar layer = e.layer || e.target || e;\n\n\t\tlayer.edited = false;\n\t\tif (layer.editing) {\n\t\t\tlayer.editing.disable();\n\t\t}\n\n\t\tdelete layer.options.editing;\n\t\tdelete layer.options.original;\n\t\t// Reset layer styles to that of before select\n\t\tif (this._selectedPathOptions) {\n\t\t\tif (layer instanceof L.Marker) {\n\t\t\t\tthis._toggleMarkerHighlight(layer);\n\t\t\t} else {\n\t\t\t\t// reset the layer style to what is was before being selected\n\t\t\t\tlayer.setStyle(layer.options.previousOptions);\n\t\t\t\t// remove the cached options for the layer object\n\t\t\t\tdelete layer.options.previousOptions;\n\t\t\t}\n\t\t}\n\n\t\tif (layer instanceof L.Marker) {\n\t\t\tlayer.dragging.disable();\n\t\t\tlayer\n\t\t\t\t.off('dragend', this._onMarkerDragEnd, this)\n\t\t\t\t.off('touchmove', this._onTouchMove, this)\n\t\t\t\t.off('MSPointerMove', this._onTouchMove, this)\n\t\t\t\t.off('touchend', this._onMarkerDragEnd, this)\n\t\t\t\t.off('MSPointerUp', this._onMarkerDragEnd, this);\n\t\t} else {\n\t\t\tlayer.editing.disable();\n\t\t}\n\t},\n\n\t_onMouseMove: function (e) {\n\t\tthis._tooltip.updatePosition(e.latlng);\n\t},\n\n\t_onMarkerDragEnd: function (e) {\n\t\tvar layer = e.target;\n\t\tlayer.edited = true;\n\t\tthis._map.fire(L.Draw.Event.EDITMOVE, {layer: layer});\n\t},\n\n\t_onTouchMove: function (e) {\n\t\tvar touchEvent = e.originalEvent.changedTouches[0],\n\t\t\tlayerPoint = this._map.mouseEventToLayerPoint(touchEvent),\n\t\t\tlatlng = this._map.layerPointToLatLng(layerPoint);\n\t\te.target.setLatLng(latlng);\n\t},\n\n\t_hasAvailableLayers: function () {\n\t\treturn this._featureGroup.getLayers().length !== 0;\n\t}\n});\n","/**\n * @class L.EditToolbar.Delete\n * @aka EditToolbar.Delete\n */\nL.EditToolbar.Delete = L.Handler.extend({\n\tstatics: {\n\t\tTYPE: 'remove' // not delete as delete is reserved in js\n\t},\n\n\t// @method intialize(): void\n\tinitialize: function (map, options) {\n\t\tL.Handler.prototype.initialize.call(this, map);\n\n\t\tL.Util.setOptions(this, options);\n\n\t\t// Store the selectable layer group for ease of access\n\t\tthis._deletableLayers = this.options.featureGroup;\n\n\t\tif (!(this._deletableLayers instanceof L.FeatureGroup)) {\n\t\t\tthrow new Error('options.featureGroup must be a L.FeatureGroup');\n\t\t}\n\n\t\t// Save the type so super can fire, need to do this as cannot do this.TYPE :(\n\t\tthis.type = L.EditToolbar.Delete.TYPE;\n\n\t\tvar version = L.version.split('.');\n\t\t//If Version is >= 1.2.0\n\t\tif (parseInt(version[0], 10) === 1 && parseInt(version[1], 10) >= 2) {\n\t\t\tL.EditToolbar.Delete.include(L.Evented.prototype);\n\t\t} else {\n\t\t\tL.EditToolbar.Delete.include(L.Mixin.Events);\n\t\t}\n\n\t},\n\n\t// @method enable(): void\n\t// Enable the delete toolbar\n\tenable: function () {\n\t\tif (this._enabled || !this._hasAvailableLayers()) {\n\t\t\treturn;\n\t\t}\n\t\tthis.fire('enabled', {handler: this.type});\n\n\t\tthis._map.fire(L.Draw.Event.DELETESTART, {handler: this.type});\n\n\t\tL.Handler.prototype.enable.call(this);\n\n\t\tthis._deletableLayers\n\t\t\t.on('layeradd', this._enableLayerDelete, this)\n\t\t\t.on('layerremove', this._disableLayerDelete, this);\n\t},\n\n\t// @method disable(): void\n\t// Disable the delete toolbar\n\tdisable: function () {\n\t\tif (!this._enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis._deletableLayers\n\t\t\t.off('layeradd', this._enableLayerDelete, this)\n\t\t\t.off('layerremove', this._disableLayerDelete, this);\n\n\t\tL.Handler.prototype.disable.call(this);\n\n\t\tthis._map.fire(L.Draw.Event.DELETESTOP, {handler: this.type});\n\n\t\tthis.fire('disabled', {handler: this.type});\n\t},\n\n\t// @method addHooks(): void\n\t// Add listener hooks to this handler\n\taddHooks: function () {\n\t\tvar map = this._map;\n\n\t\tif (map) {\n\t\t\tmap.getContainer().focus();\n\n\t\t\tthis._deletableLayers.eachLayer(this._enableLayerDelete, this);\n\t\t\tthis._deletedLayers = new L.LayerGroup();\n\n\t\t\tthis._tooltip = new L.Draw.Tooltip(this._map);\n\t\t\tthis._tooltip.updateContent({text: L.drawLocal.edit.handlers.remove.tooltip.text});\n\n\t\t\tthis._map.on('mousemove', this._onMouseMove, this);\n\t\t}\n\t},\n\n\t// @method removeHooks(): void\n\t// Remove listener hooks from this handler\n\tremoveHooks: function () {\n\t\tif (this._map) {\n\t\t\tthis._deletableLayers.eachLayer(this._disableLayerDelete, this);\n\t\t\tthis._deletedLayers = null;\n\n\t\t\tthis._tooltip.dispose();\n\t\t\tthis._tooltip = null;\n\n\t\t\tthis._map.off('mousemove', this._onMouseMove, this);\n\t\t}\n\t},\n\n\t// @method revertLayers(): void\n\t// Revert the deleted layers back to their prior state.\n\trevertLayers: function () {\n\t\t// Iterate of the deleted layers and add them back into the featureGroup\n\t\tthis._deletedLayers.eachLayer(function (layer) {\n\t\t\tthis._deletableLayers.addLayer(layer);\n\t\t\tlayer.fire('revert-deleted', {layer: layer});\n\t\t}, this);\n\t},\n\n\t// @method save(): void\n\t// Save deleted layers\n\tsave: function () {\n\t\tthis._map.fire(L.Draw.Event.DELETED, {layers: this._deletedLayers});\n\t},\n\n\t// @method removeAllLayers(): void\n\t// Remove all delateable layers\n\tremoveAllLayers: function () {\n\t\t// Iterate of the delateable layers and add remove them\n\t\tthis._deletableLayers.eachLayer(function (layer) {\n\t\t\tthis._removeLayer({layer: layer});\n\t\t}, this);\n\t\tthis.save();\n\t},\n\n\t_enableLayerDelete: function (e) {\n\t\tvar layer = e.layer || e.target || e;\n\n\t\tlayer.on('click', this._removeLayer, this);\n\t},\n\n\t_disableLayerDelete: function (e) {\n\t\tvar layer = e.layer || e.target || e;\n\n\t\tlayer.off('click', this._removeLayer, this);\n\n\t\t// Remove from the deleted layers so we can't accidentally revert if the user presses cancel\n\t\tthis._deletedLayers.removeLayer(layer);\n\t},\n\n\t_removeLayer: function (e) {\n\t\tvar layer = e.layer || e.target || e;\n\n\t\tthis._deletableLayers.removeLayer(layer);\n\n\t\tthis._deletedLayers.addLayer(layer);\n\n\t\tlayer.fire('deleted');\n\t},\n\n\t_onMouseMove: function (e) {\n\t\tthis._tooltip.updatePosition(e.latlng);\n\t},\n\n\t_hasAvailableLayers: function () {\n\t\treturn this._deletableLayers.getLayers().length !== 0;\n\t}\n});\n"]} \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw.css b/www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw.css
new file mode 100644
index 00000000..a0194106
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw.css
@@ -0,0 +1,10 @@
+.leaflet-draw-section{position:relative}.leaflet-draw-toolbar{margin-top:12px}.leaflet-draw-toolbar-top{margin-top:0}.leaflet-draw-toolbar-notop a:first-child{border-top-right-radius:0}.leaflet-draw-toolbar-nobottom a:last-child{border-bottom-right-radius:0}.leaflet-draw-toolbar a{background-image:url('images/spritesheet.png');background-image:linear-gradient(transparent,transparent),url('images/spritesheet.svg');background-repeat:no-repeat;background-size:300px 30px;background-clip:padding-box}.leaflet-retina .leaflet-draw-toolbar a{background-image:url('images/spritesheet-2x.png');background-image:linear-gradient(transparent,transparent),url('images/spritesheet.svg')}
+.leaflet-draw a{display:block;text-align:center;text-decoration:none}.leaflet-draw a .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.leaflet-draw-actions{display:none;list-style:none;margin:0;padding:0;position:absolute;left:26px;top:0;white-space:nowrap}.leaflet-touch .leaflet-draw-actions{left:32px}.leaflet-right .leaflet-draw-actions{right:26px;left:auto}.leaflet-touch .leaflet-right .leaflet-draw-actions{right:32px;left:auto}.leaflet-draw-actions li{display:inline-block}
+.leaflet-draw-actions li:first-child a{border-left:0}.leaflet-draw-actions li:last-child a{-webkit-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.leaflet-right .leaflet-draw-actions li:last-child a{-webkit-border-radius:0;border-radius:0}.leaflet-right .leaflet-draw-actions li:first-child a{-webkit-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.leaflet-draw-actions a{background-color:#919187;border-left:1px solid #AAA;color:#FFF;font:11px/19px "Helvetica Neue",Arial,Helvetica,sans-serif;line-height:28px;text-decoration:none;padding-left:10px;padding-right:10px;height:28px}
+.leaflet-touch .leaflet-draw-actions a{font-size:12px;line-height:30px;height:30px}.leaflet-draw-actions-bottom{margin-top:0}.leaflet-draw-actions-top{margin-top:1px}.leaflet-draw-actions-top a,.leaflet-draw-actions-bottom a{height:27px;line-height:27px}.leaflet-draw-actions a:hover{background-color:#a0a098}.leaflet-draw-actions-top.leaflet-draw-actions-bottom a{height:26px;line-height:26px}.leaflet-draw-toolbar .leaflet-draw-draw-polyline{background-position:-2px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-polyline{background-position:0 -1px}
+.leaflet-draw-toolbar .leaflet-draw-draw-polygon{background-position:-31px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-polygon{background-position:-29px -1px}.leaflet-draw-toolbar .leaflet-draw-draw-rectangle{background-position:-62px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-rectangle{background-position:-60px -1px}.leaflet-draw-toolbar .leaflet-draw-draw-circle{background-position:-92px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-circle{background-position:-90px -1px}
+.leaflet-draw-toolbar .leaflet-draw-draw-marker{background-position:-122px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-marker{background-position:-120px -1px}.leaflet-draw-toolbar .leaflet-draw-draw-circlemarker{background-position:-273px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-draw-circlemarker{background-position:-271px -1px}.leaflet-draw-toolbar .leaflet-draw-edit-edit{background-position:-152px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-edit{background-position:-150px -1px}
+.leaflet-draw-toolbar .leaflet-draw-edit-remove{background-position:-182px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-remove{background-position:-180px -1px}.leaflet-draw-toolbar .leaflet-draw-edit-edit.leaflet-disabled{background-position:-212px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-edit.leaflet-disabled{background-position:-210px -1px}.leaflet-draw-toolbar .leaflet-draw-edit-remove.leaflet-disabled{background-position:-242px -2px}.leaflet-touch .leaflet-draw-toolbar .leaflet-draw-edit-remove.leaflet-disabled{background-position:-240px -2px}
+.leaflet-mouse-marker{background-color:#fff;cursor:crosshair}.leaflet-draw-tooltip{background:#363636;background:rgba(0,0,0,0.5);border:1px solid transparent;-webkit-border-radius:4px;border-radius:4px;color:#fff;font:12px/18px "Helvetica Neue",Arial,Helvetica,sans-serif;margin-left:20px;margin-top:-21px;padding:4px 8px;position:absolute;visibility:hidden;white-space:nowrap;z-index:6}.leaflet-draw-tooltip:before{border-right:6px solid black;border-right-color:rgba(0,0,0,0.5);border-top:6px solid transparent;border-bottom:6px solid transparent;content:"";position:absolute;top:7px;left:-7px}
+.leaflet-error-draw-tooltip{background-color:#f2dede;border:1px solid #e6b6bd;color:#b94a48}.leaflet-error-draw-tooltip:before{border-right-color:#e6b6bd}.leaflet-draw-tooltip-single{margin-top:-12px}.leaflet-draw-tooltip-subtext{color:#f8d5e4}.leaflet-draw-guide-dash{font-size:1%;opacity:.6;position:absolute;width:5px;height:5px}.leaflet-edit-marker-selected{background-color:rgba(254,87,161,0.1);border:4px dashed rgba(254,87,161,0.6);-webkit-border-radius:4px;border-radius:4px;box-sizing:content-box}
+.leaflet-edit-move{cursor:move}.leaflet-edit-resize{cursor:pointer}.leaflet-oldie .leaflet-draw-toolbar{border:1px solid #999} \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw.js b/www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw.js
new file mode 100644
index 00000000..71aeb33b
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.draw/leaflet.draw.js
@@ -0,0 +1,10 @@
+/*
+ Leaflet.draw 1.0.4, a plugin that adds drawing and editing tools to Leaflet powered maps.
+ (c) 2012-2017, Jacob Toye, Jon West, Smartrak, Leaflet
+
+ https://github.com/Leaflet/Leaflet.draw
+ http://leafletjs.com
+ */
+!function(t,e,i){function o(t,e){for(;(t=t.parentElement)&&!t.classList.contains(e););return t}L.drawVersion="1.0.4",L.Draw={},L.drawLocal={draw:{toolbar:{actions:{title:"Cancel drawing",text:"Cancel"},finish:{title:"Finish drawing",text:"Finish"},undo:{title:"Delete last point drawn",text:"Delete last point"},buttons:{polyline:"Draw a polyline",polygon:"Draw a polygon",rectangle:"Draw a rectangle",circle:"Draw a circle",marker:"Draw a marker",circlemarker:"Draw a circlemarker"}},handlers:{circle:{tooltip:{start:"Click and drag to draw circle."},radius:"Radius"},circlemarker:{tooltip:{start:"Click map to place circle marker."}},marker:{tooltip:{start:"Click map to place marker."}},polygon:{tooltip:{start:"Click to start drawing shape.",cont:"Click to continue drawing shape.",end:"Click first point to close this shape."}},polyline:{error:"<strong>Error:</strong> shape edges cannot cross!",tooltip:{start:"Click to start drawing line.",cont:"Click to continue drawing line.",end:"Click last point to finish line."}},rectangle:{tooltip:{start:"Click and drag to draw rectangle."}},simpleshape:{tooltip:{end:"Release mouse to finish drawing."}}}},edit:{toolbar:{actions:{save:{title:"Save changes",text:"Save"},cancel:{title:"Cancel editing, discards all changes",text:"Cancel"},clearAll:{title:"Clear all layers",text:"Clear All"}},buttons:{edit:"Edit layers",editDisabled:"No layers to edit",remove:"Delete layers",removeDisabled:"No layers to delete"}},handlers:{edit:{tooltip:{text:"Drag handles or markers to edit features.",subtext:"Click cancel to undo changes."}},remove:{tooltip:{text:"Click on a feature to remove."}}}}},L.Draw.Event={},L.Draw.Event.CREATED="draw:created",L.Draw.Event.EDITED="draw:edited",L.Draw.Event.DELETED="draw:deleted",L.Draw.Event.DRAWSTART="draw:drawstart",L.Draw.Event.DRAWSTOP="draw:drawstop",L.Draw.Event.DRAWVERTEX="draw:drawvertex",L.Draw.Event.EDITSTART="draw:editstart",L.Draw.Event.EDITMOVE="draw:editmove",L.Draw.Event.EDITRESIZE="draw:editresize",L.Draw.Event.EDITVERTEX="draw:editvertex",L.Draw.Event.EDITSTOP="draw:editstop",L.Draw.Event.DELETESTART="draw:deletestart",L.Draw.Event.DELETESTOP="draw:deletestop",L.Draw.Event.TOOLBAROPENED="draw:toolbaropened",L.Draw.Event.TOOLBARCLOSED="draw:toolbarclosed",L.Draw.Event.MARKERCONTEXT="draw:markercontext",L.Draw=L.Draw||{},L.Draw.Feature=L.Handler.extend({initialize:function(t,e){this._map=t,this._container=t._container,this._overlayPane=t._panes.overlayPane,this._popupPane=t._panes.popupPane,e&&e.shapeOptions&&(e.shapeOptions=L.Util.extend({},this.options.shapeOptions,e.shapeOptions)),L.setOptions(this,e);var i=L.version.split(".");1===parseInt(i[0],10)&&parseInt(i[1],10)>=2?L.Draw.Feature.include(L.Evented.prototype):L.Draw.Feature.include(L.Mixin.Events)},enable:function(){this._enabled||(L.Handler.prototype.enable.call(this),this.fire("enabled",{handler:this.type}),this._map.fire(L.Draw.Event.DRAWSTART,{layerType:this.type}))},disable:function(){this._enabled&&(L.Handler.prototype.disable.call(this),this._map.fire(L.Draw.Event.DRAWSTOP,{layerType:this.type}),this.fire("disabled",{handler:this.type}))},addHooks:function(){var t=this._map;t&&(L.DomUtil.disableTextSelection(),t.getContainer().focus(),this._tooltip=new L.Draw.Tooltip(this._map),L.DomEvent.on(this._container,"keyup",this._cancelDrawing,this))},removeHooks:function(){this._map&&(L.DomUtil.enableTextSelection(),this._tooltip.dispose(),this._tooltip=null,L.DomEvent.off(this._container,"keyup",this._cancelDrawing,this))},setOptions:function(t){L.setOptions(this,t)},_fireCreatedEvent:function(t){this._map.fire(L.Draw.Event.CREATED,{layer:t,layerType:this.type})},_cancelDrawing:function(t){27===t.keyCode&&(this._map.fire("draw:canceled",{layerType:this.type}),this.disable())}}),L.Draw.Polyline=L.Draw.Feature.extend({statics:{TYPE:"polyline"},Poly:L.Polyline,options:{allowIntersection:!0,repeatMode:!1,drawError:{color:"#b00b00",timeout:2500},icon:new L.DivIcon({iconSize:new L.Point(8,8),className:"leaflet-div-icon leaflet-editing-icon"}),touchIcon:new L.DivIcon({iconSize:new L.Point(20,20),className:"leaflet-div-icon leaflet-editing-icon leaflet-touch-icon"}),guidelineDistance:20,maxGuideLineLength:4e3,shapeOptions:{stroke:!0,color:"#3388ff",weight:4,opacity:.5,fill:!1,clickable:!0},metric:!0,feet:!0,nautic:!1,showLength:!0,zIndexOffset:2e3,factor:1,maxPoints:0},initialize:function(t,e){L.Browser.touch&&(this.options.icon=this.options.touchIcon),this.options.drawError.message=L.drawLocal.draw.handlers.polyline.error,e&&e.drawError&&(e.drawError=L.Util.extend({},this.options.drawError,e.drawError)),this.type=L.Draw.Polyline.TYPE,L.Draw.Feature.prototype.initialize.call(this,t,e)},addHooks:function(){L.Draw.Feature.prototype.addHooks.call(this),this._map&&(this._markers=[],this._markerGroup=new L.LayerGroup,this._map.addLayer(this._markerGroup),this._poly=new L.Polyline([],this.options.shapeOptions),this._tooltip.updateContent(this._getTooltipText()),this._mouseMarker||(this._mouseMarker=L.marker(this._map.getCenter(),{icon:L.divIcon({className:"leaflet-mouse-marker",iconAnchor:[20,20],iconSize:[40,40]}),opacity:0,zIndexOffset:this.options.zIndexOffset})),this._mouseMarker.on("mouseout",this._onMouseOut,this).on("mousemove",this._onMouseMove,this).on("mousedown",this._onMouseDown,this).on("mouseup",this._onMouseUp,this).addTo(this._map),this._map.on("mouseup",this._onMouseUp,this).on("mousemove",this._onMouseMove,this).on("zoomlevelschange",this._onZoomEnd,this).on("touchstart",this._onTouch,this).on("zoomend",this._onZoomEnd,this))},removeHooks:function(){L.Draw.Feature.prototype.removeHooks.call(this),this._clearHideErrorTimeout(),this._cleanUpShape(),this._map.removeLayer(this._markerGroup),delete this._markerGroup,delete this._markers,this._map.removeLayer(this._poly),delete this._poly,this._mouseMarker.off("mousedown",this._onMouseDown,this).off("mouseout",this._onMouseOut,this).off("mouseup",this._onMouseUp,this).off("mousemove",this._onMouseMove,this),this._map.removeLayer(this._mouseMarker),delete this._mouseMarker,this._clearGuides(),this._map.off("mouseup",this._onMouseUp,this).off("mousemove",this._onMouseMove,this).off("zoomlevelschange",this._onZoomEnd,this).off("zoomend",this._onZoomEnd,this).off("touchstart",this._onTouch,this).off("click",this._onTouch,this)},deleteLastVertex:function(){if(!(this._markers.length<=1)){var t=this._markers.pop(),e=this._poly,i=e.getLatLngs(),o=i.splice(-1,1)[0];this._poly.setLatLngs(i),this._markerGroup.removeLayer(t),e.getLatLngs().length<2&&this._map.removeLayer(e),this._vertexChanged(o,!1)}},addVertex:function(t){if(this._markers.length>=2&&!this.options.allowIntersection&&this._poly.newLatLngIntersects(t))return void this._showErrorTooltip();this._errorShown&&this._hideErrorTooltip(),this._markers.push(this._createMarker(t)),this._poly.addLatLng(t),2===this._poly.getLatLngs().length&&this._map.addLayer(this._poly),this._vertexChanged(t,!0)},completeShape:function(){this._markers.length<=1||!this._shapeIsValid()||(this._fireCreatedEvent(),this.disable(),this.options.repeatMode&&this.enable())},_finishShape:function(){var t=this._poly._defaultShape?this._poly._defaultShape():this._poly.getLatLngs(),e=this._poly.newLatLngIntersects(t[t.length-1]);if(!this.options.allowIntersection&&e||!this._shapeIsValid())return void this._showErrorTooltip();this._fireCreatedEvent(),this.disable(),this.options.repeatMode&&this.enable()},_shapeIsValid:function(){return!0},_onZoomEnd:function(){null!==this._markers&&this._updateGuide()},_onMouseMove:function(t){var e=this._map.mouseEventToLayerPoint(t.originalEvent),i=this._map.layerPointToLatLng(e);this._currentLatLng=i,this._updateTooltip(i),this._updateGuide(e),this._mouseMarker.setLatLng(i),L.DomEvent.preventDefault(t.originalEvent)},_vertexChanged:function(t,e){this._map.fire(L.Draw.Event.DRAWVERTEX,{layers:this._markerGroup}),this._updateFinishHandler(),this._updateRunningMeasure(t,e),this._clearGuides(),this._updateTooltip()},_onMouseDown:function(t){if(!this._clickHandled&&!this._touchHandled&&!this._disableMarkers){this._onMouseMove(t),this._clickHandled=!0,this._disableNewMarkers();var e=t.originalEvent,i=e.clientX,o=e.clientY;this._startPoint.call(this,i,o)}},_startPoint:function(t,e){this._mouseDownOrigin=L.point(t,e)},_onMouseUp:function(t){var e=t.originalEvent,i=e.clientX,o=e.clientY;this._endPoint.call(this,i,o,t),this._clickHandled=null},_endPoint:function(e,i,o){if(this._mouseDownOrigin){var a=L.point(e,i).distanceTo(this._mouseDownOrigin),n=this._calculateFinishDistance(o.latlng);this.options.maxPoints>1&&this.options.maxPoints==this._markers.length+1?(this.addVertex(o.latlng),this._finishShape()):n<10&&L.Browser.touch?this._finishShape():Math.abs(a)<9*(t.devicePixelRatio||1)&&this.addVertex(o.latlng),this._enableNewMarkers()}this._mouseDownOrigin=null},_onTouch:function(t){var e,i,o=t.originalEvent;!o.touches||!o.touches[0]||this._clickHandled||this._touchHandled||this._disableMarkers||(e=o.touches[0].clientX,i=o.touches[0].clientY,this._disableNewMarkers(),this._touchHandled=!0,this._startPoint.call(this,e,i),this._endPoint.call(this,e,i,t),this._touchHandled=null),this._clickHandled=null},_onMouseOut:function(){this._tooltip&&this._tooltip._onMouseOut.call(this._tooltip)},_calculateFinishDistance:function(t){var e;if(this._markers.length>0){var i;if(this.type===L.Draw.Polyline.TYPE)i=this._markers[this._markers.length-1];else{if(this.type!==L.Draw.Polygon.TYPE)return 1/0;i=this._markers[0]}var o=this._map.latLngToContainerPoint(i.getLatLng()),a=new L.Marker(t,{icon:this.options.icon,zIndexOffset:2*this.options.zIndexOffset}),n=this._map.latLngToContainerPoint(a.getLatLng());e=o.distanceTo(n)}else e=1/0;return e},_updateFinishHandler:function(){var t=this._markers.length;t>1&&this._markers[t-1].on("click",this._finishShape,this),t>2&&this._markers[t-2].off("click",this._finishShape,this)},_createMarker:function(t){var e=new L.Marker(t,{icon:this.options.icon,zIndexOffset:2*this.options.zIndexOffset});return this._markerGroup.addLayer(e),e},_updateGuide:function(t){var e=this._markers?this._markers.length:0;e>0&&(t=t||this._map.latLngToLayerPoint(this._currentLatLng),this._clearGuides(),this._drawGuide(this._map.latLngToLayerPoint(this._markers[e-1].getLatLng()),t))},_updateTooltip:function(t){var e=this._getTooltipText();t&&this._tooltip.updatePosition(t),this._errorShown||this._tooltip.updateContent(e)},_drawGuide:function(t,e){var i,o,a,n=Math.floor(Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))),s=this.options.guidelineDistance,r=this.options.maxGuideLineLength,l=n>r?n-r:s;for(this._guidesContainer||(this._guidesContainer=L.DomUtil.create("div","leaflet-draw-guides",this._overlayPane));l<n;l+=this.options.guidelineDistance)i=l/n,o={x:Math.floor(t.x*(1-i)+i*e.x),y:Math.floor(t.y*(1-i)+i*e.y)},a=L.DomUtil.create("div","leaflet-draw-guide-dash",this._guidesContainer),a.style.backgroundColor=this._errorShown?this.options.drawError.color:this.options.shapeOptions.color,L.DomUtil.setPosition(a,o)},_updateGuideColor:function(t){if(this._guidesContainer)for(var e=0,i=this._guidesContainer.childNodes.length;e<i;e++)this._guidesContainer.childNodes[e].style.backgroundColor=t},_clearGuides:function(){if(this._guidesContainer)for(;this._guidesContainer.firstChild;)this._guidesContainer.removeChild(this._guidesContainer.firstChild)},_getTooltipText:function(){var t,e,i=this.options.showLength;return 0===this._markers.length?t={text:L.drawLocal.draw.handlers.polyline.tooltip.start}:(e=i?this._getMeasurementString():"",t=1===this._markers.length?{text:L.drawLocal.draw.handlers.polyline.tooltip.cont,subtext:e}:{text:L.drawLocal.draw.handlers.polyline.tooltip.end,subtext:e}),t},_updateRunningMeasure:function(t,e){var i,o,a=this._markers.length;1===this._markers.length?this._measurementRunningTotal=0:(i=a-(e?2:1),o=L.GeometryUtil.isVersion07x()?t.distanceTo(this._markers[i].getLatLng())*(this.options.factor||1):this._map.distance(t,this._markers[i].getLatLng())*(this.options.factor||1),this._measurementRunningTotal+=o*(e?1:-1))},_getMeasurementString:function(){var t,e=this._currentLatLng,i=this._markers[this._markers.length-1].getLatLng();return t=L.GeometryUtil.isVersion07x()?i&&e&&e.distanceTo?this._measurementRunningTotal+e.distanceTo(i)*(this.options.factor||1):this._measurementRunningTotal||0:i&&e?this._measurementRunningTotal+this._map.distance(e,i)*(this.options.factor||1):this._measurementRunningTotal||0,L.GeometryUtil.readableDistance(t,this.options.metric,this.options.feet,this.options.nautic,this.options.precision)},_showErrorTooltip:function(){this._errorShown=!0,this._tooltip.showAsError().updateContent({text:this.options.drawError.message}),this._updateGuideColor(this.options.drawError.color),this._poly.setStyle({color:this.options.drawError.color}),this._clearHideErrorTimeout(),this._hideErrorTimeout=setTimeout(L.Util.bind(this._hideErrorTooltip,this),this.options.drawError.timeout)},_hideErrorTooltip:function(){this._errorShown=!1,this._clearHideErrorTimeout(),this._tooltip.removeError().updateContent(this._getTooltipText()),this._updateGuideColor(this.options.shapeOptions.color),this._poly.setStyle({color:this.options.shapeOptions.color})},_clearHideErrorTimeout:function(){this._hideErrorTimeout&&(clearTimeout(this._hideErrorTimeout),this._hideErrorTimeout=null)},_disableNewMarkers:function(){this._disableMarkers=!0},_enableNewMarkers:function(){setTimeout(function(){this._disableMarkers=!1}.bind(this),50)},_cleanUpShape:function(){this._markers.length>1&&this._markers[this._markers.length-1].off("click",this._finishShape,this)},_fireCreatedEvent:function(){var t=new this.Poly(this._poly.getLatLngs(),this.options.shapeOptions);L.Draw.Feature.prototype._fireCreatedEvent.call(this,t)}}),L.Draw.Polygon=L.Draw.Polyline.extend({statics:{TYPE:"polygon"},Poly:L.Polygon,options:{showArea:!1,showLength:!1,shapeOptions:{stroke:!0,color:"#3388ff",weight:4,opacity:.5,fill:!0,fillColor:null,fillOpacity:.2,clickable:!0},metric:!0,feet:!0,nautic:!1,precision:{}},initialize:function(t,e){L.Draw.Polyline.prototype.initialize.call(this,t,e),this.type=L.Draw.Polygon.TYPE},_updateFinishHandler:function(){var t=this._markers.length;1===t&&this._markers[0].on("click",this._finishShape,this),t>2&&(this._markers[t-1].on("dblclick",this._finishShape,this),t>3&&this._markers[t-2].off("dblclick",this._finishShape,this))},_getTooltipText:function(){var t,e;return 0===this._markers.length?t=L.drawLocal.draw.handlers.polygon.tooltip.start:this._markers.length<3?(t=L.drawLocal.draw.handlers.polygon.tooltip.cont,e=this._getMeasurementString()):(t=L.drawLocal.draw.handlers.polygon.tooltip.end,e=this._getMeasurementString()),{text:t,subtext:e}},_getMeasurementString:function(){var t=this._area,e="";return t||this.options.showLength?(this.options.showLength&&(e=L.Draw.Polyline.prototype._getMeasurementString.call(this)),t&&(e+="<br>"+L.GeometryUtil.readableArea(t,this.options.metric,this.options.precision)),e):null},_shapeIsValid:function(){return this._markers.length>=3},_vertexChanged:function(t,e){var i;!this.options.allowIntersection&&this.options.showArea&&(i=this._poly.getLatLngs(),this._area=L.GeometryUtil.geodesicArea(i)),L.Draw.Polyline.prototype._vertexChanged.call(this,t,e)},_cleanUpShape:function(){var t=this._markers.length;t>0&&(this._markers[0].off("click",this._finishShape,this),t>2&&this._markers[t-1].off("dblclick",this._finishShape,this))}}),L.SimpleShape={},L.Draw.SimpleShape=L.Draw.Feature.extend({options:{repeatMode:!1},initialize:function(t,e){this._endLabelText=L.drawLocal.draw.handlers.simpleshape.tooltip.end,L.Draw.Feature.prototype.initialize.call(this,t,e)},addHooks:function(){L.Draw.Feature.prototype.addHooks.call(this),this._map&&(this._mapDraggable=this._map.dragging.enabled(),this._mapDraggable&&this._map.dragging.disable(),this._container.style.cursor="crosshair",this._tooltip.updateContent({text:this._initialLabelText}),this._map.on("mousedown",this._onMouseDown,this).on("mousemove",this._onMouseMove,this).on("touchstart",this._onMouseDown,this).on("touchmove",this._onMouseMove,this),e.addEventListener("touchstart",L.DomEvent.preventDefault,{passive:!1}))},removeHooks:function(){L.Draw.Feature.prototype.removeHooks.call(this),this._map&&(this._mapDraggable&&this._map.dragging.enable(),this._container.style.cursor="",this._map.off("mousedown",this._onMouseDown,this).off("mousemove",this._onMouseMove,this).off("touchstart",this._onMouseDown,this).off("touchmove",this._onMouseMove,this),L.DomEvent.off(e,"mouseup",this._onMouseUp,this),L.DomEvent.off(e,"touchend",this._onMouseUp,this),e.removeEventListener("touchstart",L.DomEvent.preventDefault),this._shape&&(this._map.removeLayer(this._shape),delete this._shape)),this._isDrawing=!1},_getTooltipText:function(){return{text:this._endLabelText}},_onMouseDown:function(t){this._isDrawing=!0,this._startLatLng=t.latlng,L.DomEvent.on(e,"mouseup",this._onMouseUp,this).on(e,"touchend",this._onMouseUp,this).preventDefault(t.originalEvent)},_onMouseMove:function(t){var e=t.latlng;this._tooltip.updatePosition(e),this._isDrawing&&(this._tooltip.updateContent(this._getTooltipText()),this._drawShape(e))},_onMouseUp:function(){this._shape&&this._fireCreatedEvent(),this.disable(),this.options.repeatMode&&this.enable()}}),L.Draw.Rectangle=L.Draw.SimpleShape.extend({statics:{TYPE:"rectangle"},options:{shapeOptions:{stroke:!0,color:"#3388ff",weight:4,opacity:.5,fill:!0,fillColor:null,fillOpacity:.2,clickable:!0},showArea:!0,metric:!0},initialize:function(t,e){this.type=L.Draw.Rectangle.TYPE,this._initialLabelText=L.drawLocal.draw.handlers.rectangle.tooltip.start,L.Draw.SimpleShape.prototype.initialize.call(this,t,e)},disable:function(){this._enabled&&(this._isCurrentlyTwoClickDrawing=!1,L.Draw.SimpleShape.prototype.disable.call(this))},_onMouseUp:function(t){if(!this._shape&&!this._isCurrentlyTwoClickDrawing)return void(this._isCurrentlyTwoClickDrawing=!0);this._isCurrentlyTwoClickDrawing&&!o(t.target,"leaflet-pane")||L.Draw.SimpleShape.prototype._onMouseUp.call(this)},_drawShape:function(t){this._shape?this._shape.setBounds(new L.LatLngBounds(this._startLatLng,t)):(this._shape=new L.Rectangle(new L.LatLngBounds(this._startLatLng,t),this.options.shapeOptions),this._map.addLayer(this._shape))},_fireCreatedEvent:function(){var t=new L.Rectangle(this._shape.getBounds(),this.options.shapeOptions);L.Draw.SimpleShape.prototype._fireCreatedEvent.call(this,t)},_getTooltipText:function(){var t,e,i,o=L.Draw.SimpleShape.prototype._getTooltipText.call(this),a=this._shape,n=this.options.showArea;return a&&(t=this._shape._defaultShape?this._shape._defaultShape():this._shape.getLatLngs(),e=L.GeometryUtil.geodesicArea(t),i=n?L.GeometryUtil.readableArea(e,this.options.metric):""),{text:o.text,subtext:i}}}),L.Draw.Marker=L.Draw.Feature.extend({statics:{TYPE:"marker"},options:{icon:new L.Icon.Default,repeatMode:!1,zIndexOffset:2e3},initialize:function(t,e){this.type=L.Draw.Marker.TYPE,this._initialLabelText=L.drawLocal.draw.handlers.marker.tooltip.start,L.Draw.Feature.prototype.initialize.call(this,t,e)},addHooks:function(){L.Draw.Feature.prototype.addHooks.call(this),this._map&&(this._tooltip.updateContent({text:this._initialLabelText}),this._mouseMarker||(this._mouseMarker=L.marker(this._map.getCenter(),{icon:L.divIcon({className:"leaflet-mouse-marker",iconAnchor:[20,20],iconSize:[40,40]}),opacity:0,zIndexOffset:this.options.zIndexOffset})),this._mouseMarker.on("click",this._onClick,this).addTo(this._map),this._map.on("mousemove",this._onMouseMove,this),this._map.on("click",this._onTouch,this))},removeHooks:function(){L.Draw.Feature.prototype.removeHooks.call(this),this._map&&(this._map.off("click",this._onClick,this).off("click",this._onTouch,this),this._marker&&(this._marker.off("click",this._onClick,this),this._map.removeLayer(this._marker),delete this._marker),this._mouseMarker.off("click",this._onClick,this),this._map.removeLayer(this._mouseMarker),delete this._mouseMarker,this._map.off("mousemove",this._onMouseMove,this))},_onMouseMove:function(t){var e=t.latlng;this._tooltip.updatePosition(e),this._mouseMarker.setLatLng(e),this._marker?(e=this._mouseMarker.getLatLng(),this._marker.setLatLng(e)):(this._marker=this._createMarker(e),this._marker.on("click",this._onClick,this),this._map.on("click",this._onClick,this).addLayer(this._marker))},_createMarker:function(t){return new L.Marker(t,{icon:this.options.icon,zIndexOffset:this.options.zIndexOffset})},_onClick:function(){this._fireCreatedEvent(),this.disable(),this.options.repeatMode&&this.enable()},_onTouch:function(t){this._onMouseMove(t),this._onClick()},_fireCreatedEvent:function(){var t=new L.Marker.Touch(this._marker.getLatLng(),{icon:this.options.icon});L.Draw.Feature.prototype._fireCreatedEvent.call(this,t)}}),L.Draw.CircleMarker=L.Draw.Marker.extend({statics:{TYPE:"circlemarker"},options:{stroke:!0,color:"#3388ff",weight:4,opacity:.5,fill:!0,fillColor:null,fillOpacity:.2,clickable:!0,zIndexOffset:2e3},initialize:function(t,e){this.type=L.Draw.CircleMarker.TYPE,this._initialLabelText=L.drawLocal.draw.handlers.circlemarker.tooltip.start,L.Draw.Feature.prototype.initialize.call(this,t,e)},_fireCreatedEvent:function(){var t=new L.CircleMarker(this._marker.getLatLng(),this.options);L.Draw.Feature.prototype._fireCreatedEvent.call(this,t)},_createMarker:function(t){return new L.CircleMarker(t,this.options)}}),L.Draw.Circle=L.Draw.SimpleShape.extend({statics:{TYPE:"circle"},options:{shapeOptions:{stroke:!0,color:"#3388ff",weight:4,opacity:.5,fill:!0,fillColor:null,fillOpacity:.2,clickable:!0},showRadius:!0,metric:!0,feet:!0,nautic:!1},initialize:function(t,e){this.type=L.Draw.Circle.TYPE,this._initialLabelText=L.drawLocal.draw.handlers.circle.tooltip.start,L.Draw.SimpleShape.prototype.initialize.call(this,t,e)},_drawShape:function(t){if(L.GeometryUtil.isVersion07x())var e=this._startLatLng.distanceTo(t);else var e=this._map.distance(this._startLatLng,t);this._shape?this._shape.setRadius(e):(this._shape=new L.Circle(this._startLatLng,e,this.options.shapeOptions),this._map.addLayer(this._shape))},_fireCreatedEvent:function(){var t=new L.Circle(this._startLatLng,this._shape.getRadius(),this.options.shapeOptions);L.Draw.SimpleShape.prototype._fireCreatedEvent.call(this,t)},_onMouseMove:function(t){var e,i=t.latlng,o=this.options.showRadius,a=this.options.metric;if(this._tooltip.updatePosition(i),this._isDrawing){this._drawShape(i),e=this._shape.getRadius().toFixed(1);var n="";o&&(n=L.drawLocal.draw.handlers.circle.radius+": "+L.GeometryUtil.readableDistance(e,a,this.options.feet,this.options.nautic)),this._tooltip.updateContent({text:this._endLabelText,subtext:n})}}}),L.Edit=L.Edit||{},L.Edit.Marker=L.Handler.extend({initialize:function(t,e){this._marker=t,L.setOptions(this,e)},addHooks:function(){var t=this._marker;t.dragging.enable(),t.on("dragend",this._onDragEnd,t),this._toggleMarkerHighlight()},removeHooks:function(){var t=this._marker;t.dragging.disable(),t.off("dragend",this._onDragEnd,t),this._toggleMarkerHighlight()},_onDragEnd:function(t){var e=t.target;e.edited=!0,this._map.fire(L.Draw.Event.EDITMOVE,{layer:e})},_toggleMarkerHighlight:function(){var t=this._marker._icon;t&&(t.style.display="none",L.DomUtil.hasClass(t,"leaflet-edit-marker-selected")?(L.DomUtil.removeClass(t,"leaflet-edit-marker-selected"),this._offsetMarker(t,-4)):(L.DomUtil.addClass(t,"leaflet-edit-marker-selected"),this._offsetMarker(t,4)),t.style.display="")},_offsetMarker:function(t,e){var i=parseInt(t.style.marginTop,10)-e,o=parseInt(t.style.marginLeft,10)-e;t.style.marginTop=i+"px",t.style.marginLeft=o+"px"}}),L.Marker.addInitHook(function(){L.Edit.Marker&&(this.editing=new L.Edit.Marker(this),this.options.editable&&this.editing.enable())}),L.Edit=L.Edit||{},L.Edit.Poly=L.Handler.extend({initialize:function(t){this.latlngs=[t._latlngs],t._holes&&(this.latlngs=this.latlngs.concat(t._holes)),this._poly=t,this._poly.on("revert-edited",this._updateLatLngs,this)},_defaultShape:function(){return L.Polyline._flat?L.Polyline._flat(this._poly._latlngs)?this._poly._latlngs:this._poly._latlngs[0]:this._poly._latlngs},_eachVertexHandler:function(t){for(var e=0;e<this._verticesHandlers.length;e++)t(this._verticesHandlers[e])},addHooks:function(){this._initHandlers(),this._eachVertexHandler(function(t){t.addHooks()})},removeHooks:function(){this._eachVertexHandler(function(t){t.removeHooks()})},updateMarkers:function(){this._eachVertexHandler(function(t){t.updateMarkers()})},_initHandlers:function(){this._verticesHandlers=[];for(var t=0;t<this.latlngs.length;t++)this._verticesHandlers.push(new L.Edit.PolyVerticesEdit(this._poly,this.latlngs[t],this._poly.options.poly))},_updateLatLngs:function(t){this.latlngs=[t.layer._latlngs],t.layer._holes&&(this.latlngs=this.latlngs.concat(t.layer._holes))}}),L.Edit.PolyVerticesEdit=L.Handler.extend({options:{icon:new L.DivIcon({iconSize:new L.Point(8,8),className:"leaflet-div-icon leaflet-editing-icon"}),touchIcon:new L.DivIcon({iconSize:new L.Point(20,20),className:"leaflet-div-icon leaflet-editing-icon leaflet-touch-icon"}),drawError:{color:"#b00b00",timeout:1e3}},initialize:function(t,e,i){L.Browser.touch&&(this.options.icon=this.options.touchIcon),this._poly=t,i&&i.drawError&&(i.drawError=L.Util.extend({},this.options.drawError,i.drawError)),this._latlngs=e,L.setOptions(this,i)},_defaultShape:function(){return L.Polyline._flat?L.Polyline._flat(this._latlngs)?this._latlngs:this._latlngs[0]:this._latlngs},addHooks:function(){var t=this._poly,e=t._path;t instanceof L.Polygon||(t.options.fill=!1,t.options.editing&&(t.options.editing.fill=!1)),e&&t.options.editing&&t.options.editing.className&&(t.options.original.className&&t.options.original.className.split(" ").forEach(function(t){L.DomUtil.removeClass(e,t)}),t.options.editing.className.split(" ").forEach(function(t){L.DomUtil.addClass(e,t)})),t.setStyle(t.options.editing),this._poly._map&&(this._map=this._poly._map,this._markerGroup||this._initMarkers(),this._poly._map.addLayer(this._markerGroup))},removeHooks:function(){var t=this._poly,e=t._path;e&&t.options.editing&&t.options.editing.className&&(t.options.editing.className.split(" ").forEach(function(t){L.DomUtil.removeClass(e,t)}),t.options.original.className&&t.options.original.className.split(" ").forEach(function(t){L.DomUtil.addClass(e,t)})),t.setStyle(t.options.original),t._map&&(t._map.removeLayer(this._markerGroup),delete this._markerGroup,delete this._markers)},updateMarkers:function(){this._markerGroup.clearLayers(),this._initMarkers()},_initMarkers:function(){this._markerGroup||(this._markerGroup=new L.LayerGroup),this._markers=[];var t,e,i,o,a=this._defaultShape();for(t=0,i=a.length;t<i;t++)o=this._createMarker(a[t],t),o.on("click",this._onMarkerClick,this),o.on("contextmenu",this._onContextMenu,this),this._markers.push(o);var n,s;for(t=0,e=i-1;t<i;e=t++)(0!==t||L.Polygon&&this._poly instanceof L.Polygon)&&(n=this._markers[e],s=this._markers[t],this._createMiddleMarker(n,s),this._updatePrevNext(n,s))},_createMarker:function(t,e){var i=new L.Marker.Touch(t,{draggable:!0,icon:this.options.icon});return i._origLatLng=t,i._index=e,i.on("dragstart",this._onMarkerDragStart,this).on("drag",this._onMarkerDrag,this).on("dragend",this._fireEdit,this).on("touchmove",this._onTouchMove,this).on("touchend",this._fireEdit,this).on("MSPointerMove",this._onTouchMove,this).on("MSPointerUp",this._fireEdit,this),this._markerGroup.addLayer(i),i},_onMarkerDragStart:function(){this._poly.fire("editstart")},_spliceLatLngs:function(){var t=this._defaultShape(),e=[].splice.apply(t,arguments);return this._poly._convertLatLngs(t,!0),this._poly.redraw(),e},_removeMarker:function(t){var e=t._index;this._markerGroup.removeLayer(t),this._markers.splice(e,1),this._spliceLatLngs(e,1),this._updateIndexes(e,-1),t.off("dragstart",this._onMarkerDragStart,this).off("drag",this._onMarkerDrag,this).off("dragend",this._fireEdit,this).off("touchmove",this._onMarkerDrag,this).off("touchend",this._fireEdit,this).off("click",this._onMarkerClick,this).off("MSPointerMove",this._onTouchMove,this).off("MSPointerUp",this._fireEdit,this)},_fireEdit:function(){this._poly.edited=!0,this._poly.fire("edit"),this._poly._map.fire(L.Draw.Event.EDITVERTEX,{layers:this._markerGroup,poly:this._poly})},_onMarkerDrag:function(t){var e=t.target,i=this._poly,o=L.LatLngUtil.cloneLatLng(e._origLatLng);if(L.extend(e._origLatLng,e._latlng),i.options.poly){var a=i._map._editTooltip;if(!i.options.poly.allowIntersection&&i.intersects()){L.extend(e._origLatLng,o),e.setLatLng(o);var n=i.options.color;i.setStyle({color:this.options.drawError.color}),a&&a.updateContent({text:L.drawLocal.draw.handlers.polyline.error}),setTimeout(function(){i.setStyle({color:n}),a&&a.updateContent({text:L.drawLocal.edit.handlers.edit.tooltip.text,subtext:L.drawLocal.edit.handlers.edit.tooltip.subtext})},1e3)}}e._middleLeft&&e._middleLeft.setLatLng(this._getMiddleLatLng(e._prev,e)),e._middleRight&&e._middleRight.setLatLng(this._getMiddleLatLng(e,e._next)),this._poly._bounds._southWest=L.latLng(1/0,1/0),this._poly._bounds._northEast=L.latLng(-1/0,-1/0);var s=this._poly.getLatLngs();this._poly._convertLatLngs(s,!0),this._poly.redraw(),this._poly.fire("editdrag")},_onMarkerClick:function(t){var e=L.Polygon&&this._poly instanceof L.Polygon?4:3,i=t.target;this._defaultShape().length<e||(this._removeMarker(i),this._updatePrevNext(i._prev,i._next),i._middleLeft&&this._markerGroup.removeLayer(i._middleLeft),i._middleRight&&this._markerGroup.removeLayer(i._middleRight),i._prev&&i._next?this._createMiddleMarker(i._prev,i._next):i._prev?i._next||(i._prev._middleRight=null):i._next._middleLeft=null,this._fireEdit())},_onContextMenu:function(t){var e=t.target;this._poly;this._poly._map.fire(L.Draw.Event.MARKERCONTEXT,{marker:e,layers:this._markerGroup,poly:this._poly}),L.DomEvent.stopPropagation},_onTouchMove:function(t){var e=this._map.mouseEventToLayerPoint(t.originalEvent.touches[0]),i=this._map.layerPointToLatLng(e),o=t.target;L.extend(o._origLatLng,i),o._middleLeft&&o._middleLeft.setLatLng(this._getMiddleLatLng(o._prev,o)),o._middleRight&&o._middleRight.setLatLng(this._getMiddleLatLng(o,o._next)),this._poly.redraw(),this.updateMarkers()},_updateIndexes:function(t,e){this._markerGroup.eachLayer(function(i){i._index>t&&(i._index+=e)})},_createMiddleMarker:function(t,e){var i,o,a,n=this._getMiddleLatLng(t,e),s=this._createMarker(n);s.setOpacity(.6),t._middleRight=e._middleLeft=s,o=function(){s.off("touchmove",o,this);var a=e._index;s._index=a,s.off("click",i,this).on("click",this._onMarkerClick,this),n.lat=s.getLatLng().lat,n.lng=s.getLatLng().lng,this._spliceLatLngs(a,0,n),this._markers.splice(a,0,s),s.setOpacity(1),this._updateIndexes(a,1),e._index++,this._updatePrevNext(t,s),this._updatePrevNext(s,e),this._poly.fire("editstart")},a=function(){s.off("dragstart",o,this),s.off("dragend",a,this),s.off("touchmove",o,this),this._createMiddleMarker(t,s),this._createMiddleMarker(s,e)},i=function(){o.call(this),a.call(this),this._fireEdit()},s.on("click",i,this).on("dragstart",o,this).on("dragend",a,this).on("touchmove",o,this),this._markerGroup.addLayer(s)},_updatePrevNext:function(t,e){t&&(t._next=e),e&&(e._prev=t)},_getMiddleLatLng:function(t,e){var i=this._poly._map,o=i.project(t.getLatLng()),a=i.project(e.getLatLng());return i.unproject(o._add(a)._divideBy(2))}}),L.Polyline.addInitHook(function(){this.editing||(L.Edit.Poly&&(this.editing=new L.Edit.Poly(this),this.options.editable&&this.editing.enable()),this.on("add",function(){this.editing&&this.editing.enabled()&&this.editing.addHooks()}),this.on("remove",function(){this.editing&&this.editing.enabled()&&this.editing.removeHooks()}))}),L.Edit=L.Edit||{},L.Edit.SimpleShape=L.Handler.extend({options:{moveIcon:new L.DivIcon({iconSize:new L.Point(8,8),className:"leaflet-div-icon leaflet-editing-icon leaflet-edit-move"}),resizeIcon:new L.DivIcon({iconSize:new L.Point(8,8),
+className:"leaflet-div-icon leaflet-editing-icon leaflet-edit-resize"}),touchMoveIcon:new L.DivIcon({iconSize:new L.Point(20,20),className:"leaflet-div-icon leaflet-editing-icon leaflet-edit-move leaflet-touch-icon"}),touchResizeIcon:new L.DivIcon({iconSize:new L.Point(20,20),className:"leaflet-div-icon leaflet-editing-icon leaflet-edit-resize leaflet-touch-icon"})},initialize:function(t,e){L.Browser.touch&&(this.options.moveIcon=this.options.touchMoveIcon,this.options.resizeIcon=this.options.touchResizeIcon),this._shape=t,L.Util.setOptions(this,e)},addHooks:function(){var t=this._shape;this._shape._map&&(this._map=this._shape._map,t.setStyle(t.options.editing),t._map&&(this._map=t._map,this._markerGroup||this._initMarkers(),this._map.addLayer(this._markerGroup)))},removeHooks:function(){var t=this._shape;if(t.setStyle(t.options.original),t._map){this._unbindMarker(this._moveMarker);for(var e=0,i=this._resizeMarkers.length;e<i;e++)this._unbindMarker(this._resizeMarkers[e]);this._resizeMarkers=null,this._map.removeLayer(this._markerGroup),delete this._markerGroup}this._map=null},updateMarkers:function(){this._markerGroup.clearLayers(),this._initMarkers()},_initMarkers:function(){this._markerGroup||(this._markerGroup=new L.LayerGroup),this._createMoveMarker(),this._createResizeMarker()},_createMoveMarker:function(){},_createResizeMarker:function(){},_createMarker:function(t,e){var i=new L.Marker.Touch(t,{draggable:!0,icon:e,zIndexOffset:10});return this._bindMarker(i),this._markerGroup.addLayer(i),i},_bindMarker:function(t){t.on("dragstart",this._onMarkerDragStart,this).on("drag",this._onMarkerDrag,this).on("dragend",this._onMarkerDragEnd,this).on("touchstart",this._onTouchStart,this).on("touchmove",this._onTouchMove,this).on("MSPointerMove",this._onTouchMove,this).on("touchend",this._onTouchEnd,this).on("MSPointerUp",this._onTouchEnd,this)},_unbindMarker:function(t){t.off("dragstart",this._onMarkerDragStart,this).off("drag",this._onMarkerDrag,this).off("dragend",this._onMarkerDragEnd,this).off("touchstart",this._onTouchStart,this).off("touchmove",this._onTouchMove,this).off("MSPointerMove",this._onTouchMove,this).off("touchend",this._onTouchEnd,this).off("MSPointerUp",this._onTouchEnd,this)},_onMarkerDragStart:function(t){t.target.setOpacity(0),this._shape.fire("editstart")},_fireEdit:function(){this._shape.edited=!0,this._shape.fire("edit")},_onMarkerDrag:function(t){var e=t.target,i=e.getLatLng();e===this._moveMarker?this._move(i):this._resize(i),this._shape.redraw(),this._shape.fire("editdrag")},_onMarkerDragEnd:function(t){t.target.setOpacity(1),this._fireEdit()},_onTouchStart:function(t){if(L.Edit.SimpleShape.prototype._onMarkerDragStart.call(this,t),"function"==typeof this._getCorners){var e=this._getCorners(),i=t.target,o=i._cornerIndex;i.setOpacity(0),this._oppositeCorner=e[(o+2)%4],this._toggleCornerMarkers(0,o)}this._shape.fire("editstart")},_onTouchMove:function(t){var e=this._map.mouseEventToLayerPoint(t.originalEvent.touches[0]),i=this._map.layerPointToLatLng(e);return t.target===this._moveMarker?this._move(i):this._resize(i),this._shape.redraw(),!1},_onTouchEnd:function(t){t.target.setOpacity(1),this.updateMarkers(),this._fireEdit()},_move:function(){},_resize:function(){}}),L.Edit=L.Edit||{},L.Edit.Rectangle=L.Edit.SimpleShape.extend({_createMoveMarker:function(){var t=this._shape.getBounds(),e=t.getCenter();this._moveMarker=this._createMarker(e,this.options.moveIcon)},_createResizeMarker:function(){var t=this._getCorners();this._resizeMarkers=[];for(var e=0,i=t.length;e<i;e++)this._resizeMarkers.push(this._createMarker(t[e],this.options.resizeIcon)),this._resizeMarkers[e]._cornerIndex=e},_onMarkerDragStart:function(t){L.Edit.SimpleShape.prototype._onMarkerDragStart.call(this,t);var e=this._getCorners(),i=t.target,o=i._cornerIndex;this._oppositeCorner=e[(o+2)%4],this._toggleCornerMarkers(0,o)},_onMarkerDragEnd:function(t){var e,i,o=t.target;o===this._moveMarker&&(e=this._shape.getBounds(),i=e.getCenter(),o.setLatLng(i)),this._toggleCornerMarkers(1),this._repositionCornerMarkers(),L.Edit.SimpleShape.prototype._onMarkerDragEnd.call(this,t)},_move:function(t){for(var e,i=this._shape._defaultShape?this._shape._defaultShape():this._shape.getLatLngs(),o=this._shape.getBounds(),a=o.getCenter(),n=[],s=0,r=i.length;s<r;s++)e=[i[s].lat-a.lat,i[s].lng-a.lng],n.push([t.lat+e[0],t.lng+e[1]]);this._shape.setLatLngs(n),this._repositionCornerMarkers(),this._map.fire(L.Draw.Event.EDITMOVE,{layer:this._shape})},_resize:function(t){var e;this._shape.setBounds(L.latLngBounds(t,this._oppositeCorner)),e=this._shape.getBounds(),this._moveMarker.setLatLng(e.getCenter()),this._map.fire(L.Draw.Event.EDITRESIZE,{layer:this._shape})},_getCorners:function(){var t=this._shape.getBounds();return[t.getNorthWest(),t.getNorthEast(),t.getSouthEast(),t.getSouthWest()]},_toggleCornerMarkers:function(t){for(var e=0,i=this._resizeMarkers.length;e<i;e++)this._resizeMarkers[e].setOpacity(t)},_repositionCornerMarkers:function(){for(var t=this._getCorners(),e=0,i=this._resizeMarkers.length;e<i;e++)this._resizeMarkers[e].setLatLng(t[e])}}),L.Rectangle.addInitHook(function(){L.Edit.Rectangle&&(this.editing=new L.Edit.Rectangle(this),this.options.editable&&this.editing.enable())}),L.Edit=L.Edit||{},L.Edit.CircleMarker=L.Edit.SimpleShape.extend({_createMoveMarker:function(){var t=this._shape.getLatLng();this._moveMarker=this._createMarker(t,this.options.moveIcon)},_createResizeMarker:function(){this._resizeMarkers=[]},_move:function(t){if(this._resizeMarkers.length){var e=this._getResizeMarkerPoint(t);this._resizeMarkers[0].setLatLng(e)}this._shape.setLatLng(t),this._map.fire(L.Draw.Event.EDITMOVE,{layer:this._shape})}}),L.CircleMarker.addInitHook(function(){L.Edit.CircleMarker&&(this.editing=new L.Edit.CircleMarker(this),this.options.editable&&this.editing.enable()),this.on("add",function(){this.editing&&this.editing.enabled()&&this.editing.addHooks()}),this.on("remove",function(){this.editing&&this.editing.enabled()&&this.editing.removeHooks()})}),L.Edit=L.Edit||{},L.Edit.Circle=L.Edit.CircleMarker.extend({_createResizeMarker:function(){var t=this._shape.getLatLng(),e=this._getResizeMarkerPoint(t);this._resizeMarkers=[],this._resizeMarkers.push(this._createMarker(e,this.options.resizeIcon))},_getResizeMarkerPoint:function(t){var e=this._shape._radius*Math.cos(Math.PI/4),i=this._map.project(t);return this._map.unproject([i.x+e,i.y-e])},_resize:function(t){var e=this._moveMarker.getLatLng();L.GeometryUtil.isVersion07x()?radius=e.distanceTo(t):radius=this._map.distance(e,t),this._shape.setRadius(radius),this._map.editTooltip&&this._map._editTooltip.updateContent({text:L.drawLocal.edit.handlers.edit.tooltip.subtext+"<br />"+L.drawLocal.edit.handlers.edit.tooltip.text,subtext:L.drawLocal.draw.handlers.circle.radius+": "+L.GeometryUtil.readableDistance(radius,!0,this.options.feet,this.options.nautic)}),this._shape.setRadius(radius),this._map.fire(L.Draw.Event.EDITRESIZE,{layer:this._shape})}}),L.Circle.addInitHook(function(){L.Edit.Circle&&(this.editing=new L.Edit.Circle(this),this.options.editable&&this.editing.enable())}),L.Map.mergeOptions({touchExtend:!0}),L.Map.TouchExtend=L.Handler.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane},addHooks:function(){L.DomEvent.on(this._container,"touchstart",this._onTouchStart,this),L.DomEvent.on(this._container,"touchend",this._onTouchEnd,this),L.DomEvent.on(this._container,"touchmove",this._onTouchMove,this),this._detectIE()?(L.DomEvent.on(this._container,"MSPointerDown",this._onTouchStart,this),L.DomEvent.on(this._container,"MSPointerUp",this._onTouchEnd,this),L.DomEvent.on(this._container,"MSPointerMove",this._onTouchMove,this),L.DomEvent.on(this._container,"MSPointerCancel",this._onTouchCancel,this)):(L.DomEvent.on(this._container,"touchcancel",this._onTouchCancel,this),L.DomEvent.on(this._container,"touchleave",this._onTouchLeave,this))},removeHooks:function(){L.DomEvent.off(this._container,"touchstart",this._onTouchStart,this),L.DomEvent.off(this._container,"touchend",this._onTouchEnd,this),L.DomEvent.off(this._container,"touchmove",this._onTouchMove,this),this._detectIE()?(L.DomEvent.off(this._container,"MSPointerDown",this._onTouchStart,this),L.DomEvent.off(this._container,"MSPointerUp",this._onTouchEnd,this),L.DomEvent.off(this._container,"MSPointerMove",this._onTouchMove,this),L.DomEvent.off(this._container,"MSPointerCancel",this._onTouchCancel,this)):(L.DomEvent.off(this._container,"touchcancel",this._onTouchCancel,this),L.DomEvent.off(this._container,"touchleave",this._onTouchLeave,this))},_touchEvent:function(t,e){var i={};if(void 0!==t.touches){if(!t.touches.length)return;i=t.touches[0]}else{if("touch"!==t.pointerType)return;if(i=t,!this._filterClick(t))return}var o=this._map.mouseEventToContainerPoint(i),a=this._map.mouseEventToLayerPoint(i),n=this._map.layerPointToLatLng(a);this._map.fire(e,{latlng:n,layerPoint:a,containerPoint:o,pageX:i.pageX,pageY:i.pageY,originalEvent:t})},_filterClick:function(t){var e=t.timeStamp||t.originalEvent.timeStamp,i=L.DomEvent._lastClick&&e-L.DomEvent._lastClick;return i&&i>100&&i<500||t.target._simulatedClick&&!t._simulated?(L.DomEvent.stop(t),!1):(L.DomEvent._lastClick=e,!0)},_onTouchStart:function(t){if(this._map._loaded){this._touchEvent(t,"touchstart")}},_onTouchEnd:function(t){if(this._map._loaded){this._touchEvent(t,"touchend")}},_onTouchCancel:function(t){if(this._map._loaded){var e="touchcancel";this._detectIE()&&(e="pointercancel"),this._touchEvent(t,e)}},_onTouchLeave:function(t){if(this._map._loaded){this._touchEvent(t,"touchleave")}},_onTouchMove:function(t){if(this._map._loaded){this._touchEvent(t,"touchmove")}},_detectIE:function(){var e=t.navigator.userAgent,i=e.indexOf("MSIE ");if(i>0)return parseInt(e.substring(i+5,e.indexOf(".",i)),10);if(e.indexOf("Trident/")>0){var o=e.indexOf("rv:");return parseInt(e.substring(o+3,e.indexOf(".",o)),10)}var a=e.indexOf("Edge/");return a>0&&parseInt(e.substring(a+5,e.indexOf(".",a)),10)}}),L.Map.addInitHook("addHandler","touchExtend",L.Map.TouchExtend),L.Marker.Touch=L.Marker.extend({_initInteraction:function(){return this.addInteractiveTarget?L.Marker.prototype._initInteraction.apply(this):this._initInteractionLegacy()},_initInteractionLegacy:function(){if(this.options.clickable){var t=this._icon,e=["dblclick","mousedown","mouseover","mouseout","contextmenu","touchstart","touchend","touchmove"];this._detectIE?e.concat(["MSPointerDown","MSPointerUp","MSPointerMove","MSPointerCancel"]):e.concat(["touchcancel"]),L.DomUtil.addClass(t,"leaflet-clickable"),L.DomEvent.on(t,"click",this._onMouseClick,this),L.DomEvent.on(t,"keypress",this._onKeyPress,this);for(var i=0;i<e.length;i++)L.DomEvent.on(t,e[i],this._fireMouseEvent,this);L.Handler.MarkerDrag&&(this.dragging=new L.Handler.MarkerDrag(this),this.options.draggable&&this.dragging.enable())}},_detectIE:function(){var e=t.navigator.userAgent,i=e.indexOf("MSIE ");if(i>0)return parseInt(e.substring(i+5,e.indexOf(".",i)),10);if(e.indexOf("Trident/")>0){var o=e.indexOf("rv:");return parseInt(e.substring(o+3,e.indexOf(".",o)),10)}var a=e.indexOf("Edge/");return a>0&&parseInt(e.substring(a+5,e.indexOf(".",a)),10)}}),L.LatLngUtil={cloneLatLngs:function(t){for(var e=[],i=0,o=t.length;i<o;i++)Array.isArray(t[i])?e.push(L.LatLngUtil.cloneLatLngs(t[i])):e.push(this.cloneLatLng(t[i]));return e},cloneLatLng:function(t){return L.latLng(t.lat,t.lng)}},function(){var t={km:2,ha:2,m:0,mi:2,ac:2,yd:0,ft:0,nm:2};L.GeometryUtil=L.extend(L.GeometryUtil||{},{geodesicArea:function(t){var e,i,o=t.length,a=0,n=Math.PI/180;if(o>2){for(var s=0;s<o;s++)e=t[s],i=t[(s+1)%o],a+=(i.lng-e.lng)*n*(2+Math.sin(e.lat*n)+Math.sin(i.lat*n));a=6378137*a*6378137/2}return Math.abs(a)},formattedNumber:function(t,e){var i=parseFloat(t).toFixed(e),o=L.drawLocal.format&&L.drawLocal.format.numeric,a=o&&o.delimiters,n=a&&a.thousands,s=a&&a.decimal;if(n||s){var r=i.split(".");i=n?r[0].replace(/(\d)(?=(\d{3})+(?!\d))/g,"$1"+n):r[0],s=s||".",r.length>1&&(i=i+s+r[1])}return i},readableArea:function(e,i,o){var a,n,o=L.Util.extend({},t,o);return i?(n=["ha","m"],type=typeof i,"string"===type?n=[i]:"boolean"!==type&&(n=i),a=e>=1e6&&-1!==n.indexOf("km")?L.GeometryUtil.formattedNumber(1e-6*e,o.km)+" km²":e>=1e4&&-1!==n.indexOf("ha")?L.GeometryUtil.formattedNumber(1e-4*e,o.ha)+" ha":L.GeometryUtil.formattedNumber(e,o.m)+" m²"):(e/=.836127,a=e>=3097600?L.GeometryUtil.formattedNumber(e/3097600,o.mi)+" mi²":e>=4840?L.GeometryUtil.formattedNumber(e/4840,o.ac)+" acres":L.GeometryUtil.formattedNumber(e,o.yd)+" yd²"),a},readableDistance:function(e,i,o,a,n){var s,n=L.Util.extend({},t,n);switch(i?"string"==typeof i?i:"metric":o?"feet":a?"nauticalMile":"yards"){case"metric":s=e>1e3?L.GeometryUtil.formattedNumber(e/1e3,n.km)+" km":L.GeometryUtil.formattedNumber(e,n.m)+" m";break;case"feet":e*=3.28083,s=L.GeometryUtil.formattedNumber(e,n.ft)+" ft";break;case"nauticalMile":e*=.53996,s=L.GeometryUtil.formattedNumber(e/1e3,n.nm)+" nm";break;case"yards":default:e*=1.09361,s=e>1760?L.GeometryUtil.formattedNumber(e/1760,n.mi)+" miles":L.GeometryUtil.formattedNumber(e,n.yd)+" yd"}return s},isVersion07x:function(){var t=L.version.split(".");return 0===parseInt(t[0],10)&&7===parseInt(t[1],10)}})}(),L.Util.extend(L.LineUtil,{segmentsIntersect:function(t,e,i,o){return this._checkCounterclockwise(t,i,o)!==this._checkCounterclockwise(e,i,o)&&this._checkCounterclockwise(t,e,i)!==this._checkCounterclockwise(t,e,o)},_checkCounterclockwise:function(t,e,i){return(i.y-t.y)*(e.x-t.x)>(e.y-t.y)*(i.x-t.x)}}),L.Polyline.include({intersects:function(){var t,e,i,o=this._getProjectedPoints(),a=o?o.length:0;if(this._tooFewPointsForIntersection())return!1;for(t=a-1;t>=3;t--)if(e=o[t-1],i=o[t],this._lineSegmentsIntersectsRange(e,i,t-2))return!0;return!1},newLatLngIntersects:function(t,e){return!!this._map&&this.newPointIntersects(this._map.latLngToLayerPoint(t),e)},newPointIntersects:function(t,e){var i=this._getProjectedPoints(),o=i?i.length:0,a=i?i[o-1]:null,n=o-2;return!this._tooFewPointsForIntersection(1)&&this._lineSegmentsIntersectsRange(a,t,n,e?1:0)},_tooFewPointsForIntersection:function(t){var e=this._getProjectedPoints(),i=e?e.length:0;return i+=t||0,!e||i<=3},_lineSegmentsIntersectsRange:function(t,e,i,o){var a,n,s=this._getProjectedPoints();o=o||0;for(var r=i;r>o;r--)if(a=s[r-1],n=s[r],L.LineUtil.segmentsIntersect(t,e,a,n))return!0;return!1},_getProjectedPoints:function(){if(!this._defaultShape)return this._originalPoints;for(var t=[],e=this._defaultShape(),i=0;i<e.length;i++)t.push(this._map.latLngToLayerPoint(e[i]));return t}}),L.Polygon.include({intersects:function(){var t,e,i,o,a=this._getProjectedPoints();return!this._tooFewPointsForIntersection()&&(!!L.Polyline.prototype.intersects.call(this)||(t=a.length,e=a[0],i=a[t-1],o=t-2,this._lineSegmentsIntersectsRange(i,e,o,1)))}}),L.Control.Draw=L.Control.extend({options:{position:"topleft",draw:{},edit:!1},initialize:function(t){if(L.version<"0.7")throw new Error("Leaflet.draw 0.2.3+ requires Leaflet 0.7.0+. Download latest from https://github.com/Leaflet/Leaflet/");L.Control.prototype.initialize.call(this,t);var e;this._toolbars={},L.DrawToolbar&&this.options.draw&&(e=new L.DrawToolbar(this.options.draw),this._toolbars[L.DrawToolbar.TYPE]=e,this._toolbars[L.DrawToolbar.TYPE].on("enable",this._toolbarEnabled,this)),L.EditToolbar&&this.options.edit&&(e=new L.EditToolbar(this.options.edit),this._toolbars[L.EditToolbar.TYPE]=e,this._toolbars[L.EditToolbar.TYPE].on("enable",this._toolbarEnabled,this)),L.toolbar=this},onAdd:function(t){var e,i=L.DomUtil.create("div","leaflet-draw"),o=!1;for(var a in this._toolbars)this._toolbars.hasOwnProperty(a)&&(e=this._toolbars[a].addToolbar(t))&&(o||(L.DomUtil.hasClass(e,"leaflet-draw-toolbar-top")||L.DomUtil.addClass(e.childNodes[0],"leaflet-draw-toolbar-top"),o=!0),i.appendChild(e));return i},onRemove:function(){for(var t in this._toolbars)this._toolbars.hasOwnProperty(t)&&this._toolbars[t].removeToolbar()},setDrawingOptions:function(t){for(var e in this._toolbars)this._toolbars[e]instanceof L.DrawToolbar&&this._toolbars[e].setOptions(t)},_toolbarEnabled:function(t){var e=t.target;for(var i in this._toolbars)this._toolbars[i]!==e&&this._toolbars[i].disable()}}),L.Map.mergeOptions({drawControlTooltips:!0,drawControl:!1}),L.Map.addInitHook(function(){this.options.drawControl&&(this.drawControl=new L.Control.Draw,this.addControl(this.drawControl))}),L.Toolbar=L.Class.extend({initialize:function(t){L.setOptions(this,t),this._modes={},this._actionButtons=[],this._activeMode=null;var e=L.version.split(".");1===parseInt(e[0],10)&&parseInt(e[1],10)>=2?L.Toolbar.include(L.Evented.prototype):L.Toolbar.include(L.Mixin.Events)},enabled:function(){return null!==this._activeMode},disable:function(){this.enabled()&&this._activeMode.handler.disable()},addToolbar:function(t){var e,i=L.DomUtil.create("div","leaflet-draw-section"),o=0,a=this._toolbarClass||"",n=this.getModeHandlers(t);for(this._toolbarContainer=L.DomUtil.create("div","leaflet-draw-toolbar leaflet-bar"),this._map=t,e=0;e<n.length;e++)n[e].enabled&&this._initModeHandler(n[e].handler,this._toolbarContainer,o++,a,n[e].title);if(o)return this._lastButtonIndex=--o,this._actionsContainer=L.DomUtil.create("ul","leaflet-draw-actions"),i.appendChild(this._toolbarContainer),i.appendChild(this._actionsContainer),i},removeToolbar:function(){for(var t in this._modes)this._modes.hasOwnProperty(t)&&(this._disposeButton(this._modes[t].button,this._modes[t].handler.enable,this._modes[t].handler),this._modes[t].handler.disable(),this._modes[t].handler.off("enabled",this._handlerActivated,this).off("disabled",this._handlerDeactivated,this));this._modes={};for(var e=0,i=this._actionButtons.length;e<i;e++)this._disposeButton(this._actionButtons[e].button,this._actionButtons[e].callback,this);this._actionButtons=[],this._actionsContainer=null},_initModeHandler:function(t,e,i,o,a){var n=t.type;this._modes[n]={},this._modes[n].handler=t,this._modes[n].button=this._createButton({type:n,title:a,className:o+"-"+n,container:e,callback:this._modes[n].handler.enable,context:this._modes[n].handler}),this._modes[n].buttonIndex=i,this._modes[n].handler.on("enabled",this._handlerActivated,this).on("disabled",this._handlerDeactivated,this)},_detectIOS:function(){return/iPad|iPhone|iPod/.test(navigator.userAgent)&&!t.MSStream},_createButton:function(t){var e=L.DomUtil.create("a",t.className||"",t.container),i=L.DomUtil.create("span","sr-only",t.container);e.href="#",e.appendChild(i),t.title&&(e.title=t.title,i.innerHTML=t.title),t.text&&(e.innerHTML=t.text,i.innerHTML=t.text);var o=this._detectIOS()?"touchstart":"click";return L.DomEvent.on(e,"click",L.DomEvent.stopPropagation).on(e,"mousedown",L.DomEvent.stopPropagation).on(e,"dblclick",L.DomEvent.stopPropagation).on(e,"touchstart",L.DomEvent.stopPropagation).on(e,"click",L.DomEvent.preventDefault).on(e,o,t.callback,t.context),e},_disposeButton:function(t,e){var i=this._detectIOS()?"touchstart":"click";L.DomEvent.off(t,"click",L.DomEvent.stopPropagation).off(t,"mousedown",L.DomEvent.stopPropagation).off(t,"dblclick",L.DomEvent.stopPropagation).off(t,"touchstart",L.DomEvent.stopPropagation).off(t,"click",L.DomEvent.preventDefault).off(t,i,e)},_handlerActivated:function(t){this.disable(),this._activeMode=this._modes[t.handler],L.DomUtil.addClass(this._activeMode.button,"leaflet-draw-toolbar-button-enabled"),this._showActionsToolbar(),this.fire("enable")},_handlerDeactivated:function(){this._hideActionsToolbar(),L.DomUtil.removeClass(this._activeMode.button,"leaflet-draw-toolbar-button-enabled"),this._activeMode=null,this.fire("disable")},_createActions:function(t){var e,i,o,a,n=this._actionsContainer,s=this.getActions(t),r=s.length;for(i=0,o=this._actionButtons.length;i<o;i++)this._disposeButton(this._actionButtons[i].button,this._actionButtons[i].callback);for(this._actionButtons=[];n.firstChild;)n.removeChild(n.firstChild);for(var l=0;l<r;l++)"enabled"in s[l]&&!s[l].enabled||(e=L.DomUtil.create("li","",n),a=this._createButton({title:s[l].title,text:s[l].text,container:e,callback:s[l].callback,context:s[l].context}),this._actionButtons.push({button:a,callback:s[l].callback}))},_showActionsToolbar:function(){var t=this._activeMode.buttonIndex,e=this._lastButtonIndex,i=this._activeMode.button.offsetTop-1;this._createActions(this._activeMode.handler),this._actionsContainer.style.top=i+"px",0===t&&(L.DomUtil.addClass(this._toolbarContainer,"leaflet-draw-toolbar-notop"),L.DomUtil.addClass(this._actionsContainer,"leaflet-draw-actions-top")),t===e&&(L.DomUtil.addClass(this._toolbarContainer,"leaflet-draw-toolbar-nobottom"),L.DomUtil.addClass(this._actionsContainer,"leaflet-draw-actions-bottom")),this._actionsContainer.style.display="block",this._map.fire(L.Draw.Event.TOOLBAROPENED)},_hideActionsToolbar:function(){this._actionsContainer.style.display="none",L.DomUtil.removeClass(this._toolbarContainer,"leaflet-draw-toolbar-notop"),L.DomUtil.removeClass(this._toolbarContainer,"leaflet-draw-toolbar-nobottom"),L.DomUtil.removeClass(this._actionsContainer,"leaflet-draw-actions-top"),L.DomUtil.removeClass(this._actionsContainer,"leaflet-draw-actions-bottom"),this._map.fire(L.Draw.Event.TOOLBARCLOSED)}}),L.Draw=L.Draw||{},L.Draw.Tooltip=L.Class.extend({initialize:function(t){this._map=t,this._popupPane=t._panes.popupPane,this._visible=!1,this._container=t.options.drawControlTooltips?L.DomUtil.create("div","leaflet-draw-tooltip",this._popupPane):null,this._singleLineLabel=!1,this._map.on("mouseout",this._onMouseOut,this)},dispose:function(){this._map.off("mouseout",this._onMouseOut,this),this._container&&(this._popupPane.removeChild(this._container),this._container=null)},updateContent:function(t){return this._container?(t.subtext=t.subtext||"",0!==t.subtext.length||this._singleLineLabel?t.subtext.length>0&&this._singleLineLabel&&(L.DomUtil.removeClass(this._container,"leaflet-draw-tooltip-single"),this._singleLineLabel=!1):(L.DomUtil.addClass(this._container,"leaflet-draw-tooltip-single"),this._singleLineLabel=!0),this._container.innerHTML=(t.subtext.length>0?'<span class="leaflet-draw-tooltip-subtext">'+t.subtext+"</span><br />":"")+"<span>"+t.text+"</span>",t.text||t.subtext?(this._visible=!0,this._container.style.visibility="inherit"):(this._visible=!1,this._container.style.visibility="hidden"),this):this},updatePosition:function(t){var e=this._map.latLngToLayerPoint(t),i=this._container;return this._container&&(this._visible&&(i.style.visibility="inherit"),L.DomUtil.setPosition(i,e)),this},showAsError:function(){return this._container&&L.DomUtil.addClass(this._container,"leaflet-error-draw-tooltip"),this},removeError:function(){return this._container&&L.DomUtil.removeClass(this._container,"leaflet-error-draw-tooltip"),this},_onMouseOut:function(){this._container&&(this._container.style.visibility="hidden")}}),L.DrawToolbar=L.Toolbar.extend({statics:{TYPE:"draw"},options:{polyline:{},polygon:{},rectangle:{},circle:{},marker:{},circlemarker:{}},initialize:function(t){for(var e in this.options)this.options.hasOwnProperty(e)&&t[e]&&(t[e]=L.extend({},this.options[e],t[e]));this._toolbarClass="leaflet-draw-draw",L.Toolbar.prototype.initialize.call(this,t)},getModeHandlers:function(t){return[{enabled:this.options.polyline,handler:new L.Draw.Polyline(t,this.options.polyline),title:L.drawLocal.draw.toolbar.buttons.polyline},{enabled:this.options.polygon,handler:new L.Draw.Polygon(t,this.options.polygon),title:L.drawLocal.draw.toolbar.buttons.polygon},{enabled:this.options.rectangle,handler:new L.Draw.Rectangle(t,this.options.rectangle),title:L.drawLocal.draw.toolbar.buttons.rectangle},{enabled:this.options.circle,handler:new L.Draw.Circle(t,this.options.circle),title:L.drawLocal.draw.toolbar.buttons.circle},{enabled:this.options.marker,handler:new L.Draw.Marker(t,this.options.marker),title:L.drawLocal.draw.toolbar.buttons.marker},{enabled:this.options.circlemarker,handler:new L.Draw.CircleMarker(t,this.options.circlemarker),title:L.drawLocal.draw.toolbar.buttons.circlemarker}]},getActions:function(t){return[{enabled:t.completeShape,title:L.drawLocal.draw.toolbar.finish.title,text:L.drawLocal.draw.toolbar.finish.text,callback:t.completeShape,context:t},{enabled:t.deleteLastVertex,title:L.drawLocal.draw.toolbar.undo.title,text:L.drawLocal.draw.toolbar.undo.text,callback:t.deleteLastVertex,context:t},{title:L.drawLocal.draw.toolbar.actions.title,text:L.drawLocal.draw.toolbar.actions.text,callback:this.disable,context:this}]},setOptions:function(t){L.setOptions(this,t);for(var e in this._modes)this._modes.hasOwnProperty(e)&&t.hasOwnProperty(e)&&this._modes[e].handler.setOptions(t[e])}}),L.EditToolbar=L.Toolbar.extend({statics:{TYPE:"edit"},options:{edit:{selectedPathOptions:{dashArray:"10, 10",fill:!0,fillColor:"#fe57a1",fillOpacity:.1,maintainColor:!1}},remove:{},poly:null,featureGroup:null},initialize:function(t){t.edit&&(void 0===t.edit.selectedPathOptions&&(t.edit.selectedPathOptions=this.options.edit.selectedPathOptions),t.edit.selectedPathOptions=L.extend({},this.options.edit.selectedPathOptions,t.edit.selectedPathOptions)),t.remove&&(t.remove=L.extend({},this.options.remove,t.remove)),t.poly&&(t.poly=L.extend({},this.options.poly,t.poly)),this._toolbarClass="leaflet-draw-edit",L.Toolbar.prototype.initialize.call(this,t),this._selectedFeatureCount=0},getModeHandlers:function(t){var e=this.options.featureGroup;return[{enabled:this.options.edit,handler:new L.EditToolbar.Edit(t,{featureGroup:e,selectedPathOptions:this.options.edit.selectedPathOptions,poly:this.options.poly}),title:L.drawLocal.edit.toolbar.buttons.edit},{enabled:this.options.remove,handler:new L.EditToolbar.Delete(t,{featureGroup:e}),title:L.drawLocal.edit.toolbar.buttons.remove}]},getActions:function(t){var e=[{title:L.drawLocal.edit.toolbar.actions.save.title,text:L.drawLocal.edit.toolbar.actions.save.text,callback:this._save,context:this},{title:L.drawLocal.edit.toolbar.actions.cancel.title,text:L.drawLocal.edit.toolbar.actions.cancel.text,callback:this.disable,context:this}];return t.removeAllLayers&&e.push({title:L.drawLocal.edit.toolbar.actions.clearAll.title,text:L.drawLocal.edit.toolbar.actions.clearAll.text,callback:this._clearAllLayers,context:this}),e},addToolbar:function(t){var e=L.Toolbar.prototype.addToolbar.call(this,t);return this._checkDisabled(),this.options.featureGroup.on("layeradd layerremove",this._checkDisabled,this),e},removeToolbar:function(){this.options.featureGroup.off("layeradd layerremove",this._checkDisabled,this),L.Toolbar.prototype.removeToolbar.call(this)},disable:function(){this.enabled()&&(this._activeMode.handler.revertLayers(),L.Toolbar.prototype.disable.call(this))},_save:function(){this._activeMode.handler.save(),this._activeMode&&this._activeMode.handler.disable()},_clearAllLayers:function(){this._activeMode.handler.removeAllLayers(),this._activeMode&&this._activeMode.handler.disable()},_checkDisabled:function(){var t,e=this.options.featureGroup,i=0!==e.getLayers().length;this.options.edit&&(t=this._modes[L.EditToolbar.Edit.TYPE].button,i?L.DomUtil.removeClass(t,"leaflet-disabled"):L.DomUtil.addClass(t,"leaflet-disabled"),t.setAttribute("title",i?L.drawLocal.edit.toolbar.buttons.edit:L.drawLocal.edit.toolbar.buttons.editDisabled)),this.options.remove&&(t=this._modes[L.EditToolbar.Delete.TYPE].button,i?L.DomUtil.removeClass(t,"leaflet-disabled"):L.DomUtil.addClass(t,"leaflet-disabled"),t.setAttribute("title",i?L.drawLocal.edit.toolbar.buttons.remove:L.drawLocal.edit.toolbar.buttons.removeDisabled))}}),L.EditToolbar.Edit=L.Handler.extend({statics:{TYPE:"edit"},initialize:function(t,e){if(L.Handler.prototype.initialize.call(this,t),L.setOptions(this,e),this._featureGroup=e.featureGroup,!(this._featureGroup instanceof L.FeatureGroup))throw new Error("options.featureGroup must be a L.FeatureGroup");this._uneditedLayerProps={},this.type=L.EditToolbar.Edit.TYPE;var i=L.version.split(".");1===parseInt(i[0],10)&&parseInt(i[1],10)>=2?L.EditToolbar.Edit.include(L.Evented.prototype):L.EditToolbar.Edit.include(L.Mixin.Events)},enable:function(){!this._enabled&&this._hasAvailableLayers()&&(this.fire("enabled",{handler:this.type}),this._map.fire(L.Draw.Event.EDITSTART,{handler:this.type}),L.Handler.prototype.enable.call(this),this._featureGroup.on("layeradd",this._enableLayerEdit,this).on("layerremove",this._disableLayerEdit,this))},disable:function(){this._enabled&&(this._featureGroup.off("layeradd",this._enableLayerEdit,this).off("layerremove",this._disableLayerEdit,this),L.Handler.prototype.disable.call(this),this._map.fire(L.Draw.Event.EDITSTOP,{handler:this.type}),this.fire("disabled",{handler:this.type}))},addHooks:function(){var t=this._map;t&&(t.getContainer().focus(),this._featureGroup.eachLayer(this._enableLayerEdit,this),this._tooltip=new L.Draw.Tooltip(this._map),this._tooltip.updateContent({text:L.drawLocal.edit.handlers.edit.tooltip.text,subtext:L.drawLocal.edit.handlers.edit.tooltip.subtext}),t._editTooltip=this._tooltip,this._updateTooltip(),this._map.on("mousemove",this._onMouseMove,this).on("touchmove",this._onMouseMove,this).on("MSPointerMove",this._onMouseMove,this).on(L.Draw.Event.EDITVERTEX,this._updateTooltip,this))},removeHooks:function(){this._map&&(this._featureGroup.eachLayer(this._disableLayerEdit,this),this._uneditedLayerProps={},this._tooltip.dispose(),this._tooltip=null,this._map.off("mousemove",this._onMouseMove,this).off("touchmove",this._onMouseMove,this).off("MSPointerMove",this._onMouseMove,this).off(L.Draw.Event.EDITVERTEX,this._updateTooltip,this))},revertLayers:function(){this._featureGroup.eachLayer(function(t){this._revertLayer(t)},this)},save:function(){var t=new L.LayerGroup;this._featureGroup.eachLayer(function(e){e.edited&&(t.addLayer(e),e.edited=!1)}),this._map.fire(L.Draw.Event.EDITED,{layers:t})},_backupLayer:function(t){var e=L.Util.stamp(t);this._uneditedLayerProps[e]||(t instanceof L.Polyline||t instanceof L.Polygon||t instanceof L.Rectangle?this._uneditedLayerProps[e]={latlngs:L.LatLngUtil.cloneLatLngs(t.getLatLngs())}:t instanceof L.Circle?this._uneditedLayerProps[e]={latlng:L.LatLngUtil.cloneLatLng(t.getLatLng()),radius:t.getRadius()}:(t instanceof L.Marker||t instanceof L.CircleMarker)&&(this._uneditedLayerProps[e]={latlng:L.LatLngUtil.cloneLatLng(t.getLatLng())}))},_getTooltipText:function(){return{text:L.drawLocal.edit.handlers.edit.tooltip.text,subtext:L.drawLocal.edit.handlers.edit.tooltip.subtext}},_updateTooltip:function(){this._tooltip.updateContent(this._getTooltipText())},_revertLayer:function(t){var e=L.Util.stamp(t);t.edited=!1,this._uneditedLayerProps.hasOwnProperty(e)&&(t instanceof L.Polyline||t instanceof L.Polygon||t instanceof L.Rectangle?t.setLatLngs(this._uneditedLayerProps[e].latlngs):t instanceof L.Circle?(t.setLatLng(this._uneditedLayerProps[e].latlng),t.setRadius(this._uneditedLayerProps[e].radius)):(t instanceof L.Marker||t instanceof L.CircleMarker)&&t.setLatLng(this._uneditedLayerProps[e].latlng),t.fire("revert-edited",{layer:t}))},_enableLayerEdit:function(t){var e,i,o=t.layer||t.target||t;this._backupLayer(o),this.options.poly&&(i=L.Util.extend({},this.options.poly),o.options.poly=i),this.options.selectedPathOptions&&(e=L.Util.extend({},this.options.selectedPathOptions),e.maintainColor&&(e.color=o.options.color,e.fillColor=o.options.fillColor),o.options.original=L.extend({},o.options),o.options.editing=e),o instanceof L.Marker?(o.editing&&o.editing.enable(),o.dragging.enable(),o.on("dragend",this._onMarkerDragEnd).on("touchmove",this._onTouchMove,this).on("MSPointerMove",this._onTouchMove,this).on("touchend",this._onMarkerDragEnd,this).on("MSPointerUp",this._onMarkerDragEnd,this)):o.editing.enable()},_disableLayerEdit:function(t){var e=t.layer||t.target||t;e.edited=!1,e.editing&&e.editing.disable(),delete e.options.editing,delete e.options.original,
+this._selectedPathOptions&&(e instanceof L.Marker?this._toggleMarkerHighlight(e):(e.setStyle(e.options.previousOptions),delete e.options.previousOptions)),e instanceof L.Marker?(e.dragging.disable(),e.off("dragend",this._onMarkerDragEnd,this).off("touchmove",this._onTouchMove,this).off("MSPointerMove",this._onTouchMove,this).off("touchend",this._onMarkerDragEnd,this).off("MSPointerUp",this._onMarkerDragEnd,this)):e.editing.disable()},_onMouseMove:function(t){this._tooltip.updatePosition(t.latlng)},_onMarkerDragEnd:function(t){var e=t.target;e.edited=!0,this._map.fire(L.Draw.Event.EDITMOVE,{layer:e})},_onTouchMove:function(t){var e=t.originalEvent.changedTouches[0],i=this._map.mouseEventToLayerPoint(e),o=this._map.layerPointToLatLng(i);t.target.setLatLng(o)},_hasAvailableLayers:function(){return 0!==this._featureGroup.getLayers().length}}),L.EditToolbar.Delete=L.Handler.extend({statics:{TYPE:"remove"},initialize:function(t,e){if(L.Handler.prototype.initialize.call(this,t),L.Util.setOptions(this,e),this._deletableLayers=this.options.featureGroup,!(this._deletableLayers instanceof L.FeatureGroup))throw new Error("options.featureGroup must be a L.FeatureGroup");this.type=L.EditToolbar.Delete.TYPE;var i=L.version.split(".");1===parseInt(i[0],10)&&parseInt(i[1],10)>=2?L.EditToolbar.Delete.include(L.Evented.prototype):L.EditToolbar.Delete.include(L.Mixin.Events)},enable:function(){!this._enabled&&this._hasAvailableLayers()&&(this.fire("enabled",{handler:this.type}),this._map.fire(L.Draw.Event.DELETESTART,{handler:this.type}),L.Handler.prototype.enable.call(this),this._deletableLayers.on("layeradd",this._enableLayerDelete,this).on("layerremove",this._disableLayerDelete,this))},disable:function(){this._enabled&&(this._deletableLayers.off("layeradd",this._enableLayerDelete,this).off("layerremove",this._disableLayerDelete,this),L.Handler.prototype.disable.call(this),this._map.fire(L.Draw.Event.DELETESTOP,{handler:this.type}),this.fire("disabled",{handler:this.type}))},addHooks:function(){var t=this._map;t&&(t.getContainer().focus(),this._deletableLayers.eachLayer(this._enableLayerDelete,this),this._deletedLayers=new L.LayerGroup,this._tooltip=new L.Draw.Tooltip(this._map),this._tooltip.updateContent({text:L.drawLocal.edit.handlers.remove.tooltip.text}),this._map.on("mousemove",this._onMouseMove,this))},removeHooks:function(){this._map&&(this._deletableLayers.eachLayer(this._disableLayerDelete,this),this._deletedLayers=null,this._tooltip.dispose(),this._tooltip=null,this._map.off("mousemove",this._onMouseMove,this))},revertLayers:function(){this._deletedLayers.eachLayer(function(t){this._deletableLayers.addLayer(t),t.fire("revert-deleted",{layer:t})},this)},save:function(){this._map.fire(L.Draw.Event.DELETED,{layers:this._deletedLayers})},removeAllLayers:function(){this._deletableLayers.eachLayer(function(t){this._removeLayer({layer:t})},this),this.save()},_enableLayerDelete:function(t){(t.layer||t.target||t).on("click",this._removeLayer,this)},_disableLayerDelete:function(t){var e=t.layer||t.target||t;e.off("click",this._removeLayer,this),this._deletedLayers.removeLayer(e)},_removeLayer:function(t){var e=t.layer||t.target||t;this._deletableLayers.removeLayer(e),this._deletedLayers.addLayer(e),e.fire("deleted")},_onMouseMove:function(t){this._tooltip.updatePosition(t.latlng)},_hasAvailableLayers:function(){return 0!==this._deletableLayers.getLayers().length}})}(window,document); \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/Control.FullScreen.css b/www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/Control.FullScreen.css
new file mode 100644
index 00000000..96ae11e6
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/Control.FullScreen.css
@@ -0,0 +1,8 @@
+.fullscreen-icon { background-image: url(icon-fullscreen.png); }
+.leaflet-retina .fullscreen-icon { background-image: url(icon-fullscreen-2x.png); background-size: 26px 26px; }
+/* one selector per rule as explained here : http://www.sitepoint.com/html5-full-screen-api/ */
+.leaflet-container:-webkit-full-screen { width: 100% !important; height: 100% !important; z-index: 99999; }
+.leaflet-container:-ms-fullscreen { width: 100% !important; height: 100% !important; z-index: 99999; }
+.leaflet-container:full-screen { width: 100% !important; height: 100% !important; z-index: 99999; }
+.leaflet-container:fullscreen { width: 100% !important; height: 100% !important; z-index: 99999; }
+.leaflet-pseudo-fullscreen { position: fixed !important; width: 100% !important; height: 100% !important; top: 0px !important; left: 0px !important; z-index: 99999; } \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/Control.FullScreen.js b/www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/Control.FullScreen.js
new file mode 100644
index 00000000..44da9a13
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/Control.FullScreen.js
@@ -0,0 +1,228 @@
+(function () {
+
+L.Control.FullScreen = L.Control.extend({
+ options: {
+ position: 'topleft',
+ title: 'Full Screen',
+ titleCancel: 'Exit Full Screen',
+ forceSeparateButton: false,
+ forcePseudoFullscreen: false,
+ fullscreenElement: false
+ },
+
+ onAdd: function (map) {
+ var className = 'leaflet-control-zoom-fullscreen', container, content = '';
+
+ if (map.zoomControl && !this.options.forceSeparateButton) {
+ container = map.zoomControl._container;
+ } else {
+ container = L.DomUtil.create('div', 'leaflet-bar');
+ }
+
+ if (this.options.content) {
+ content = this.options.content;
+ } else {
+ className += ' fullscreen-icon';
+ }
+
+ this._createButton(this.options.title, className, content, container, this.toggleFullScreen, this);
+ this._map.fullscreenControl = this;
+
+ this._map.on('enterFullscreen exitFullscreen', this._toggleTitle, this);
+
+ return container;
+ },
+
+ onRemove: function (map) {
+ L.DomEvent
+ .off(this.link, 'click', L.DomEvent.stopPropagation)
+ .off(this.link, 'click', L.DomEvent.preventDefault)
+ .off(this.link, 'click', this.toggleFullScreen, this);
+
+ L.DomEvent
+ .off(this._container, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation)
+ .off(this._container, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault)
+ .off(this._container, fullScreenApi.fullScreenEventName, this._handleFullscreenChange, this);
+
+ L.DomEvent
+ .off(document, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation)
+ .off(document, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault)
+ .off(document, fullScreenApi.fullScreenEventName, this._handleFullscreenChange, this);
+ },
+
+ _createButton: function (title, className, content, container, fn, context) {
+ this.link = L.DomUtil.create('a', className, container);
+ this.link.href = '#';
+ this.link.title = title;
+ this.link.innerHTML = content;
+
+ this.link.setAttribute('role', 'button');
+ this.link.setAttribute('aria-label', title);
+
+ L.DomEvent
+ .on(this.link, 'click', L.DomEvent.stopPropagation)
+ .on(this.link, 'click', L.DomEvent.preventDefault)
+ .on(this.link, 'click', fn, context);
+
+ L.DomEvent
+ .on(container, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation)
+ .on(container, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault)
+ .on(container, fullScreenApi.fullScreenEventName, this._handleFullscreenChange, context);
+
+ L.DomEvent
+ .on(document, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation)
+ .on(document, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault)
+ .on(document, fullScreenApi.fullScreenEventName, this._handleFullscreenChange, context);
+
+ return this.link;
+ },
+
+ toggleFullScreen: function () {
+ var map = this._map;
+ map._exitFired = false;
+ if (map._isFullscreen) {
+ if (fullScreenApi.supportsFullScreen && !this.options.forcePseudoFullscreen) {
+ fullScreenApi.cancelFullScreen();
+ } else {
+ L.DomUtil.removeClass(this.options.fullscreenElement ? this.options.fullscreenElement : map._container, 'leaflet-pseudo-fullscreen');
+ }
+ map.fire('exitFullscreen');
+ map._exitFired = true;
+ map._isFullscreen = false;
+ }
+ else {
+ if (fullScreenApi.supportsFullScreen && !this.options.forcePseudoFullscreen) {
+ fullScreenApi.requestFullScreen(this.options.fullscreenElement ? this.options.fullscreenElement : map._container);
+ } else {
+ L.DomUtil.addClass(this.options.fullscreenElement ? this.options.fullscreenElement : map._container, 'leaflet-pseudo-fullscreen');
+ }
+ map.fire('enterFullscreen');
+ map._isFullscreen = true;
+ }
+ },
+
+ _toggleTitle: function () {
+ this.link.title = this._map._isFullscreen ? this.options.title : this.options.titleCancel;
+ },
+
+ _handleFullscreenChange: function () {
+ var map = this._map;
+ map.invalidateSize();
+ if (!fullScreenApi.isFullScreen() && !map._exitFired) {
+ map.fire('exitFullscreen');
+ map._exitFired = true;
+ map._isFullscreen = false;
+ }
+ }
+});
+
+L.Map.include({
+ toggleFullscreen: function () {
+ this.fullscreenControl.toggleFullScreen();
+ }
+});
+
+L.Map.addInitHook(function () {
+ if (this.options.fullscreenControl) {
+ this.addControl(L.control.fullscreen(this.options.fullscreenControlOptions));
+ }
+});
+
+L.control.fullscreen = function (options) {
+ return new L.Control.FullScreen(options);
+};
+
+/*
+Native FullScreen JavaScript API
+-------------
+Assumes Mozilla naming conventions instead of W3C for now
+
+source : http://johndyer.name/native-fullscreen-javascript-api-plus-jquery-plugin/
+
+*/
+
+ var
+ fullScreenApi = {
+ supportsFullScreen: false,
+ isFullScreen: function () { return false; },
+ requestFullScreen: function () {},
+ cancelFullScreen: function () {},
+ fullScreenEventName: '',
+ prefix: ''
+ },
+ browserPrefixes = 'webkit moz o ms khtml'.split(' ');
+
+ // check for native support
+ if (typeof document.exitFullscreen !== 'undefined') {
+ fullScreenApi.supportsFullScreen = true;
+ } else {
+ // check for fullscreen support by vendor prefix
+ for (var i = 0, il = browserPrefixes.length; i < il; i++) {
+ fullScreenApi.prefix = browserPrefixes[i];
+ if (typeof document[fullScreenApi.prefix + 'CancelFullScreen'] !== 'undefined') {
+ fullScreenApi.supportsFullScreen = true;
+ break;
+ }
+ }
+ if (typeof document['msExitFullscreen'] !== 'undefined') {
+ fullScreenApi.prefix = 'ms';
+ fullScreenApi.supportsFullScreen = true;
+ }
+ }
+
+ // update methods to do something useful
+ if (fullScreenApi.supportsFullScreen) {
+ if (fullScreenApi.prefix === 'ms') {
+ fullScreenApi.fullScreenEventName = 'MSFullscreenChange';
+ } else {
+ fullScreenApi.fullScreenEventName = fullScreenApi.prefix + 'fullscreenchange';
+ }
+ fullScreenApi.isFullScreen = function () {
+ switch (this.prefix) {
+ case '':
+ return document.fullscreen;
+ case 'webkit':
+ return document.webkitIsFullScreen;
+ case 'ms':
+ return document.msFullscreenElement;
+ default:
+ return document[this.prefix + 'FullScreen'];
+ }
+ };
+ fullScreenApi.requestFullScreen = function (el) {
+ switch (this.prefix) {
+ case '':
+ return el.requestFullscreen();
+ case 'ms':
+ return el.msRequestFullscreen();
+ default:
+ return el[this.prefix + 'RequestFullScreen']();
+ }
+ };
+ fullScreenApi.cancelFullScreen = function () {
+ switch (this.prefix) {
+ case '':
+ return document.exitFullscreen();
+ case 'ms':
+ return document.msExitFullscreen();
+ default:
+ return document[this.prefix + 'CancelFullScreen']();
+ }
+ };
+ }
+
+ // jQuery plugin
+ if (typeof jQuery !== 'undefined') {
+ jQuery.fn.requestFullScreen = function () {
+ return this.each(function () {
+ var el = jQuery(this);
+ if (fullScreenApi.supportsFullScreen) {
+ fullScreenApi.requestFullScreen(el);
+ }
+ });
+ };
+ }
+
+ // export api
+ window.fullScreenApi = fullScreenApi;
+})();
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/icon-fullscreen-2x.png b/www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/icon-fullscreen-2x.png
new file mode 100644
index 00000000..efc0dfbe
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/icon-fullscreen-2x.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/icon-fullscreen.png b/www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/icon-fullscreen.png
new file mode 100644
index 00000000..8ee24472
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/icon-fullscreen.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/package.json b/www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/package.json
index e0c9aa7b..c07bdfa5 100644
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/package.json
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.fullscreen/package.json
@@ -1,10 +1,10 @@
{
"name": "leaflet.fullscreen",
- "version": "1.1.4",
+ "version": "1.6.0",
"description": "Simple plugin for Leaflet that adds fullscreen button to your maps.",
"main": "Control.FullScreen.js",
"scripts": {
- "test": "jshint Control.FullScreen.js"
+ "test": "eslint --config .eslintrc Control.FullScreen.js"
},
"repository": {
"type": "git",
@@ -17,7 +17,7 @@
"fullscreen"
],
"devDependencies": {
- "jshint": "2.5.0"
+ "eslint": "^5.4.0"
},
"author": "b_b",
"license": "MIT License",
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.markercluster/MarkerCluster.css b/www/wiki/extensions/Maps/resources/lib/leaflet.markercluster/MarkerCluster.css
index c60d71b7..c60d71b7 100644
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet.markercluster/MarkerCluster.css
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.markercluster/MarkerCluster.css
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet.markercluster/leaflet.markercluster.js b/www/wiki/extensions/Maps/resources/lib/leaflet.markercluster/leaflet.markercluster.js
new file mode 100644
index 00000000..1111da43
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet.markercluster/leaflet.markercluster.js
@@ -0,0 +1,2689 @@
+/*
+ * Leaflet.markercluster 1.4.1+master.37ab9a2,
+ * Provides Beautiful Animated Marker Clustering functionality for Leaflet, a JS library for interactive maps.
+ * https://github.com/Leaflet/Leaflet.markercluster
+ * (c) 2012-2017, Dave Leaver, smartrak
+ */
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
+ (factory((global.Leaflet = global.Leaflet || {}, global.Leaflet.markercluster = global.Leaflet.markercluster || {})));
+}(this, (function (exports) { 'use strict';
+
+/*
+ * L.MarkerClusterGroup extends L.FeatureGroup by clustering the markers contained within
+ */
+
+var MarkerClusterGroup = L.MarkerClusterGroup = L.FeatureGroup.extend({
+
+ options: {
+ maxClusterRadius: 80, //A cluster will cover at most this many pixels from its center
+ iconCreateFunction: null,
+ clusterPane: L.Marker.prototype.options.pane,
+
+ spiderfyOnMaxZoom: true,
+ showCoverageOnHover: true,
+ zoomToBoundsOnClick: true,
+ singleMarkerMode: false,
+
+ disableClusteringAtZoom: null,
+
+ // Setting this to false prevents the removal of any clusters outside of the viewpoint, which
+ // is the default behaviour for performance reasons.
+ removeOutsideVisibleBounds: true,
+
+ // Set to false to disable all animations (zoom and spiderfy).
+ // If false, option animateAddingMarkers below has no effect.
+ // If L.DomUtil.TRANSITION is falsy, this option has no effect.
+ animate: true,
+
+ //Whether to animate adding markers after adding the MarkerClusterGroup to the map
+ // If you are adding individual markers set to true, if adding bulk markers leave false for massive performance gains.
+ animateAddingMarkers: false,
+
+ //Increase to increase the distance away that spiderfied markers appear from the center
+ spiderfyDistanceMultiplier: 1,
+
+ // Make it possible to specify a polyline options on a spider leg
+ spiderLegPolylineOptions: { weight: 1.5, color: '#222', opacity: 0.5 },
+
+ // When bulk adding layers, adds markers in chunks. Means addLayers may not add all the layers in the call, others will be loaded during setTimeouts
+ chunkedLoading: false,
+ chunkInterval: 200, // process markers for a maximum of ~ n milliseconds (then trigger the chunkProgress callback)
+ chunkDelay: 50, // at the end of each interval, give n milliseconds back to system/browser
+ chunkProgress: null, // progress callback: function(processed, total, elapsed) (e.g. for a progress indicator)
+
+ //Options to pass to the L.Polygon constructor
+ polygonOptions: {}
+ },
+
+ initialize: function (options) {
+ L.Util.setOptions(this, options);
+ if (!this.options.iconCreateFunction) {
+ this.options.iconCreateFunction = this._defaultIconCreateFunction;
+ }
+
+ this._featureGroup = L.featureGroup();
+ this._featureGroup.addEventParent(this);
+
+ this._nonPointGroup = L.featureGroup();
+ this._nonPointGroup.addEventParent(this);
+
+ this._inZoomAnimation = 0;
+ this._needsClustering = [];
+ this._needsRemoving = []; //Markers removed while we aren't on the map need to be kept track of
+ //The bounds of the currently shown area (from _getExpandedVisibleBounds) Updated on zoom/move
+ this._currentShownBounds = null;
+
+ this._queue = [];
+
+ this._childMarkerEventHandlers = {
+ 'dragstart': this._childMarkerDragStart,
+ 'move': this._childMarkerMoved,
+ 'dragend': this._childMarkerDragEnd,
+ };
+
+ // Hook the appropriate animation methods.
+ var animate = L.DomUtil.TRANSITION && this.options.animate;
+ L.extend(this, animate ? this._withAnimation : this._noAnimation);
+ // Remember which MarkerCluster class to instantiate (animated or not).
+ this._markerCluster = animate ? L.MarkerCluster : L.MarkerClusterNonAnimated;
+ },
+
+ addLayer: function (layer) {
+
+ if (layer instanceof L.LayerGroup) {
+ return this.addLayers([layer]);
+ }
+
+ //Don't cluster non point data
+ if (!layer.getLatLng) {
+ this._nonPointGroup.addLayer(layer);
+ this.fire('layeradd', { layer: layer });
+ return this;
+ }
+
+ if (!this._map) {
+ this._needsClustering.push(layer);
+ this.fire('layeradd', { layer: layer });
+ return this;
+ }
+
+ if (this.hasLayer(layer)) {
+ return this;
+ }
+
+
+ //If we have already clustered we'll need to add this one to a cluster
+
+ if (this._unspiderfy) {
+ this._unspiderfy();
+ }
+
+ this._addLayer(layer, this._maxZoom);
+ this.fire('layeradd', { layer: layer });
+
+ // Refresh bounds and weighted positions.
+ this._topClusterLevel._recalculateBounds();
+
+ this._refreshClustersIcons();
+
+ //Work out what is visible
+ var visibleLayer = layer,
+ currentZoom = this._zoom;
+ if (layer.__parent) {
+ while (visibleLayer.__parent._zoom >= currentZoom) {
+ visibleLayer = visibleLayer.__parent;
+ }
+ }
+
+ if (this._currentShownBounds.contains(visibleLayer.getLatLng())) {
+ if (this.options.animateAddingMarkers) {
+ this._animationAddLayer(layer, visibleLayer);
+ } else {
+ this._animationAddLayerNonAnimated(layer, visibleLayer);
+ }
+ }
+ return this;
+ },
+
+ removeLayer: function (layer) {
+
+ if (layer instanceof L.LayerGroup) {
+ return this.removeLayers([layer]);
+ }
+
+ //Non point layers
+ if (!layer.getLatLng) {
+ this._nonPointGroup.removeLayer(layer);
+ this.fire('layerremove', { layer: layer });
+ return this;
+ }
+
+ if (!this._map) {
+ if (!this._arraySplice(this._needsClustering, layer) && this.hasLayer(layer)) {
+ this._needsRemoving.push({ layer: layer, latlng: layer._latlng });
+ }
+ this.fire('layerremove', { layer: layer });
+ return this;
+ }
+
+ if (!layer.__parent) {
+ return this;
+ }
+
+ if (this._unspiderfy) {
+ this._unspiderfy();
+ this._unspiderfyLayer(layer);
+ }
+
+ //Remove the marker from clusters
+ this._removeLayer(layer, true);
+ this.fire('layerremove', { layer: layer });
+
+ // Refresh bounds and weighted positions.
+ this._topClusterLevel._recalculateBounds();
+
+ this._refreshClustersIcons();
+
+ layer.off(this._childMarkerEventHandlers, this);
+
+ if (this._featureGroup.hasLayer(layer)) {
+ this._featureGroup.removeLayer(layer);
+ if (layer.clusterShow) {
+ layer.clusterShow();
+ }
+ }
+
+ return this;
+ },
+
+ //Takes an array of markers and adds them in bulk
+ addLayers: function (layersArray, skipLayerAddEvent) {
+ if (!L.Util.isArray(layersArray)) {
+ return this.addLayer(layersArray);
+ }
+
+ var fg = this._featureGroup,
+ npg = this._nonPointGroup,
+ chunked = this.options.chunkedLoading,
+ chunkInterval = this.options.chunkInterval,
+ chunkProgress = this.options.chunkProgress,
+ l = layersArray.length,
+ offset = 0,
+ originalArray = true,
+ m;
+
+ if (this._map) {
+ var started = (new Date()).getTime();
+ var process = L.bind(function () {
+ var start = (new Date()).getTime();
+ for (; offset < l; offset++) {
+ if (chunked && offset % 200 === 0) {
+ // every couple hundred markers, instrument the time elapsed since processing started:
+ var elapsed = (new Date()).getTime() - start;
+ if (elapsed > chunkInterval) {
+ break; // been working too hard, time to take a break :-)
+ }
+ }
+
+ m = layersArray[offset];
+
+ // Group of layers, append children to layersArray and skip.
+ // Side effects:
+ // - Total increases, so chunkProgress ratio jumps backward.
+ // - Groups are not included in this group, only their non-group child layers (hasLayer).
+ // Changing array length while looping does not affect performance in current browsers:
+ // http://jsperf.com/for-loop-changing-length/6
+ if (m instanceof L.LayerGroup) {
+ if (originalArray) {
+ layersArray = layersArray.slice();
+ originalArray = false;
+ }
+ this._extractNonGroupLayers(m, layersArray);
+ l = layersArray.length;
+ continue;
+ }
+
+ //Not point data, can't be clustered
+ if (!m.getLatLng) {
+ npg.addLayer(m);
+ if (!skipLayerAddEvent) {
+ this.fire('layeradd', { layer: m });
+ }
+ continue;
+ }
+
+ if (this.hasLayer(m)) {
+ continue;
+ }
+
+ this._addLayer(m, this._maxZoom);
+ if (!skipLayerAddEvent) {
+ this.fire('layeradd', { layer: m });
+ }
+
+ //If we just made a cluster of size 2 then we need to remove the other marker from the map (if it is) or we never will
+ if (m.__parent) {
+ if (m.__parent.getChildCount() === 2) {
+ var markers = m.__parent.getAllChildMarkers(),
+ otherMarker = markers[0] === m ? markers[1] : markers[0];
+ fg.removeLayer(otherMarker);
+ }
+ }
+ }
+
+ if (chunkProgress) {
+ // report progress and time elapsed:
+ chunkProgress(offset, l, (new Date()).getTime() - started);
+ }
+
+ // Completed processing all markers.
+ if (offset === l) {
+
+ // Refresh bounds and weighted positions.
+ this._topClusterLevel._recalculateBounds();
+
+ this._refreshClustersIcons();
+
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
+ } else {
+ setTimeout(process, this.options.chunkDelay);
+ }
+ }, this);
+
+ process();
+ } else {
+ var needsClustering = this._needsClustering;
+
+ for (; offset < l; offset++) {
+ m = layersArray[offset];
+
+ // Group of layers, append children to layersArray and skip.
+ if (m instanceof L.LayerGroup) {
+ if (originalArray) {
+ layersArray = layersArray.slice();
+ originalArray = false;
+ }
+ this._extractNonGroupLayers(m, layersArray);
+ l = layersArray.length;
+ continue;
+ }
+
+ //Not point data, can't be clustered
+ if (!m.getLatLng) {
+ npg.addLayer(m);
+ continue;
+ }
+
+ if (this.hasLayer(m)) {
+ continue;
+ }
+
+ needsClustering.push(m);
+ }
+ }
+ return this;
+ },
+
+ //Takes an array of markers and removes them in bulk
+ removeLayers: function (layersArray) {
+ var i, m,
+ l = layersArray.length,
+ fg = this._featureGroup,
+ npg = this._nonPointGroup,
+ originalArray = true;
+
+ if (!this._map) {
+ for (i = 0; i < l; i++) {
+ m = layersArray[i];
+
+ // Group of layers, append children to layersArray and skip.
+ if (m instanceof L.LayerGroup) {
+ if (originalArray) {
+ layersArray = layersArray.slice();
+ originalArray = false;
+ }
+ this._extractNonGroupLayers(m, layersArray);
+ l = layersArray.length;
+ continue;
+ }
+
+ this._arraySplice(this._needsClustering, m);
+ npg.removeLayer(m);
+ if (this.hasLayer(m)) {
+ this._needsRemoving.push({ layer: m, latlng: m._latlng });
+ }
+ this.fire('layerremove', { layer: m });
+ }
+ return this;
+ }
+
+ if (this._unspiderfy) {
+ this._unspiderfy();
+
+ // Work on a copy of the array, so that next loop is not affected.
+ var layersArray2 = layersArray.slice(),
+ l2 = l;
+ for (i = 0; i < l2; i++) {
+ m = layersArray2[i];
+
+ // Group of layers, append children to layersArray and skip.
+ if (m instanceof L.LayerGroup) {
+ this._extractNonGroupLayers(m, layersArray2);
+ l2 = layersArray2.length;
+ continue;
+ }
+
+ this._unspiderfyLayer(m);
+ }
+ }
+
+ for (i = 0; i < l; i++) {
+ m = layersArray[i];
+
+ // Group of layers, append children to layersArray and skip.
+ if (m instanceof L.LayerGroup) {
+ if (originalArray) {
+ layersArray = layersArray.slice();
+ originalArray = false;
+ }
+ this._extractNonGroupLayers(m, layersArray);
+ l = layersArray.length;
+ continue;
+ }
+
+ if (!m.__parent) {
+ npg.removeLayer(m);
+ this.fire('layerremove', { layer: m });
+ continue;
+ }
+
+ this._removeLayer(m, true, true);
+ this.fire('layerremove', { layer: m });
+
+ if (fg.hasLayer(m)) {
+ fg.removeLayer(m);
+ if (m.clusterShow) {
+ m.clusterShow();
+ }
+ }
+ }
+
+ // Refresh bounds and weighted positions.
+ this._topClusterLevel._recalculateBounds();
+
+ this._refreshClustersIcons();
+
+ //Fix up the clusters and markers on the map
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
+
+ return this;
+ },
+
+ //Removes all layers from the MarkerClusterGroup
+ clearLayers: function () {
+ //Need our own special implementation as the LayerGroup one doesn't work for us
+
+ //If we aren't on the map (yet), blow away the markers we know of
+ if (!this._map) {
+ this._needsClustering = [];
+ this._needsRemoving = [];
+ delete this._gridClusters;
+ delete this._gridUnclustered;
+ }
+
+ if (this._noanimationUnspiderfy) {
+ this._noanimationUnspiderfy();
+ }
+
+ //Remove all the visible layers
+ this._featureGroup.clearLayers();
+ this._nonPointGroup.clearLayers();
+
+ this.eachLayer(function (marker) {
+ marker.off(this._childMarkerEventHandlers, this);
+ delete marker.__parent;
+ }, this);
+
+ if (this._map) {
+ //Reset _topClusterLevel and the DistanceGrids
+ this._generateInitialClusters();
+ }
+
+ return this;
+ },
+
+ //Override FeatureGroup.getBounds as it doesn't work
+ getBounds: function () {
+ var bounds = new L.LatLngBounds();
+
+ if (this._topClusterLevel) {
+ bounds.extend(this._topClusterLevel._bounds);
+ }
+
+ for (var i = this._needsClustering.length - 1; i >= 0; i--) {
+ bounds.extend(this._needsClustering[i].getLatLng());
+ }
+
+ bounds.extend(this._nonPointGroup.getBounds());
+
+ return bounds;
+ },
+
+ //Overrides LayerGroup.eachLayer
+ eachLayer: function (method, context) {
+ var markers = this._needsClustering.slice(),
+ needsRemoving = this._needsRemoving,
+ thisNeedsRemoving, i, j;
+
+ if (this._topClusterLevel) {
+ this._topClusterLevel.getAllChildMarkers(markers);
+ }
+
+ for (i = markers.length - 1; i >= 0; i--) {
+ thisNeedsRemoving = true;
+
+ for (j = needsRemoving.length - 1; j >= 0; j--) {
+ if (needsRemoving[j].layer === markers[i]) {
+ thisNeedsRemoving = false;
+ break;
+ }
+ }
+
+ if (thisNeedsRemoving) {
+ method.call(context, markers[i]);
+ }
+ }
+
+ this._nonPointGroup.eachLayer(method, context);
+ },
+
+ //Overrides LayerGroup.getLayers
+ getLayers: function () {
+ var layers = [];
+ this.eachLayer(function (l) {
+ layers.push(l);
+ });
+ return layers;
+ },
+
+ //Overrides LayerGroup.getLayer, WARNING: Really bad performance
+ getLayer: function (id) {
+ var result = null;
+
+ id = parseInt(id, 10);
+
+ this.eachLayer(function (l) {
+ if (L.stamp(l) === id) {
+ result = l;
+ }
+ });
+
+ return result;
+ },
+
+ //Returns true if the given layer is in this MarkerClusterGroup
+ hasLayer: function (layer) {
+ if (!layer) {
+ return false;
+ }
+
+ var i, anArray = this._needsClustering;
+
+ for (i = anArray.length - 1; i >= 0; i--) {
+ if (anArray[i] === layer) {
+ return true;
+ }
+ }
+
+ anArray = this._needsRemoving;
+ for (i = anArray.length - 1; i >= 0; i--) {
+ if (anArray[i].layer === layer) {
+ return false;
+ }
+ }
+
+ return !!(layer.__parent && layer.__parent._group === this) || this._nonPointGroup.hasLayer(layer);
+ },
+
+ //Zoom down to show the given layer (spiderfying if necessary) then calls the callback
+ zoomToShowLayer: function (layer, callback) {
+
+ if (typeof callback !== 'function') {
+ callback = function () {};
+ }
+
+ var showMarker = function () {
+ if ((layer._icon || layer.__parent._icon) && !this._inZoomAnimation) {
+ this._map.off('moveend', showMarker, this);
+ this.off('animationend', showMarker, this);
+
+ if (layer._icon) {
+ callback();
+ } else if (layer.__parent._icon) {
+ this.once('spiderfied', callback, this);
+ layer.__parent.spiderfy();
+ }
+ }
+ };
+
+ if (layer._icon && this._map.getBounds().contains(layer.getLatLng())) {
+ //Layer is visible ond on screen, immediate return
+ callback();
+ } else if (layer.__parent._zoom < Math.round(this._map._zoom)) {
+ //Layer should be visible at this zoom level. It must not be on screen so just pan over to it
+ this._map.on('moveend', showMarker, this);
+ this._map.panTo(layer.getLatLng());
+ } else {
+ this._map.on('moveend', showMarker, this);
+ this.on('animationend', showMarker, this);
+ layer.__parent.zoomToBounds();
+ }
+ },
+
+ //Overrides FeatureGroup.onAdd
+ onAdd: function (map) {
+ this._map = map;
+ var i, l, layer;
+
+ if (!isFinite(this._map.getMaxZoom())) {
+ throw "Map has no maxZoom specified";
+ }
+
+ this._featureGroup.addTo(map);
+ this._nonPointGroup.addTo(map);
+
+ if (!this._gridClusters) {
+ this._generateInitialClusters();
+ }
+
+ this._maxLat = map.options.crs.projection.MAX_LATITUDE;
+
+ //Restore all the positions as they are in the MCG before removing them
+ for (i = 0, l = this._needsRemoving.length; i < l; i++) {
+ layer = this._needsRemoving[i];
+ layer.newlatlng = layer.layer._latlng;
+ layer.layer._latlng = layer.latlng;
+ }
+ //Remove them, then restore their new positions
+ for (i = 0, l = this._needsRemoving.length; i < l; i++) {
+ layer = this._needsRemoving[i];
+ this._removeLayer(layer.layer, true);
+ layer.layer._latlng = layer.newlatlng;
+ }
+ this._needsRemoving = [];
+
+ //Remember the current zoom level and bounds
+ this._zoom = Math.round(this._map._zoom);
+ this._currentShownBounds = this._getExpandedVisibleBounds();
+
+ this._map.on('zoomend', this._zoomEnd, this);
+ this._map.on('moveend', this._moveEnd, this);
+
+ if (this._spiderfierOnAdd) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
+ this._spiderfierOnAdd();
+ }
+
+ this._bindEvents();
+
+ //Actually add our markers to the map:
+ l = this._needsClustering;
+ this._needsClustering = [];
+ this.addLayers(l, true);
+ },
+
+ //Overrides FeatureGroup.onRemove
+ onRemove: function (map) {
+ map.off('zoomend', this._zoomEnd, this);
+ map.off('moveend', this._moveEnd, this);
+
+ this._unbindEvents();
+
+ //In case we are in a cluster animation
+ this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
+
+ if (this._spiderfierOnRemove) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
+ this._spiderfierOnRemove();
+ }
+
+ delete this._maxLat;
+
+ //Clean up all the layers we added to the map
+ this._hideCoverage();
+ this._featureGroup.remove();
+ this._nonPointGroup.remove();
+
+ this._featureGroup.clearLayers();
+
+ this._map = null;
+ },
+
+ getVisibleParent: function (marker) {
+ var vMarker = marker;
+ while (vMarker && !vMarker._icon) {
+ vMarker = vMarker.__parent;
+ }
+ return vMarker || null;
+ },
+
+ //Remove the given object from the given array
+ _arraySplice: function (anArray, obj) {
+ for (var i = anArray.length - 1; i >= 0; i--) {
+ if (anArray[i] === obj) {
+ anArray.splice(i, 1);
+ return true;
+ }
+ }
+ },
+
+ /**
+ * Removes a marker from all _gridUnclustered zoom levels, starting at the supplied zoom.
+ * @param marker to be removed from _gridUnclustered.
+ * @param z integer bottom start zoom level (included)
+ * @private
+ */
+ _removeFromGridUnclustered: function (marker, z) {
+ var map = this._map,
+ gridUnclustered = this._gridUnclustered,
+ minZoom = Math.floor(this._map.getMinZoom());
+
+ for (; z >= minZoom; z--) {
+ if (!gridUnclustered[z].removeObject(marker, map.project(marker.getLatLng(), z))) {
+ break;
+ }
+ }
+ },
+
+ _childMarkerDragStart: function (e) {
+ e.target.__dragStart = e.target._latlng;
+ },
+
+ _childMarkerMoved: function (e) {
+ if (!this._ignoreMove && !e.target.__dragStart) {
+ var isPopupOpen = e.target._popup && e.target._popup.isOpen();
+
+ this._moveChild(e.target, e.oldLatLng, e.latlng);
+
+ if (isPopupOpen) {
+ e.target.openPopup();
+ }
+ }
+ },
+
+ _moveChild: function (layer, from, to) {
+ layer._latlng = from;
+ this.removeLayer(layer);
+
+ layer._latlng = to;
+ this.addLayer(layer);
+ },
+
+ _childMarkerDragEnd: function (e) {
+ var dragStart = e.target.__dragStart;
+ delete e.target.__dragStart;
+ if (dragStart) {
+ this._moveChild(e.target, dragStart, e.target._latlng);
+ }
+ },
+
+
+ //Internal function for removing a marker from everything.
+ //dontUpdateMap: set to true if you will handle updating the map manually (for bulk functions)
+ _removeLayer: function (marker, removeFromDistanceGrid, dontUpdateMap) {
+ var gridClusters = this._gridClusters,
+ gridUnclustered = this._gridUnclustered,
+ fg = this._featureGroup,
+ map = this._map,
+ minZoom = Math.floor(this._map.getMinZoom());
+
+ //Remove the marker from distance clusters it might be in
+ if (removeFromDistanceGrid) {
+ this._removeFromGridUnclustered(marker, this._maxZoom);
+ }
+
+ //Work our way up the clusters removing them as we go if required
+ var cluster = marker.__parent,
+ markers = cluster._markers,
+ otherMarker;
+
+ //Remove the marker from the immediate parents marker list
+ this._arraySplice(markers, marker);
+
+ while (cluster) {
+ cluster._childCount--;
+ cluster._boundsNeedUpdate = true;
+
+ if (cluster._zoom < minZoom) {
+ //Top level, do nothing
+ break;
+ } else if (removeFromDistanceGrid && cluster._childCount <= 1) { //Cluster no longer required
+ //We need to push the other marker up to the parent
+ otherMarker = cluster._markers[0] === marker ? cluster._markers[1] : cluster._markers[0];
+
+ //Update distance grid
+ gridClusters[cluster._zoom].removeObject(cluster, map.project(cluster._cLatLng, cluster._zoom));
+ gridUnclustered[cluster._zoom].addObject(otherMarker, map.project(otherMarker.getLatLng(), cluster._zoom));
+
+ //Move otherMarker up to parent
+ this._arraySplice(cluster.__parent._childClusters, cluster);
+ cluster.__parent._markers.push(otherMarker);
+ otherMarker.__parent = cluster.__parent;
+
+ if (cluster._icon) {
+ //Cluster is currently on the map, need to put the marker on the map instead
+ fg.removeLayer(cluster);
+ if (!dontUpdateMap) {
+ fg.addLayer(otherMarker);
+ }
+ }
+ } else {
+ cluster._iconNeedsUpdate = true;
+ }
+
+ cluster = cluster.__parent;
+ }
+
+ delete marker.__parent;
+ },
+
+ _isOrIsParent: function (el, oel) {
+ while (oel) {
+ if (el === oel) {
+ return true;
+ }
+ oel = oel.parentNode;
+ }
+ return false;
+ },
+
+ //Override L.Evented.fire
+ fire: function (type, data, propagate) {
+ if (data && data.layer instanceof L.MarkerCluster) {
+ //Prevent multiple clustermouseover/off events if the icon is made up of stacked divs (Doesn't work in ie <= 8, no relatedTarget)
+ if (data.originalEvent && this._isOrIsParent(data.layer._icon, data.originalEvent.relatedTarget)) {
+ return;
+ }
+ type = 'cluster' + type;
+ }
+
+ L.FeatureGroup.prototype.fire.call(this, type, data, propagate);
+ },
+
+ //Override L.Evented.listens
+ listens: function (type, propagate) {
+ return L.FeatureGroup.prototype.listens.call(this, type, propagate) || L.FeatureGroup.prototype.listens.call(this, 'cluster' + type, propagate);
+ },
+
+ //Default functionality
+ _defaultIconCreateFunction: function (cluster) {
+ var childCount = cluster.getChildCount();
+
+ var c = ' marker-cluster-';
+ if (childCount < 10) {
+ c += 'small';
+ } else if (childCount < 100) {
+ c += 'medium';
+ } else {
+ c += 'large';
+ }
+
+ return new L.DivIcon({ html: '<div><span>' + childCount + '</span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) });
+ },
+
+ _bindEvents: function () {
+ var map = this._map,
+ spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
+ showCoverageOnHover = this.options.showCoverageOnHover,
+ zoomToBoundsOnClick = this.options.zoomToBoundsOnClick;
+
+ //Zoom on cluster click or spiderfy if we are at the lowest level
+ if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
+ this.on('clusterclick', this._zoomOrSpiderfy, this);
+ }
+
+ //Show convex hull (boundary) polygon on mouse over
+ if (showCoverageOnHover) {
+ this.on('clustermouseover', this._showCoverage, this);
+ this.on('clustermouseout', this._hideCoverage, this);
+ map.on('zoomend', this._hideCoverage, this);
+ }
+ },
+
+ _zoomOrSpiderfy: function (e) {
+ var cluster = e.layer,
+ bottomCluster = cluster;
+
+ while (bottomCluster._childClusters.length === 1) {
+ bottomCluster = bottomCluster._childClusters[0];
+ }
+
+ if (bottomCluster._zoom === this._maxZoom &&
+ bottomCluster._childCount === cluster._childCount &&
+ this.options.spiderfyOnMaxZoom) {
+
+ // All child markers are contained in a single cluster from this._maxZoom to this cluster.
+ cluster.spiderfy();
+ } else if (this.options.zoomToBoundsOnClick) {
+ cluster.zoomToBounds();
+ }
+
+ // Focus the map again for keyboard users.
+ if (e.originalEvent && e.originalEvent.keyCode === 13) {
+ this._map._container.focus();
+ }
+ },
+
+ _showCoverage: function (e) {
+ var map = this._map;
+ if (this._inZoomAnimation) {
+ return;
+ }
+ if (this._shownPolygon) {
+ map.removeLayer(this._shownPolygon);
+ }
+ if (e.layer.getChildCount() > 2 && e.layer !== this._spiderfied) {
+ this._shownPolygon = new L.Polygon(e.layer.getConvexHull(), this.options.polygonOptions);
+ map.addLayer(this._shownPolygon);
+ }
+ },
+
+ _hideCoverage: function () {
+ if (this._shownPolygon) {
+ this._map.removeLayer(this._shownPolygon);
+ this._shownPolygon = null;
+ }
+ },
+
+ _unbindEvents: function () {
+ var spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
+ showCoverageOnHover = this.options.showCoverageOnHover,
+ zoomToBoundsOnClick = this.options.zoomToBoundsOnClick,
+ map = this._map;
+
+ if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
+ this.off('clusterclick', this._zoomOrSpiderfy, this);
+ }
+ if (showCoverageOnHover) {
+ this.off('clustermouseover', this._showCoverage, this);
+ this.off('clustermouseout', this._hideCoverage, this);
+ map.off('zoomend', this._hideCoverage, this);
+ }
+ },
+
+ _zoomEnd: function () {
+ if (!this._map) { //May have been removed from the map by a zoomEnd handler
+ return;
+ }
+ this._mergeSplitClusters();
+
+ this._zoom = Math.round(this._map._zoom);
+ this._currentShownBounds = this._getExpandedVisibleBounds();
+ },
+
+ _moveEnd: function () {
+ if (this._inZoomAnimation) {
+ return;
+ }
+
+ var newBounds = this._getExpandedVisibleBounds();
+
+ this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), this._zoom, newBounds);
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, Math.round(this._map._zoom), newBounds);
+
+ this._currentShownBounds = newBounds;
+ return;
+ },
+
+ _generateInitialClusters: function () {
+ var maxZoom = Math.ceil(this._map.getMaxZoom()),
+ minZoom = Math.floor(this._map.getMinZoom()),
+ radius = this.options.maxClusterRadius,
+ radiusFn = radius;
+
+ //If we just set maxClusterRadius to a single number, we need to create
+ //a simple function to return that number. Otherwise, we just have to
+ //use the function we've passed in.
+ if (typeof radius !== "function") {
+ radiusFn = function () { return radius; };
+ }
+
+ if (this.options.disableClusteringAtZoom !== null) {
+ maxZoom = this.options.disableClusteringAtZoom - 1;
+ }
+ this._maxZoom = maxZoom;
+ this._gridClusters = {};
+ this._gridUnclustered = {};
+
+ //Set up DistanceGrids for each zoom
+ for (var zoom = maxZoom; zoom >= minZoom; zoom--) {
+ this._gridClusters[zoom] = new L.DistanceGrid(radiusFn(zoom));
+ this._gridUnclustered[zoom] = new L.DistanceGrid(radiusFn(zoom));
+ }
+
+ // Instantiate the appropriate L.MarkerCluster class (animated or not).
+ this._topClusterLevel = new this._markerCluster(this, minZoom - 1);
+ },
+
+ //Zoom: Zoom to start adding at (Pass this._maxZoom to start at the bottom)
+ _addLayer: function (layer, zoom) {
+ var gridClusters = this._gridClusters,
+ gridUnclustered = this._gridUnclustered,
+ minZoom = Math.floor(this._map.getMinZoom()),
+ markerPoint, z;
+
+ if (this.options.singleMarkerMode) {
+ this._overrideMarkerIcon(layer);
+ }
+
+ layer.on(this._childMarkerEventHandlers, this);
+
+ //Find the lowest zoom level to slot this one in
+ for (; zoom >= minZoom; zoom--) {
+ markerPoint = this._map.project(layer.getLatLng(), zoom); // calculate pixel position
+
+ //Try find a cluster close by
+ var closest = gridClusters[zoom].getNearObject(markerPoint);
+ if (closest) {
+ closest._addChild(layer);
+ layer.__parent = closest;
+ return;
+ }
+
+ //Try find a marker close by to form a new cluster with
+ closest = gridUnclustered[zoom].getNearObject(markerPoint);
+ if (closest) {
+ var parent = closest.__parent;
+ if (parent) {
+ this._removeLayer(closest, false);
+ }
+
+ //Create new cluster with these 2 in it
+
+ var newCluster = new this._markerCluster(this, zoom, closest, layer);
+ gridClusters[zoom].addObject(newCluster, this._map.project(newCluster._cLatLng, zoom));
+ closest.__parent = newCluster;
+ layer.__parent = newCluster;
+
+ //First create any new intermediate parent clusters that don't exist
+ var lastParent = newCluster;
+ for (z = zoom - 1; z > parent._zoom; z--) {
+ lastParent = new this._markerCluster(this, z, lastParent);
+ gridClusters[z].addObject(lastParent, this._map.project(closest.getLatLng(), z));
+ }
+ parent._addChild(lastParent);
+
+ //Remove closest from this zoom level and any above that it is in, replace with newCluster
+ this._removeFromGridUnclustered(closest, zoom);
+
+ return;
+ }
+
+ //Didn't manage to cluster in at this zoom, record us as a marker here and continue upwards
+ gridUnclustered[zoom].addObject(layer, markerPoint);
+ }
+
+ //Didn't get in anything, add us to the top
+ this._topClusterLevel._addChild(layer);
+ layer.__parent = this._topClusterLevel;
+ return;
+ },
+
+ /**
+ * Refreshes the icon of all "dirty" visible clusters.
+ * Non-visible "dirty" clusters will be updated when they are added to the map.
+ * @private
+ */
+ _refreshClustersIcons: function () {
+ this._featureGroup.eachLayer(function (c) {
+ if (c instanceof L.MarkerCluster && c._iconNeedsUpdate) {
+ c._updateIcon();
+ }
+ });
+ },
+
+ //Enqueue code to fire after the marker expand/contract has happened
+ _enqueue: function (fn) {
+ this._queue.push(fn);
+ if (!this._queueTimeout) {
+ this._queueTimeout = setTimeout(L.bind(this._processQueue, this), 300);
+ }
+ },
+ _processQueue: function () {
+ for (var i = 0; i < this._queue.length; i++) {
+ this._queue[i].call(this);
+ }
+ this._queue.length = 0;
+ clearTimeout(this._queueTimeout);
+ this._queueTimeout = null;
+ },
+
+ //Merge and split any existing clusters that are too big or small
+ _mergeSplitClusters: function () {
+ var mapZoom = Math.round(this._map._zoom);
+
+ //In case we are starting to split before the animation finished
+ this._processQueue();
+
+ if (this._zoom < mapZoom && this._currentShownBounds.intersects(this._getExpandedVisibleBounds())) { //Zoom in, split
+ this._animationStart();
+ //Remove clusters now off screen
+ this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), this._zoom, this._getExpandedVisibleBounds());
+
+ this._animationZoomIn(this._zoom, mapZoom);
+
+ } else if (this._zoom > mapZoom) { //Zoom out, merge
+ this._animationStart();
+
+ this._animationZoomOut(this._zoom, mapZoom);
+ } else {
+ this._moveEnd();
+ }
+ },
+
+ //Gets the maps visible bounds expanded in each direction by the size of the screen (so the user cannot see an area we do not cover in one pan)
+ _getExpandedVisibleBounds: function () {
+ if (!this.options.removeOutsideVisibleBounds) {
+ return this._mapBoundsInfinite;
+ } else if (L.Browser.mobile) {
+ return this._checkBoundsMaxLat(this._map.getBounds());
+ }
+
+ return this._checkBoundsMaxLat(this._map.getBounds().pad(1)); // Padding expands the bounds by its own dimensions but scaled with the given factor.
+ },
+
+ /**
+ * Expands the latitude to Infinity (or -Infinity) if the input bounds reach the map projection maximum defined latitude
+ * (in the case of Web/Spherical Mercator, it is 85.0511287798 / see https://en.wikipedia.org/wiki/Web_Mercator#Formulas).
+ * Otherwise, the removeOutsideVisibleBounds option will remove markers beyond that limit, whereas the same markers without
+ * this option (or outside MCG) will have their position floored (ceiled) by the projection and rendered at that limit,
+ * making the user think that MCG "eats" them and never displays them again.
+ * @param bounds L.LatLngBounds
+ * @returns {L.LatLngBounds}
+ * @private
+ */
+ _checkBoundsMaxLat: function (bounds) {
+ var maxLat = this._maxLat;
+
+ if (maxLat !== undefined) {
+ if (bounds.getNorth() >= maxLat) {
+ bounds._northEast.lat = Infinity;
+ }
+ if (bounds.getSouth() <= -maxLat) {
+ bounds._southWest.lat = -Infinity;
+ }
+ }
+
+ return bounds;
+ },
+
+ //Shared animation code
+ _animationAddLayerNonAnimated: function (layer, newCluster) {
+ if (newCluster === layer) {
+ this._featureGroup.addLayer(layer);
+ } else if (newCluster._childCount === 2) {
+ newCluster._addToMap();
+
+ var markers = newCluster.getAllChildMarkers();
+ this._featureGroup.removeLayer(markers[0]);
+ this._featureGroup.removeLayer(markers[1]);
+ } else {
+ newCluster._updateIcon();
+ }
+ },
+
+ /**
+ * Extracts individual (i.e. non-group) layers from a Layer Group.
+ * @param group to extract layers from.
+ * @param output {Array} in which to store the extracted layers.
+ * @returns {*|Array}
+ * @private
+ */
+ _extractNonGroupLayers: function (group, output) {
+ var layers = group.getLayers(),
+ i = 0,
+ layer;
+
+ output = output || [];
+
+ for (; i < layers.length; i++) {
+ layer = layers[i];
+
+ if (layer instanceof L.LayerGroup) {
+ this._extractNonGroupLayers(layer, output);
+ continue;
+ }
+
+ output.push(layer);
+ }
+
+ return output;
+ },
+
+ /**
+ * Implements the singleMarkerMode option.
+ * @param layer Marker to re-style using the Clusters iconCreateFunction.
+ * @returns {L.Icon} The newly created icon.
+ * @private
+ */
+ _overrideMarkerIcon: function (layer) {
+ var icon = layer.options.icon = this.options.iconCreateFunction({
+ getChildCount: function () {
+ return 1;
+ },
+ getAllChildMarkers: function () {
+ return [layer];
+ }
+ });
+
+ return icon;
+ }
+});
+
+// Constant bounds used in case option "removeOutsideVisibleBounds" is set to false.
+L.MarkerClusterGroup.include({
+ _mapBoundsInfinite: new L.LatLngBounds(new L.LatLng(-Infinity, -Infinity), new L.LatLng(Infinity, Infinity))
+});
+
+L.MarkerClusterGroup.include({
+ _noAnimation: {
+ //Non Animated versions of everything
+ _animationStart: function () {
+ //Do nothing...
+ },
+ _animationZoomIn: function (previousZoomLevel, newZoomLevel) {
+ this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel);
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
+
+ //We didn't actually animate, but we use this event to mean "clustering animations have finished"
+ this.fire('animationend');
+ },
+ _animationZoomOut: function (previousZoomLevel, newZoomLevel) {
+ this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel);
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
+
+ //We didn't actually animate, but we use this event to mean "clustering animations have finished"
+ this.fire('animationend');
+ },
+ _animationAddLayer: function (layer, newCluster) {
+ this._animationAddLayerNonAnimated(layer, newCluster);
+ }
+ },
+
+ _withAnimation: {
+ //Animated versions here
+ _animationStart: function () {
+ this._map._mapPane.className += ' leaflet-cluster-anim';
+ this._inZoomAnimation++;
+ },
+
+ _animationZoomIn: function (previousZoomLevel, newZoomLevel) {
+ var bounds = this._getExpandedVisibleBounds(),
+ fg = this._featureGroup,
+ minZoom = Math.floor(this._map.getMinZoom()),
+ i;
+
+ this._ignoreMove = true;
+
+ //Add all children of current clusters to map and remove those clusters from map
+ this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function (c) {
+ var startPos = c._latlng,
+ markers = c._markers,
+ m;
+
+ if (!bounds.contains(startPos)) {
+ startPos = null;
+ }
+
+ if (c._isSingleParent() && previousZoomLevel + 1 === newZoomLevel) { //Immediately add the new child and remove us
+ fg.removeLayer(c);
+ c._recursivelyAddChildrenToMap(null, newZoomLevel, bounds);
+ } else {
+ //Fade out old cluster
+ c.clusterHide();
+ c._recursivelyAddChildrenToMap(startPos, newZoomLevel, bounds);
+ }
+
+ //Remove all markers that aren't visible any more
+ //TODO: Do we actually need to do this on the higher levels too?
+ for (i = markers.length - 1; i >= 0; i--) {
+ m = markers[i];
+ if (!bounds.contains(m._latlng)) {
+ fg.removeLayer(m);
+ }
+ }
+
+ });
+
+ this._forceLayout();
+
+ //Update opacities
+ this._topClusterLevel._recursivelyBecomeVisible(bounds, newZoomLevel);
+ //TODO Maybe? Update markers in _recursivelyBecomeVisible
+ fg.eachLayer(function (n) {
+ if (!(n instanceof L.MarkerCluster) && n._icon) {
+ n.clusterShow();
+ }
+ });
+
+ //update the positions of the just added clusters/markers
+ this._topClusterLevel._recursively(bounds, previousZoomLevel, newZoomLevel, function (c) {
+ c._recursivelyRestoreChildPositions(newZoomLevel);
+ });
+
+ this._ignoreMove = false;
+
+ //Remove the old clusters and close the zoom animation
+ this._enqueue(function () {
+ //update the positions of the just added clusters/markers
+ this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function (c) {
+ fg.removeLayer(c);
+ c.clusterShow();
+ });
+
+ this._animationEnd();
+ });
+ },
+
+ _animationZoomOut: function (previousZoomLevel, newZoomLevel) {
+ this._animationZoomOutSingle(this._topClusterLevel, previousZoomLevel - 1, newZoomLevel);
+
+ //Need to add markers for those that weren't on the map before but are now
+ this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
+ //Remove markers that were on the map before but won't be now
+ this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel, this._getExpandedVisibleBounds());
+ },
+
+ _animationAddLayer: function (layer, newCluster) {
+ var me = this,
+ fg = this._featureGroup;
+
+ fg.addLayer(layer);
+ if (newCluster !== layer) {
+ if (newCluster._childCount > 2) { //Was already a cluster
+
+ newCluster._updateIcon();
+ this._forceLayout();
+ this._animationStart();
+
+ layer._setPos(this._map.latLngToLayerPoint(newCluster.getLatLng()));
+ layer.clusterHide();
+
+ this._enqueue(function () {
+ fg.removeLayer(layer);
+ layer.clusterShow();
+
+ me._animationEnd();
+ });
+
+ } else { //Just became a cluster
+ this._forceLayout();
+
+ me._animationStart();
+ me._animationZoomOutSingle(newCluster, this._map.getMaxZoom(), this._zoom);
+ }
+ }
+ }
+ },
+
+ // Private methods for animated versions.
+ _animationZoomOutSingle: function (cluster, previousZoomLevel, newZoomLevel) {
+ var bounds = this._getExpandedVisibleBounds(),
+ minZoom = Math.floor(this._map.getMinZoom());
+
+ //Animate all of the markers in the clusters to move to their cluster center point
+ cluster._recursivelyAnimateChildrenInAndAddSelfToMap(bounds, minZoom, previousZoomLevel + 1, newZoomLevel);
+
+ var me = this;
+
+ //Update the opacity (If we immediately set it they won't animate)
+ this._forceLayout();
+ cluster._recursivelyBecomeVisible(bounds, newZoomLevel);
+
+ //TODO: Maybe use the transition timing stuff to make this more reliable
+ //When the animations are done, tidy up
+ this._enqueue(function () {
+
+ //This cluster stopped being a cluster before the timeout fired
+ if (cluster._childCount === 1) {
+ var m = cluster._markers[0];
+ //If we were in a cluster animation at the time then the opacity and position of our child could be wrong now, so fix it
+ this._ignoreMove = true;
+ m.setLatLng(m.getLatLng());
+ this._ignoreMove = false;
+ if (m.clusterShow) {
+ m.clusterShow();
+ }
+ } else {
+ cluster._recursively(bounds, newZoomLevel, minZoom, function (c) {
+ c._recursivelyRemoveChildrenFromMap(bounds, minZoom, previousZoomLevel + 1);
+ });
+ }
+ me._animationEnd();
+ });
+ },
+
+ _animationEnd: function () {
+ if (this._map) {
+ this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
+ }
+ this._inZoomAnimation--;
+ this.fire('animationend');
+ },
+
+ //Force a browser layout of stuff in the map
+ // Should apply the current opacity and location to all elements so we can update them again for an animation
+ _forceLayout: function () {
+ //In my testing this works, infact offsetWidth of any element seems to work.
+ //Could loop all this._layers and do this for each _icon if it stops working
+
+ L.Util.falseFn(document.body.offsetWidth);
+ }
+});
+
+L.markerClusterGroup = function (options) {
+ return new L.MarkerClusterGroup(options);
+};
+
+var MarkerCluster = L.MarkerCluster = L.Marker.extend({
+ options: L.Icon.prototype.options,
+
+ initialize: function (group, zoom, a, b) {
+
+ L.Marker.prototype.initialize.call(this, a ? (a._cLatLng || a.getLatLng()) : new L.LatLng(0, 0),
+ { icon: this, pane: group.options.clusterPane });
+
+ this._group = group;
+ this._zoom = zoom;
+
+ this._markers = [];
+ this._childClusters = [];
+ this._childCount = 0;
+ this._iconNeedsUpdate = true;
+ this._boundsNeedUpdate = true;
+
+ this._bounds = new L.LatLngBounds();
+
+ if (a) {
+ this._addChild(a);
+ }
+ if (b) {
+ this._addChild(b);
+ }
+ },
+
+ //Recursively retrieve all child markers of this cluster
+ getAllChildMarkers: function (storageArray, ignoreDraggedMarker) {
+ storageArray = storageArray || [];
+
+ for (var i = this._childClusters.length - 1; i >= 0; i--) {
+ this._childClusters[i].getAllChildMarkers(storageArray);
+ }
+
+ for (var j = this._markers.length - 1; j >= 0; j--) {
+ if (ignoreDraggedMarker && this._markers[j].__dragStart) {
+ continue;
+ }
+ storageArray.push(this._markers[j]);
+ }
+
+ return storageArray;
+ },
+
+ //Returns the count of how many child markers we have
+ getChildCount: function () {
+ return this._childCount;
+ },
+
+ //Zoom to the minimum of showing all of the child markers, or the extents of this cluster
+ zoomToBounds: function (fitBoundsOptions) {
+ var childClusters = this._childClusters.slice(),
+ map = this._group._map,
+ boundsZoom = map.getBoundsZoom(this._bounds),
+ zoom = this._zoom + 1,
+ mapZoom = map.getZoom(),
+ i;
+
+ //calculate how far we need to zoom down to see all of the markers
+ while (childClusters.length > 0 && boundsZoom > zoom) {
+ zoom++;
+ var newClusters = [];
+ for (i = 0; i < childClusters.length; i++) {
+ newClusters = newClusters.concat(childClusters[i]._childClusters);
+ }
+ childClusters = newClusters;
+ }
+
+ if (boundsZoom > zoom) {
+ this._group._map.setView(this._latlng, zoom);
+ } else if (boundsZoom <= mapZoom) { //If fitBounds wouldn't zoom us down, zoom us down instead
+ this._group._map.setView(this._latlng, mapZoom + 1);
+ } else {
+ this._group._map.fitBounds(this._bounds, fitBoundsOptions);
+ }
+ },
+
+ getBounds: function () {
+ var bounds = new L.LatLngBounds();
+ bounds.extend(this._bounds);
+ return bounds;
+ },
+
+ _updateIcon: function () {
+ this._iconNeedsUpdate = true;
+ if (this._icon) {
+ this.setIcon(this);
+ }
+ },
+
+ //Cludge for Icon, we pretend to be an icon for performance
+ createIcon: function () {
+ if (this._iconNeedsUpdate) {
+ this._iconObj = this._group.options.iconCreateFunction(this);
+ this._iconNeedsUpdate = false;
+ }
+ return this._iconObj.createIcon();
+ },
+ createShadow: function () {
+ return this._iconObj.createShadow();
+ },
+
+
+ _addChild: function (new1, isNotificationFromChild) {
+
+ this._iconNeedsUpdate = true;
+
+ this._boundsNeedUpdate = true;
+ this._setClusterCenter(new1);
+
+ if (new1 instanceof L.MarkerCluster) {
+ if (!isNotificationFromChild) {
+ this._childClusters.push(new1);
+ new1.__parent = this;
+ }
+ this._childCount += new1._childCount;
+ } else {
+ if (!isNotificationFromChild) {
+ this._markers.push(new1);
+ }
+ this._childCount++;
+ }
+
+ if (this.__parent) {
+ this.__parent._addChild(new1, true);
+ }
+ },
+
+ /**
+ * Makes sure the cluster center is set. If not, uses the child center if it is a cluster, or the marker position.
+ * @param child L.MarkerCluster|L.Marker that will be used as cluster center if not defined yet.
+ * @private
+ */
+ _setClusterCenter: function (child) {
+ if (!this._cLatLng) {
+ // when clustering, take position of the first point as the cluster center
+ this._cLatLng = child._cLatLng || child._latlng;
+ }
+ },
+
+ /**
+ * Assigns impossible bounding values so that the next extend entirely determines the new bounds.
+ * This method avoids having to trash the previous L.LatLngBounds object and to create a new one, which is much slower for this class.
+ * As long as the bounds are not extended, most other methods would probably fail, as they would with bounds initialized but not extended.
+ * @private
+ */
+ _resetBounds: function () {
+ var bounds = this._bounds;
+
+ if (bounds._southWest) {
+ bounds._southWest.lat = Infinity;
+ bounds._southWest.lng = Infinity;
+ }
+ if (bounds._northEast) {
+ bounds._northEast.lat = -Infinity;
+ bounds._northEast.lng = -Infinity;
+ }
+ },
+
+ _recalculateBounds: function () {
+ var markers = this._markers,
+ childClusters = this._childClusters,
+ latSum = 0,
+ lngSum = 0,
+ totalCount = this._childCount,
+ i, child, childLatLng, childCount;
+
+ // Case where all markers are removed from the map and we are left with just an empty _topClusterLevel.
+ if (totalCount === 0) {
+ return;
+ }
+
+ // Reset rather than creating a new object, for performance.
+ this._resetBounds();
+
+ // Child markers.
+ for (i = 0; i < markers.length; i++) {
+ childLatLng = markers[i]._latlng;
+
+ this._bounds.extend(childLatLng);
+
+ latSum += childLatLng.lat;
+ lngSum += childLatLng.lng;
+ }
+
+ // Child clusters.
+ for (i = 0; i < childClusters.length; i++) {
+ child = childClusters[i];
+
+ // Re-compute child bounds and weighted position first if necessary.
+ if (child._boundsNeedUpdate) {
+ child._recalculateBounds();
+ }
+
+ this._bounds.extend(child._bounds);
+
+ childLatLng = child._wLatLng;
+ childCount = child._childCount;
+
+ latSum += childLatLng.lat * childCount;
+ lngSum += childLatLng.lng * childCount;
+ }
+
+ this._latlng = this._wLatLng = new L.LatLng(latSum / totalCount, lngSum / totalCount);
+
+ // Reset dirty flag.
+ this._boundsNeedUpdate = false;
+ },
+
+ //Set our markers position as given and add it to the map
+ _addToMap: function (startPos) {
+ if (startPos) {
+ this._backupLatlng = this._latlng;
+ this.setLatLng(startPos);
+ }
+ this._group._featureGroup.addLayer(this);
+ },
+
+ _recursivelyAnimateChildrenIn: function (bounds, center, maxZoom) {
+ this._recursively(bounds, this._group._map.getMinZoom(), maxZoom - 1,
+ function (c) {
+ var markers = c._markers,
+ i, m;
+ for (i = markers.length - 1; i >= 0; i--) {
+ m = markers[i];
+
+ //Only do it if the icon is still on the map
+ if (m._icon) {
+ m._setPos(center);
+ m.clusterHide();
+ }
+ }
+ },
+ function (c) {
+ var childClusters = c._childClusters,
+ j, cm;
+ for (j = childClusters.length - 1; j >= 0; j--) {
+ cm = childClusters[j];
+ if (cm._icon) {
+ cm._setPos(center);
+ cm.clusterHide();
+ }
+ }
+ }
+ );
+ },
+
+ _recursivelyAnimateChildrenInAndAddSelfToMap: function (bounds, mapMinZoom, previousZoomLevel, newZoomLevel) {
+ this._recursively(bounds, newZoomLevel, mapMinZoom,
+ function (c) {
+ c._recursivelyAnimateChildrenIn(bounds, c._group._map.latLngToLayerPoint(c.getLatLng()).round(), previousZoomLevel);
+
+ //TODO: depthToAnimateIn affects _isSingleParent, if there is a multizoom we may/may not be.
+ //As a hack we only do a animation free zoom on a single level zoom, if someone does multiple levels then we always animate
+ if (c._isSingleParent() && previousZoomLevel - 1 === newZoomLevel) {
+ c.clusterShow();
+ c._recursivelyRemoveChildrenFromMap(bounds, mapMinZoom, previousZoomLevel); //Immediately remove our children as we are replacing them. TODO previousBounds not bounds
+ } else {
+ c.clusterHide();
+ }
+
+ c._addToMap();
+ }
+ );
+ },
+
+ _recursivelyBecomeVisible: function (bounds, zoomLevel) {
+ this._recursively(bounds, this._group._map.getMinZoom(), zoomLevel, null, function (c) {
+ c.clusterShow();
+ });
+ },
+
+ _recursivelyAddChildrenToMap: function (startPos, zoomLevel, bounds) {
+ this._recursively(bounds, this._group._map.getMinZoom() - 1, zoomLevel,
+ function (c) {
+ if (zoomLevel === c._zoom) {
+ return;
+ }
+
+ //Add our child markers at startPos (so they can be animated out)
+ for (var i = c._markers.length - 1; i >= 0; i--) {
+ var nm = c._markers[i];
+
+ if (!bounds.contains(nm._latlng)) {
+ continue;
+ }
+
+ if (startPos) {
+ nm._backupLatlng = nm.getLatLng();
+
+ nm.setLatLng(startPos);
+ if (nm.clusterHide) {
+ nm.clusterHide();
+ }
+ }
+
+ c._group._featureGroup.addLayer(nm);
+ }
+ },
+ function (c) {
+ c._addToMap(startPos);
+ }
+ );
+ },
+
+ _recursivelyRestoreChildPositions: function (zoomLevel) {
+ //Fix positions of child markers
+ for (var i = this._markers.length - 1; i >= 0; i--) {
+ var nm = this._markers[i];
+ if (nm._backupLatlng) {
+ nm.setLatLng(nm._backupLatlng);
+ delete nm._backupLatlng;
+ }
+ }
+
+ if (zoomLevel - 1 === this._zoom) {
+ //Reposition child clusters
+ for (var j = this._childClusters.length - 1; j >= 0; j--) {
+ this._childClusters[j]._restorePosition();
+ }
+ } else {
+ for (var k = this._childClusters.length - 1; k >= 0; k--) {
+ this._childClusters[k]._recursivelyRestoreChildPositions(zoomLevel);
+ }
+ }
+ },
+
+ _restorePosition: function () {
+ if (this._backupLatlng) {
+ this.setLatLng(this._backupLatlng);
+ delete this._backupLatlng;
+ }
+ },
+
+ //exceptBounds: If set, don't remove any markers/clusters in it
+ _recursivelyRemoveChildrenFromMap: function (previousBounds, mapMinZoom, zoomLevel, exceptBounds) {
+ var m, i;
+ this._recursively(previousBounds, mapMinZoom - 1, zoomLevel - 1,
+ function (c) {
+ //Remove markers at every level
+ for (i = c._markers.length - 1; i >= 0; i--) {
+ m = c._markers[i];
+ if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
+ c._group._featureGroup.removeLayer(m);
+ if (m.clusterShow) {
+ m.clusterShow();
+ }
+ }
+ }
+ },
+ function (c) {
+ //Remove child clusters at just the bottom level
+ for (i = c._childClusters.length - 1; i >= 0; i--) {
+ m = c._childClusters[i];
+ if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
+ c._group._featureGroup.removeLayer(m);
+ if (m.clusterShow) {
+ m.clusterShow();
+ }
+ }
+ }
+ }
+ );
+ },
+
+ //Run the given functions recursively to this and child clusters
+ // boundsToApplyTo: a L.LatLngBounds representing the bounds of what clusters to recurse in to
+ // zoomLevelToStart: zoom level to start running functions (inclusive)
+ // zoomLevelToStop: zoom level to stop running functions (inclusive)
+ // runAtEveryLevel: function that takes an L.MarkerCluster as an argument that should be applied on every level
+ // runAtBottomLevel: function that takes an L.MarkerCluster as an argument that should be applied at only the bottom level
+ _recursively: function (boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) {
+ var childClusters = this._childClusters,
+ zoom = this._zoom,
+ i, c;
+
+ if (zoomLevelToStart <= zoom) {
+ if (runAtEveryLevel) {
+ runAtEveryLevel(this);
+ }
+ if (runAtBottomLevel && zoom === zoomLevelToStop) {
+ runAtBottomLevel(this);
+ }
+ }
+
+ if (zoom < zoomLevelToStart || zoom < zoomLevelToStop) {
+ for (i = childClusters.length - 1; i >= 0; i--) {
+ c = childClusters[i];
+ if (c._boundsNeedUpdate) {
+ c._recalculateBounds();
+ }
+ if (boundsToApplyTo.intersects(c._bounds)) {
+ c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel);
+ }
+ }
+ }
+ },
+
+ //Returns true if we are the parent of only one cluster and that cluster is the same as us
+ _isSingleParent: function () {
+ //Don't need to check this._markers as the rest won't work if there are any
+ return this._childClusters.length > 0 && this._childClusters[0]._childCount === this._childCount;
+ }
+});
+
+/*
+* Extends L.Marker to include two extra methods: clusterHide and clusterShow.
+*
+* They work as setOpacity(0) and setOpacity(1) respectively, but
+* don't overwrite the options.opacity
+*
+*/
+
+L.Marker.include({
+ clusterHide: function () {
+ var backup = this.options.opacity;
+ this.setOpacity(0);
+ this.options.opacity = backup;
+ return this;
+ },
+
+ clusterShow: function () {
+ return this.setOpacity(this.options.opacity);
+ }
+});
+
+L.DistanceGrid = function (cellSize) {
+ this._cellSize = cellSize;
+ this._sqCellSize = cellSize * cellSize;
+ this._grid = {};
+ this._objectPoint = { };
+};
+
+L.DistanceGrid.prototype = {
+
+ addObject: function (obj, point) {
+ var x = this._getCoord(point.x),
+ y = this._getCoord(point.y),
+ grid = this._grid,
+ row = grid[y] = grid[y] || {},
+ cell = row[x] = row[x] || [],
+ stamp = L.Util.stamp(obj);
+
+ this._objectPoint[stamp] = point;
+
+ cell.push(obj);
+ },
+
+ updateObject: function (obj, point) {
+ this.removeObject(obj);
+ this.addObject(obj, point);
+ },
+
+ //Returns true if the object was found
+ removeObject: function (obj, point) {
+ var x = this._getCoord(point.x),
+ y = this._getCoord(point.y),
+ grid = this._grid,
+ row = grid[y] = grid[y] || {},
+ cell = row[x] = row[x] || [],
+ i, len;
+
+ delete this._objectPoint[L.Util.stamp(obj)];
+
+ for (i = 0, len = cell.length; i < len; i++) {
+ if (cell[i] === obj) {
+
+ cell.splice(i, 1);
+
+ if (len === 1) {
+ delete row[x];
+ }
+
+ return true;
+ }
+ }
+
+ },
+
+ eachObject: function (fn, context) {
+ var i, j, k, len, row, cell, removed,
+ grid = this._grid;
+
+ for (i in grid) {
+ row = grid[i];
+
+ for (j in row) {
+ cell = row[j];
+
+ for (k = 0, len = cell.length; k < len; k++) {
+ removed = fn.call(context, cell[k]);
+ if (removed) {
+ k--;
+ len--;
+ }
+ }
+ }
+ }
+ },
+
+ getNearObject: function (point) {
+ var x = this._getCoord(point.x),
+ y = this._getCoord(point.y),
+ i, j, k, row, cell, len, obj, dist,
+ objectPoint = this._objectPoint,
+ closestDistSq = this._sqCellSize,
+ closest = null;
+
+ for (i = y - 1; i <= y + 1; i++) {
+ row = this._grid[i];
+ if (row) {
+
+ for (j = x - 1; j <= x + 1; j++) {
+ cell = row[j];
+ if (cell) {
+
+ for (k = 0, len = cell.length; k < len; k++) {
+ obj = cell[k];
+ dist = this._sqDist(objectPoint[L.Util.stamp(obj)], point);
+ if (dist < closestDistSq ||
+ dist <= closestDistSq && closest === null) {
+ closestDistSq = dist;
+ closest = obj;
+ }
+ }
+ }
+ }
+ }
+ }
+ return closest;
+ },
+
+ _getCoord: function (x) {
+ var coord = Math.floor(x / this._cellSize);
+ return isFinite(coord) ? coord : x;
+ },
+
+ _sqDist: function (p, p2) {
+ var dx = p2.x - p.x,
+ dy = p2.y - p.y;
+ return dx * dx + dy * dy;
+ }
+};
+
+/* Copyright (c) 2012 the authors listed at the following URL, and/or
+the authors of referenced articles or incorporated external code:
+http://en.literateprograms.org/Quickhull_(Javascript)?action=history&offset=20120410175256
+
+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.
+
+Retrieved from: http://en.literateprograms.org/Quickhull_(Javascript)?oldid=18434
+*/
+
+(function () {
+ L.QuickHull = {
+
+ /*
+ * @param {Object} cpt a point to be measured from the baseline
+ * @param {Array} bl the baseline, as represented by a two-element
+ * array of latlng objects.
+ * @returns {Number} an approximate distance measure
+ */
+ getDistant: function (cpt, bl) {
+ var vY = bl[1].lat - bl[0].lat,
+ vX = bl[0].lng - bl[1].lng;
+ return (vX * (cpt.lat - bl[0].lat) + vY * (cpt.lng - bl[0].lng));
+ },
+
+ /*
+ * @param {Array} baseLine a two-element array of latlng objects
+ * representing the baseline to project from
+ * @param {Array} latLngs an array of latlng objects
+ * @returns {Object} the maximum point and all new points to stay
+ * in consideration for the hull.
+ */
+ findMostDistantPointFromBaseLine: function (baseLine, latLngs) {
+ var maxD = 0,
+ maxPt = null,
+ newPoints = [],
+ i, pt, d;
+
+ for (i = latLngs.length - 1; i >= 0; i--) {
+ pt = latLngs[i];
+ d = this.getDistant(pt, baseLine);
+
+ if (d > 0) {
+ newPoints.push(pt);
+ } else {
+ continue;
+ }
+
+ if (d > maxD) {
+ maxD = d;
+ maxPt = pt;
+ }
+ }
+
+ return { maxPoint: maxPt, newPoints: newPoints };
+ },
+
+
+ /*
+ * Given a baseline, compute the convex hull of latLngs as an array
+ * of latLngs.
+ *
+ * @param {Array} latLngs
+ * @returns {Array}
+ */
+ buildConvexHull: function (baseLine, latLngs) {
+ var convexHullBaseLines = [],
+ t = this.findMostDistantPointFromBaseLine(baseLine, latLngs);
+
+ if (t.maxPoint) { // if there is still a point "outside" the base line
+ convexHullBaseLines =
+ convexHullBaseLines.concat(
+ this.buildConvexHull([baseLine[0], t.maxPoint], t.newPoints)
+ );
+ convexHullBaseLines =
+ convexHullBaseLines.concat(
+ this.buildConvexHull([t.maxPoint, baseLine[1]], t.newPoints)
+ );
+ return convexHullBaseLines;
+ } else { // if there is no more point "outside" the base line, the current base line is part of the convex hull
+ return [baseLine[0]];
+ }
+ },
+
+ /*
+ * Given an array of latlngs, compute a convex hull as an array
+ * of latlngs
+ *
+ * @param {Array} latLngs
+ * @returns {Array}
+ */
+ getConvexHull: function (latLngs) {
+ // find first baseline
+ var maxLat = false, minLat = false,
+ maxLng = false, minLng = false,
+ maxLatPt = null, minLatPt = null,
+ maxLngPt = null, minLngPt = null,
+ maxPt = null, minPt = null,
+ i;
+
+ for (i = latLngs.length - 1; i >= 0; i--) {
+ var pt = latLngs[i];
+ if (maxLat === false || pt.lat > maxLat) {
+ maxLatPt = pt;
+ maxLat = pt.lat;
+ }
+ if (minLat === false || pt.lat < minLat) {
+ minLatPt = pt;
+ minLat = pt.lat;
+ }
+ if (maxLng === false || pt.lng > maxLng) {
+ maxLngPt = pt;
+ maxLng = pt.lng;
+ }
+ if (minLng === false || pt.lng < minLng) {
+ minLngPt = pt;
+ minLng = pt.lng;
+ }
+ }
+
+ if (minLat !== maxLat) {
+ minPt = minLatPt;
+ maxPt = maxLatPt;
+ } else {
+ minPt = minLngPt;
+ maxPt = maxLngPt;
+ }
+
+ var ch = [].concat(this.buildConvexHull([minPt, maxPt], latLngs),
+ this.buildConvexHull([maxPt, minPt], latLngs));
+ return ch;
+ }
+ };
+}());
+
+L.MarkerCluster.include({
+ getConvexHull: function () {
+ var childMarkers = this.getAllChildMarkers(),
+ points = [],
+ p, i;
+
+ for (i = childMarkers.length - 1; i >= 0; i--) {
+ p = childMarkers[i].getLatLng();
+ points.push(p);
+ }
+
+ return L.QuickHull.getConvexHull(points);
+ }
+});
+
+//This code is 100% based on https://github.com/jawj/OverlappingMarkerSpiderfier-Leaflet
+//Huge thanks to jawj for implementing it first to make my job easy :-)
+
+L.MarkerCluster.include({
+
+ _2PI: Math.PI * 2,
+ _circleFootSeparation: 25, //related to circumference of circle
+ _circleStartAngle: 0,
+
+ _spiralFootSeparation: 28, //related to size of spiral (experiment!)
+ _spiralLengthStart: 11,
+ _spiralLengthFactor: 5,
+
+ _circleSpiralSwitchover: 9, //show spiral instead of circle from this marker count upwards.
+ // 0 -> always spiral; Infinity -> always circle
+
+ spiderfy: function () {
+ if (this._group._spiderfied === this || this._group._inZoomAnimation) {
+ return;
+ }
+
+ var childMarkers = this.getAllChildMarkers(null, true),
+ group = this._group,
+ map = group._map,
+ center = map.latLngToLayerPoint(this._latlng),
+ positions;
+
+ this._group._unspiderfy();
+ this._group._spiderfied = this;
+
+ //TODO Maybe: childMarkers order by distance to center
+
+ if (childMarkers.length >= this._circleSpiralSwitchover) {
+ positions = this._generatePointsSpiral(childMarkers.length, center);
+ } else {
+ center.y += 10; // Otherwise circles look wrong => hack for standard blue icon, renders differently for other icons.
+ positions = this._generatePointsCircle(childMarkers.length, center);
+ }
+
+ this._animationSpiderfy(childMarkers, positions);
+ },
+
+ unspiderfy: function (zoomDetails) {
+ /// <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param>
+ if (this._group._inZoomAnimation) {
+ return;
+ }
+ this._animationUnspiderfy(zoomDetails);
+
+ this._group._spiderfied = null;
+ },
+
+ _generatePointsCircle: function (count, centerPt) {
+ var circumference = this._group.options.spiderfyDistanceMultiplier * this._circleFootSeparation * (2 + count),
+ legLength = circumference / this._2PI, //radius from circumference
+ angleStep = this._2PI / count,
+ res = [],
+ i, angle;
+
+ legLength = Math.max(legLength, 35); // Minimum distance to get outside the cluster icon.
+
+ res.length = count;
+
+ for (i = 0; i < count; i++) { // Clockwise, like spiral.
+ angle = this._circleStartAngle + i * angleStep;
+ res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
+ }
+
+ return res;
+ },
+
+ _generatePointsSpiral: function (count, centerPt) {
+ var spiderfyDistanceMultiplier = this._group.options.spiderfyDistanceMultiplier,
+ legLength = spiderfyDistanceMultiplier * this._spiralLengthStart,
+ separation = spiderfyDistanceMultiplier * this._spiralFootSeparation,
+ lengthFactor = spiderfyDistanceMultiplier * this._spiralLengthFactor * this._2PI,
+ angle = 0,
+ res = [],
+ i;
+
+ res.length = count;
+
+ // Higher index, closer position to cluster center.
+ for (i = count; i >= 0; i--) {
+ // Skip the first position, so that we are already farther from center and we avoid
+ // being under the default cluster icon (especially important for Circle Markers).
+ if (i < count) {
+ res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
+ }
+ angle += separation / legLength + i * 0.0005;
+ legLength += lengthFactor / angle;
+ }
+ return res;
+ },
+
+ _noanimationUnspiderfy: function () {
+ var group = this._group,
+ map = group._map,
+ fg = group._featureGroup,
+ childMarkers = this.getAllChildMarkers(null, true),
+ m, i;
+
+ group._ignoreMove = true;
+
+ this.setOpacity(1);
+ for (i = childMarkers.length - 1; i >= 0; i--) {
+ m = childMarkers[i];
+
+ fg.removeLayer(m);
+
+ if (m._preSpiderfyLatlng) {
+ m.setLatLng(m._preSpiderfyLatlng);
+ delete m._preSpiderfyLatlng;
+ }
+ if (m.setZIndexOffset) {
+ m.setZIndexOffset(0);
+ }
+
+ if (m._spiderLeg) {
+ map.removeLayer(m._spiderLeg);
+ delete m._spiderLeg;
+ }
+ }
+
+ group.fire('unspiderfied', {
+ cluster: this,
+ markers: childMarkers
+ });
+ group._ignoreMove = false;
+ group._spiderfied = null;
+ }
+});
+
+//Non Animated versions of everything
+L.MarkerClusterNonAnimated = L.MarkerCluster.extend({
+ _animationSpiderfy: function (childMarkers, positions) {
+ var group = this._group,
+ map = group._map,
+ fg = group._featureGroup,
+ legOptions = this._group.options.spiderLegPolylineOptions,
+ i, m, leg, newPos;
+
+ group._ignoreMove = true;
+
+ // Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition.
+ // The reverse order trick no longer improves performance on modern browsers.
+ for (i = 0; i < childMarkers.length; i++) {
+ newPos = map.layerPointToLatLng(positions[i]);
+ m = childMarkers[i];
+
+ // Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it.
+ leg = new L.Polyline([this._latlng, newPos], legOptions);
+ map.addLayer(leg);
+ m._spiderLeg = leg;
+
+ // Now add the marker.
+ m._preSpiderfyLatlng = m._latlng;
+ m.setLatLng(newPos);
+ if (m.setZIndexOffset) {
+ m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING
+ }
+
+ fg.addLayer(m);
+ }
+ this.setOpacity(0.3);
+
+ group._ignoreMove = false;
+ group.fire('spiderfied', {
+ cluster: this,
+ markers: childMarkers
+ });
+ },
+
+ _animationUnspiderfy: function () {
+ this._noanimationUnspiderfy();
+ }
+});
+
+//Animated versions here
+L.MarkerCluster.include({
+
+ _animationSpiderfy: function (childMarkers, positions) {
+ var me = this,
+ group = this._group,
+ map = group._map,
+ fg = group._featureGroup,
+ thisLayerLatLng = this._latlng,
+ thisLayerPos = map.latLngToLayerPoint(thisLayerLatLng),
+ svg = L.Path.SVG,
+ legOptions = L.extend({}, this._group.options.spiderLegPolylineOptions), // Copy the options so that we can modify them for animation.
+ finalLegOpacity = legOptions.opacity,
+ i, m, leg, legPath, legLength, newPos;
+
+ if (finalLegOpacity === undefined) {
+ finalLegOpacity = L.MarkerClusterGroup.prototype.options.spiderLegPolylineOptions.opacity;
+ }
+
+ if (svg) {
+ // If the initial opacity of the spider leg is not 0 then it appears before the animation starts.
+ legOptions.opacity = 0;
+
+ // Add the class for CSS transitions.
+ legOptions.className = (legOptions.className || '') + ' leaflet-cluster-spider-leg';
+ } else {
+ // Make sure we have a defined opacity.
+ legOptions.opacity = finalLegOpacity;
+ }
+
+ group._ignoreMove = true;
+
+ // Add markers and spider legs to map, hidden at our center point.
+ // Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition.
+ // The reverse order trick no longer improves performance on modern browsers.
+ for (i = 0; i < childMarkers.length; i++) {
+ m = childMarkers[i];
+
+ newPos = map.layerPointToLatLng(positions[i]);
+
+ // Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it.
+ leg = new L.Polyline([thisLayerLatLng, newPos], legOptions);
+ map.addLayer(leg);
+ m._spiderLeg = leg;
+
+ // Explanations: https://jakearchibald.com/2013/animated-line-drawing-svg/
+ // In our case the transition property is declared in the CSS file.
+ if (svg) {
+ legPath = leg._path;
+ legLength = legPath.getTotalLength() + 0.1; // Need a small extra length to avoid remaining dot in Firefox.
+ legPath.style.strokeDasharray = legLength; // Just 1 length is enough, it will be duplicated.
+ legPath.style.strokeDashoffset = legLength;
+ }
+
+ // If it is a marker, add it now and we'll animate it out
+ if (m.setZIndexOffset) {
+ m.setZIndexOffset(1000000); // Make normal markers appear on top of EVERYTHING
+ }
+ if (m.clusterHide) {
+ m.clusterHide();
+ }
+
+ // Vectors just get immediately added
+ fg.addLayer(m);
+
+ if (m._setPos) {
+ m._setPos(thisLayerPos);
+ }
+ }
+
+ group._forceLayout();
+ group._animationStart();
+
+ // Reveal markers and spider legs.
+ for (i = childMarkers.length - 1; i >= 0; i--) {
+ newPos = map.layerPointToLatLng(positions[i]);
+ m = childMarkers[i];
+
+ //Move marker to new position
+ m._preSpiderfyLatlng = m._latlng;
+ m.setLatLng(newPos);
+
+ if (m.clusterShow) {
+ m.clusterShow();
+ }
+
+ // Animate leg (animation is actually delegated to CSS transition).
+ if (svg) {
+ leg = m._spiderLeg;
+ legPath = leg._path;
+ legPath.style.strokeDashoffset = 0;
+ //legPath.style.strokeOpacity = finalLegOpacity;
+ leg.setStyle({opacity: finalLegOpacity});
+ }
+ }
+ this.setOpacity(0.3);
+
+ group._ignoreMove = false;
+
+ setTimeout(function () {
+ group._animationEnd();
+ group.fire('spiderfied', {
+ cluster: me,
+ markers: childMarkers
+ });
+ }, 200);
+ },
+
+ _animationUnspiderfy: function (zoomDetails) {
+ var me = this,
+ group = this._group,
+ map = group._map,
+ fg = group._featureGroup,
+ thisLayerPos = zoomDetails ? map._latLngToNewLayerPoint(this._latlng, zoomDetails.zoom, zoomDetails.center) : map.latLngToLayerPoint(this._latlng),
+ childMarkers = this.getAllChildMarkers(null, true),
+ svg = L.Path.SVG,
+ m, i, leg, legPath, legLength, nonAnimatable;
+
+ group._ignoreMove = true;
+ group._animationStart();
+
+ //Make us visible and bring the child markers back in
+ this.setOpacity(1);
+ for (i = childMarkers.length - 1; i >= 0; i--) {
+ m = childMarkers[i];
+
+ //Marker was added to us after we were spiderfied
+ if (!m._preSpiderfyLatlng) {
+ continue;
+ }
+
+ //Close any popup on the marker first, otherwise setting the location of the marker will make the map scroll
+ m.closePopup();
+
+ //Fix up the location to the real one
+ m.setLatLng(m._preSpiderfyLatlng);
+ delete m._preSpiderfyLatlng;
+
+ //Hack override the location to be our center
+ nonAnimatable = true;
+ if (m._setPos) {
+ m._setPos(thisLayerPos);
+ nonAnimatable = false;
+ }
+ if (m.clusterHide) {
+ m.clusterHide();
+ nonAnimatable = false;
+ }
+ if (nonAnimatable) {
+ fg.removeLayer(m);
+ }
+
+ // Animate the spider leg back in (animation is actually delegated to CSS transition).
+ if (svg) {
+ leg = m._spiderLeg;
+ legPath = leg._path;
+ legLength = legPath.getTotalLength() + 0.1;
+ legPath.style.strokeDashoffset = legLength;
+ leg.setStyle({opacity: 0});
+ }
+ }
+
+ group._ignoreMove = false;
+
+ setTimeout(function () {
+ //If we have only <= one child left then that marker will be shown on the map so don't remove it!
+ var stillThereChildCount = 0;
+ for (i = childMarkers.length - 1; i >= 0; i--) {
+ m = childMarkers[i];
+ if (m._spiderLeg) {
+ stillThereChildCount++;
+ }
+ }
+
+
+ for (i = childMarkers.length - 1; i >= 0; i--) {
+ m = childMarkers[i];
+
+ if (!m._spiderLeg) { //Has already been unspiderfied
+ continue;
+ }
+
+ if (m.clusterShow) {
+ m.clusterShow();
+ }
+ if (m.setZIndexOffset) {
+ m.setZIndexOffset(0);
+ }
+
+ if (stillThereChildCount > 1) {
+ fg.removeLayer(m);
+ }
+
+ map.removeLayer(m._spiderLeg);
+ delete m._spiderLeg;
+ }
+ group._animationEnd();
+ group.fire('unspiderfied', {
+ cluster: me,
+ markers: childMarkers
+ });
+ }, 200);
+ }
+});
+
+
+L.MarkerClusterGroup.include({
+ //The MarkerCluster currently spiderfied (if any)
+ _spiderfied: null,
+
+ unspiderfy: function () {
+ this._unspiderfy.apply(this, arguments);
+ },
+
+ _spiderfierOnAdd: function () {
+ this._map.on('click', this._unspiderfyWrapper, this);
+
+ if (this._map.options.zoomAnimation) {
+ this._map.on('zoomstart', this._unspiderfyZoomStart, this);
+ }
+ //Browsers without zoomAnimation or a big zoom don't fire zoomstart
+ this._map.on('zoomend', this._noanimationUnspiderfy, this);
+
+ if (!L.Browser.touch) {
+ this._map.getRenderer(this);
+ //Needs to happen in the pageload, not after, or animations don't work in webkit
+ // http://stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements
+ //Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable
+ }
+ },
+
+ _spiderfierOnRemove: function () {
+ this._map.off('click', this._unspiderfyWrapper, this);
+ this._map.off('zoomstart', this._unspiderfyZoomStart, this);
+ this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
+ this._map.off('zoomend', this._noanimationUnspiderfy, this);
+
+ //Ensure that markers are back where they should be
+ // Use no animation to avoid a sticky leaflet-cluster-anim class on mapPane
+ this._noanimationUnspiderfy();
+ },
+
+ //On zoom start we add a zoomanim handler so that we are guaranteed to be last (after markers are animated)
+ //This means we can define the animation they do rather than Markers doing an animation to their actual location
+ _unspiderfyZoomStart: function () {
+ if (!this._map) { //May have been removed from the map by a zoomEnd handler
+ return;
+ }
+
+ this._map.on('zoomanim', this._unspiderfyZoomAnim, this);
+ },
+
+ _unspiderfyZoomAnim: function (zoomDetails) {
+ //Wait until the first zoomanim after the user has finished touch-zooming before running the animation
+ if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) {
+ return;
+ }
+
+ this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
+ this._unspiderfy(zoomDetails);
+ },
+
+ _unspiderfyWrapper: function () {
+ /// <summary>_unspiderfy but passes no arguments</summary>
+ this._unspiderfy();
+ },
+
+ _unspiderfy: function (zoomDetails) {
+ if (this._spiderfied) {
+ this._spiderfied.unspiderfy(zoomDetails);
+ }
+ },
+
+ _noanimationUnspiderfy: function () {
+ if (this._spiderfied) {
+ this._spiderfied._noanimationUnspiderfy();
+ }
+ },
+
+ //If the given layer is currently being spiderfied then we unspiderfy it so it isn't on the map anymore etc
+ _unspiderfyLayer: function (layer) {
+ if (layer._spiderLeg) {
+ this._featureGroup.removeLayer(layer);
+
+ if (layer.clusterShow) {
+ layer.clusterShow();
+ }
+ //Position will be fixed up immediately in _animationUnspiderfy
+ if (layer.setZIndexOffset) {
+ layer.setZIndexOffset(0);
+ }
+
+ this._map.removeLayer(layer._spiderLeg);
+ delete layer._spiderLeg;
+ }
+ }
+});
+
+/**
+ * Adds 1 public method to MCG and 1 to L.Marker to facilitate changing
+ * markers' icon options and refreshing their icon and their parent clusters
+ * accordingly (case where their iconCreateFunction uses data of childMarkers
+ * to make up the cluster icon).
+ */
+
+
+L.MarkerClusterGroup.include({
+ /**
+ * Updates the icon of all clusters which are parents of the given marker(s).
+ * In singleMarkerMode, also updates the given marker(s) icon.
+ * @param layers L.MarkerClusterGroup|L.LayerGroup|Array(L.Marker)|Map(L.Marker)|
+ * L.MarkerCluster|L.Marker (optional) list of markers (or single marker) whose parent
+ * clusters need to be updated. If not provided, retrieves all child markers of this.
+ * @returns {L.MarkerClusterGroup}
+ */
+ refreshClusters: function (layers) {
+ if (!layers) {
+ layers = this._topClusterLevel.getAllChildMarkers();
+ } else if (layers instanceof L.MarkerClusterGroup) {
+ layers = layers._topClusterLevel.getAllChildMarkers();
+ } else if (layers instanceof L.LayerGroup) {
+ layers = layers._layers;
+ } else if (layers instanceof L.MarkerCluster) {
+ layers = layers.getAllChildMarkers();
+ } else if (layers instanceof L.Marker) {
+ layers = [layers];
+ } // else: must be an Array(L.Marker)|Map(L.Marker)
+ this._flagParentsIconsNeedUpdate(layers);
+ this._refreshClustersIcons();
+
+ // In case of singleMarkerMode, also re-draw the markers.
+ if (this.options.singleMarkerMode) {
+ this._refreshSingleMarkerModeMarkers(layers);
+ }
+
+ return this;
+ },
+
+ /**
+ * Simply flags all parent clusters of the given markers as having a "dirty" icon.
+ * @param layers Array(L.Marker)|Map(L.Marker) list of markers.
+ * @private
+ */
+ _flagParentsIconsNeedUpdate: function (layers) {
+ var id, parent;
+
+ // Assumes layers is an Array or an Object whose prototype is non-enumerable.
+ for (id in layers) {
+ // Flag parent clusters' icon as "dirty", all the way up.
+ // Dumb process that flags multiple times upper parents, but still
+ // much more efficient than trying to be smart and make short lists,
+ // at least in the case of a hierarchy following a power law:
+ // http://jsperf.com/flag-nodes-in-power-hierarchy/2
+ parent = layers[id].__parent;
+ while (parent) {
+ parent._iconNeedsUpdate = true;
+ parent = parent.__parent;
+ }
+ }
+ },
+
+ /**
+ * Re-draws the icon of the supplied markers.
+ * To be used in singleMarkerMode only.
+ * @param layers Array(L.Marker)|Map(L.Marker) list of markers.
+ * @private
+ */
+ _refreshSingleMarkerModeMarkers: function (layers) {
+ var id, layer;
+
+ for (id in layers) {
+ layer = layers[id];
+
+ // Make sure we do not override markers that do not belong to THIS group.
+ if (this.hasLayer(layer)) {
+ // Need to re-create the icon first, then re-draw the marker.
+ layer.setIcon(this._overrideMarkerIcon(layer));
+ }
+ }
+ }
+});
+
+L.Marker.include({
+ /**
+ * Updates the given options in the marker's icon and refreshes the marker.
+ * @param options map object of icon options.
+ * @param directlyRefreshClusters boolean (optional) true to trigger
+ * MCG.refreshClustersOf() right away with this single marker.
+ * @returns {L.Marker}
+ */
+ refreshIconOptions: function (options, directlyRefreshClusters) {
+ var icon = this.options.icon;
+
+ L.setOptions(icon, options);
+
+ this.setIcon(icon);
+
+ // Shortcut to refresh the associated MCG clusters right away.
+ // To be used when refreshing a single marker.
+ // Otherwise, better use MCG.refreshClusters() once at the end with
+ // the list of modified markers.
+ if (directlyRefreshClusters && this.__parent) {
+ this.__parent._group.refreshClusters(this);
+ }
+
+ return this;
+ }
+});
+
+exports.MarkerClusterGroup = MarkerClusterGroup;
+exports.MarkerCluster = MarkerCluster;
+
+})));
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/layers-2x.png b/www/wiki/extensions/Maps/resources/lib/leaflet/images/layers-2x.png
index 200c333d..200c333d 100644
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/layers-2x.png
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet/images/layers-2x.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/layers.png b/www/wiki/extensions/Maps/resources/lib/leaflet/images/layers.png
index 1a72e578..1a72e578 100644
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/layers.png
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet/images/layers.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet/images/marker-icon-2x.png b/www/wiki/extensions/Maps/resources/lib/leaflet/images/marker-icon-2x.png
new file mode 100644
index 00000000..e4abba3b
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet/images/marker-icon-2x.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet/images/marker-icon.png b/www/wiki/extensions/Maps/resources/lib/leaflet/images/marker-icon.png
new file mode 100644
index 00000000..950edf24
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet/images/marker-icon.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-shadow.png b/www/wiki/extensions/Maps/resources/lib/leaflet/images/marker-shadow.png
index 9fd29795..9fd29795 100644
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-shadow.png
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet/images/marker-shadow.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet/leaflet.css b/www/wiki/extensions/Maps/resources/lib/leaflet/leaflet.css
index a0932d57..983d6059 100644
--- a/www/wiki/extensions/Maps/resources/leaflet/leaflet/leaflet.css
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet/leaflet.css
@@ -25,6 +25,10 @@
user-select: none;
-webkit-user-drag: none;
}
+/* Prevents IE11 from highlighting tiles in blue */
+.leaflet-tile::selection {
+ background: transparent;
+}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
@@ -237,7 +241,8 @@
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
-.leaflet-pane > svg path.leaflet-interactive {
+.leaflet-pane > svg path.leaflet-interactive,
+svg.leaflet-image-layer.leaflet-interactive path {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
diff --git a/www/wiki/extensions/Maps/resources/lib/leaflet/leaflet.js b/www/wiki/extensions/Maps/resources/lib/leaflet/leaflet.js
new file mode 100644
index 00000000..9a71094d
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/lib/leaflet/leaflet.js
@@ -0,0 +1,14079 @@
+/* @preserve
+ * Leaflet 1.6.0+Detached: 0c81bdf904d864fd12a286e3d1979f47aba17991.0c81bdf, a JS library for interactive maps. http://leafletjs.com
+ * (c) 2010-2019 Vladimir Agafonkin, (c) 2010-2011 CloudMade
+ */
+
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
+ (factory((global.L = {})));
+}(this, (function (exports) { 'use strict';
+
+var version = "1.6.0+HEAD.0c81bdf";
+
+/*
+ * @namespace Util
+ *
+ * Various utility functions, used by Leaflet internally.
+ */
+
+var freeze = Object.freeze;
+Object.freeze = function (obj) { return obj; };
+
+// @function extend(dest: Object, src?: Object): Object
+// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
+function extend(dest) {
+ var i, j, len, src;
+
+ for (j = 1, len = arguments.length; j < len; j++) {
+ src = arguments[j];
+ for (i in src) {
+ dest[i] = src[i];
+ }
+ }
+ return dest;
+}
+
+// @function create(proto: Object, properties?: Object): Object
+// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
+var create = Object.create || (function () {
+ function F() {}
+ return function (proto) {
+ F.prototype = proto;
+ return new F();
+ };
+})();
+
+// @function bind(fn: Function, …): Function
+// Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
+// Has a `L.bind()` shortcut.
+function bind(fn, obj) {
+ var slice = Array.prototype.slice;
+
+ if (fn.bind) {
+ return fn.bind.apply(fn, slice.call(arguments, 1));
+ }
+
+ var args = slice.call(arguments, 2);
+
+ return function () {
+ return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
+ };
+}
+
+// @property lastId: Number
+// Last unique ID used by [`stamp()`](#util-stamp)
+var lastId = 0;
+
+// @function stamp(obj: Object): Number
+// Returns the unique ID of an object, assigning it one if it doesn't have it.
+function stamp(obj) {
+ /*eslint-disable */
+ obj._leaflet_id = obj._leaflet_id || ++lastId;
+ return obj._leaflet_id;
+ /* eslint-enable */
+}
+
+// @function throttle(fn: Function, time: Number, context: Object): Function
+// Returns a function which executes function `fn` with the given scope `context`
+// (so that the `this` keyword refers to `context` inside `fn`'s code). The function
+// `fn` will be called no more than one time per given amount of `time`. The arguments
+// received by the bound function will be any arguments passed when binding the
+// function, followed by any arguments passed when invoking the bound function.
+// Has an `L.throttle` shortcut.
+function throttle(fn, time, context) {
+ var lock, args, wrapperFn, later;
+
+ later = function () {
+ // reset lock and call if queued
+ lock = false;
+ if (args) {
+ wrapperFn.apply(context, args);
+ args = false;
+ }
+ };
+
+ wrapperFn = function () {
+ if (lock) {
+ // called too soon, queue to call later
+ args = arguments;
+
+ } else {
+ // call and lock until later
+ fn.apply(context, arguments);
+ setTimeout(later, time);
+ lock = true;
+ }
+ };
+
+ return wrapperFn;
+}
+
+// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
+// Returns the number `num` modulo `range` in such a way so it lies within
+// `range[0]` and `range[1]`. The returned value will be always smaller than
+// `range[1]` unless `includeMax` is set to `true`.
+function wrapNum(x, range, includeMax) {
+ var max = range[1],
+ min = range[0],
+ d = max - min;
+ return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
+}
+
+// @function falseFn(): Function
+// Returns a function which always returns `false`.
+function falseFn() { return false; }
+
+// @function formatNum(num: Number, digits?: Number): Number
+// Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
+function formatNum(num, digits) {
+ var pow = Math.pow(10, (digits === undefined ? 6 : digits));
+ return Math.round(num * pow) / pow;
+}
+
+// @function trim(str: String): String
+// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
+function trim(str) {
+ return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
+}
+
+// @function splitWords(str: String): String[]
+// Trims and splits the string on whitespace and returns the array of parts.
+function splitWords(str) {
+ return trim(str).split(/\s+/);
+}
+
+// @function setOptions(obj: Object, options: Object): Object
+// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
+function setOptions(obj, options) {
+ if (!obj.hasOwnProperty('options')) {
+ obj.options = obj.options ? create(obj.options) : {};
+ }
+ for (var i in options) {
+ obj.options[i] = options[i];
+ }
+ return obj.options;
+}
+
+// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
+// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
+// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
+// be appended at the end. If `uppercase` is `true`, the parameter names will
+// be uppercased (e.g. `'?A=foo&B=bar'`)
+function getParamString(obj, existingUrl, uppercase) {
+ var params = [];
+ for (var i in obj) {
+ params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
+ }
+ return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
+}
+
+var templateRe = /\{ *([\w_-]+) *\}/g;
+
+// @function template(str: String, data: Object): String
+// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
+// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
+// `('Hello foo, bar')`. You can also specify functions instead of strings for
+// data values — they will be evaluated passing `data` as an argument.
+function template(str, data) {
+ return str.replace(templateRe, function (str, key) {
+ var value = data[key];
+
+ if (value === undefined) {
+ throw new Error('No value provided for variable ' + str);
+
+ } else if (typeof value === 'function') {
+ value = value(data);
+ }
+ return value;
+ });
+}
+
+// @function isArray(obj): Boolean
+// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
+var isArray = Array.isArray || function (obj) {
+ return (Object.prototype.toString.call(obj) === '[object Array]');
+};
+
+// @function indexOf(array: Array, el: Object): Number
+// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
+function indexOf(array, el) {
+ for (var i = 0; i < array.length; i++) {
+ if (array[i] === el) { return i; }
+ }
+ return -1;
+}
+
+// @property emptyImageUrl: String
+// Data URI string containing a base64-encoded empty GIF image.
+// Used as a hack to free memory from unused images on WebKit-powered
+// mobile devices (by setting image `src` to this string).
+var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
+
+// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+
+function getPrefixed(name) {
+ return window['webkit' + name] || window['moz' + name] || window['ms' + name];
+}
+
+var lastTime = 0;
+
+// fallback for IE 7-8
+function timeoutDefer(fn) {
+ var time = +new Date(),
+ timeToCall = Math.max(0, 16 - (time - lastTime));
+
+ lastTime = time + timeToCall;
+ return window.setTimeout(fn, timeToCall);
+}
+
+var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
+var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
+ getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
+
+// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
+// Schedules `fn` to be executed when the browser repaints. `fn` is bound to
+// `context` if given. When `immediate` is set, `fn` is called immediately if
+// the browser doesn't have native support for
+// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
+// otherwise it's delayed. Returns a request ID that can be used to cancel the request.
+function requestAnimFrame(fn, context, immediate) {
+ if (immediate && requestFn === timeoutDefer) {
+ fn.call(context);
+ } else {
+ return requestFn.call(window, bind(fn, context));
+ }
+}
+
+// @function cancelAnimFrame(id: Number): undefined
+// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
+function cancelAnimFrame(id) {
+ if (id) {
+ cancelFn.call(window, id);
+ }
+}
+
+
+var Util = (Object.freeze || Object)({
+ freeze: freeze,
+ extend: extend,
+ create: create,
+ bind: bind,
+ lastId: lastId,
+ stamp: stamp,
+ throttle: throttle,
+ wrapNum: wrapNum,
+ falseFn: falseFn,
+ formatNum: formatNum,
+ trim: trim,
+ splitWords: splitWords,
+ setOptions: setOptions,
+ getParamString: getParamString,
+ template: template,
+ isArray: isArray,
+ indexOf: indexOf,
+ emptyImageUrl: emptyImageUrl,
+ requestFn: requestFn,
+ cancelFn: cancelFn,
+ requestAnimFrame: requestAnimFrame,
+ cancelAnimFrame: cancelAnimFrame
+});
+
+// @class Class
+// @aka L.Class
+
+// @section
+// @uninheritable
+
+// Thanks to John Resig and Dean Edwards for inspiration!
+
+function Class() {}
+
+Class.extend = function (props) {
+
+ // @function extend(props: Object): Function
+ // [Extends the current class](#class-inheritance) given the properties to be included.
+ // Returns a Javascript function that is a class constructor (to be called with `new`).
+ var NewClass = function () {
+
+ // call the constructor
+ if (this.initialize) {
+ this.initialize.apply(this, arguments);
+ }
+
+ // call all constructor hooks
+ this.callInitHooks();
+ };
+
+ var parentProto = NewClass.__super__ = this.prototype;
+
+ var proto = create(parentProto);
+ proto.constructor = NewClass;
+
+ NewClass.prototype = proto;
+
+ // inherit parent's statics
+ for (var i in this) {
+ if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
+ NewClass[i] = this[i];
+ }
+ }
+
+ // mix static properties into the class
+ if (props.statics) {
+ extend(NewClass, props.statics);
+ delete props.statics;
+ }
+
+ // mix includes into the prototype
+ if (props.includes) {
+ checkDeprecatedMixinEvents(props.includes);
+ extend.apply(null, [proto].concat(props.includes));
+ delete props.includes;
+ }
+
+ // merge options
+ if (proto.options) {
+ props.options = extend(create(proto.options), props.options);
+ }
+
+ // mix given properties into the prototype
+ extend(proto, props);
+
+ proto._initHooks = [];
+
+ // add method for calling all hooks
+ proto.callInitHooks = function () {
+
+ if (this._initHooksCalled) { return; }
+
+ if (parentProto.callInitHooks) {
+ parentProto.callInitHooks.call(this);
+ }
+
+ this._initHooksCalled = true;
+
+ for (var i = 0, len = proto._initHooks.length; i < len; i++) {
+ proto._initHooks[i].call(this);
+ }
+ };
+
+ return NewClass;
+};
+
+
+// @function include(properties: Object): this
+// [Includes a mixin](#class-includes) into the current class.
+Class.include = function (props) {
+ extend(this.prototype, props);
+ return this;
+};
+
+// @function mergeOptions(options: Object): this
+// [Merges `options`](#class-options) into the defaults of the class.
+Class.mergeOptions = function (options) {
+ extend(this.prototype.options, options);
+ return this;
+};
+
+// @function addInitHook(fn: Function): this
+// Adds a [constructor hook](#class-constructor-hooks) to the class.
+Class.addInitHook = function (fn) { // (Function) || (String, args...)
+ var args = Array.prototype.slice.call(arguments, 1);
+
+ var init = typeof fn === 'function' ? fn : function () {
+ this[fn].apply(this, args);
+ };
+
+ this.prototype._initHooks = this.prototype._initHooks || [];
+ this.prototype._initHooks.push(init);
+ return this;
+};
+
+function checkDeprecatedMixinEvents(includes) {
+ if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
+
+ includes = isArray(includes) ? includes : [includes];
+
+ for (var i = 0; i < includes.length; i++) {
+ if (includes[i] === L.Mixin.Events) {
+ console.warn('Deprecated include of L.Mixin.Events: ' +
+ 'this property will be removed in future releases, ' +
+ 'please inherit from L.Evented instead.', new Error().stack);
+ }
+ }
+}
+
+/*
+ * @class Evented
+ * @aka L.Evented
+ * @inherits Class
+ *
+ * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
+ *
+ * @example
+ *
+ * ```js
+ * map.on('click', function(e) {
+ * alert(e.latlng);
+ * } );
+ * ```
+ *
+ * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
+ *
+ * ```js
+ * function onClick(e) { ... }
+ *
+ * map.on('click', onClick);
+ * map.off('click', onClick);
+ * ```
+ */
+
+var Events = {
+ /* @method on(type: String, fn: Function, context?: Object): this
+ * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
+ *
+ * @alternative
+ * @method on(eventMap: Object): this
+ * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
+ */
+ on: function (types, fn, context) {
+
+ // types can be a map of types/handlers
+ if (typeof types === 'object') {
+ for (var type in types) {
+ // we don't process space-separated events here for performance;
+ // it's a hot path since Layer uses the on(obj) syntax
+ this._on(type, types[type], fn);
+ }
+
+ } else {
+ // types can be a string of space-separated words
+ types = splitWords(types);
+
+ for (var i = 0, len = types.length; i < len; i++) {
+ this._on(types[i], fn, context);
+ }
+ }
+
+ return this;
+ },
+
+ /* @method off(type: String, fn?: Function, context?: Object): this
+ * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
+ *
+ * @alternative
+ * @method off(eventMap: Object): this
+ * Removes a set of type/listener pairs.
+ *
+ * @alternative
+ * @method off: this
+ * Removes all listeners to all events on the object. This includes implicitly attached events.
+ */
+ off: function (types, fn, context) {
+
+ if (!types) {
+ // clear all listeners if called without arguments
+ delete this._events;
+
+ } else if (typeof types === 'object') {
+ for (var type in types) {
+ this._off(type, types[type], fn);
+ }
+
+ } else {
+ types = splitWords(types);
+
+ for (var i = 0, len = types.length; i < len; i++) {
+ this._off(types[i], fn, context);
+ }
+ }
+
+ return this;
+ },
+
+ // attach listener (without syntactic sugar now)
+ _on: function (type, fn, context) {
+ this._events = this._events || {};
+
+ /* get/init listeners for type */
+ var typeListeners = this._events[type];
+ if (!typeListeners) {
+ typeListeners = [];
+ this._events[type] = typeListeners;
+ }
+
+ if (context === this) {
+ // Less memory footprint.
+ context = undefined;
+ }
+ var newListener = {fn: fn, ctx: context},
+ listeners = typeListeners;
+
+ // check if fn already there
+ for (var i = 0, len = listeners.length; i < len; i++) {
+ if (listeners[i].fn === fn && listeners[i].ctx === context) {
+ return;
+ }
+ }
+
+ listeners.push(newListener);
+ },
+
+ _off: function (type, fn, context) {
+ var listeners,
+ i,
+ len;
+
+ if (!this._events) { return; }
+
+ listeners = this._events[type];
+
+ if (!listeners) {
+ return;
+ }
+
+ if (!fn) {
+ // Set all removed listeners to noop so they are not called if remove happens in fire
+ for (i = 0, len = listeners.length; i < len; i++) {
+ listeners[i].fn = falseFn;
+ }
+ // clear all listeners for a type if function isn't specified
+ delete this._events[type];
+ return;
+ }
+
+ if (context === this) {
+ context = undefined;
+ }
+
+ if (listeners) {
+
+ // find fn and remove it
+ for (i = 0, len = listeners.length; i < len; i++) {
+ var l = listeners[i];
+ if (l.ctx !== context) { continue; }
+ if (l.fn === fn) {
+
+ // set the removed listener to noop so that's not called if remove happens in fire
+ l.fn = falseFn;
+
+ if (this._firingCount) {
+ /* copy array in case events are being fired */
+ this._events[type] = listeners = listeners.slice();
+ }
+ listeners.splice(i, 1);
+
+ return;
+ }
+ }
+ }
+ },
+
+ // @method fire(type: String, data?: Object, propagate?: Boolean): this
+ // Fires an event of the specified type. You can optionally provide an data
+ // object — the first argument of the listener function will contain its
+ // properties. The event can optionally be propagated to event parents.
+ fire: function (type, data, propagate) {
+ if (!this.listens(type, propagate)) { return this; }
+
+ var event = extend({}, data, {
+ type: type,
+ target: this,
+ sourceTarget: data && data.sourceTarget || this
+ });
+
+ if (this._events) {
+ var listeners = this._events[type];
+
+ if (listeners) {
+ this._firingCount = (this._firingCount + 1) || 1;
+ for (var i = 0, len = listeners.length; i < len; i++) {
+ var l = listeners[i];
+ l.fn.call(l.ctx || this, event);
+ }
+
+ this._firingCount--;
+ }
+ }
+
+ if (propagate) {
+ // propagate the event to parents (set with addEventParent)
+ this._propagateEvent(event);
+ }
+
+ return this;
+ },
+
+ // @method listens(type: String): Boolean
+ // Returns `true` if a particular event type has any listeners attached to it.
+ listens: function (type, propagate) {
+ var listeners = this._events && this._events[type];
+ if (listeners && listeners.length) { return true; }
+
+ if (propagate) {
+ // also check parents for listeners if event propagates
+ for (var id in this._eventParents) {
+ if (this._eventParents[id].listens(type, propagate)) { return true; }
+ }
+ }
+ return false;
+ },
+
+ // @method once(…): this
+ // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
+ once: function (types, fn, context) {
+
+ if (typeof types === 'object') {
+ for (var type in types) {
+ this.once(type, types[type], fn);
+ }
+ return this;
+ }
+
+ var handler = bind(function () {
+ this
+ .off(types, fn, context)
+ .off(types, handler, context);
+ }, this);
+
+ // add a listener that's executed once and removed after that
+ return this
+ .on(types, fn, context)
+ .on(types, handler, context);
+ },
+
+ // @method addEventParent(obj: Evented): this
+ // Adds an event parent - an `Evented` that will receive propagated events
+ addEventParent: function (obj) {
+ this._eventParents = this._eventParents || {};
+ this._eventParents[stamp(obj)] = obj;
+ return this;
+ },
+
+ // @method removeEventParent(obj: Evented): this
+ // Removes an event parent, so it will stop receiving propagated events
+ removeEventParent: function (obj) {
+ if (this._eventParents) {
+ delete this._eventParents[stamp(obj)];
+ }
+ return this;
+ },
+
+ _propagateEvent: function (e) {
+ for (var id in this._eventParents) {
+ this._eventParents[id].fire(e.type, extend({
+ layer: e.target,
+ propagatedFrom: e.target
+ }, e), true);
+ }
+ }
+};
+
+// aliases; we should ditch those eventually
+
+// @method addEventListener(…): this
+// Alias to [`on(…)`](#evented-on)
+Events.addEventListener = Events.on;
+
+// @method removeEventListener(…): this
+// Alias to [`off(…)`](#evented-off)
+
+// @method clearAllEventListeners(…): this
+// Alias to [`off()`](#evented-off)
+Events.removeEventListener = Events.clearAllEventListeners = Events.off;
+
+// @method addOneTimeEventListener(…): this
+// Alias to [`once(…)`](#evented-once)
+Events.addOneTimeEventListener = Events.once;
+
+// @method fireEvent(…): this
+// Alias to [`fire(…)`](#evented-fire)
+Events.fireEvent = Events.fire;
+
+// @method hasEventListeners(…): Boolean
+// Alias to [`listens(…)`](#evented-listens)
+Events.hasEventListeners = Events.listens;
+
+var Evented = Class.extend(Events);
+
+/*
+ * @class Point
+ * @aka L.Point
+ *
+ * Represents a point with `x` and `y` coordinates in pixels.
+ *
+ * @example
+ *
+ * ```js
+ * var point = L.point(200, 300);
+ * ```
+ *
+ * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:
+ *
+ * ```js
+ * map.panBy([200, 300]);
+ * map.panBy(L.point(200, 300));
+ * ```
+ *
+ * Note that `Point` does not inherit from Leafet's `Class` object,
+ * which means new classes can't inherit from it, and new methods
+ * can't be added to it with the `include` function.
+ */
+
+function Point(x, y, round) {
+ // @property x: Number; The `x` coordinate of the point
+ this.x = (round ? Math.round(x) : x);
+ // @property y: Number; The `y` coordinate of the point
+ this.y = (round ? Math.round(y) : y);
+}
+
+var trunc = Math.trunc || function (v) {
+ return v > 0 ? Math.floor(v) : Math.ceil(v);
+};
+
+Point.prototype = {
+
+ // @method clone(): Point
+ // Returns a copy of the current point.
+ clone: function () {
+ return new Point(this.x, this.y);
+ },
+
+ // @method add(otherPoint: Point): Point
+ // Returns the result of addition of the current and the given points.
+ add: function (point) {
+ // non-destructive, returns a new point
+ return this.clone()._add(toPoint(point));
+ },
+
+ _add: function (point) {
+ // destructive, used directly for performance in situations where it's safe to modify existing point
+ this.x += point.x;
+ this.y += point.y;
+ return this;
+ },
+
+ // @method subtract(otherPoint: Point): Point
+ // Returns the result of subtraction of the given point from the current.
+ subtract: function (point) {
+ return this.clone()._subtract(toPoint(point));
+ },
+
+ _subtract: function (point) {
+ this.x -= point.x;
+ this.y -= point.y;
+ return this;
+ },
+
+ // @method divideBy(num: Number): Point
+ // Returns the result of division of the current point by the given number.
+ divideBy: function (num) {
+ return this.clone()._divideBy(num);
+ },
+
+ _divideBy: function (num) {
+ this.x /= num;
+ this.y /= num;
+ return this;
+ },
+
+ // @method multiplyBy(num: Number): Point
+ // Returns the result of multiplication of the current point by the given number.
+ multiplyBy: function (num) {
+ return this.clone()._multiplyBy(num);
+ },
+
+ _multiplyBy: function (num) {
+ this.x *= num;
+ this.y *= num;
+ return this;
+ },
+
+ // @method scaleBy(scale: Point): Point
+ // Multiply each coordinate of the current point by each coordinate of
+ // `scale`. In linear algebra terms, multiply the point by the
+ // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
+ // defined by `scale`.
+ scaleBy: function (point) {
+ return new Point(this.x * point.x, this.y * point.y);
+ },
+
+ // @method unscaleBy(scale: Point): Point
+ // Inverse of `scaleBy`. Divide each coordinate of the current point by
+ // each coordinate of `scale`.
+ unscaleBy: function (point) {
+ return new Point(this.x / point.x, this.y / point.y);
+ },
+
+ // @method round(): Point
+ // Returns a copy of the current point with rounded coordinates.
+ round: function () {
+ return this.clone()._round();
+ },
+
+ _round: function () {
+ this.x = Math.round(this.x);
+ this.y = Math.round(this.y);
+ return this;
+ },
+
+ // @method floor(): Point
+ // Returns a copy of the current point with floored coordinates (rounded down).
+ floor: function () {
+ return this.clone()._floor();
+ },
+
+ _floor: function () {
+ this.x = Math.floor(this.x);
+ this.y = Math.floor(this.y);
+ return this;
+ },
+
+ // @method ceil(): Point
+ // Returns a copy of the current point with ceiled coordinates (rounded up).
+ ceil: function () {
+ return this.clone()._ceil();
+ },
+
+ _ceil: function () {
+ this.x = Math.ceil(this.x);
+ this.y = Math.ceil(this.y);
+ return this;
+ },
+
+ // @method trunc(): Point
+ // Returns a copy of the current point with truncated coordinates (rounded towards zero).
+ trunc: function () {
+ return this.clone()._trunc();
+ },
+
+ _trunc: function () {
+ this.x = trunc(this.x);
+ this.y = trunc(this.y);
+ return this;
+ },
+
+ // @method distanceTo(otherPoint: Point): Number
+ // Returns the cartesian distance between the current and the given points.
+ distanceTo: function (point) {
+ point = toPoint(point);
+
+ var x = point.x - this.x,
+ y = point.y - this.y;
+
+ return Math.sqrt(x * x + y * y);
+ },
+
+ // @method equals(otherPoint: Point): Boolean
+ // Returns `true` if the given point has the same coordinates.
+ equals: function (point) {
+ point = toPoint(point);
+
+ return point.x === this.x &&
+ point.y === this.y;
+ },
+
+ // @method contains(otherPoint: Point): Boolean
+ // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
+ contains: function (point) {
+ point = toPoint(point);
+
+ return Math.abs(point.x) <= Math.abs(this.x) &&
+ Math.abs(point.y) <= Math.abs(this.y);
+ },
+
+ // @method toString(): String
+ // Returns a string representation of the point for debugging purposes.
+ toString: function () {
+ return 'Point(' +
+ formatNum(this.x) + ', ' +
+ formatNum(this.y) + ')';
+ }
+};
+
+// @factory L.point(x: Number, y: Number, round?: Boolean)
+// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
+
+// @alternative
+// @factory L.point(coords: Number[])
+// Expects an array of the form `[x, y]` instead.
+
+// @alternative
+// @factory L.point(coords: Object)
+// Expects a plain object of the form `{x: Number, y: Number}` instead.
+function toPoint(x, y, round) {
+ if (x instanceof Point) {
+ return x;
+ }
+ if (isArray(x)) {
+ return new Point(x[0], x[1]);
+ }
+ if (x === undefined || x === null) {
+ return x;
+ }
+ if (typeof x === 'object' && 'x' in x && 'y' in x) {
+ return new Point(x.x, x.y);
+ }
+ return new Point(x, y, round);
+}
+
+/*
+ * @class Bounds
+ * @aka L.Bounds
+ *
+ * Represents a rectangular area in pixel coordinates.
+ *
+ * @example
+ *
+ * ```js
+ * var p1 = L.point(10, 10),
+ * p2 = L.point(40, 60),
+ * bounds = L.bounds(p1, p2);
+ * ```
+ *
+ * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
+ *
+ * ```js
+ * otherBounds.intersects([[10, 10], [40, 60]]);
+ * ```
+ *
+ * Note that `Bounds` does not inherit from Leafet's `Class` object,
+ * which means new classes can't inherit from it, and new methods
+ * can't be added to it with the `include` function.
+ */
+
+function Bounds(a, b) {
+ if (!a) { return; }
+
+ var points = b ? [a, b] : a;
+
+ for (var i = 0, len = points.length; i < len; i++) {
+ this.extend(points[i]);
+ }
+}
+
+Bounds.prototype = {
+ // @method extend(point: Point): this
+ // Extends the bounds to contain the given point.
+ extend: function (point) { // (Point)
+ point = toPoint(point);
+
+ // @property min: Point
+ // The top left corner of the rectangle.
+ // @property max: Point
+ // The bottom right corner of the rectangle.
+ if (!this.min && !this.max) {
+ this.min = point.clone();
+ this.max = point.clone();
+ } else {
+ this.min.x = Math.min(point.x, this.min.x);
+ this.max.x = Math.max(point.x, this.max.x);
+ this.min.y = Math.min(point.y, this.min.y);
+ this.max.y = Math.max(point.y, this.max.y);
+ }
+ return this;
+ },
+
+ // @method getCenter(round?: Boolean): Point
+ // Returns the center point of the bounds.
+ getCenter: function (round) {
+ return new Point(
+ (this.min.x + this.max.x) / 2,
+ (this.min.y + this.max.y) / 2, round);
+ },
+
+ // @method getBottomLeft(): Point
+ // Returns the bottom-left point of the bounds.
+ getBottomLeft: function () {
+ return new Point(this.min.x, this.max.y);
+ },
+
+ // @method getTopRight(): Point
+ // Returns the top-right point of the bounds.
+ getTopRight: function () { // -> Point
+ return new Point(this.max.x, this.min.y);
+ },
+
+ // @method getTopLeft(): Point
+ // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
+ getTopLeft: function () {
+ return this.min; // left, top
+ },
+
+ // @method getBottomRight(): Point
+ // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
+ getBottomRight: function () {
+ return this.max; // right, bottom
+ },
+
+ // @method getSize(): Point
+ // Returns the size of the given bounds
+ getSize: function () {
+ return this.max.subtract(this.min);
+ },
+
+ // @method contains(otherBounds: Bounds): Boolean
+ // Returns `true` if the rectangle contains the given one.
+ // @alternative
+ // @method contains(point: Point): Boolean
+ // Returns `true` if the rectangle contains the given point.
+ contains: function (obj) {
+ var min, max;
+
+ if (typeof obj[0] === 'number' || obj instanceof Point) {
+ obj = toPoint(obj);
+ } else {
+ obj = toBounds(obj);
+ }
+
+ if (obj instanceof Bounds) {
+ min = obj.min;
+ max = obj.max;
+ } else {
+ min = max = obj;
+ }
+
+ return (min.x >= this.min.x) &&
+ (max.x <= this.max.x) &&
+ (min.y >= this.min.y) &&
+ (max.y <= this.max.y);
+ },
+
+ // @method intersects(otherBounds: Bounds): Boolean
+ // Returns `true` if the rectangle intersects the given bounds. Two bounds
+ // intersect if they have at least one point in common.
+ intersects: function (bounds) { // (Bounds) -> Boolean
+ bounds = toBounds(bounds);
+
+ var min = this.min,
+ max = this.max,
+ min2 = bounds.min,
+ max2 = bounds.max,
+ xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
+ yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
+
+ return xIntersects && yIntersects;
+ },
+
+ // @method overlaps(otherBounds: Bounds): Boolean
+ // Returns `true` if the rectangle overlaps the given bounds. Two bounds
+ // overlap if their intersection is an area.
+ overlaps: function (bounds) { // (Bounds) -> Boolean
+ bounds = toBounds(bounds);
+
+ var min = this.min,
+ max = this.max,
+ min2 = bounds.min,
+ max2 = bounds.max,
+ xOverlaps = (max2.x > min.x) && (min2.x < max.x),
+ yOverlaps = (max2.y > min.y) && (min2.y < max.y);
+
+ return xOverlaps && yOverlaps;
+ },
+
+ isValid: function () {
+ return !!(this.min && this.max);
+ }
+};
+
+
+// @factory L.bounds(corner1: Point, corner2: Point)
+// Creates a Bounds object from two corners coordinate pairs.
+// @alternative
+// @factory L.bounds(points: Point[])
+// Creates a Bounds object from the given array of points.
+function toBounds(a, b) {
+ if (!a || a instanceof Bounds) {
+ return a;
+ }
+ return new Bounds(a, b);
+}
+
+/*
+ * @class LatLngBounds
+ * @aka L.LatLngBounds
+ *
+ * Represents a rectangular geographical area on a map.
+ *
+ * @example
+ *
+ * ```js
+ * var corner1 = L.latLng(40.712, -74.227),
+ * corner2 = L.latLng(40.774, -74.125),
+ * bounds = L.latLngBounds(corner1, corner2);
+ * ```
+ *
+ * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
+ *
+ * ```js
+ * map.fitBounds([
+ * [40.712, -74.227],
+ * [40.774, -74.125]
+ * ]);
+ * ```
+ *
+ * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
+ *
+ * Note that `LatLngBounds` does not inherit from Leafet's `Class` object,
+ * which means new classes can't inherit from it, and new methods
+ * can't be added to it with the `include` function.
+ */
+
+function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
+ if (!corner1) { return; }
+
+ var latlngs = corner2 ? [corner1, corner2] : corner1;
+
+ for (var i = 0, len = latlngs.length; i < len; i++) {
+ this.extend(latlngs[i]);
+ }
+}
+
+LatLngBounds.prototype = {
+
+ // @method extend(latlng: LatLng): this
+ // Extend the bounds to contain the given point
+
+ // @alternative
+ // @method extend(otherBounds: LatLngBounds): this
+ // Extend the bounds to contain the given bounds
+ extend: function (obj) {
+ var sw = this._southWest,
+ ne = this._northEast,
+ sw2, ne2;
+
+ if (obj instanceof LatLng) {
+ sw2 = obj;
+ ne2 = obj;
+
+ } else if (obj instanceof LatLngBounds) {
+ sw2 = obj._southWest;
+ ne2 = obj._northEast;
+
+ if (!sw2 || !ne2) { return this; }
+
+ } else {
+ return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
+ }
+
+ if (!sw && !ne) {
+ this._southWest = new LatLng(sw2.lat, sw2.lng);
+ this._northEast = new LatLng(ne2.lat, ne2.lng);
+ } else {
+ sw.lat = Math.min(sw2.lat, sw.lat);
+ sw.lng = Math.min(sw2.lng, sw.lng);
+ ne.lat = Math.max(ne2.lat, ne.lat);
+ ne.lng = Math.max(ne2.lng, ne.lng);
+ }
+
+ return this;
+ },
+
+ // @method pad(bufferRatio: Number): LatLngBounds
+ // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
+ // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
+ // Negative values will retract the bounds.
+ pad: function (bufferRatio) {
+ var sw = this._southWest,
+ ne = this._northEast,
+ heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
+ widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
+
+ return new LatLngBounds(
+ new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
+ new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
+ },
+
+ // @method getCenter(): LatLng
+ // Returns the center point of the bounds.
+ getCenter: function () {
+ return new LatLng(
+ (this._southWest.lat + this._northEast.lat) / 2,
+ (this._southWest.lng + this._northEast.lng) / 2);
+ },
+
+ // @method getSouthWest(): LatLng
+ // Returns the south-west point of the bounds.
+ getSouthWest: function () {
+ return this._southWest;
+ },
+
+ // @method getNorthEast(): LatLng
+ // Returns the north-east point of the bounds.
+ getNorthEast: function () {
+ return this._northEast;
+ },
+
+ // @method getNorthWest(): LatLng
+ // Returns the north-west point of the bounds.
+ getNorthWest: function () {
+ return new LatLng(this.getNorth(), this.getWest());
+ },
+
+ // @method getSouthEast(): LatLng
+ // Returns the south-east point of the bounds.
+ getSouthEast: function () {
+ return new LatLng(this.getSouth(), this.getEast());
+ },
+
+ // @method getWest(): Number
+ // Returns the west longitude of the bounds
+ getWest: function () {
+ return this._southWest.lng;
+ },
+
+ // @method getSouth(): Number
+ // Returns the south latitude of the bounds
+ getSouth: function () {
+ return this._southWest.lat;
+ },
+
+ // @method getEast(): Number
+ // Returns the east longitude of the bounds
+ getEast: function () {
+ return this._northEast.lng;
+ },
+
+ // @method getNorth(): Number
+ // Returns the north latitude of the bounds
+ getNorth: function () {
+ return this._northEast.lat;
+ },
+
+ // @method contains(otherBounds: LatLngBounds): Boolean
+ // Returns `true` if the rectangle contains the given one.
+
+ // @alternative
+ // @method contains (latlng: LatLng): Boolean
+ // Returns `true` if the rectangle contains the given point.
+ contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
+ if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
+ obj = toLatLng(obj);
+ } else {
+ obj = toLatLngBounds(obj);
+ }
+
+ var sw = this._southWest,
+ ne = this._northEast,
+ sw2, ne2;
+
+ if (obj instanceof LatLngBounds) {
+ sw2 = obj.getSouthWest();
+ ne2 = obj.getNorthEast();
+ } else {
+ sw2 = ne2 = obj;
+ }
+
+ return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
+ (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
+ },
+
+ // @method intersects(otherBounds: LatLngBounds): Boolean
+ // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
+ intersects: function (bounds) {
+ bounds = toLatLngBounds(bounds);
+
+ var sw = this._southWest,
+ ne = this._northEast,
+ sw2 = bounds.getSouthWest(),
+ ne2 = bounds.getNorthEast(),
+
+ latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
+ lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
+
+ return latIntersects && lngIntersects;
+ },
+
+ // @method overlaps(otherBounds: Bounds): Boolean
+ // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
+ overlaps: function (bounds) {
+ bounds = toLatLngBounds(bounds);
+
+ var sw = this._southWest,
+ ne = this._northEast,
+ sw2 = bounds.getSouthWest(),
+ ne2 = bounds.getNorthEast(),
+
+ latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
+ lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
+
+ return latOverlaps && lngOverlaps;
+ },
+
+ // @method toBBoxString(): String
+ // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
+ toBBoxString: function () {
+ return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
+ },
+
+ // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
+ // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
+ equals: function (bounds, maxMargin) {
+ if (!bounds) { return false; }
+
+ bounds = toLatLngBounds(bounds);
+
+ return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
+ this._northEast.equals(bounds.getNorthEast(), maxMargin);
+ },
+
+ // @method isValid(): Boolean
+ // Returns `true` if the bounds are properly initialized.
+ isValid: function () {
+ return !!(this._southWest && this._northEast);
+ }
+};
+
+// TODO International date line?
+
+// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
+// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
+
+// @alternative
+// @factory L.latLngBounds(latlngs: LatLng[])
+// Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
+function toLatLngBounds(a, b) {
+ if (a instanceof LatLngBounds) {
+ return a;
+ }
+ return new LatLngBounds(a, b);
+}
+
+/* @class LatLng
+ * @aka L.LatLng
+ *
+ * Represents a geographical point with a certain latitude and longitude.
+ *
+ * @example
+ *
+ * ```
+ * var latlng = L.latLng(50.5, 30.5);
+ * ```
+ *
+ * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent:
+ *
+ * ```
+ * map.panTo([50, 30]);
+ * map.panTo({lon: 30, lat: 50});
+ * map.panTo({lat: 50, lng: 30});
+ * map.panTo(L.latLng(50, 30));
+ * ```
+ *
+ * Note that `LatLng` does not inherit from Leaflet's `Class` object,
+ * which means new classes can't inherit from it, and new methods
+ * can't be added to it with the `include` function.
+ */
+
+function LatLng(lat, lng, alt) {
+ if (isNaN(lat) || isNaN(lng)) {
+ throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
+ }
+
+ // @property lat: Number
+ // Latitude in degrees
+ this.lat = +lat;
+
+ // @property lng: Number
+ // Longitude in degrees
+ this.lng = +lng;
+
+ // @property alt: Number
+ // Altitude in meters (optional)
+ if (alt !== undefined) {
+ this.alt = +alt;
+ }
+}
+
+LatLng.prototype = {
+ // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
+ // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number.
+ equals: function (obj, maxMargin) {
+ if (!obj) { return false; }
+
+ obj = toLatLng(obj);
+
+ var margin = Math.max(
+ Math.abs(this.lat - obj.lat),
+ Math.abs(this.lng - obj.lng));
+
+ return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
+ },
+
+ // @method toString(): String
+ // Returns a string representation of the point (for debugging purposes).
+ toString: function (precision) {
+ return 'LatLng(' +
+ formatNum(this.lat, precision) + ', ' +
+ formatNum(this.lng, precision) + ')';
+ },
+
+ // @method distanceTo(otherLatLng: LatLng): Number
+ // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
+ distanceTo: function (other) {
+ return Earth.distance(this, toLatLng(other));
+ },
+
+ // @method wrap(): LatLng
+ // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
+ wrap: function () {
+ return Earth.wrapLatLng(this);
+ },
+
+ // @method toBounds(sizeInMeters: Number): LatLngBounds
+ // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
+ toBounds: function (sizeInMeters) {
+ var latAccuracy = 180 * sizeInMeters / 40075017,
+ lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
+
+ return toLatLngBounds(
+ [this.lat - latAccuracy, this.lng - lngAccuracy],
+ [this.lat + latAccuracy, this.lng + lngAccuracy]);
+ },
+
+ clone: function () {
+ return new LatLng(this.lat, this.lng, this.alt);
+ }
+};
+
+
+
+// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
+// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
+
+// @alternative
+// @factory L.latLng(coords: Array): LatLng
+// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
+
+// @alternative
+// @factory L.latLng(coords: Object): LatLng
+// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
+
+function toLatLng(a, b, c) {
+ if (a instanceof LatLng) {
+ return a;
+ }
+ if (isArray(a) && typeof a[0] !== 'object') {
+ if (a.length === 3) {
+ return new LatLng(a[0], a[1], a[2]);
+ }
+ if (a.length === 2) {
+ return new LatLng(a[0], a[1]);
+ }
+ return null;
+ }
+ if (a === undefined || a === null) {
+ return a;
+ }
+ if (typeof a === 'object' && 'lat' in a) {
+ return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
+ }
+ if (b === undefined) {
+ return null;
+ }
+ return new LatLng(a, b, c);
+}
+
+/*
+ * @namespace CRS
+ * @crs L.CRS.Base
+ * Object that defines coordinate reference systems for projecting
+ * geographical points into pixel (screen) coordinates and back (and to
+ * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
+ * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
+ *
+ * Leaflet defines the most usual CRSs by default. If you want to use a
+ * CRS not defined by default, take a look at the
+ * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
+ *
+ * Note that the CRS instances do not inherit from Leafet's `Class` object,
+ * and can't be instantiated. Also, new classes can't inherit from them,
+ * and methods can't be added to them with the `include` function.
+ */
+
+var CRS = {
+ // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
+ // Projects geographical coordinates into pixel coordinates for a given zoom.
+ latLngToPoint: function (latlng, zoom) {
+ var projectedPoint = this.projection.project(latlng),
+ scale = this.scale(zoom);
+
+ return this.transformation._transform(projectedPoint, scale);
+ },
+
+ // @method pointToLatLng(point: Point, zoom: Number): LatLng
+ // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
+ // zoom into geographical coordinates.
+ pointToLatLng: function (point, zoom) {
+ var scale = this.scale(zoom),
+ untransformedPoint = this.transformation.untransform(point, scale);
+
+ return this.projection.unproject(untransformedPoint);
+ },
+
+ // @method project(latlng: LatLng): Point
+ // Projects geographical coordinates into coordinates in units accepted for
+ // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
+ project: function (latlng) {
+ return this.projection.project(latlng);
+ },
+
+ // @method unproject(point: Point): LatLng
+ // Given a projected coordinate returns the corresponding LatLng.
+ // The inverse of `project`.
+ unproject: function (point) {
+ return this.projection.unproject(point);
+ },
+
+ // @method scale(zoom: Number): Number
+ // Returns the scale used when transforming projected coordinates into
+ // pixel coordinates for a particular zoom. For example, it returns
+ // `256 * 2^zoom` for Mercator-based CRS.
+ scale: function (zoom) {
+ return 256 * Math.pow(2, zoom);
+ },
+
+ // @method zoom(scale: Number): Number
+ // Inverse of `scale()`, returns the zoom level corresponding to a scale
+ // factor of `scale`.
+ zoom: function (scale) {
+ return Math.log(scale / 256) / Math.LN2;
+ },
+
+ // @method getProjectedBounds(zoom: Number): Bounds
+ // Returns the projection's bounds scaled and transformed for the provided `zoom`.
+ getProjectedBounds: function (zoom) {
+ if (this.infinite) { return null; }
+
+ var b = this.projection.bounds,
+ s = this.scale(zoom),
+ min = this.transformation.transform(b.min, s),
+ max = this.transformation.transform(b.max, s);
+
+ return new Bounds(min, max);
+ },
+
+ // @method distance(latlng1: LatLng, latlng2: LatLng): Number
+ // Returns the distance between two geographical coordinates.
+
+ // @property code: String
+ // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
+ //
+ // @property wrapLng: Number[]
+ // An array of two numbers defining whether the longitude (horizontal) coordinate
+ // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
+ // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
+ //
+ // @property wrapLat: Number[]
+ // Like `wrapLng`, but for the latitude (vertical) axis.
+
+ // wrapLng: [min, max],
+ // wrapLat: [min, max],
+
+ // @property infinite: Boolean
+ // If true, the coordinate space will be unbounded (infinite in both axes)
+ infinite: false,
+
+ // @method wrapLatLng(latlng: LatLng): LatLng
+ // Returns a `LatLng` where lat and lng has been wrapped according to the
+ // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
+ wrapLatLng: function (latlng) {
+ var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
+ lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
+ alt = latlng.alt;
+
+ return new LatLng(lat, lng, alt);
+ },
+
+ // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
+ // Returns a `LatLngBounds` with the same size as the given one, ensuring
+ // that its center is within the CRS's bounds.
+ // Only accepts actual `L.LatLngBounds` instances, not arrays.
+ wrapLatLngBounds: function (bounds) {
+ var center = bounds.getCenter(),
+ newCenter = this.wrapLatLng(center),
+ latShift = center.lat - newCenter.lat,
+ lngShift = center.lng - newCenter.lng;
+
+ if (latShift === 0 && lngShift === 0) {
+ return bounds;
+ }
+
+ var sw = bounds.getSouthWest(),
+ ne = bounds.getNorthEast(),
+ newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
+ newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
+
+ return new LatLngBounds(newSw, newNe);
+ }
+};
+
+/*
+ * @namespace CRS
+ * @crs L.CRS.Earth
+ *
+ * Serves as the base for CRS that are global such that they cover the earth.
+ * Can only be used as the base for other CRS and cannot be used directly,
+ * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
+ * meters.
+ */
+
+var Earth = extend({}, CRS, {
+ wrapLng: [-180, 180],
+
+ // Mean Earth Radius, as recommended for use by
+ // the International Union of Geodesy and Geophysics,
+ // see http://rosettacode.org/wiki/Haversine_formula
+ R: 6371000,
+
+ // distance between two geographical points using spherical law of cosines approximation
+ distance: function (latlng1, latlng2) {
+ var rad = Math.PI / 180,
+ lat1 = latlng1.lat * rad,
+ lat2 = latlng2.lat * rad,
+ sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
+ sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
+ a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
+ c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ return this.R * c;
+ }
+});
+
+/*
+ * @namespace Projection
+ * @projection L.Projection.SphericalMercator
+ *
+ * Spherical Mercator projection — the most common projection for online maps,
+ * used by almost all free and commercial tile providers. Assumes that Earth is
+ * a sphere. Used by the `EPSG:3857` CRS.
+ */
+
+var earthRadius = 6378137;
+
+var SphericalMercator = {
+
+ R: earthRadius,
+ MAX_LATITUDE: 85.0511287798,
+
+ project: function (latlng) {
+ var d = Math.PI / 180,
+ max = this.MAX_LATITUDE,
+ lat = Math.max(Math.min(max, latlng.lat), -max),
+ sin = Math.sin(lat * d);
+
+ return new Point(
+ this.R * latlng.lng * d,
+ this.R * Math.log((1 + sin) / (1 - sin)) / 2);
+ },
+
+ unproject: function (point) {
+ var d = 180 / Math.PI;
+
+ return new LatLng(
+ (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
+ point.x * d / this.R);
+ },
+
+ bounds: (function () {
+ var d = earthRadius * Math.PI;
+ return new Bounds([-d, -d], [d, d]);
+ })()
+};
+
+/*
+ * @class Transformation
+ * @aka L.Transformation
+ *
+ * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
+ * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
+ * the reverse. Used by Leaflet in its projections code.
+ *
+ * @example
+ *
+ * ```js
+ * var transformation = L.transformation(2, 5, -1, 10),
+ * p = L.point(1, 2),
+ * p2 = transformation.transform(p), // L.point(7, 8)
+ * p3 = transformation.untransform(p2); // L.point(1, 2)
+ * ```
+ */
+
+
+// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
+// Creates a `Transformation` object with the given coefficients.
+function Transformation(a, b, c, d) {
+ if (isArray(a)) {
+ // use array properties
+ this._a = a[0];
+ this._b = a[1];
+ this._c = a[2];
+ this._d = a[3];
+ return;
+ }
+ this._a = a;
+ this._b = b;
+ this._c = c;
+ this._d = d;
+}
+
+Transformation.prototype = {
+ // @method transform(point: Point, scale?: Number): Point
+ // Returns a transformed point, optionally multiplied by the given scale.
+ // Only accepts actual `L.Point` instances, not arrays.
+ transform: function (point, scale) { // (Point, Number) -> Point
+ return this._transform(point.clone(), scale);
+ },
+
+ // destructive transform (faster)
+ _transform: function (point, scale) {
+ scale = scale || 1;
+ point.x = scale * (this._a * point.x + this._b);
+ point.y = scale * (this._c * point.y + this._d);
+ return point;
+ },
+
+ // @method untransform(point: Point, scale?: Number): Point
+ // Returns the reverse transformation of the given point, optionally divided
+ // by the given scale. Only accepts actual `L.Point` instances, not arrays.
+ untransform: function (point, scale) {
+ scale = scale || 1;
+ return new Point(
+ (point.x / scale - this._b) / this._a,
+ (point.y / scale - this._d) / this._c);
+ }
+};
+
+// factory L.transformation(a: Number, b: Number, c: Number, d: Number)
+
+// @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
+// Instantiates a Transformation object with the given coefficients.
+
+// @alternative
+// @factory L.transformation(coefficients: Array): Transformation
+// Expects an coefficients array of the form
+// `[a: Number, b: Number, c: Number, d: Number]`.
+
+function toTransformation(a, b, c, d) {
+ return new Transformation(a, b, c, d);
+}
+
+/*
+ * @namespace CRS
+ * @crs L.CRS.EPSG3857
+ *
+ * The most common CRS for online maps, used by almost all free and commercial
+ * tile providers. Uses Spherical Mercator projection. Set in by default in
+ * Map's `crs` option.
+ */
+
+var EPSG3857 = extend({}, Earth, {
+ code: 'EPSG:3857',
+ projection: SphericalMercator,
+
+ transformation: (function () {
+ var scale = 0.5 / (Math.PI * SphericalMercator.R);
+ return toTransformation(scale, 0.5, -scale, 0.5);
+ }())
+});
+
+var EPSG900913 = extend({}, EPSG3857, {
+ code: 'EPSG:900913'
+});
+
+// @namespace SVG; @section
+// There are several static functions which can be called without instantiating L.SVG:
+
+// @function create(name: String): SVGElement
+// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
+// corresponding to the class name passed. For example, using 'line' will return
+// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
+function svgCreate(name) {
+ return document.createElementNS('http://www.w3.org/2000/svg', name);
+}
+
+// @function pointsToPath(rings: Point[], closed: Boolean): String
+// Generates a SVG path string for multiple rings, with each ring turning
+// into "M..L..L.." instructions
+function pointsToPath(rings, closed) {
+ var str = '',
+ i, j, len, len2, points, p;
+
+ for (i = 0, len = rings.length; i < len; i++) {
+ points = rings[i];
+
+ for (j = 0, len2 = points.length; j < len2; j++) {
+ p = points[j];
+ str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
+ }
+
+ // closes the ring for polygons; "x" is VML syntax
+ str += closed ? (svg ? 'z' : 'x') : '';
+ }
+
+ // SVG complains about empty path strings
+ return str || 'M0 0';
+}
+
+/*
+ * @namespace Browser
+ * @aka L.Browser
+ *
+ * A namespace with static properties for browser/feature detection used by Leaflet internally.
+ *
+ * @example
+ *
+ * ```js
+ * if (L.Browser.ielt9) {
+ * alert('Upgrade your browser, dude!');
+ * }
+ * ```
+ */
+
+var style$1 = document.documentElement.style;
+
+// @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
+var ie = 'ActiveXObject' in window;
+
+// @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
+var ielt9 = ie && !document.addEventListener;
+
+// @property edge: Boolean; `true` for the Edge web browser.
+var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
+
+// @property webkit: Boolean;
+// `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
+var webkit = userAgentContains('webkit');
+
+// @property android: Boolean
+// `true` for any browser running on an Android platform.
+var android = userAgentContains('android');
+
+// @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
+var android23 = userAgentContains('android 2') || userAgentContains('android 3');
+
+/* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
+var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
+// @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
+var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
+
+// @property opera: Boolean; `true` for the Opera browser
+var opera = !!window.opera;
+
+// @property chrome: Boolean; `true` for the Chrome browser.
+var chrome = userAgentContains('chrome');
+
+// @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
+var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
+
+// @property safari: Boolean; `true` for the Safari browser.
+var safari = !chrome && userAgentContains('safari');
+
+var phantom = userAgentContains('phantom');
+
+// @property opera12: Boolean
+// `true` for the Opera browser supporting CSS transforms (version 12 or later).
+var opera12 = 'OTransition' in style$1;
+
+// @property win: Boolean; `true` when the browser is running in a Windows platform
+var win = navigator.platform.indexOf('Win') === 0;
+
+// @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
+var ie3d = ie && ('transition' in style$1);
+
+// @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
+var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
+
+// @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
+var gecko3d = 'MozPerspective' in style$1;
+
+// @property any3d: Boolean
+// `true` for all browsers supporting CSS transforms.
+var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
+
+// @property mobile: Boolean; `true` for all browsers running in a mobile device.
+var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
+
+// @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
+var mobileWebkit = mobile && webkit;
+
+// @property mobileWebkit3d: Boolean
+// `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
+var mobileWebkit3d = mobile && webkit3d;
+
+// @property msPointer: Boolean
+// `true` for browsers implementing the Microsoft touch events model (notably IE10).
+var msPointer = !window.PointerEvent && window.MSPointerEvent;
+
+// @property pointer: Boolean
+// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
+var pointer = !webkit && !!(window.PointerEvent || msPointer);
+
+// @property touch: Boolean
+// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
+// This does not necessarily mean that the browser is running in a computer with
+// a touchscreen, it only means that the browser is capable of understanding
+// touch events.
+var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
+ (window.DocumentTouch && document instanceof window.DocumentTouch));
+
+// @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
+var mobileOpera = mobile && opera;
+
+// @property mobileGecko: Boolean
+// `true` for gecko-based browsers running in a mobile device.
+var mobileGecko = mobile && gecko;
+
+// @property retina: Boolean
+// `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
+var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
+
+// @property passiveEvents: Boolean
+// `true` for browsers that support passive events.
+var passiveEvents = (function () {
+ var supportsPassiveOption = false;
+ try {
+ var opts = Object.defineProperty({}, 'passive', {
+ get: function () {
+ supportsPassiveOption = true;
+ }
+ });
+ window.addEventListener('testPassiveEventSupport', falseFn, opts);
+ window.removeEventListener('testPassiveEventSupport', falseFn, opts);
+ } catch (e) {
+ // Errors can safely be ignored since this is only a browser support test.
+ }
+ return supportsPassiveOption;
+});
+
+// @property canvas: Boolean
+// `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
+var canvas = (function () {
+ return !!document.createElement('canvas').getContext;
+}());
+
+// @property svg: Boolean
+// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
+var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
+
+// @property vml: Boolean
+// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
+var vml = !svg && (function () {
+ try {
+ var div = document.createElement('div');
+ div.innerHTML = '<v:shape adj="1"/>';
+
+ var shape = div.firstChild;
+ shape.style.behavior = 'url(#default#VML)';
+
+ return shape && (typeof shape.adj === 'object');
+
+ } catch (e) {
+ return false;
+ }
+}());
+
+
+function userAgentContains(str) {
+ return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
+}
+
+
+var Browser = (Object.freeze || Object)({
+ ie: ie,
+ ielt9: ielt9,
+ edge: edge,
+ webkit: webkit,
+ android: android,
+ android23: android23,
+ androidStock: androidStock,
+ opera: opera,
+ chrome: chrome,
+ gecko: gecko,
+ safari: safari,
+ phantom: phantom,
+ opera12: opera12,
+ win: win,
+ ie3d: ie3d,
+ webkit3d: webkit3d,
+ gecko3d: gecko3d,
+ any3d: any3d,
+ mobile: mobile,
+ mobileWebkit: mobileWebkit,
+ mobileWebkit3d: mobileWebkit3d,
+ msPointer: msPointer,
+ pointer: pointer,
+ touch: touch,
+ mobileOpera: mobileOpera,
+ mobileGecko: mobileGecko,
+ retina: retina,
+ passiveEvents: passiveEvents,
+ canvas: canvas,
+ svg: svg,
+ vml: vml
+});
+
+/*
+ * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
+ */
+
+
+var POINTER_DOWN = msPointer ? 'MSPointerDown' : 'pointerdown';
+var POINTER_MOVE = msPointer ? 'MSPointerMove' : 'pointermove';
+var POINTER_UP = msPointer ? 'MSPointerUp' : 'pointerup';
+var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
+var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
+
+var _pointers = {};
+var _pointerDocListener = false;
+
+// DomEvent.DoubleTap needs to know about this
+var _pointersCount = 0;
+
+// Provides a touch events wrapper for (ms)pointer events.
+// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
+
+function addPointerListener(obj, type, handler, id) {
+ if (type === 'touchstart') {
+ _addPointerStart(obj, handler, id);
+
+ } else if (type === 'touchmove') {
+ _addPointerMove(obj, handler, id);
+
+ } else if (type === 'touchend') {
+ _addPointerEnd(obj, handler, id);
+ }
+
+ return this;
+}
+
+function removePointerListener(obj, type, id) {
+ var handler = obj['_leaflet_' + type + id];
+
+ if (type === 'touchstart') {
+ obj.removeEventListener(POINTER_DOWN, handler, false);
+
+ } else if (type === 'touchmove') {
+ obj.removeEventListener(POINTER_MOVE, handler, false);
+
+ } else if (type === 'touchend') {
+ obj.removeEventListener(POINTER_UP, handler, false);
+ obj.removeEventListener(POINTER_CANCEL, handler, false);
+ }
+
+ return this;
+}
+
+function _addPointerStart(obj, handler, id) {
+ var onDown = bind(function (e) {
+ if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
+ // In IE11, some touch events needs to fire for form controls, or
+ // the controls will stop working. We keep a whitelist of tag names that
+ // need these events. For other target tags, we prevent default on the event.
+ if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
+ preventDefault(e);
+ } else {
+ return;
+ }
+ }
+
+ _handlePointer(e, handler);
+ });
+
+ obj['_leaflet_touchstart' + id] = onDown;
+ obj.addEventListener(POINTER_DOWN, onDown, false);
+
+ // need to keep track of what pointers and how many are active to provide e.touches emulation
+ if (!_pointerDocListener) {
+ // we listen documentElement as any drags that end by moving the touch off the screen get fired there
+ document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
+ document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
+ document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
+ document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
+
+ _pointerDocListener = true;
+ }
+}
+
+function _globalPointerDown(e) {
+ _pointers[e.pointerId] = e;
+ _pointersCount++;
+}
+
+function _globalPointerMove(e) {
+ if (_pointers[e.pointerId]) {
+ _pointers[e.pointerId] = e;
+ }
+}
+
+function _globalPointerUp(e) {
+ delete _pointers[e.pointerId];
+ _pointersCount--;
+}
+
+function _handlePointer(e, handler) {
+ e.touches = [];
+ for (var i in _pointers) {
+ e.touches.push(_pointers[i]);
+ }
+ e.changedTouches = [e];
+
+ handler(e);
+}
+
+function _addPointerMove(obj, handler, id) {
+ var onMove = function (e) {
+ // don't fire touch moves when mouse isn't down
+ if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
+
+ _handlePointer(e, handler);
+ };
+
+ obj['_leaflet_touchmove' + id] = onMove;
+ obj.addEventListener(POINTER_MOVE, onMove, false);
+}
+
+function _addPointerEnd(obj, handler, id) {
+ var onUp = function (e) {
+ _handlePointer(e, handler);
+ };
+
+ obj['_leaflet_touchend' + id] = onUp;
+ obj.addEventListener(POINTER_UP, onUp, false);
+ obj.addEventListener(POINTER_CANCEL, onUp, false);
+}
+
+/*
+ * Extends the event handling code with double tap support for mobile browsers.
+ */
+
+var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
+var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
+var _pre = '_leaflet_';
+
+// inspired by Zepto touch code by Thomas Fuchs
+function addDoubleTapListener(obj, handler, id) {
+ var last, touch$$1,
+ doubleTap = false,
+ delay = 250;
+
+ function onTouchStart(e) {
+ var count;
+
+ if (pointer) {
+ if ((!edge) || e.pointerType === 'mouse') { return; }
+ count = _pointersCount;
+ } else {
+ count = e.touches.length;
+ }
+
+ if (count > 1) { return; }
+
+ var now = Date.now(),
+ delta = now - (last || now);
+
+ touch$$1 = e.touches ? e.touches[0] : e;
+ doubleTap = (delta > 0 && delta <= delay);
+ last = now;
+ }
+
+ function onTouchEnd(e) {
+ if (doubleTap && !touch$$1.cancelBubble) {
+ if (pointer) {
+ if ((!edge) || e.pointerType === 'mouse') { return; }
+ // work around .type being readonly with MSPointer* events
+ var newTouch = {},
+ prop, i;
+
+ for (i in touch$$1) {
+ prop = touch$$1[i];
+ newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
+ }
+ touch$$1 = newTouch;
+ }
+ touch$$1.type = 'dblclick';
+ touch$$1.button = 0;
+ handler(touch$$1);
+ last = null;
+ }
+ }
+
+ obj[_pre + _touchstart + id] = onTouchStart;
+ obj[_pre + _touchend + id] = onTouchEnd;
+ obj[_pre + 'dblclick' + id] = handler;
+
+ obj.addEventListener(_touchstart, onTouchStart, passiveEvents ? {passive: false} : false);
+ obj.addEventListener(_touchend, onTouchEnd, passiveEvents ? {passive: false} : false);
+
+ // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
+ // the browser doesn't fire touchend/pointerup events but does fire
+ // native dblclicks. See #4127.
+ // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
+ obj.addEventListener('dblclick', handler, false);
+
+ return this;
+}
+
+function removeDoubleTapListener(obj, id) {
+ var touchstart = obj[_pre + _touchstart + id],
+ touchend = obj[_pre + _touchend + id],
+ dblclick = obj[_pre + 'dblclick' + id];
+
+ obj.removeEventListener(_touchstart, touchstart, passiveEvents ? {passive: false} : false);
+ obj.removeEventListener(_touchend, touchend, passiveEvents ? {passive: false} : false);
+ if (!edge) {
+ obj.removeEventListener('dblclick', dblclick, false);
+ }
+
+ return this;
+}
+
+/*
+ * @namespace DomUtil
+ *
+ * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
+ * tree, used by Leaflet internally.
+ *
+ * Most functions expecting or returning a `HTMLElement` also work for
+ * SVG elements. The only difference is that classes refer to CSS classes
+ * in HTML and SVG classes in SVG.
+ */
+
+
+// @property TRANSFORM: String
+// Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
+var TRANSFORM = testProp(
+ ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
+
+// webkitTransition comes first because some browser versions that drop vendor prefix don't do
+// the same for the transitionend event, in particular the Android 4.1 stock browser
+
+// @property TRANSITION: String
+// Vendor-prefixed transition style name.
+var TRANSITION = testProp(
+ ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
+
+// @property TRANSITION_END: String
+// Vendor-prefixed transitionend event name.
+var TRANSITION_END =
+ TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
+
+
+// @function get(id: String|HTMLElement): HTMLElement
+// Returns an element given its DOM id, or returns the element itself
+// if it was passed directly.
+function get(id) {
+ return typeof id === 'string' ? document.getElementById(id) : id;
+}
+
+// @function getStyle(el: HTMLElement, styleAttrib: String): String
+// Returns the value for a certain style attribute on an element,
+// including computed values or values set through CSS.
+function getStyle(el, style) {
+ var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
+
+ if ((!value || value === 'auto') && document.defaultView) {
+ var css = document.defaultView.getComputedStyle(el, null);
+ value = css ? css[style] : null;
+ }
+ return value === 'auto' ? null : value;
+}
+
+// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
+// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
+function create$1(tagName, className, container) {
+ var el = document.createElement(tagName);
+ el.className = className || '';
+
+ if (container) {
+ container.appendChild(el);
+ }
+ return el;
+}
+
+// @function remove(el: HTMLElement)
+// Removes `el` from its parent element
+function remove(el) {
+ var parent = el.parentNode;
+ if (parent) {
+ parent.removeChild(el);
+ }
+}
+
+// @function empty(el: HTMLElement)
+// Removes all of `el`'s children elements from `el`
+function empty(el) {
+ while (el.firstChild) {
+ el.removeChild(el.firstChild);
+ }
+}
+
+// @function toFront(el: HTMLElement)
+// Makes `el` the last child of its parent, so it renders in front of the other children.
+function toFront(el) {
+ var parent = el.parentNode;
+ if (parent && parent.lastChild !== el) {
+ parent.appendChild(el);
+ }
+}
+
+// @function toBack(el: HTMLElement)
+// Makes `el` the first child of its parent, so it renders behind the other children.
+function toBack(el) {
+ var parent = el.parentNode;
+ if (parent && parent.firstChild !== el) {
+ parent.insertBefore(el, parent.firstChild);
+ }
+}
+
+// @function hasClass(el: HTMLElement, name: String): Boolean
+// Returns `true` if the element's class attribute contains `name`.
+function hasClass(el, name) {
+ if (el.classList !== undefined) {
+ return el.classList.contains(name);
+ }
+ var className = getClass(el);
+ return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
+}
+
+// @function addClass(el: HTMLElement, name: String)
+// Adds `name` to the element's class attribute.
+function addClass(el, name) {
+ if (el.classList !== undefined) {
+ var classes = splitWords(name);
+ for (var i = 0, len = classes.length; i < len; i++) {
+ el.classList.add(classes[i]);
+ }
+ } else if (!hasClass(el, name)) {
+ var className = getClass(el);
+ setClass(el, (className ? className + ' ' : '') + name);
+ }
+}
+
+// @function removeClass(el: HTMLElement, name: String)
+// Removes `name` from the element's class attribute.
+function removeClass(el, name) {
+ if (el.classList !== undefined) {
+ el.classList.remove(name);
+ } else {
+ setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
+ }
+}
+
+// @function setClass(el: HTMLElement, name: String)
+// Sets the element's class.
+function setClass(el, name) {
+ if (el.className.baseVal === undefined) {
+ el.className = name;
+ } else {
+ // in case of SVG element
+ el.className.baseVal = name;
+ }
+}
+
+// @function getClass(el: HTMLElement): String
+// Returns the element's class.
+function getClass(el) {
+ // Check if the element is an SVGElementInstance and use the correspondingElement instead
+ // (Required for linked SVG elements in IE11.)
+ if (el.correspondingElement) {
+ el = el.correspondingElement;
+ }
+ return el.className.baseVal === undefined ? el.className : el.className.baseVal;
+}
+
+// @function setOpacity(el: HTMLElement, opacity: Number)
+// Set the opacity of an element (including old IE support).
+// `opacity` must be a number from `0` to `1`.
+function setOpacity(el, value) {
+ if ('opacity' in el.style) {
+ el.style.opacity = value;
+ } else if ('filter' in el.style) {
+ _setOpacityIE(el, value);
+ }
+}
+
+function _setOpacityIE(el, value) {
+ var filter = false,
+ filterName = 'DXImageTransform.Microsoft.Alpha';
+
+ // filters collection throws an error if we try to retrieve a filter that doesn't exist
+ try {
+ filter = el.filters.item(filterName);
+ } catch (e) {
+ // don't set opacity to 1 if we haven't already set an opacity,
+ // it isn't needed and breaks transparent pngs.
+ if (value === 1) { return; }
+ }
+
+ value = Math.round(value * 100);
+
+ if (filter) {
+ filter.Enabled = (value !== 100);
+ filter.Opacity = value;
+ } else {
+ el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
+ }
+}
+
+// @function testProp(props: String[]): String|false
+// Goes through the array of style names and returns the first name
+// that is a valid style name for an element. If no such name is found,
+// it returns false. Useful for vendor-prefixed styles like `transform`.
+function testProp(props) {
+ var style = document.documentElement.style;
+
+ for (var i = 0; i < props.length; i++) {
+ if (props[i] in style) {
+ return props[i];
+ }
+ }
+ return false;
+}
+
+// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
+// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
+// and optionally scaled by `scale`. Does not have an effect if the
+// browser doesn't support 3D CSS transforms.
+function setTransform(el, offset, scale) {
+ var pos = offset || new Point(0, 0);
+
+ el.style[TRANSFORM] =
+ (ie3d ?
+ 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
+ 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
+ (scale ? ' scale(' + scale + ')' : '');
+}
+
+// @function setPosition(el: HTMLElement, position: Point)
+// Sets the position of `el` to coordinates specified by `position`,
+// using CSS translate or top/left positioning depending on the browser
+// (used by Leaflet internally to position its layers).
+function setPosition(el, point) {
+
+ /*eslint-disable */
+ el._leaflet_pos = point;
+ /* eslint-enable */
+
+ if (any3d) {
+ setTransform(el, point);
+ } else {
+ el.style.left = point.x + 'px';
+ el.style.top = point.y + 'px';
+ }
+}
+
+// @function getPosition(el: HTMLElement): Point
+// Returns the coordinates of an element previously positioned with setPosition.
+function getPosition(el) {
+ // this method is only used for elements previously positioned using setPosition,
+ // so it's safe to cache the position for performance
+
+ return el._leaflet_pos || new Point(0, 0);
+}
+
+// @function disableTextSelection()
+// Prevents the user from generating `selectstart` DOM events, usually generated
+// when the user drags the mouse through a page with text. Used internally
+// by Leaflet to override the behaviour of any click-and-drag interaction on
+// the map. Affects drag interactions on the whole document.
+
+// @function enableTextSelection()
+// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
+var disableTextSelection;
+var enableTextSelection;
+var _userSelect;
+if ('onselectstart' in document) {
+ disableTextSelection = function () {
+ on(window, 'selectstart', preventDefault);
+ };
+ enableTextSelection = function () {
+ off(window, 'selectstart', preventDefault);
+ };
+} else {
+ var userSelectProperty = testProp(
+ ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
+
+ disableTextSelection = function () {
+ if (userSelectProperty) {
+ var style = document.documentElement.style;
+ _userSelect = style[userSelectProperty];
+ style[userSelectProperty] = 'none';
+ }
+ };
+ enableTextSelection = function () {
+ if (userSelectProperty) {
+ document.documentElement.style[userSelectProperty] = _userSelect;
+ _userSelect = undefined;
+ }
+ };
+}
+
+// @function disableImageDrag()
+// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
+// for `dragstart` DOM events, usually generated when the user drags an image.
+function disableImageDrag() {
+ on(window, 'dragstart', preventDefault);
+}
+
+// @function enableImageDrag()
+// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
+function enableImageDrag() {
+ off(window, 'dragstart', preventDefault);
+}
+
+var _outlineElement;
+var _outlineStyle;
+// @function preventOutline(el: HTMLElement)
+// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
+// of the element `el` invisible. Used internally by Leaflet to prevent
+// focusable elements from displaying an outline when the user performs a
+// drag interaction on them.
+function preventOutline(element) {
+ while (element.tabIndex === -1) {
+ element = element.parentNode;
+ }
+ if (!element.style) { return; }
+ restoreOutline();
+ _outlineElement = element;
+ _outlineStyle = element.style.outline;
+ element.style.outline = 'none';
+ on(window, 'keydown', restoreOutline);
+}
+
+// @function restoreOutline()
+// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
+function restoreOutline() {
+ if (!_outlineElement) { return; }
+ _outlineElement.style.outline = _outlineStyle;
+ _outlineElement = undefined;
+ _outlineStyle = undefined;
+ off(window, 'keydown', restoreOutline);
+}
+
+// @function getSizedParentNode(el: HTMLElement): HTMLElement
+// Finds the closest parent node which size (width and height) is not null.
+function getSizedParentNode(element) {
+ do {
+ element = element.parentNode;
+ } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
+ return element;
+}
+
+// @function getScale(el: HTMLElement): Object
+// Computes the CSS scale currently applied on the element.
+// Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
+// and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
+function getScale(element) {
+ var rect = element.getBoundingClientRect(); // Read-only in old browsers.
+
+ return {
+ x: rect.width / element.offsetWidth || 1,
+ y: rect.height / element.offsetHeight || 1,
+ boundingClientRect: rect
+ };
+}
+
+
+var DomUtil = (Object.freeze || Object)({
+ TRANSFORM: TRANSFORM,
+ TRANSITION: TRANSITION,
+ TRANSITION_END: TRANSITION_END,
+ get: get,
+ getStyle: getStyle,
+ create: create$1,
+ remove: remove,
+ empty: empty,
+ toFront: toFront,
+ toBack: toBack,
+ hasClass: hasClass,
+ addClass: addClass,
+ removeClass: removeClass,
+ setClass: setClass,
+ getClass: getClass,
+ setOpacity: setOpacity,
+ testProp: testProp,
+ setTransform: setTransform,
+ setPosition: setPosition,
+ getPosition: getPosition,
+ disableTextSelection: disableTextSelection,
+ enableTextSelection: enableTextSelection,
+ disableImageDrag: disableImageDrag,
+ enableImageDrag: enableImageDrag,
+ preventOutline: preventOutline,
+ restoreOutline: restoreOutline,
+ getSizedParentNode: getSizedParentNode,
+ getScale: getScale
+});
+
+/*
+ * @namespace DomEvent
+ * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
+ */
+
+// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
+
+// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
+// Adds a listener function (`fn`) to a particular DOM event type of the
+// element `el`. You can optionally specify the context of the listener
+// (object the `this` keyword will point to). You can also pass several
+// space-separated types (e.g. `'click dblclick'`).
+
+// @alternative
+// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
+// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
+function on(obj, types, fn, context) {
+
+ if (typeof types === 'object') {
+ for (var type in types) {
+ addOne(obj, type, types[type], fn);
+ }
+ } else {
+ types = splitWords(types);
+
+ for (var i = 0, len = types.length; i < len; i++) {
+ addOne(obj, types[i], fn, context);
+ }
+ }
+
+ return this;
+}
+
+var eventsKey = '_leaflet_events';
+
+// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
+// Removes a previously added listener function.
+// Note that if you passed a custom context to on, you must pass the same
+// context to `off` in order to remove the listener.
+
+// @alternative
+// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
+// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
+function off(obj, types, fn, context) {
+
+ if (typeof types === 'object') {
+ for (var type in types) {
+ removeOne(obj, type, types[type], fn);
+ }
+ } else if (types) {
+ types = splitWords(types);
+
+ for (var i = 0, len = types.length; i < len; i++) {
+ removeOne(obj, types[i], fn, context);
+ }
+ } else {
+ for (var j in obj[eventsKey]) {
+ removeOne(obj, j, obj[eventsKey][j]);
+ }
+ delete obj[eventsKey];
+ }
+
+ return this;
+}
+
+function addOne(obj, type, fn, context) {
+ var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
+
+ if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
+
+ var handler = function (e) {
+ return fn.call(context || obj, e || window.event);
+ };
+
+ var originalHandler = handler;
+
+ if (pointer && type.indexOf('touch') === 0) {
+ // Needs DomEvent.Pointer.js
+ addPointerListener(obj, type, handler, id);
+
+ } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
+ !(pointer && chrome)) {
+ // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
+ // See #5180
+ addDoubleTapListener(obj, handler, id);
+
+ } else if ('addEventListener' in obj) {
+
+ if (type === 'mousewheel') {
+ obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false);
+
+ } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
+ handler = function (e) {
+ e = e || window.event;
+ if (isExternalTarget(obj, e)) {
+ originalHandler(e);
+ }
+ };
+ obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
+
+ } else {
+ if (type === 'click' && android) {
+ handler = function (e) {
+ filterClick(e, originalHandler);
+ };
+ }
+ obj.addEventListener(type, handler, false);
+ }
+
+ } else if ('attachEvent' in obj) {
+ obj.attachEvent('on' + type, handler);
+ }
+
+ obj[eventsKey] = obj[eventsKey] || {};
+ obj[eventsKey][id] = handler;
+}
+
+function removeOne(obj, type, fn, context) {
+
+ var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
+ handler = obj[eventsKey] && obj[eventsKey][id];
+
+ if (!handler) { return this; }
+
+ if (pointer && type.indexOf('touch') === 0) {
+ removePointerListener(obj, type, id);
+
+ } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
+ !(pointer && chrome)) {
+ removeDoubleTapListener(obj, id);
+
+ } else if ('removeEventListener' in obj) {
+
+ if (type === 'mousewheel') {
+ obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false);
+
+ } else {
+ obj.removeEventListener(
+ type === 'mouseenter' ? 'mouseover' :
+ type === 'mouseleave' ? 'mouseout' : type, handler, false);
+ }
+
+ } else if ('detachEvent' in obj) {
+ obj.detachEvent('on' + type, handler);
+ }
+
+ obj[eventsKey][id] = null;
+}
+
+// @function stopPropagation(ev: DOMEvent): this
+// Stop the given event from propagation to parent elements. Used inside the listener functions:
+// ```js
+// L.DomEvent.on(div, 'click', function (ev) {
+// L.DomEvent.stopPropagation(ev);
+// });
+// ```
+function stopPropagation(e) {
+
+ if (e.stopPropagation) {
+ e.stopPropagation();
+ } else if (e.originalEvent) { // In case of Leaflet event.
+ e.originalEvent._stopped = true;
+ } else {
+ e.cancelBubble = true;
+ }
+ skipped(e);
+
+ return this;
+}
+
+// @function disableScrollPropagation(el: HTMLElement): this
+// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
+function disableScrollPropagation(el) {
+ addOne(el, 'mousewheel', stopPropagation);
+ return this;
+}
+
+// @function disableClickPropagation(el: HTMLElement): this
+// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
+// `'mousedown'` and `'touchstart'` events (plus browser variants).
+function disableClickPropagation(el) {
+ on(el, 'mousedown touchstart dblclick', stopPropagation);
+ addOne(el, 'click', fakeStop);
+ return this;
+}
+
+// @function preventDefault(ev: DOMEvent): this
+// Prevents the default action of the DOM Event `ev` from happening (such as
+// following a link in the href of the a element, or doing a POST request
+// with page reload when a `<form>` is submitted).
+// Use it inside listener functions.
+function preventDefault(e) {
+ if (e.preventDefault) {
+ e.preventDefault();
+ } else {
+ e.returnValue = false;
+ }
+ return this;
+}
+
+// @function stop(ev: DOMEvent): this
+// Does `stopPropagation` and `preventDefault` at the same time.
+function stop(e) {
+ preventDefault(e);
+ stopPropagation(e);
+ return this;
+}
+
+// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
+// Gets normalized mouse position from a DOM event relative to the
+// `container` (border excluded) or to the whole page if not specified.
+function getMousePosition(e, container) {
+ if (!container) {
+ return new Point(e.clientX, e.clientY);
+ }
+
+ var scale = getScale(container),
+ offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
+
+ return new Point(
+ // offset.left/top values are in page scale (like clientX/Y),
+ // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
+ (e.clientX - offset.left) / scale.x - container.clientLeft,
+ (e.clientY - offset.top) / scale.y - container.clientTop
+ );
+}
+
+// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
+// and Firefox scrolls device pixels, not CSS pixels
+var wheelPxFactor =
+ (win && chrome) ? 2 * window.devicePixelRatio :
+ gecko ? window.devicePixelRatio : 1;
+
+// @function getWheelDelta(ev: DOMEvent): Number
+// Gets normalized wheel delta from a mousewheel DOM event, in vertical
+// pixels scrolled (negative if scrolling down).
+// Events from pointing devices without precise scrolling are mapped to
+// a best guess of 60 pixels.
+function getWheelDelta(e) {
+ return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
+ (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
+ (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
+ (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
+ (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events
+ e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
+ (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
+ e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
+ 0;
+}
+
+var skipEvents = {};
+
+function fakeStop(e) {
+ // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
+ skipEvents[e.type] = true;
+}
+
+function skipped(e) {
+ var events = skipEvents[e.type];
+ // reset when checking, as it's only used in map container and propagates outside of the map
+ skipEvents[e.type] = false;
+ return events;
+}
+
+// check if element really left/entered the event target (for mouseenter/mouseleave)
+function isExternalTarget(el, e) {
+
+ var related = e.relatedTarget;
+
+ if (!related) { return true; }
+
+ try {
+ while (related && (related !== el)) {
+ related = related.parentNode;
+ }
+ } catch (err) {
+ return false;
+ }
+ return (related !== el);
+}
+
+var lastClick;
+
+// this is a horrible workaround for a bug in Android where a single touch triggers two click events
+function filterClick(e, handler) {
+ var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
+ elapsed = lastClick && (timeStamp - lastClick);
+
+ // are they closer together than 500ms yet more than 100ms?
+ // Android typically triggers them ~300ms apart while multiple listeners
+ // on the same event should be triggered far faster;
+ // or check if click is simulated on the element, and if it is, reject any non-simulated events
+
+ if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
+ stop(e);
+ return;
+ }
+ lastClick = timeStamp;
+
+ handler(e);
+}
+
+
+
+
+var DomEvent = (Object.freeze || Object)({
+ on: on,
+ off: off,
+ stopPropagation: stopPropagation,
+ disableScrollPropagation: disableScrollPropagation,
+ disableClickPropagation: disableClickPropagation,
+ preventDefault: preventDefault,
+ stop: stop,
+ getMousePosition: getMousePosition,
+ getWheelDelta: getWheelDelta,
+ fakeStop: fakeStop,
+ skipped: skipped,
+ isExternalTarget: isExternalTarget,
+ addListener: on,
+ removeListener: off
+});
+
+/*
+ * @class PosAnimation
+ * @aka L.PosAnimation
+ * @inherits Evented
+ * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
+ *
+ * @example
+ * ```js
+ * var fx = new L.PosAnimation();
+ * fx.run(el, [300, 500], 0.5);
+ * ```
+ *
+ * @constructor L.PosAnimation()
+ * Creates a `PosAnimation` object.
+ *
+ */
+
+var PosAnimation = Evented.extend({
+
+ // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
+ // Run an animation of a given element to a new position, optionally setting
+ // duration in seconds (`0.25` by default) and easing linearity factor (3rd
+ // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
+ // `0.5` by default).
+ run: function (el, newPos, duration, easeLinearity) {
+ this.stop();
+
+ this._el = el;
+ this._inProgress = true;
+ this._duration = duration || 0.25;
+ this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
+
+ this._startPos = getPosition(el);
+ this._offset = newPos.subtract(this._startPos);
+ this._startTime = +new Date();
+
+ // @event start: Event
+ // Fired when the animation starts
+ this.fire('start');
+
+ this._animate();
+ },
+
+ // @method stop()
+ // Stops the animation (if currently running).
+ stop: function () {
+ if (!this._inProgress) { return; }
+
+ this._step(true);
+ this._complete();
+ },
+
+ _animate: function () {
+ // animation loop
+ this._animId = requestAnimFrame(this._animate, this);
+ this._step();
+ },
+
+ _step: function (round) {
+ var elapsed = (+new Date()) - this._startTime,
+ duration = this._duration * 1000;
+
+ if (elapsed < duration) {
+ this._runFrame(this._easeOut(elapsed / duration), round);
+ } else {
+ this._runFrame(1);
+ this._complete();
+ }
+ },
+
+ _runFrame: function (progress, round) {
+ var pos = this._startPos.add(this._offset.multiplyBy(progress));
+ if (round) {
+ pos._round();
+ }
+ setPosition(this._el, pos);
+
+ // @event step: Event
+ // Fired continuously during the animation.
+ this.fire('step');
+ },
+
+ _complete: function () {
+ cancelAnimFrame(this._animId);
+
+ this._inProgress = false;
+ // @event end: Event
+ // Fired when the animation ends.
+ this.fire('end');
+ },
+
+ _easeOut: function (t) {
+ return 1 - Math.pow(1 - t, this._easeOutPower);
+ }
+});
+
+/*
+ * @class Map
+ * @aka L.Map
+ * @inherits Evented
+ *
+ * The central class of the API — it is used to create a map on a page and manipulate it.
+ *
+ * @example
+ *
+ * ```js
+ * // initialize the map on the "map" div with a given center and zoom
+ * var map = L.map('map', {
+ * center: [51.505, -0.09],
+ * zoom: 13
+ * });
+ * ```
+ *
+ */
+
+var Map = Evented.extend({
+
+ options: {
+ // @section Map State Options
+ // @option crs: CRS = L.CRS.EPSG3857
+ // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
+ // sure what it means.
+ crs: EPSG3857,
+
+ // @option center: LatLng = undefined
+ // Initial geographic center of the map
+ center: undefined,
+
+ // @option zoom: Number = undefined
+ // Initial map zoom level
+ zoom: undefined,
+
+ // @option minZoom: Number = *
+ // Minimum zoom level of the map.
+ // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
+ // the lowest of their `minZoom` options will be used instead.
+ minZoom: undefined,
+
+ // @option maxZoom: Number = *
+ // Maximum zoom level of the map.
+ // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
+ // the highest of their `maxZoom` options will be used instead.
+ maxZoom: undefined,
+
+ // @option layers: Layer[] = []
+ // Array of layers that will be added to the map initially
+ layers: [],
+
+ // @option maxBounds: LatLngBounds = null
+ // When this option is set, the map restricts the view to the given
+ // geographical bounds, bouncing the user back if the user tries to pan
+ // outside the view. To set the restriction dynamically, use
+ // [`setMaxBounds`](#map-setmaxbounds) method.
+ maxBounds: undefined,
+
+ // @option renderer: Renderer = *
+ // The default method for drawing vector layers on the map. `L.SVG`
+ // or `L.Canvas` by default depending on browser support.
+ renderer: undefined,
+
+
+ // @section Animation Options
+ // @option zoomAnimation: Boolean = true
+ // Whether the map zoom animation is enabled. By default it's enabled
+ // in all browsers that support CSS3 Transitions except Android.
+ zoomAnimation: true,
+
+ // @option zoomAnimationThreshold: Number = 4
+ // Won't animate zoom if the zoom difference exceeds this value.
+ zoomAnimationThreshold: 4,
+
+ // @option fadeAnimation: Boolean = true
+ // Whether the tile fade animation is enabled. By default it's enabled
+ // in all browsers that support CSS3 Transitions except Android.
+ fadeAnimation: true,
+
+ // @option markerZoomAnimation: Boolean = true
+ // Whether markers animate their zoom with the zoom animation, if disabled
+ // they will disappear for the length of the animation. By default it's
+ // enabled in all browsers that support CSS3 Transitions except Android.
+ markerZoomAnimation: true,
+
+ // @option transform3DLimit: Number = 2^23
+ // Defines the maximum size of a CSS translation transform. The default
+ // value should not be changed unless a web browser positions layers in
+ // the wrong place after doing a large `panBy`.
+ transform3DLimit: 8388608, // Precision limit of a 32-bit float
+
+ // @section Interaction Options
+ // @option zoomSnap: Number = 1
+ // Forces the map's zoom level to always be a multiple of this, particularly
+ // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
+ // By default, the zoom level snaps to the nearest integer; lower values
+ // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
+ // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
+ zoomSnap: 1,
+
+ // @option zoomDelta: Number = 1
+ // Controls how much the map's zoom level will change after a
+ // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
+ // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
+ // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
+ zoomDelta: 1,
+
+ // @option trackResize: Boolean = true
+ // Whether the map automatically handles browser window resize to update itself.
+ trackResize: true
+ },
+
+ initialize: function (id, options) { // (HTMLElement or String, Object)
+ options = setOptions(this, options);
+
+ // Make sure to assign internal flags at the beginning,
+ // to avoid inconsistent state in some edge cases.
+ this._handlers = [];
+ this._layers = {};
+ this._zoomBoundLayers = {};
+ this._sizeChanged = true;
+
+ this._initContainer(id);
+ this._initLayout();
+
+ // hack for https://github.com/Leaflet/Leaflet/issues/1980
+ this._onResize = bind(this._onResize, this);
+
+ this._initEvents();
+
+ if (options.maxBounds) {
+ this.setMaxBounds(options.maxBounds);
+ }
+
+ if (options.zoom !== undefined) {
+ this._zoom = this._limitZoom(options.zoom);
+ }
+
+ if (options.center && options.zoom !== undefined) {
+ this.setView(toLatLng(options.center), options.zoom, {reset: true});
+ }
+
+ this.callInitHooks();
+
+ // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
+ this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
+ this.options.zoomAnimation;
+
+ // zoom transitions run with the same duration for all layers, so if one of transitionend events
+ // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
+ if (this._zoomAnimated) {
+ this._createAnimProxy();
+ on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
+ }
+
+ this._addLayers(this.options.layers);
+ },
+
+
+ // @section Methods for modifying map state
+
+ // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
+ // Sets the view of the map (geographical center and zoom) with the given
+ // animation options.
+ setView: function (center, zoom, options) {
+
+ zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
+ center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
+ options = options || {};
+
+ this._stop();
+
+ if (this._loaded && !options.reset && options !== true) {
+
+ if (options.animate !== undefined) {
+ options.zoom = extend({animate: options.animate}, options.zoom);
+ options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
+ }
+
+ // try animating pan or zoom
+ var moved = (this._zoom !== zoom) ?
+ this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
+ this._tryAnimatedPan(center, options.pan);
+
+ if (moved) {
+ // prevent resize handler call, the view will refresh after animation anyway
+ clearTimeout(this._sizeTimer);
+ return this;
+ }
+ }
+
+ // animation didn't start, just reset the map view
+ this._resetView(center, zoom);
+
+ return this;
+ },
+
+ // @method setZoom(zoom: Number, options?: Zoom/pan options): this
+ // Sets the zoom of the map.
+ setZoom: function (zoom, options) {
+ if (!this._loaded) {
+ this._zoom = zoom;
+ return this;
+ }
+ return this.setView(this.getCenter(), zoom, {zoom: options});
+ },
+
+ // @method zoomIn(delta?: Number, options?: Zoom options): this
+ // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
+ zoomIn: function (delta, options) {
+ delta = delta || (any3d ? this.options.zoomDelta : 1);
+ return this.setZoom(this._zoom + delta, options);
+ },
+
+ // @method zoomOut(delta?: Number, options?: Zoom options): this
+ // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
+ zoomOut: function (delta, options) {
+ delta = delta || (any3d ? this.options.zoomDelta : 1);
+ return this.setZoom(this._zoom - delta, options);
+ },
+
+ // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
+ // Zooms the map while keeping a specified geographical point on the map
+ // stationary (e.g. used internally for scroll zoom and double-click zoom).
+ // @alternative
+ // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
+ // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
+ setZoomAround: function (latlng, zoom, options) {
+ var scale = this.getZoomScale(zoom),
+ viewHalf = this.getSize().divideBy(2),
+ containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
+
+ centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
+ newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
+
+ return this.setView(newCenter, zoom, {zoom: options});
+ },
+
+ _getBoundsCenterZoom: function (bounds, options) {
+
+ options = options || {};
+ bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
+
+ var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
+ paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
+
+ zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
+
+ zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
+
+ if (zoom === Infinity) {
+ return {
+ center: bounds.getCenter(),
+ zoom: zoom
+ };
+ }
+
+ var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
+
+ swPoint = this.project(bounds.getSouthWest(), zoom),
+ nePoint = this.project(bounds.getNorthEast(), zoom),
+ center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
+
+ return {
+ center: center,
+ zoom: zoom
+ };
+ },
+
+ // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
+ // Sets a map view that contains the given geographical bounds with the
+ // maximum zoom level possible.
+ fitBounds: function (bounds, options) {
+
+ bounds = toLatLngBounds(bounds);
+
+ if (!bounds.isValid()) {
+ throw new Error('Bounds are not valid.');
+ }
+
+ var target = this._getBoundsCenterZoom(bounds, options);
+ return this.setView(target.center, target.zoom, options);
+ },
+
+ // @method fitWorld(options?: fitBounds options): this
+ // Sets a map view that mostly contains the whole world with the maximum
+ // zoom level possible.
+ fitWorld: function (options) {
+ return this.fitBounds([[-90, -180], [90, 180]], options);
+ },
+
+ // @method panTo(latlng: LatLng, options?: Pan options): this
+ // Pans the map to a given center.
+ panTo: function (center, options) { // (LatLng)
+ return this.setView(center, this._zoom, {pan: options});
+ },
+
+ // @method panBy(offset: Point, options?: Pan options): this
+ // Pans the map by a given number of pixels (animated).
+ panBy: function (offset, options) {
+ offset = toPoint(offset).round();
+ options = options || {};
+
+ if (!offset.x && !offset.y) {
+ return this.fire('moveend');
+ }
+ // If we pan too far, Chrome gets issues with tiles
+ // and makes them disappear or appear in the wrong place (slightly offset) #2602
+ if (options.animate !== true && !this.getSize().contains(offset)) {
+ this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
+ return this;
+ }
+
+ if (!this._panAnim) {
+ this._panAnim = new PosAnimation();
+
+ this._panAnim.on({
+ 'step': this._onPanTransitionStep,
+ 'end': this._onPanTransitionEnd
+ }, this);
+ }
+
+ // don't fire movestart if animating inertia
+ if (!options.noMoveStart) {
+ this.fire('movestart');
+ }
+
+ // animate pan unless animate: false specified
+ if (options.animate !== false) {
+ addClass(this._mapPane, 'leaflet-pan-anim');
+
+ var newPos = this._getMapPanePos().subtract(offset).round();
+ this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
+ } else {
+ this._rawPanBy(offset);
+ this.fire('move').fire('moveend');
+ }
+
+ return this;
+ },
+
+ // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
+ // Sets the view of the map (geographical center and zoom) performing a smooth
+ // pan-zoom animation.
+ flyTo: function (targetCenter, targetZoom, options) {
+
+ options = options || {};
+ if (options.animate === false || !any3d) {
+ return this.setView(targetCenter, targetZoom, options);
+ }
+
+ this._stop();
+
+ var from = this.project(this.getCenter()),
+ to = this.project(targetCenter),
+ size = this.getSize(),
+ startZoom = this._zoom;
+
+ targetCenter = toLatLng(targetCenter);
+ targetZoom = targetZoom === undefined ? startZoom : targetZoom;
+
+ var w0 = Math.max(size.x, size.y),
+ w1 = w0 * this.getZoomScale(startZoom, targetZoom),
+ u1 = (to.distanceTo(from)) || 1,
+ rho = 1.42,
+ rho2 = rho * rho;
+
+ function r(i) {
+ var s1 = i ? -1 : 1,
+ s2 = i ? w1 : w0,
+ t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
+ b1 = 2 * s2 * rho2 * u1,
+ b = t1 / b1,
+ sq = Math.sqrt(b * b + 1) - b;
+
+ // workaround for floating point precision bug when sq = 0, log = -Infinite,
+ // thus triggering an infinite loop in flyTo
+ var log = sq < 0.000000001 ? -18 : Math.log(sq);
+
+ return log;
+ }
+
+ function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
+ function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
+ function tanh(n) { return sinh(n) / cosh(n); }
+
+ var r0 = r(0);
+
+ function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
+ function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
+
+ function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
+
+ var start = Date.now(),
+ S = (r(1) - r0) / rho,
+ duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
+
+ function frame() {
+ var t = (Date.now() - start) / duration,
+ s = easeOut(t) * S;
+
+ if (t <= 1) {
+ this._flyToFrame = requestAnimFrame(frame, this);
+
+ this._move(
+ this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
+ this.getScaleZoom(w0 / w(s), startZoom),
+ {flyTo: true});
+
+ } else {
+ this
+ ._move(targetCenter, targetZoom)
+ ._moveEnd(true);
+ }
+ }
+
+ this._moveStart(true, options.noMoveStart);
+
+ frame.call(this);
+ return this;
+ },
+
+ // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
+ // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
+ // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
+ flyToBounds: function (bounds, options) {
+ var target = this._getBoundsCenterZoom(bounds, options);
+ return this.flyTo(target.center, target.zoom, options);
+ },
+
+ // @method setMaxBounds(bounds: Bounds): this
+ // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
+ setMaxBounds: function (bounds) {
+ bounds = toLatLngBounds(bounds);
+
+ if (!bounds.isValid()) {
+ this.options.maxBounds = null;
+ return this.off('moveend', this._panInsideMaxBounds);
+ } else if (this.options.maxBounds) {
+ this.off('moveend', this._panInsideMaxBounds);
+ }
+
+ this.options.maxBounds = bounds;
+
+ if (this._loaded) {
+ this._panInsideMaxBounds();
+ }
+
+ return this.on('moveend', this._panInsideMaxBounds);
+ },
+
+ // @method setMinZoom(zoom: Number): this
+ // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
+ setMinZoom: function (zoom) {
+ var oldZoom = this.options.minZoom;
+ this.options.minZoom = zoom;
+
+ if (this._loaded && oldZoom !== zoom) {
+ this.fire('zoomlevelschange');
+
+ if (this.getZoom() < this.options.minZoom) {
+ return this.setZoom(zoom);
+ }
+ }
+
+ return this;
+ },
+
+ // @method setMaxZoom(zoom: Number): this
+ // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
+ setMaxZoom: function (zoom) {
+ var oldZoom = this.options.maxZoom;
+ this.options.maxZoom = zoom;
+
+ if (this._loaded && oldZoom !== zoom) {
+ this.fire('zoomlevelschange');
+
+ if (this.getZoom() > this.options.maxZoom) {
+ return this.setZoom(zoom);
+ }
+ }
+
+ return this;
+ },
+
+ // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
+ // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
+ panInsideBounds: function (bounds, options) {
+ this._enforcingBounds = true;
+ var center = this.getCenter(),
+ newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
+
+ if (!center.equals(newCenter)) {
+ this.panTo(newCenter, options);
+ }
+
+ this._enforcingBounds = false;
+ return this;
+ },
+
+ // @method panInside(latlng: LatLng, options?: options): this
+ // Pans the map the minimum amount to make the `latlng` visible. Use
+ // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit
+ // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds).
+ // If `latlng` is already within the (optionally padded) display bounds,
+ // the map will not be panned.
+ panInside: function (latlng, options) {
+ options = options || {};
+
+ var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
+ paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
+ center = this.getCenter(),
+ pixelCenter = this.project(center),
+ pixelPoint = this.project(latlng),
+ pixelBounds = this.getPixelBounds(),
+ halfPixelBounds = pixelBounds.getSize().divideBy(2),
+ paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]);
+
+ if (!paddedBounds.contains(pixelPoint)) {
+ this._enforcingBounds = true;
+ var diff = pixelCenter.subtract(pixelPoint),
+ newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y);
+
+ if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) {
+ newCenter.x = pixelCenter.x - diff.x;
+ if (diff.x > 0) {
+ newCenter.x += halfPixelBounds.x - paddingTL.x;
+ } else {
+ newCenter.x -= halfPixelBounds.x - paddingBR.x;
+ }
+ }
+ if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) {
+ newCenter.y = pixelCenter.y - diff.y;
+ if (diff.y > 0) {
+ newCenter.y += halfPixelBounds.y - paddingTL.y;
+ } else {
+ newCenter.y -= halfPixelBounds.y - paddingBR.y;
+ }
+ }
+ this.panTo(this.unproject(newCenter), options);
+ this._enforcingBounds = false;
+ }
+ return this;
+ },
+
+ // @method invalidateSize(options: Zoom/pan options): this
+ // Checks if the map container size changed and updates the map if so —
+ // call it after you've changed the map size dynamically, also animating
+ // pan by default. If `options.pan` is `false`, panning will not occur.
+ // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
+ // that it doesn't happen often even if the method is called many
+ // times in a row.
+
+ // @alternative
+ // @method invalidateSize(animate: Boolean): this
+ // Checks if the map container size changed and updates the map if so —
+ // call it after you've changed the map size dynamically, also animating
+ // pan by default.
+ invalidateSize: function (options) {
+ if (!this._loaded) { return this; }
+
+ options = extend({
+ animate: false,
+ pan: true
+ }, options === true ? {animate: true} : options);
+
+ var oldSize = this.getSize();
+ this._sizeChanged = true;
+ this._lastCenter = null;
+
+ var newSize = this.getSize(),
+ oldCenter = oldSize.divideBy(2).round(),
+ newCenter = newSize.divideBy(2).round(),
+ offset = oldCenter.subtract(newCenter);
+
+ if (!offset.x && !offset.y) { return this; }
+
+ if (options.animate && options.pan) {
+ this.panBy(offset);
+
+ } else {
+ if (options.pan) {
+ this._rawPanBy(offset);
+ }
+
+ this.fire('move');
+
+ if (options.debounceMoveend) {
+ clearTimeout(this._sizeTimer);
+ this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
+ } else {
+ this.fire('moveend');
+ }
+ }
+
+ // @section Map state change events
+ // @event resize: ResizeEvent
+ // Fired when the map is resized.
+ return this.fire('resize', {
+ oldSize: oldSize,
+ newSize: newSize
+ });
+ },
+
+ // @section Methods for modifying map state
+ // @method stop(): this
+ // Stops the currently running `panTo` or `flyTo` animation, if any.
+ stop: function () {
+ this.setZoom(this._limitZoom(this._zoom));
+ if (!this.options.zoomSnap) {
+ this.fire('viewreset');
+ }
+ return this._stop();
+ },
+
+ // @section Geolocation methods
+ // @method locate(options?: Locate options): this
+ // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
+ // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
+ // and optionally sets the map view to the user's location with respect to
+ // detection accuracy (or to the world view if geolocation failed).
+ // Note that, if your page doesn't use HTTPS, this method will fail in
+ // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
+ // See `Locate options` for more details.
+ locate: function (options) {
+
+ options = this._locateOptions = extend({
+ timeout: 10000,
+ watch: false
+ // setView: false
+ // maxZoom: <Number>
+ // maximumAge: 0
+ // enableHighAccuracy: false
+ }, options);
+
+ if (!('geolocation' in navigator)) {
+ this._handleGeolocationError({
+ code: 0,
+ message: 'Geolocation not supported.'
+ });
+ return this;
+ }
+
+ var onResponse = bind(this._handleGeolocationResponse, this),
+ onError = bind(this._handleGeolocationError, this);
+
+ if (options.watch) {
+ this._locationWatchId =
+ navigator.geolocation.watchPosition(onResponse, onError, options);
+ } else {
+ navigator.geolocation.getCurrentPosition(onResponse, onError, options);
+ }
+ return this;
+ },
+
+ // @method stopLocate(): this
+ // Stops watching location previously initiated by `map.locate({watch: true})`
+ // and aborts resetting the map view if map.locate was called with
+ // `{setView: true}`.
+ stopLocate: function () {
+ if (navigator.geolocation && navigator.geolocation.clearWatch) {
+ navigator.geolocation.clearWatch(this._locationWatchId);
+ }
+ if (this._locateOptions) {
+ this._locateOptions.setView = false;
+ }
+ return this;
+ },
+
+ _handleGeolocationError: function (error) {
+ var c = error.code,
+ message = error.message ||
+ (c === 1 ? 'permission denied' :
+ (c === 2 ? 'position unavailable' : 'timeout'));
+
+ if (this._locateOptions.setView && !this._loaded) {
+ this.fitWorld();
+ }
+
+ // @section Location events
+ // @event locationerror: ErrorEvent
+ // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
+ this.fire('locationerror', {
+ code: c,
+ message: 'Geolocation error: ' + message + '.'
+ });
+ },
+
+ _handleGeolocationResponse: function (pos) {
+ var lat = pos.coords.latitude,
+ lng = pos.coords.longitude,
+ latlng = new LatLng(lat, lng),
+ bounds = latlng.toBounds(pos.coords.accuracy * 2),
+ options = this._locateOptions;
+
+ if (options.setView) {
+ var zoom = this.getBoundsZoom(bounds);
+ this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
+ }
+
+ var data = {
+ latlng: latlng,
+ bounds: bounds,
+ timestamp: pos.timestamp
+ };
+
+ for (var i in pos.coords) {
+ if (typeof pos.coords[i] === 'number') {
+ data[i] = pos.coords[i];
+ }
+ }
+
+ // @event locationfound: LocationEvent
+ // Fired when geolocation (using the [`locate`](#map-locate) method)
+ // went successfully.
+ this.fire('locationfound', data);
+ },
+
+ // TODO Appropriate docs section?
+ // @section Other Methods
+ // @method addHandler(name: String, HandlerClass: Function): this
+ // Adds a new `Handler` to the map, given its name and constructor function.
+ addHandler: function (name, HandlerClass) {
+ if (!HandlerClass) { return this; }
+
+ var handler = this[name] = new HandlerClass(this);
+
+ this._handlers.push(handler);
+
+ if (this.options[name]) {
+ handler.enable();
+ }
+
+ return this;
+ },
+
+ // @method remove(): this
+ // Destroys the map and clears all related event listeners.
+ remove: function () {
+
+ this._initEvents(true);
+
+ if (this._containerId !== this._container._leaflet_id) {
+ throw new Error('Map container is being reused by another instance');
+ }
+
+ try {
+ // throws error in IE6-8
+ delete this._container._leaflet_id;
+ delete this._containerId;
+ } catch (e) {
+ /*eslint-disable */
+ this._container._leaflet_id = undefined;
+ /* eslint-enable */
+ this._containerId = undefined;
+ }
+
+ if (this._locationWatchId !== undefined) {
+ this.stopLocate();
+ }
+
+ this._stop();
+
+ remove(this._mapPane);
+
+ if (this._clearControlPos) {
+ this._clearControlPos();
+ }
+ if (this._resizeRequest) {
+ cancelAnimFrame(this._resizeRequest);
+ this._resizeRequest = null;
+ }
+
+ this._clearHandlers();
+
+ if (this._loaded) {
+ // @section Map state change events
+ // @event unload: Event
+ // Fired when the map is destroyed with [remove](#map-remove) method.
+ this.fire('unload');
+ }
+
+ var i;
+ for (i in this._layers) {
+ this._layers[i].remove();
+ }
+ for (i in this._panes) {
+ remove(this._panes[i]);
+ }
+
+ this._layers = [];
+ this._panes = [];
+ delete this._mapPane;
+ delete this._renderer;
+
+ return this;
+ },
+
+ // @section Other Methods
+ // @method createPane(name: String, container?: HTMLElement): HTMLElement
+ // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
+ // then returns it. The pane is created as a child of `container`, or
+ // as a child of the main map pane if not set.
+ createPane: function (name, container) {
+ var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
+ pane = create$1('div', className, container || this._mapPane);
+
+ if (name) {
+ this._panes[name] = pane;
+ }
+ return pane;
+ },
+
+ // @section Methods for Getting Map State
+
+ // @method getCenter(): LatLng
+ // Returns the geographical center of the map view
+ getCenter: function () {
+ this._checkIfLoaded();
+
+ if (this._lastCenter && !this._moved()) {
+ return this._lastCenter;
+ }
+ return this.layerPointToLatLng(this._getCenterLayerPoint());
+ },
+
+ // @method getZoom(): Number
+ // Returns the current zoom level of the map view
+ getZoom: function () {
+ return this._zoom;
+ },
+
+ // @method getBounds(): LatLngBounds
+ // Returns the geographical bounds visible in the current map view
+ getBounds: function () {
+ var bounds = this.getPixelBounds(),
+ sw = this.unproject(bounds.getBottomLeft()),
+ ne = this.unproject(bounds.getTopRight());
+
+ return new LatLngBounds(sw, ne);
+ },
+
+ // @method getMinZoom(): Number
+ // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
+ getMinZoom: function () {
+ return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
+ },
+
+ // @method getMaxZoom(): Number
+ // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
+ getMaxZoom: function () {
+ return this.options.maxZoom === undefined ?
+ (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
+ this.options.maxZoom;
+ },
+
+ // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
+ // Returns the maximum zoom level on which the given bounds fit to the map
+ // view in its entirety. If `inside` (optional) is set to `true`, the method
+ // instead returns the minimum zoom level on which the map view fits into
+ // the given bounds in its entirety.
+ getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
+ bounds = toLatLngBounds(bounds);
+ padding = toPoint(padding || [0, 0]);
+
+ var zoom = this.getZoom() || 0,
+ min = this.getMinZoom(),
+ max = this.getMaxZoom(),
+ nw = bounds.getNorthWest(),
+ se = bounds.getSouthEast(),
+ size = this.getSize().subtract(padding),
+ boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
+ snap = any3d ? this.options.zoomSnap : 1,
+ scalex = size.x / boundsSize.x,
+ scaley = size.y / boundsSize.y,
+ scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
+
+ zoom = this.getScaleZoom(scale, zoom);
+
+ if (snap) {
+ zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
+ zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
+ }
+
+ return Math.max(min, Math.min(max, zoom));
+ },
+
+ // @method getSize(): Point
+ // Returns the current size of the map container (in pixels).
+ getSize: function () {
+ if (!this._size || this._sizeChanged) {
+ this._size = new Point(
+ this._container.clientWidth || 0,
+ this._container.clientHeight || 0);
+
+ this._sizeChanged = false;
+ }
+ return this._size.clone();
+ },
+
+ // @method getPixelBounds(): Bounds
+ // Returns the bounds of the current map view in projected pixel
+ // coordinates (sometimes useful in layer and overlay implementations).
+ getPixelBounds: function (center, zoom) {
+ var topLeftPoint = this._getTopLeftPoint(center, zoom);
+ return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
+ },
+
+ // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
+ // the map pane? "left point of the map layer" can be confusing, specially
+ // since there can be negative offsets.
+ // @method getPixelOrigin(): Point
+ // Returns the projected pixel coordinates of the top left point of
+ // the map layer (useful in custom layer and overlay implementations).
+ getPixelOrigin: function () {
+ this._checkIfLoaded();
+ return this._pixelOrigin;
+ },
+
+ // @method getPixelWorldBounds(zoom?: Number): Bounds
+ // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
+ // If `zoom` is omitted, the map's current zoom level is used.
+ getPixelWorldBounds: function (zoom) {
+ return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
+ },
+
+ // @section Other Methods
+
+ // @method getPane(pane: String|HTMLElement): HTMLElement
+ // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
+ getPane: function (pane) {
+ return typeof pane === 'string' ? this._panes[pane] : pane;
+ },
+
+ // @method getPanes(): Object
+ // Returns a plain object containing the names of all [panes](#map-pane) as keys and
+ // the panes as values.
+ getPanes: function () {
+ return this._panes;
+ },
+
+ // @method getContainer: HTMLElement
+ // Returns the HTML element that contains the map.
+ getContainer: function () {
+ return this._container;
+ },
+
+
+ // @section Conversion Methods
+
+ // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
+ // Returns the scale factor to be applied to a map transition from zoom level
+ // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
+ getZoomScale: function (toZoom, fromZoom) {
+ // TODO replace with universal implementation after refactoring projections
+ var crs = this.options.crs;
+ fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
+ return crs.scale(toZoom) / crs.scale(fromZoom);
+ },
+
+ // @method getScaleZoom(scale: Number, fromZoom: Number): Number
+ // Returns the zoom level that the map would end up at, if it is at `fromZoom`
+ // level and everything is scaled by a factor of `scale`. Inverse of
+ // [`getZoomScale`](#map-getZoomScale).
+ getScaleZoom: function (scale, fromZoom) {
+ var crs = this.options.crs;
+ fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
+ var zoom = crs.zoom(scale * crs.scale(fromZoom));
+ return isNaN(zoom) ? Infinity : zoom;
+ },
+
+ // @method project(latlng: LatLng, zoom: Number): Point
+ // Projects a geographical coordinate `LatLng` according to the projection
+ // of the map's CRS, then scales it according to `zoom` and the CRS's
+ // `Transformation`. The result is pixel coordinate relative to
+ // the CRS origin.
+ project: function (latlng, zoom) {
+ zoom = zoom === undefined ? this._zoom : zoom;
+ return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
+ },
+
+ // @method unproject(point: Point, zoom: Number): LatLng
+ // Inverse of [`project`](#map-project).
+ unproject: function (point, zoom) {
+ zoom = zoom === undefined ? this._zoom : zoom;
+ return this.options.crs.pointToLatLng(toPoint(point), zoom);
+ },
+
+ // @method layerPointToLatLng(point: Point): LatLng
+ // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
+ // returns the corresponding geographical coordinate (for the current zoom level).
+ layerPointToLatLng: function (point) {
+ var projectedPoint = toPoint(point).add(this.getPixelOrigin());
+ return this.unproject(projectedPoint);
+ },
+
+ // @method latLngToLayerPoint(latlng: LatLng): Point
+ // Given a geographical coordinate, returns the corresponding pixel coordinate
+ // relative to the [origin pixel](#map-getpixelorigin).
+ latLngToLayerPoint: function (latlng) {
+ var projectedPoint = this.project(toLatLng(latlng))._round();
+ return projectedPoint._subtract(this.getPixelOrigin());
+ },
+
+ // @method wrapLatLng(latlng: LatLng): LatLng
+ // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
+ // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
+ // CRS's bounds.
+ // By default this means longitude is wrapped around the dateline so its
+ // value is between -180 and +180 degrees.
+ wrapLatLng: function (latlng) {
+ return this.options.crs.wrapLatLng(toLatLng(latlng));
+ },
+
+ // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
+ // Returns a `LatLngBounds` with the same size as the given one, ensuring that
+ // its center is within the CRS's bounds.
+ // By default this means the center longitude is wrapped around the dateline so its
+ // value is between -180 and +180 degrees, and the majority of the bounds
+ // overlaps the CRS's bounds.
+ wrapLatLngBounds: function (latlng) {
+ return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
+ },
+
+ // @method distance(latlng1: LatLng, latlng2: LatLng): Number
+ // Returns the distance between two geographical coordinates according to
+ // the map's CRS. By default this measures distance in meters.
+ distance: function (latlng1, latlng2) {
+ return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
+ },
+
+ // @method containerPointToLayerPoint(point: Point): Point
+ // Given a pixel coordinate relative to the map container, returns the corresponding
+ // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
+ containerPointToLayerPoint: function (point) { // (Point)
+ return toPoint(point).subtract(this._getMapPanePos());
+ },
+
+ // @method layerPointToContainerPoint(point: Point): Point
+ // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
+ // returns the corresponding pixel coordinate relative to the map container.
+ layerPointToContainerPoint: function (point) { // (Point)
+ return toPoint(point).add(this._getMapPanePos());
+ },
+
+ // @method containerPointToLatLng(point: Point): LatLng
+ // Given a pixel coordinate relative to the map container, returns
+ // the corresponding geographical coordinate (for the current zoom level).
+ containerPointToLatLng: function (point) {
+ var layerPoint = this.containerPointToLayerPoint(toPoint(point));
+ return this.layerPointToLatLng(layerPoint);
+ },
+
+ // @method latLngToContainerPoint(latlng: LatLng): Point
+ // Given a geographical coordinate, returns the corresponding pixel coordinate
+ // relative to the map container.
+ latLngToContainerPoint: function (latlng) {
+ return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
+ },
+
+ // @method mouseEventToContainerPoint(ev: MouseEvent): Point
+ // Given a MouseEvent object, returns the pixel coordinate relative to the
+ // map container where the event took place.
+ mouseEventToContainerPoint: function (e) {
+ return getMousePosition(e, this._container);
+ },
+
+ // @method mouseEventToLayerPoint(ev: MouseEvent): Point
+ // Given a MouseEvent object, returns the pixel coordinate relative to
+ // the [origin pixel](#map-getpixelorigin) where the event took place.
+ mouseEventToLayerPoint: function (e) {
+ return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
+ },
+
+ // @method mouseEventToLatLng(ev: MouseEvent): LatLng
+ // Given a MouseEvent object, returns geographical coordinate where the
+ // event took place.
+ mouseEventToLatLng: function (e) { // (MouseEvent)
+ return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
+ },
+
+
+ // map initialization methods
+
+ _initContainer: function (id) {
+ var container = this._container = get(id);
+
+ if (!container) {
+ throw new Error('Map container not found.');
+ } else if (container._leaflet_id) {
+ throw new Error('Map container is already initialized.');
+ }
+
+ on(container, 'scroll', this._onScroll, this);
+ this._containerId = stamp(container);
+ },
+
+ _initLayout: function () {
+ var container = this._container;
+
+ this._fadeAnimated = this.options.fadeAnimation && any3d;
+
+ addClass(container, 'leaflet-container' +
+ (touch ? ' leaflet-touch' : '') +
+ (retina ? ' leaflet-retina' : '') +
+ (ielt9 ? ' leaflet-oldie' : '') +
+ (safari ? ' leaflet-safari' : '') +
+ (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
+
+ var position = getStyle(container, 'position');
+
+ if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
+ container.style.position = 'relative';
+ }
+
+ this._initPanes();
+
+ if (this._initControlPos) {
+ this._initControlPos();
+ }
+ },
+
+ _initPanes: function () {
+ var panes = this._panes = {};
+ this._paneRenderers = {};
+
+ // @section
+ //
+ // Panes are DOM elements used to control the ordering of layers on the map. You
+ // can access panes with [`map.getPane`](#map-getpane) or
+ // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
+ // [`map.createPane`](#map-createpane) method.
+ //
+ // Every map has the following default panes that differ only in zIndex.
+ //
+ // @pane mapPane: HTMLElement = 'auto'
+ // Pane that contains all other map panes
+
+ this._mapPane = this.createPane('mapPane', this._container);
+ setPosition(this._mapPane, new Point(0, 0));
+
+ // @pane tilePane: HTMLElement = 200
+ // Pane for `GridLayer`s and `TileLayer`s
+ this.createPane('tilePane');
+ // @pane overlayPane: HTMLElement = 400
+ // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
+ this.createPane('shadowPane');
+ // @pane shadowPane: HTMLElement = 500
+ // Pane for overlay shadows (e.g. `Marker` shadows)
+ this.createPane('overlayPane');
+ // @pane markerPane: HTMLElement = 600
+ // Pane for `Icon`s of `Marker`s
+ this.createPane('markerPane');
+ // @pane tooltipPane: HTMLElement = 650
+ // Pane for `Tooltip`s.
+ this.createPane('tooltipPane');
+ // @pane popupPane: HTMLElement = 700
+ // Pane for `Popup`s.
+ this.createPane('popupPane');
+
+ if (!this.options.markerZoomAnimation) {
+ addClass(panes.markerPane, 'leaflet-zoom-hide');
+ addClass(panes.shadowPane, 'leaflet-zoom-hide');
+ }
+ },
+
+
+ // private methods that modify map state
+
+ // @section Map state change events
+ _resetView: function (center, zoom) {
+ setPosition(this._mapPane, new Point(0, 0));
+
+ var loading = !this._loaded;
+ this._loaded = true;
+ zoom = this._limitZoom(zoom);
+
+ this.fire('viewprereset');
+
+ var zoomChanged = this._zoom !== zoom;
+ this
+ ._moveStart(zoomChanged, false)
+ ._move(center, zoom)
+ ._moveEnd(zoomChanged);
+
+ // @event viewreset: Event
+ // Fired when the map needs to redraw its content (this usually happens
+ // on map zoom or load). Very useful for creating custom overlays.
+ this.fire('viewreset');
+
+ // @event load: Event
+ // Fired when the map is initialized (when its center and zoom are set
+ // for the first time).
+ if (loading) {
+ this.fire('load');
+ }
+ },
+
+ _moveStart: function (zoomChanged, noMoveStart) {
+ // @event zoomstart: Event
+ // Fired when the map zoom is about to change (e.g. before zoom animation).
+ // @event movestart: Event
+ // Fired when the view of the map starts changing (e.g. user starts dragging the map).
+ if (zoomChanged) {
+ this.fire('zoomstart');
+ }
+ if (!noMoveStart) {
+ this.fire('movestart');
+ }
+ return this;
+ },
+
+ _move: function (center, zoom, data) {
+ if (zoom === undefined) {
+ zoom = this._zoom;
+ }
+ var zoomChanged = this._zoom !== zoom;
+
+ this._zoom = zoom;
+ this._lastCenter = center;
+ this._pixelOrigin = this._getNewPixelOrigin(center);
+
+ // @event zoom: Event
+ // Fired repeatedly during any change in zoom level, including zoom
+ // and fly animations.
+ if (zoomChanged || (data && data.pinch)) { // Always fire 'zoom' if pinching because #3530
+ this.fire('zoom', data);
+ }
+
+ // @event move: Event
+ // Fired repeatedly during any movement of the map, including pan and
+ // fly animations.
+ return this.fire('move', data);
+ },
+
+ _moveEnd: function (zoomChanged) {
+ // @event zoomend: Event
+ // Fired when the map has changed, after any animations.
+ if (zoomChanged) {
+ this.fire('zoomend');
+ }
+
+ // @event moveend: Event
+ // Fired when the center of the map stops changing (e.g. user stopped
+ // dragging the map).
+ return this.fire('moveend');
+ },
+
+ _stop: function () {
+ cancelAnimFrame(this._flyToFrame);
+ if (this._panAnim) {
+ this._panAnim.stop();
+ }
+ return this;
+ },
+
+ _rawPanBy: function (offset) {
+ setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
+ },
+
+ _getZoomSpan: function () {
+ return this.getMaxZoom() - this.getMinZoom();
+ },
+
+ _panInsideMaxBounds: function () {
+ if (!this._enforcingBounds) {
+ this.panInsideBounds(this.options.maxBounds);
+ }
+ },
+
+ _checkIfLoaded: function () {
+ if (!this._loaded) {
+ throw new Error('Set map center and zoom first.');
+ }
+ },
+
+ // DOM event handling
+
+ // @section Interaction events
+ _initEvents: function (remove$$1) {
+ this._targets = {};
+ this._targets[stamp(this._container)] = this;
+
+ var onOff = remove$$1 ? off : on;
+
+ // @event click: MouseEvent
+ // Fired when the user clicks (or taps) the map.
+ // @event dblclick: MouseEvent
+ // Fired when the user double-clicks (or double-taps) the map.
+ // @event mousedown: MouseEvent
+ // Fired when the user pushes the mouse button on the map.
+ // @event mouseup: MouseEvent
+ // Fired when the user releases the mouse button on the map.
+ // @event mouseover: MouseEvent
+ // Fired when the mouse enters the map.
+ // @event mouseout: MouseEvent
+ // Fired when the mouse leaves the map.
+ // @event mousemove: MouseEvent
+ // Fired while the mouse moves over the map.
+ // @event contextmenu: MouseEvent
+ // Fired when the user pushes the right mouse button on the map, prevents
+ // default browser context menu from showing if there are listeners on
+ // this event. Also fired on mobile when the user holds a single touch
+ // for a second (also called long press).
+ // @event keypress: KeyboardEvent
+ // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
+ // @event keydown: KeyboardEvent
+ // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
+ // the `keydown` event is fired for keys that produce a character value and for keys
+ // that do not produce a character value.
+ // @event keyup: KeyboardEvent
+ // Fired when the user releases a key from the keyboard while the map is focused.
+ onOff(this._container, 'click dblclick mousedown mouseup ' +
+ 'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
+
+ if (this.options.trackResize) {
+ onOff(window, 'resize', this._onResize, this);
+ }
+
+ if (any3d && this.options.transform3DLimit) {
+ (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
+ }
+ },
+
+ _onResize: function () {
+ cancelAnimFrame(this._resizeRequest);
+ this._resizeRequest = requestAnimFrame(
+ function () { this.invalidateSize({debounceMoveend: true}); }, this);
+ },
+
+ _onScroll: function () {
+ this._container.scrollTop = 0;
+ this._container.scrollLeft = 0;
+ },
+
+ _onMoveEnd: function () {
+ var pos = this._getMapPanePos();
+ if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
+ // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
+ this._resetView(this.getCenter(), this.getZoom());
+ }
+ },
+
+ _findEventTargets: function (e, type) {
+ var targets = [],
+ target,
+ isHover = type === 'mouseout' || type === 'mouseover',
+ src = e.target || e.srcElement,
+ dragging = false;
+
+ while (src) {
+ target = this._targets[stamp(src)];
+ if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
+ // Prevent firing click after you just dragged an object.
+ dragging = true;
+ break;
+ }
+ if (target && target.listens(type, true)) {
+ if (isHover && !isExternalTarget(src, e)) { break; }
+ targets.push(target);
+ if (isHover) { break; }
+ }
+ if (src === this._container) { break; }
+ src = src.parentNode;
+ }
+ if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
+ targets = [this];
+ }
+ return targets;
+ },
+
+ _handleDOMEvent: function (e) {
+ if (!this._loaded || skipped(e)) { return; }
+
+ var type = e.type;
+
+ if (type === 'mousedown' || type === 'keypress' || type === 'keyup' || type === 'keydown') {
+ // prevents outline when clicking on keyboard-focusable element
+ preventOutline(e.target || e.srcElement);
+ }
+
+ this._fireDOMEvent(e, type);
+ },
+
+ _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
+
+ _fireDOMEvent: function (e, type, targets) {
+
+ if (e.type === 'click') {
+ // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
+ // @event preclick: MouseEvent
+ // Fired before mouse click on the map (sometimes useful when you
+ // want something to happen on click before any existing click
+ // handlers start running).
+ var synth = extend({}, e);
+ synth.type = 'preclick';
+ this._fireDOMEvent(synth, synth.type, targets);
+ }
+
+ if (e._stopped) { return; }
+
+ // Find the layer the event is propagating from and its parents.
+ targets = (targets || []).concat(this._findEventTargets(e, type));
+
+ if (!targets.length) { return; }
+
+ var target = targets[0];
+ if (type === 'contextmenu' && target.listens(type, true)) {
+ preventDefault(e);
+ }
+
+ var data = {
+ originalEvent: e
+ };
+
+ if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
+ var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
+ data.containerPoint = isMarker ?
+ this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
+ data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
+ data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
+ }
+
+ for (var i = 0; i < targets.length; i++) {
+ targets[i].fire(type, data, true);
+ if (data.originalEvent._stopped ||
+ (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
+ }
+ },
+
+ _draggableMoved: function (obj) {
+ obj = obj.dragging && obj.dragging.enabled() ? obj : this;
+ return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
+ },
+
+ _clearHandlers: function () {
+ for (var i = 0, len = this._handlers.length; i < len; i++) {
+ this._handlers[i].disable();
+ }
+ },
+
+ // @section Other Methods
+
+ // @method whenReady(fn: Function, context?: Object): this
+ // Runs the given function `fn` when the map gets initialized with
+ // a view (center and zoom) and at least one layer, or immediately
+ // if it's already initialized, optionally passing a function context.
+ whenReady: function (callback, context) {
+ if (this._loaded) {
+ callback.call(context || this, {target: this});
+ } else {
+ this.on('load', callback, context);
+ }
+ return this;
+ },
+
+
+ // private methods for getting map state
+
+ _getMapPanePos: function () {
+ return getPosition(this._mapPane) || new Point(0, 0);
+ },
+
+ _moved: function () {
+ var pos = this._getMapPanePos();
+ return pos && !pos.equals([0, 0]);
+ },
+
+ _getTopLeftPoint: function (center, zoom) {
+ var pixelOrigin = center && zoom !== undefined ?
+ this._getNewPixelOrigin(center, zoom) :
+ this.getPixelOrigin();
+ return pixelOrigin.subtract(this._getMapPanePos());
+ },
+
+ _getNewPixelOrigin: function (center, zoom) {
+ var viewHalf = this.getSize()._divideBy(2);
+ return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
+ },
+
+ _latLngToNewLayerPoint: function (latlng, zoom, center) {
+ var topLeft = this._getNewPixelOrigin(center, zoom);
+ return this.project(latlng, zoom)._subtract(topLeft);
+ },
+
+ _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
+ var topLeft = this._getNewPixelOrigin(center, zoom);
+ return toBounds([
+ this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
+ this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
+ this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
+ this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
+ ]);
+ },
+
+ // layer point of the current center
+ _getCenterLayerPoint: function () {
+ return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
+ },
+
+ // offset of the specified place to the current center in pixels
+ _getCenterOffset: function (latlng) {
+ return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
+ },
+
+ // adjust center for view to get inside bounds
+ _limitCenter: function (center, zoom, bounds) {
+
+ if (!bounds) { return center; }
+
+ var centerPoint = this.project(center, zoom),
+ viewHalf = this.getSize().divideBy(2),
+ viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
+ offset = this._getBoundsOffset(viewBounds, bounds, zoom);
+
+ // If offset is less than a pixel, ignore.
+ // This prevents unstable projections from getting into
+ // an infinite loop of tiny offsets.
+ if (offset.round().equals([0, 0])) {
+ return center;
+ }
+
+ return this.unproject(centerPoint.add(offset), zoom);
+ },
+
+ // adjust offset for view to get inside bounds
+ _limitOffset: function (offset, bounds) {
+ if (!bounds) { return offset; }
+
+ var viewBounds = this.getPixelBounds(),
+ newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
+
+ return offset.add(this._getBoundsOffset(newBounds, bounds));
+ },
+
+ // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
+ _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
+ var projectedMaxBounds = toBounds(
+ this.project(maxBounds.getNorthEast(), zoom),
+ this.project(maxBounds.getSouthWest(), zoom)
+ ),
+ minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
+ maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
+
+ dx = this._rebound(minOffset.x, -maxOffset.x),
+ dy = this._rebound(minOffset.y, -maxOffset.y);
+
+ return new Point(dx, dy);
+ },
+
+ _rebound: function (left, right) {
+ return left + right > 0 ?
+ Math.round(left - right) / 2 :
+ Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
+ },
+
+ _limitZoom: function (zoom) {
+ var min = this.getMinZoom(),
+ max = this.getMaxZoom(),
+ snap = any3d ? this.options.zoomSnap : 1;
+ if (snap) {
+ zoom = Math.round(zoom / snap) * snap;
+ }
+ return Math.max(min, Math.min(max, zoom));
+ },
+
+ _onPanTransitionStep: function () {
+ this.fire('move');
+ },
+
+ _onPanTransitionEnd: function () {
+ removeClass(this._mapPane, 'leaflet-pan-anim');
+ this.fire('moveend');
+ },
+
+ _tryAnimatedPan: function (center, options) {
+ // difference between the new and current centers in pixels
+ var offset = this._getCenterOffset(center)._trunc();
+
+ // don't animate too far unless animate: true specified in options
+ if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
+
+ this.panBy(offset, options);
+
+ return true;
+ },
+
+ _createAnimProxy: function () {
+
+ var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
+ this._panes.mapPane.appendChild(proxy);
+
+ this.on('zoomanim', function (e) {
+ var prop = TRANSFORM,
+ transform = this._proxy.style[prop];
+
+ setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
+
+ // workaround for case when transform is the same and so transitionend event is not fired
+ if (transform === this._proxy.style[prop] && this._animatingZoom) {
+ this._onZoomTransitionEnd();
+ }
+ }, this);
+
+ this.on('load moveend', this._animMoveEnd, this);
+
+ this._on('unload', this._destroyAnimProxy, this);
+ },
+
+ _destroyAnimProxy: function () {
+ remove(this._proxy);
+ this.off('load moveend', this._animMoveEnd, this);
+ delete this._proxy;
+ },
+
+ _animMoveEnd: function () {
+ var c = this.getCenter(),
+ z = this.getZoom();
+ setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
+ },
+
+ _catchTransitionEnd: function (e) {
+ if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
+ this._onZoomTransitionEnd();
+ }
+ },
+
+ _nothingToAnimate: function () {
+ return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
+ },
+
+ _tryAnimatedZoom: function (center, zoom, options) {
+
+ if (this._animatingZoom) { return true; }
+
+ options = options || {};
+
+ // don't animate if disabled, not supported or zoom difference is too large
+ if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
+ Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
+
+ // offset is the pixel coords of the zoom origin relative to the current center
+ var scale = this.getZoomScale(zoom),
+ offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
+
+ // don't animate if the zoom origin isn't within one screen from the current center, unless forced
+ if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
+
+ requestAnimFrame(function () {
+ this
+ ._moveStart(true, false)
+ ._animateZoom(center, zoom, true);
+ }, this);
+
+ return true;
+ },
+
+ _animateZoom: function (center, zoom, startAnim, noUpdate) {
+ if (!this._mapPane) { return; }
+
+ if (startAnim) {
+ this._animatingZoom = true;
+
+ // remember what center/zoom to set after animation
+ this._animateToCenter = center;
+ this._animateToZoom = zoom;
+
+ addClass(this._mapPane, 'leaflet-zoom-anim');
+ }
+
+ // @section Other Events
+ // @event zoomanim: ZoomAnimEvent
+ // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
+ this.fire('zoomanim', {
+ center: center,
+ zoom: zoom,
+ noUpdate: noUpdate
+ });
+
+ // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
+ setTimeout(bind(this._onZoomTransitionEnd, this), 250);
+ },
+
+ _onZoomTransitionEnd: function () {
+ if (!this._animatingZoom) { return; }
+
+ if (this._mapPane) {
+ removeClass(this._mapPane, 'leaflet-zoom-anim');
+ }
+
+ this._animatingZoom = false;
+
+ this._move(this._animateToCenter, this._animateToZoom);
+
+ // This anim frame should prevent an obscure iOS webkit tile loading race condition.
+ requestAnimFrame(function () {
+ this._moveEnd(true);
+ }, this);
+ }
+});
+
+// @section
+
+// @factory L.map(id: String, options?: Map options)
+// Instantiates a map object given the DOM ID of a `<div>` element
+// and optionally an object literal with `Map options`.
+//
+// @alternative
+// @factory L.map(el: HTMLElement, options?: Map options)
+// Instantiates a map object given an instance of a `<div>` HTML element
+// and optionally an object literal with `Map options`.
+function createMap(id, options) {
+ return new Map(id, options);
+}
+
+/*
+ * @class Control
+ * @aka L.Control
+ * @inherits Class
+ *
+ * L.Control is a base class for implementing map controls. Handles positioning.
+ * All other controls extend from this class.
+ */
+
+var Control = Class.extend({
+ // @section
+ // @aka Control options
+ options: {
+ // @option position: String = 'topright'
+ // The position of the control (one of the map corners). Possible values are `'topleft'`,
+ // `'topright'`, `'bottomleft'` or `'bottomright'`
+ position: 'topright'
+ },
+
+ initialize: function (options) {
+ setOptions(this, options);
+ },
+
+ /* @section
+ * Classes extending L.Control will inherit the following methods:
+ *
+ * @method getPosition: string
+ * Returns the position of the control.
+ */
+ getPosition: function () {
+ return this.options.position;
+ },
+
+ // @method setPosition(position: string): this
+ // Sets the position of the control.
+ setPosition: function (position) {
+ var map = this._map;
+
+ if (map) {
+ map.removeControl(this);
+ }
+
+ this.options.position = position;
+
+ if (map) {
+ map.addControl(this);
+ }
+
+ return this;
+ },
+
+ // @method getContainer: HTMLElement
+ // Returns the HTMLElement that contains the control.
+ getContainer: function () {
+ return this._container;
+ },
+
+ // @method addTo(map: Map): this
+ // Adds the control to the given map.
+ addTo: function (map) {
+ this.remove();
+ this._map = map;
+
+ var container = this._container = this.onAdd(map),
+ pos = this.getPosition(),
+ corner = map._controlCorners[pos];
+
+ addClass(container, 'leaflet-control');
+
+ if (pos.indexOf('bottom') !== -1) {
+ corner.insertBefore(container, corner.firstChild);
+ } else {
+ corner.appendChild(container);
+ }
+
+ this._map.on('unload', this.remove, this);
+
+ return this;
+ },
+
+ // @method remove: this
+ // Removes the control from the map it is currently active on.
+ remove: function () {
+ if (!this._map) {
+ return this;
+ }
+
+ remove(this._container);
+
+ if (this.onRemove) {
+ this.onRemove(this._map);
+ }
+
+ this._map.off('unload', this.remove, this);
+ this._map = null;
+
+ return this;
+ },
+
+ _refocusOnMap: function (e) {
+ // if map exists and event is not a keyboard event
+ if (this._map && e && e.screenX > 0 && e.screenY > 0) {
+ this._map.getContainer().focus();
+ }
+ }
+});
+
+var control = function (options) {
+ return new Control(options);
+};
+
+/* @section Extension methods
+ * @uninheritable
+ *
+ * Every control should extend from `L.Control` and (re-)implement the following methods.
+ *
+ * @method onAdd(map: Map): HTMLElement
+ * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
+ *
+ * @method onRemove(map: Map)
+ * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
+ */
+
+/* @namespace Map
+ * @section Methods for Layers and Controls
+ */
+Map.include({
+ // @method addControl(control: Control): this
+ // Adds the given control to the map
+ addControl: function (control) {
+ control.addTo(this);
+ return this;
+ },
+
+ // @method removeControl(control: Control): this
+ // Removes the given control from the map
+ removeControl: function (control) {
+ control.remove();
+ return this;
+ },
+
+ _initControlPos: function () {
+ var corners = this._controlCorners = {},
+ l = 'leaflet-',
+ container = this._controlContainer =
+ create$1('div', l + 'control-container', this._container);
+
+ function createCorner(vSide, hSide) {
+ var className = l + vSide + ' ' + l + hSide;
+
+ corners[vSide + hSide] = create$1('div', className, container);
+ }
+
+ createCorner('top', 'left');
+ createCorner('top', 'right');
+ createCorner('bottom', 'left');
+ createCorner('bottom', 'right');
+ },
+
+ _clearControlPos: function () {
+ for (var i in this._controlCorners) {
+ remove(this._controlCorners[i]);
+ }
+ remove(this._controlContainer);
+ delete this._controlCorners;
+ delete this._controlContainer;
+ }
+});
+
+/*
+ * @class Control.Layers
+ * @aka L.Control.Layers
+ * @inherits Control
+ *
+ * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control/)). Extends `Control`.
+ *
+ * @example
+ *
+ * ```js
+ * var baseLayers = {
+ * "Mapbox": mapbox,
+ * "OpenStreetMap": osm
+ * };
+ *
+ * var overlays = {
+ * "Marker": marker,
+ * "Roads": roadsLayer
+ * };
+ *
+ * L.control.layers(baseLayers, overlays).addTo(map);
+ * ```
+ *
+ * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
+ *
+ * ```js
+ * {
+ * "<someName1>": layer1,
+ * "<someName2>": layer2
+ * }
+ * ```
+ *
+ * The layer names can contain HTML, which allows you to add additional styling to the items:
+ *
+ * ```js
+ * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
+ * ```
+ */
+
+var Layers = Control.extend({
+ // @section
+ // @aka Control.Layers options
+ options: {
+ // @option collapsed: Boolean = true
+ // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
+ collapsed: true,
+ position: 'topright',
+
+ // @option autoZIndex: Boolean = true
+ // If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off.
+ autoZIndex: true,
+
+ // @option hideSingleBase: Boolean = false
+ // If `true`, the base layers in the control will be hidden when there is only one.
+ hideSingleBase: false,
+
+ // @option sortLayers: Boolean = false
+ // Whether to sort the layers. When `false`, layers will keep the order
+ // in which they were added to the control.
+ sortLayers: false,
+
+ // @option sortFunction: Function = *
+ // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
+ // that will be used for sorting the layers, when `sortLayers` is `true`.
+ // The function receives both the `L.Layer` instances and their names, as in
+ // `sortFunction(layerA, layerB, nameA, nameB)`.
+ // By default, it sorts layers alphabetically by their name.
+ sortFunction: function (layerA, layerB, nameA, nameB) {
+ return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
+ }
+ },
+
+ initialize: function (baseLayers, overlays, options) {
+ setOptions(this, options);
+
+ this._layerControlInputs = [];
+ this._layers = [];
+ this._lastZIndex = 0;
+ this._handlingClick = false;
+
+ for (var i in baseLayers) {
+ this._addLayer(baseLayers[i], i);
+ }
+
+ for (i in overlays) {
+ this._addLayer(overlays[i], i, true);
+ }
+ },
+
+ onAdd: function (map) {
+ this._initLayout();
+ this._update();
+
+ this._map = map;
+ map.on('zoomend', this._checkDisabledLayers, this);
+
+ for (var i = 0; i < this._layers.length; i++) {
+ this._layers[i].layer.on('add remove', this._onLayerChange, this);
+ }
+
+ return this._container;
+ },
+
+ addTo: function (map) {
+ Control.prototype.addTo.call(this, map);
+ // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
+ return this._expandIfNotCollapsed();
+ },
+
+ onRemove: function () {
+ this._map.off('zoomend', this._checkDisabledLayers, this);
+
+ for (var i = 0; i < this._layers.length; i++) {
+ this._layers[i].layer.off('add remove', this._onLayerChange, this);
+ }
+ },
+
+ // @method addBaseLayer(layer: Layer, name: String): this
+ // Adds a base layer (radio button entry) with the given name to the control.
+ addBaseLayer: function (layer, name) {
+ this._addLayer(layer, name);
+ return (this._map) ? this._update() : this;
+ },
+
+ // @method addOverlay(layer: Layer, name: String): this
+ // Adds an overlay (checkbox entry) with the given name to the control.
+ addOverlay: function (layer, name) {
+ this._addLayer(layer, name, true);
+ return (this._map) ? this._update() : this;
+ },
+
+ // @method removeLayer(layer: Layer): this
+ // Remove the given layer from the control.
+ removeLayer: function (layer) {
+ layer.off('add remove', this._onLayerChange, this);
+
+ var obj = this._getLayer(stamp(layer));
+ if (obj) {
+ this._layers.splice(this._layers.indexOf(obj), 1);
+ }
+ return (this._map) ? this._update() : this;
+ },
+
+ // @method expand(): this
+ // Expand the control container if collapsed.
+ expand: function () {
+ addClass(this._container, 'leaflet-control-layers-expanded');
+ this._section.style.height = null;
+ var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
+ if (acceptableHeight < this._section.clientHeight) {
+ addClass(this._section, 'leaflet-control-layers-scrollbar');
+ this._section.style.height = acceptableHeight + 'px';
+ } else {
+ removeClass(this._section, 'leaflet-control-layers-scrollbar');
+ }
+ this._checkDisabledLayers();
+ return this;
+ },
+
+ // @method collapse(): this
+ // Collapse the control container if expanded.
+ collapse: function () {
+ removeClass(this._container, 'leaflet-control-layers-expanded');
+ return this;
+ },
+
+ _initLayout: function () {
+ var className = 'leaflet-control-layers',
+ container = this._container = create$1('div', className),
+ collapsed = this.options.collapsed;
+
+ // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
+ container.setAttribute('aria-haspopup', true);
+
+ disableClickPropagation(container);
+ disableScrollPropagation(container);
+
+ var section = this._section = create$1('section', className + '-list');
+
+ if (collapsed) {
+ this._map.on('click', this.collapse, this);
+
+ if (!android) {
+ on(container, {
+ mouseenter: this.expand,
+ mouseleave: this.collapse
+ }, this);
+ }
+ }
+
+ var link = this._layersLink = create$1('a', className + '-toggle', container);
+ link.href = '#';
+ link.title = 'Layers';
+
+ if (touch) {
+ on(link, 'click', stop);
+ on(link, 'click', this.expand, this);
+ } else {
+ on(link, 'focus', this.expand, this);
+ }
+
+ if (!collapsed) {
+ this.expand();
+ }
+
+ this._baseLayersList = create$1('div', className + '-base', section);
+ this._separator = create$1('div', className + '-separator', section);
+ this._overlaysList = create$1('div', className + '-overlays', section);
+
+ container.appendChild(section);
+ },
+
+ _getLayer: function (id) {
+ for (var i = 0; i < this._layers.length; i++) {
+
+ if (this._layers[i] && stamp(this._layers[i].layer) === id) {
+ return this._layers[i];
+ }
+ }
+ },
+
+ _addLayer: function (layer, name, overlay) {
+ if (this._map) {
+ layer.on('add remove', this._onLayerChange, this);
+ }
+
+ this._layers.push({
+ layer: layer,
+ name: name,
+ overlay: overlay
+ });
+
+ if (this.options.sortLayers) {
+ this._layers.sort(bind(function (a, b) {
+ return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
+ }, this));
+ }
+
+ if (this.options.autoZIndex && layer.setZIndex) {
+ this._lastZIndex++;
+ layer.setZIndex(this._lastZIndex);
+ }
+
+ this._expandIfNotCollapsed();
+ },
+
+ _update: function () {
+ if (!this._container) { return this; }
+
+ empty(this._baseLayersList);
+ empty(this._overlaysList);
+
+ this._layerControlInputs = [];
+ var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
+
+ for (i = 0; i < this._layers.length; i++) {
+ obj = this._layers[i];
+ this._addItem(obj);
+ overlaysPresent = overlaysPresent || obj.overlay;
+ baseLayersPresent = baseLayersPresent || !obj.overlay;
+ baseLayersCount += !obj.overlay ? 1 : 0;
+ }
+
+ // Hide base layers section if there's only one layer.
+ if (this.options.hideSingleBase) {
+ baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
+ this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
+ }
+
+ this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
+
+ return this;
+ },
+
+ _onLayerChange: function (e) {
+ if (!this._handlingClick) {
+ this._update();
+ }
+
+ var obj = this._getLayer(stamp(e.target));
+
+ // @namespace Map
+ // @section Layer events
+ // @event baselayerchange: LayersControlEvent
+ // Fired when the base layer is changed through the [layer control](#control-layers).
+ // @event overlayadd: LayersControlEvent
+ // Fired when an overlay is selected through the [layer control](#control-layers).
+ // @event overlayremove: LayersControlEvent
+ // Fired when an overlay is deselected through the [layer control](#control-layers).
+ // @namespace Control.Layers
+ var type = obj.overlay ?
+ (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
+ (e.type === 'add' ? 'baselayerchange' : null);
+
+ if (type) {
+ this._map.fire(type, obj);
+ }
+ },
+
+ // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
+ _createRadioElement: function (name, checked) {
+
+ var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
+ name + '"' + (checked ? ' checked="checked"' : '') + '/>';
+
+ var radioFragment = document.createElement('div');
+ radioFragment.innerHTML = radioHtml;
+
+ return radioFragment.firstChild;
+ },
+
+ _addItem: function (obj) {
+ var label = document.createElement('label'),
+ checked = this._map.hasLayer(obj.layer),
+ input;
+
+ if (obj.overlay) {
+ input = document.createElement('input');
+ input.type = 'checkbox';
+ input.className = 'leaflet-control-layers-selector';
+ input.defaultChecked = checked;
+ } else {
+ input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
+ }
+
+ this._layerControlInputs.push(input);
+ input.layerId = stamp(obj.layer);
+
+ on(input, 'click', this._onInputClick, this);
+
+ var name = document.createElement('span');
+ name.innerHTML = ' ' + obj.name;
+
+ // Helps from preventing layer control flicker when checkboxes are disabled
+ // https://github.com/Leaflet/Leaflet/issues/2771
+ var holder = document.createElement('div');
+
+ label.appendChild(holder);
+ holder.appendChild(input);
+ holder.appendChild(name);
+
+ var container = obj.overlay ? this._overlaysList : this._baseLayersList;
+ container.appendChild(label);
+
+ this._checkDisabledLayers();
+ return label;
+ },
+
+ _onInputClick: function () {
+ var inputs = this._layerControlInputs,
+ input, layer;
+ var addedLayers = [],
+ removedLayers = [];
+
+ this._handlingClick = true;
+
+ for (var i = inputs.length - 1; i >= 0; i--) {
+ input = inputs[i];
+ layer = this._getLayer(input.layerId).layer;
+
+ if (input.checked) {
+ addedLayers.push(layer);
+ } else if (!input.checked) {
+ removedLayers.push(layer);
+ }
+ }
+
+ // Bugfix issue 2318: Should remove all old layers before readding new ones
+ for (i = 0; i < removedLayers.length; i++) {
+ if (this._map.hasLayer(removedLayers[i])) {
+ this._map.removeLayer(removedLayers[i]);
+ }
+ }
+ for (i = 0; i < addedLayers.length; i++) {
+ if (!this._map.hasLayer(addedLayers[i])) {
+ this._map.addLayer(addedLayers[i]);
+ }
+ }
+
+ this._handlingClick = false;
+
+ this._refocusOnMap();
+ },
+
+ _checkDisabledLayers: function () {
+ var inputs = this._layerControlInputs,
+ input,
+ layer,
+ zoom = this._map.getZoom();
+
+ for (var i = inputs.length - 1; i >= 0; i--) {
+ input = inputs[i];
+ layer = this._getLayer(input.layerId).layer;
+ input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
+ (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
+
+ }
+ },
+
+ _expandIfNotCollapsed: function () {
+ if (this._map && !this.options.collapsed) {
+ this.expand();
+ }
+ return this;
+ },
+
+ _expand: function () {
+ // Backward compatibility, remove me in 1.1.
+ return this.expand();
+ },
+
+ _collapse: function () {
+ // Backward compatibility, remove me in 1.1.
+ return this.collapse();
+ }
+
+});
+
+
+// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
+// Creates a layers control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
+var layers = function (baseLayers, overlays, options) {
+ return new Layers(baseLayers, overlays, options);
+};
+
+/*
+ * @class Control.Zoom
+ * @aka L.Control.Zoom
+ * @inherits Control
+ *
+ * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`.
+ */
+
+var Zoom = Control.extend({
+ // @section
+ // @aka Control.Zoom options
+ options: {
+ position: 'topleft',
+
+ // @option zoomInText: String = '+'
+ // The text set on the 'zoom in' button.
+ zoomInText: '+',
+
+ // @option zoomInTitle: String = 'Zoom in'
+ // The title set on the 'zoom in' button.
+ zoomInTitle: 'Zoom in',
+
+ // @option zoomOutText: String = '&#x2212;'
+ // The text set on the 'zoom out' button.
+ zoomOutText: '&#x2212;',
+
+ // @option zoomOutTitle: String = 'Zoom out'
+ // The title set on the 'zoom out' button.
+ zoomOutTitle: 'Zoom out'
+ },
+
+ onAdd: function (map) {
+ var zoomName = 'leaflet-control-zoom',
+ container = create$1('div', zoomName + ' leaflet-bar'),
+ options = this.options;
+
+ this._zoomInButton = this._createButton(options.zoomInText, options.zoomInTitle,
+ zoomName + '-in', container, this._zoomIn);
+ this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
+ zoomName + '-out', container, this._zoomOut);
+
+ this._updateDisabled();
+ map.on('zoomend zoomlevelschange', this._updateDisabled, this);
+
+ return container;
+ },
+
+ onRemove: function (map) {
+ map.off('zoomend zoomlevelschange', this._updateDisabled, this);
+ },
+
+ disable: function () {
+ this._disabled = true;
+ this._updateDisabled();
+ return this;
+ },
+
+ enable: function () {
+ this._disabled = false;
+ this._updateDisabled();
+ return this;
+ },
+
+ _zoomIn: function (e) {
+ if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
+ this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
+ }
+ },
+
+ _zoomOut: function (e) {
+ if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
+ this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
+ }
+ },
+
+ _createButton: function (html, title, className, container, fn) {
+ var link = create$1('a', className, container);
+ link.innerHTML = html;
+ link.href = '#';
+ link.title = title;
+
+ /*
+ * Will force screen readers like VoiceOver to read this as "Zoom in - button"
+ */
+ link.setAttribute('role', 'button');
+ link.setAttribute('aria-label', title);
+
+ disableClickPropagation(link);
+ on(link, 'click', stop);
+ on(link, 'click', fn, this);
+ on(link, 'click', this._refocusOnMap, this);
+
+ return link;
+ },
+
+ _updateDisabled: function () {
+ var map = this._map,
+ className = 'leaflet-disabled';
+
+ removeClass(this._zoomInButton, className);
+ removeClass(this._zoomOutButton, className);
+
+ if (this._disabled || map._zoom === map.getMinZoom()) {
+ addClass(this._zoomOutButton, className);
+ }
+ if (this._disabled || map._zoom === map.getMaxZoom()) {
+ addClass(this._zoomInButton, className);
+ }
+ }
+});
+
+// @namespace Map
+// @section Control options
+// @option zoomControl: Boolean = true
+// Whether a [zoom control](#control-zoom) is added to the map by default.
+Map.mergeOptions({
+ zoomControl: true
+});
+
+Map.addInitHook(function () {
+ if (this.options.zoomControl) {
+ // @section Controls
+ // @property zoomControl: Control.Zoom
+ // The default zoom control (only available if the
+ // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
+ this.zoomControl = new Zoom();
+ this.addControl(this.zoomControl);
+ }
+});
+
+// @namespace Control.Zoom
+// @factory L.control.zoom(options: Control.Zoom options)
+// Creates a zoom control
+var zoom = function (options) {
+ return new Zoom(options);
+};
+
+/*
+ * @class Control.Scale
+ * @aka L.Control.Scale
+ * @inherits Control
+ *
+ * A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends `Control`.
+ *
+ * @example
+ *
+ * ```js
+ * L.control.scale().addTo(map);
+ * ```
+ */
+
+var Scale = Control.extend({
+ // @section
+ // @aka Control.Scale options
+ options: {
+ position: 'bottomleft',
+
+ // @option maxWidth: Number = 100
+ // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
+ maxWidth: 100,
+
+ // @option metric: Boolean = True
+ // Whether to show the metric scale line (m/km).
+ metric: true,
+
+ // @option imperial: Boolean = True
+ // Whether to show the imperial scale line (mi/ft).
+ imperial: true
+
+ // @option updateWhenIdle: Boolean = false
+ // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
+ },
+
+ onAdd: function (map) {
+ var className = 'leaflet-control-scale',
+ container = create$1('div', className),
+ options = this.options;
+
+ this._addScales(options, className + '-line', container);
+
+ map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
+ map.whenReady(this._update, this);
+
+ return container;
+ },
+
+ onRemove: function (map) {
+ map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
+ },
+
+ _addScales: function (options, className, container) {
+ if (options.metric) {
+ this._mScale = create$1('div', className, container);
+ }
+ if (options.imperial) {
+ this._iScale = create$1('div', className, container);
+ }
+ },
+
+ _update: function () {
+ var map = this._map,
+ y = map.getSize().y / 2;
+
+ var maxMeters = map.distance(
+ map.containerPointToLatLng([0, y]),
+ map.containerPointToLatLng([this.options.maxWidth, y]));
+
+ this._updateScales(maxMeters);
+ },
+
+ _updateScales: function (maxMeters) {
+ if (this.options.metric && maxMeters) {
+ this._updateMetric(maxMeters);
+ }
+ if (this.options.imperial && maxMeters) {
+ this._updateImperial(maxMeters);
+ }
+ },
+
+ _updateMetric: function (maxMeters) {
+ var meters = this._getRoundNum(maxMeters),
+ label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
+
+ this._updateScale(this._mScale, label, meters / maxMeters);
+ },
+
+ _updateImperial: function (maxMeters) {
+ var maxFeet = maxMeters * 3.2808399,
+ maxMiles, miles, feet;
+
+ if (maxFeet > 5280) {
+ maxMiles = maxFeet / 5280;
+ miles = this._getRoundNum(maxMiles);
+ this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
+
+ } else {
+ feet = this._getRoundNum(maxFeet);
+ this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
+ }
+ },
+
+ _updateScale: function (scale, text, ratio) {
+ scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
+ scale.innerHTML = text;
+ },
+
+ _getRoundNum: function (num) {
+ var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
+ d = num / pow10;
+
+ d = d >= 10 ? 10 :
+ d >= 5 ? 5 :
+ d >= 3 ? 3 :
+ d >= 2 ? 2 : 1;
+
+ return pow10 * d;
+ }
+});
+
+
+// @factory L.control.scale(options?: Control.Scale options)
+// Creates an scale control with the given options.
+var scale = function (options) {
+ return new Scale(options);
+};
+
+/*
+ * @class Control.Attribution
+ * @aka L.Control.Attribution
+ * @inherits Control
+ *
+ * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control.
+ */
+
+var Attribution = Control.extend({
+ // @section
+ // @aka Control.Attribution options
+ options: {
+ position: 'bottomright',
+
+ // @option prefix: String = 'Leaflet'
+ // The HTML text shown before the attributions. Pass `false` to disable.
+ prefix: '<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
+ },
+
+ initialize: function (options) {
+ setOptions(this, options);
+
+ this._attributions = {};
+ },
+
+ onAdd: function (map) {
+ map.attributionControl = this;
+ this._container = create$1('div', 'leaflet-control-attribution');
+ disableClickPropagation(this._container);
+
+ // TODO ugly, refactor
+ for (var i in map._layers) {
+ if (map._layers[i].getAttribution) {
+ this.addAttribution(map._layers[i].getAttribution());
+ }
+ }
+
+ this._update();
+
+ return this._container;
+ },
+
+ // @method setPrefix(prefix: String): this
+ // Sets the text before the attributions.
+ setPrefix: function (prefix) {
+ this.options.prefix = prefix;
+ this._update();
+ return this;
+ },
+
+ // @method addAttribution(text: String): this
+ // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
+ addAttribution: function (text) {
+ if (!text) { return this; }
+
+ if (!this._attributions[text]) {
+ this._attributions[text] = 0;
+ }
+ this._attributions[text]++;
+
+ this._update();
+
+ return this;
+ },
+
+ // @method removeAttribution(text: String): this
+ // Removes an attribution text.
+ removeAttribution: function (text) {
+ if (!text) { return this; }
+
+ if (this._attributions[text]) {
+ this._attributions[text]--;
+ this._update();
+ }
+
+ return this;
+ },
+
+ _update: function () {
+ if (!this._map) { return; }
+
+ var attribs = [];
+
+ for (var i in this._attributions) {
+ if (this._attributions[i]) {
+ attribs.push(i);
+ }
+ }
+
+ var prefixAndAttribs = [];
+
+ if (this.options.prefix) {
+ prefixAndAttribs.push(this.options.prefix);
+ }
+ if (attribs.length) {
+ prefixAndAttribs.push(attribs.join(', '));
+ }
+
+ this._container.innerHTML = prefixAndAttribs.join(' | ');
+ }
+});
+
+// @namespace Map
+// @section Control options
+// @option attributionControl: Boolean = true
+// Whether a [attribution control](#control-attribution) is added to the map by default.
+Map.mergeOptions({
+ attributionControl: true
+});
+
+Map.addInitHook(function () {
+ if (this.options.attributionControl) {
+ new Attribution().addTo(this);
+ }
+});
+
+// @namespace Control.Attribution
+// @factory L.control.attribution(options: Control.Attribution options)
+// Creates an attribution control.
+var attribution = function (options) {
+ return new Attribution(options);
+};
+
+Control.Layers = Layers;
+Control.Zoom = Zoom;
+Control.Scale = Scale;
+Control.Attribution = Attribution;
+
+control.layers = layers;
+control.zoom = zoom;
+control.scale = scale;
+control.attribution = attribution;
+
+/*
+ L.Handler is a base class for handler classes that are used internally to inject
+ interaction features like dragging to classes like Map and Marker.
+*/
+
+// @class Handler
+// @aka L.Handler
+// Abstract class for map interaction handlers
+
+var Handler = Class.extend({
+ initialize: function (map) {
+ this._map = map;
+ },
+
+ // @method enable(): this
+ // Enables the handler
+ enable: function () {
+ if (this._enabled) { return this; }
+
+ this._enabled = true;
+ this.addHooks();
+ return this;
+ },
+
+ // @method disable(): this
+ // Disables the handler
+ disable: function () {
+ if (!this._enabled) { return this; }
+
+ this._enabled = false;
+ this.removeHooks();
+ return this;
+ },
+
+ // @method enabled(): Boolean
+ // Returns `true` if the handler is enabled
+ enabled: function () {
+ return !!this._enabled;
+ }
+
+ // @section Extension methods
+ // Classes inheriting from `Handler` must implement the two following methods:
+ // @method addHooks()
+ // Called when the handler is enabled, should add event hooks.
+ // @method removeHooks()
+ // Called when the handler is disabled, should remove the event hooks added previously.
+});
+
+// @section There is static function which can be called without instantiating L.Handler:
+// @function addTo(map: Map, name: String): this
+// Adds a new Handler to the given map with the given name.
+Handler.addTo = function (map, name) {
+ map.addHandler(name, this);
+ return this;
+};
+
+var Mixin = {Events: Events};
+
+/*
+ * @class Draggable
+ * @aka L.Draggable
+ * @inherits Evented
+ *
+ * A class for making DOM elements draggable (including touch support).
+ * Used internally for map and marker dragging. Only works for elements
+ * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
+ *
+ * @example
+ * ```js
+ * var draggable = new L.Draggable(elementToDrag);
+ * draggable.enable();
+ * ```
+ */
+
+var START = touch ? 'touchstart mousedown' : 'mousedown';
+var END = {
+ mousedown: 'mouseup',
+ touchstart: 'touchend',
+ pointerdown: 'touchend',
+ MSPointerDown: 'touchend'
+};
+var MOVE = {
+ mousedown: 'mousemove',
+ touchstart: 'touchmove',
+ pointerdown: 'touchmove',
+ MSPointerDown: 'touchmove'
+};
+
+
+var Draggable = Evented.extend({
+
+ options: {
+ // @section
+ // @aka Draggable options
+ // @option clickTolerance: Number = 3
+ // The max number of pixels a user can shift the mouse pointer during a click
+ // for it to be considered a valid click (as opposed to a mouse drag).
+ clickTolerance: 3
+ },
+
+ // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
+ // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
+ initialize: function (element, dragStartTarget, preventOutline$$1, options) {
+ setOptions(this, options);
+
+ this._element = element;
+ this._dragStartTarget = dragStartTarget || element;
+ this._preventOutline = preventOutline$$1;
+ },
+
+ // @method enable()
+ // Enables the dragging ability
+ enable: function () {
+ if (this._enabled) { return; }
+
+ on(this._dragStartTarget, START, this._onDown, this);
+
+ this._enabled = true;
+ },
+
+ // @method disable()
+ // Disables the dragging ability
+ disable: function () {
+ if (!this._enabled) { return; }
+
+ // If we're currently dragging this draggable,
+ // disabling it counts as first ending the drag.
+ if (Draggable._dragging === this) {
+ this.finishDrag();
+ }
+
+ off(this._dragStartTarget, START, this._onDown, this);
+
+ this._enabled = false;
+ this._moved = false;
+ },
+
+ _onDown: function (e) {
+ // Ignore simulated events, since we handle both touch and
+ // mouse explicitly; otherwise we risk getting duplicates of
+ // touch events, see #4315.
+ // Also ignore the event if disabled; this happens in IE11
+ // under some circumstances, see #3666.
+ if (e._simulated || !this._enabled) { return; }
+
+ this._moved = false;
+
+ if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
+
+ if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
+ Draggable._dragging = this; // Prevent dragging multiple objects at once.
+
+ if (this._preventOutline) {
+ preventOutline(this._element);
+ }
+
+ disableImageDrag();
+ disableTextSelection();
+
+ if (this._moving) { return; }
+
+ // @event down: Event
+ // Fired when a drag is about to start.
+ this.fire('down');
+
+ var first = e.touches ? e.touches[0] : e,
+ sizedParent = getSizedParentNode(this._element);
+
+ this._startPoint = new Point(first.clientX, first.clientY);
+
+ // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
+ this._parentScale = getScale(sizedParent);
+
+ on(document, MOVE[e.type], this._onMove, this);
+ on(document, END[e.type], this._onUp, this);
+ },
+
+ _onMove: function (e) {
+ // Ignore simulated events, since we handle both touch and
+ // mouse explicitly; otherwise we risk getting duplicates of
+ // touch events, see #4315.
+ // Also ignore the event if disabled; this happens in IE11
+ // under some circumstances, see #3666.
+ if (e._simulated || !this._enabled) { return; }
+
+ if (e.touches && e.touches.length > 1) {
+ this._moved = true;
+ return;
+ }
+
+ var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
+ offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
+
+ if (!offset.x && !offset.y) { return; }
+ if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
+
+ // We assume that the parent container's position, border and scale do not change for the duration of the drag.
+ // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
+ // and we can use the cached value for the scale.
+ offset.x /= this._parentScale.x;
+ offset.y /= this._parentScale.y;
+
+ preventDefault(e);
+
+ if (!this._moved) {
+ // @event dragstart: Event
+ // Fired when a drag starts
+ this.fire('dragstart');
+
+ this._moved = true;
+ this._startPos = getPosition(this._element).subtract(offset);
+
+ addClass(document.body, 'leaflet-dragging');
+
+ this._lastTarget = e.target || e.srcElement;
+ // IE and Edge do not give the <use> element, so fetch it
+ // if necessary
+ if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
+ this._lastTarget = this._lastTarget.correspondingUseElement;
+ }
+ addClass(this._lastTarget, 'leaflet-drag-target');
+ }
+
+ this._newPos = this._startPos.add(offset);
+ this._moving = true;
+
+ cancelAnimFrame(this._animRequest);
+ this._lastEvent = e;
+ this._animRequest = requestAnimFrame(this._updatePosition, this, true);
+ },
+
+ _updatePosition: function () {
+ var e = {originalEvent: this._lastEvent};
+
+ // @event predrag: Event
+ // Fired continuously during dragging *before* each corresponding
+ // update of the element's position.
+ this.fire('predrag', e);
+ setPosition(this._element, this._newPos);
+
+ // @event drag: Event
+ // Fired continuously during dragging.
+ this.fire('drag', e);
+ },
+
+ _onUp: function (e) {
+ // Ignore simulated events, since we handle both touch and
+ // mouse explicitly; otherwise we risk getting duplicates of
+ // touch events, see #4315.
+ // Also ignore the event if disabled; this happens in IE11
+ // under some circumstances, see #3666.
+ if (e._simulated || !this._enabled) { return; }
+ this.finishDrag();
+ },
+
+ finishDrag: function () {
+ removeClass(document.body, 'leaflet-dragging');
+
+ if (this._lastTarget) {
+ removeClass(this._lastTarget, 'leaflet-drag-target');
+ this._lastTarget = null;
+ }
+
+ for (var i in MOVE) {
+ off(document, MOVE[i], this._onMove, this);
+ off(document, END[i], this._onUp, this);
+ }
+
+ enableImageDrag();
+ enableTextSelection();
+
+ if (this._moved && this._moving) {
+ // ensure drag is not fired after dragend
+ cancelAnimFrame(this._animRequest);
+
+ // @event dragend: DragEndEvent
+ // Fired when the drag ends.
+ this.fire('dragend', {
+ distance: this._newPos.distanceTo(this._startPos)
+ });
+ }
+
+ this._moving = false;
+ Draggable._dragging = false;
+ }
+
+});
+
+/*
+ * @namespace LineUtil
+ *
+ * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
+ */
+
+// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
+// Improves rendering performance dramatically by lessening the number of points to draw.
+
+// @function simplify(points: Point[], tolerance: Number): Point[]
+// Dramatically reduces the number of points in a polyline while retaining
+// its shape and returns a new array of simplified points, using the
+// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
+// Used for a huge performance boost when processing/displaying Leaflet polylines for
+// each zoom level and also reducing visual noise. tolerance affects the amount of
+// simplification (lesser value means higher quality but slower and with more points).
+// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
+function simplify(points, tolerance) {
+ if (!tolerance || !points.length) {
+ return points.slice();
+ }
+
+ var sqTolerance = tolerance * tolerance;
+
+ // stage 1: vertex reduction
+ points = _reducePoints(points, sqTolerance);
+
+ // stage 2: Douglas-Peucker simplification
+ points = _simplifyDP(points, sqTolerance);
+
+ return points;
+}
+
+// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
+// Returns the distance between point `p` and segment `p1` to `p2`.
+function pointToSegmentDistance(p, p1, p2) {
+ return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
+}
+
+// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
+// Returns the closest point from a point `p` on a segment `p1` to `p2`.
+function closestPointOnSegment(p, p1, p2) {
+ return _sqClosestPointOnSegment(p, p1, p2);
+}
+
+// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
+function _simplifyDP(points, sqTolerance) {
+
+ var len = points.length,
+ ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
+ markers = new ArrayConstructor(len);
+
+ markers[0] = markers[len - 1] = 1;
+
+ _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
+
+ var i,
+ newPoints = [];
+
+ for (i = 0; i < len; i++) {
+ if (markers[i]) {
+ newPoints.push(points[i]);
+ }
+ }
+
+ return newPoints;
+}
+
+function _simplifyDPStep(points, markers, sqTolerance, first, last) {
+
+ var maxSqDist = 0,
+ index, i, sqDist;
+
+ for (i = first + 1; i <= last - 1; i++) {
+ sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
+
+ if (sqDist > maxSqDist) {
+ index = i;
+ maxSqDist = sqDist;
+ }
+ }
+
+ if (maxSqDist > sqTolerance) {
+ markers[index] = 1;
+
+ _simplifyDPStep(points, markers, sqTolerance, first, index);
+ _simplifyDPStep(points, markers, sqTolerance, index, last);
+ }
+}
+
+// reduce points that are too close to each other to a single point
+function _reducePoints(points, sqTolerance) {
+ var reducedPoints = [points[0]];
+
+ for (var i = 1, prev = 0, len = points.length; i < len; i++) {
+ if (_sqDist(points[i], points[prev]) > sqTolerance) {
+ reducedPoints.push(points[i]);
+ prev = i;
+ }
+ }
+ if (prev < len - 1) {
+ reducedPoints.push(points[len - 1]);
+ }
+ return reducedPoints;
+}
+
+var _lastCode;
+
+// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
+// Clips the segment a to b by rectangular bounds with the
+// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
+// (modifying the segment points directly!). Used by Leaflet to only show polyline
+// points that are on the screen or near, increasing performance.
+function clipSegment(a, b, bounds, useLastCode, round) {
+ var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
+ codeB = _getBitCode(b, bounds),
+
+ codeOut, p, newCode;
+
+ // save 2nd code to avoid calculating it on the next segment
+ _lastCode = codeB;
+
+ while (true) {
+ // if a,b is inside the clip window (trivial accept)
+ if (!(codeA | codeB)) {
+ return [a, b];
+ }
+
+ // if a,b is outside the clip window (trivial reject)
+ if (codeA & codeB) {
+ return false;
+ }
+
+ // other cases
+ codeOut = codeA || codeB;
+ p = _getEdgeIntersection(a, b, codeOut, bounds, round);
+ newCode = _getBitCode(p, bounds);
+
+ if (codeOut === codeA) {
+ a = p;
+ codeA = newCode;
+ } else {
+ b = p;
+ codeB = newCode;
+ }
+ }
+}
+
+function _getEdgeIntersection(a, b, code, bounds, round) {
+ var dx = b.x - a.x,
+ dy = b.y - a.y,
+ min = bounds.min,
+ max = bounds.max,
+ x, y;
+
+ if (code & 8) { // top
+ x = a.x + dx * (max.y - a.y) / dy;
+ y = max.y;
+
+ } else if (code & 4) { // bottom
+ x = a.x + dx * (min.y - a.y) / dy;
+ y = min.y;
+
+ } else if (code & 2) { // right
+ x = max.x;
+ y = a.y + dy * (max.x - a.x) / dx;
+
+ } else if (code & 1) { // left
+ x = min.x;
+ y = a.y + dy * (min.x - a.x) / dx;
+ }
+
+ return new Point(x, y, round);
+}
+
+function _getBitCode(p, bounds) {
+ var code = 0;
+
+ if (p.x < bounds.min.x) { // left
+ code |= 1;
+ } else if (p.x > bounds.max.x) { // right
+ code |= 2;
+ }
+
+ if (p.y < bounds.min.y) { // bottom
+ code |= 4;
+ } else if (p.y > bounds.max.y) { // top
+ code |= 8;
+ }
+
+ return code;
+}
+
+// square distance (to avoid unnecessary Math.sqrt calls)
+function _sqDist(p1, p2) {
+ var dx = p2.x - p1.x,
+ dy = p2.y - p1.y;
+ return dx * dx + dy * dy;
+}
+
+// return closest point on segment or distance to that point
+function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
+ var x = p1.x,
+ y = p1.y,
+ dx = p2.x - x,
+ dy = p2.y - y,
+ dot = dx * dx + dy * dy,
+ t;
+
+ if (dot > 0) {
+ t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
+
+ if (t > 1) {
+ x = p2.x;
+ y = p2.y;
+ } else if (t > 0) {
+ x += dx * t;
+ y += dy * t;
+ }
+ }
+
+ dx = p.x - x;
+ dy = p.y - y;
+
+ return sqDist ? dx * dx + dy * dy : new Point(x, y);
+}
+
+
+// @function isFlat(latlngs: LatLng[]): Boolean
+// Returns true if `latlngs` is a flat array, false is nested.
+function isFlat(latlngs) {
+ return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
+}
+
+function _flat(latlngs) {
+ console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
+ return isFlat(latlngs);
+}
+
+
+var LineUtil = (Object.freeze || Object)({
+ simplify: simplify,
+ pointToSegmentDistance: pointToSegmentDistance,
+ closestPointOnSegment: closestPointOnSegment,
+ clipSegment: clipSegment,
+ _getEdgeIntersection: _getEdgeIntersection,
+ _getBitCode: _getBitCode,
+ _sqClosestPointOnSegment: _sqClosestPointOnSegment,
+ isFlat: isFlat,
+ _flat: _flat
+});
+
+/*
+ * @namespace PolyUtil
+ * Various utility functions for polygon geometries.
+ */
+
+/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
+ * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
+ * Used by Leaflet to only show polygon points that are on the screen or near, increasing
+ * performance. Note that polygon points needs different algorithm for clipping
+ * than polyline, so there's a separate method for it.
+ */
+function clipPolygon(points, bounds, round) {
+ var clippedPoints,
+ edges = [1, 4, 2, 8],
+ i, j, k,
+ a, b,
+ len, edge, p;
+
+ for (i = 0, len = points.length; i < len; i++) {
+ points[i]._code = _getBitCode(points[i], bounds);
+ }
+
+ // for each edge (left, bottom, right, top)
+ for (k = 0; k < 4; k++) {
+ edge = edges[k];
+ clippedPoints = [];
+
+ for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
+ a = points[i];
+ b = points[j];
+
+ // if a is inside the clip window
+ if (!(a._code & edge)) {
+ // if b is outside the clip window (a->b goes out of screen)
+ if (b._code & edge) {
+ p = _getEdgeIntersection(b, a, edge, bounds, round);
+ p._code = _getBitCode(p, bounds);
+ clippedPoints.push(p);
+ }
+ clippedPoints.push(a);
+
+ // else if b is inside the clip window (a->b enters the screen)
+ } else if (!(b._code & edge)) {
+ p = _getEdgeIntersection(b, a, edge, bounds, round);
+ p._code = _getBitCode(p, bounds);
+ clippedPoints.push(p);
+ }
+ }
+ points = clippedPoints;
+ }
+
+ return points;
+}
+
+
+var PolyUtil = (Object.freeze || Object)({
+ clipPolygon: clipPolygon
+});
+
+/*
+ * @namespace Projection
+ * @section
+ * Leaflet comes with a set of already defined Projections out of the box:
+ *
+ * @projection L.Projection.LonLat
+ *
+ * Equirectangular, or Plate Carree projection — the most simple projection,
+ * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
+ * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
+ * `EPSG:4326` and `Simple` CRS.
+ */
+
+var LonLat = {
+ project: function (latlng) {
+ return new Point(latlng.lng, latlng.lat);
+ },
+
+ unproject: function (point) {
+ return new LatLng(point.y, point.x);
+ },
+
+ bounds: new Bounds([-180, -90], [180, 90])
+};
+
+/*
+ * @namespace Projection
+ * @projection L.Projection.Mercator
+ *
+ * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
+ */
+
+var Mercator = {
+ R: 6378137,
+ R_MINOR: 6356752.314245179,
+
+ bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
+
+ project: function (latlng) {
+ var d = Math.PI / 180,
+ r = this.R,
+ y = latlng.lat * d,
+ tmp = this.R_MINOR / r,
+ e = Math.sqrt(1 - tmp * tmp),
+ con = e * Math.sin(y);
+
+ var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
+ y = -r * Math.log(Math.max(ts, 1E-10));
+
+ return new Point(latlng.lng * d * r, y);
+ },
+
+ unproject: function (point) {
+ var d = 180 / Math.PI,
+ r = this.R,
+ tmp = this.R_MINOR / r,
+ e = Math.sqrt(1 - tmp * tmp),
+ ts = Math.exp(-point.y / r),
+ phi = Math.PI / 2 - 2 * Math.atan(ts);
+
+ for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
+ con = e * Math.sin(phi);
+ con = Math.pow((1 - con) / (1 + con), e / 2);
+ dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
+ phi += dphi;
+ }
+
+ return new LatLng(phi * d, point.x * d / r);
+ }
+};
+
+/*
+ * @class Projection
+
+ * An object with methods for projecting geographical coordinates of the world onto
+ * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
+
+ * @property bounds: Bounds
+ * The bounds (specified in CRS units) where the projection is valid
+
+ * @method project(latlng: LatLng): Point
+ * Projects geographical coordinates into a 2D point.
+ * Only accepts actual `L.LatLng` instances, not arrays.
+
+ * @method unproject(point: Point): LatLng
+ * The inverse of `project`. Projects a 2D point into a geographical location.
+ * Only accepts actual `L.Point` instances, not arrays.
+
+ * Note that the projection instances do not inherit from Leafet's `Class` object,
+ * and can't be instantiated. Also, new classes can't inherit from them,
+ * and methods can't be added to them with the `include` function.
+
+ */
+
+
+
+
+var index = (Object.freeze || Object)({
+ LonLat: LonLat,
+ Mercator: Mercator,
+ SphericalMercator: SphericalMercator
+});
+
+/*
+ * @namespace CRS
+ * @crs L.CRS.EPSG3395
+ *
+ * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
+ */
+var EPSG3395 = extend({}, Earth, {
+ code: 'EPSG:3395',
+ projection: Mercator,
+
+ transformation: (function () {
+ var scale = 0.5 / (Math.PI * Mercator.R);
+ return toTransformation(scale, 0.5, -scale, 0.5);
+ }())
+});
+
+/*
+ * @namespace CRS
+ * @crs L.CRS.EPSG4326
+ *
+ * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
+ *
+ * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
+ * which is a breaking change from 0.7.x behaviour. If you are using a `TileLayer`
+ * with this CRS, ensure that there are two 256x256 pixel tiles covering the
+ * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
+ * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
+ */
+
+var EPSG4326 = extend({}, Earth, {
+ code: 'EPSG:4326',
+ projection: LonLat,
+ transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
+});
+
+/*
+ * @namespace CRS
+ * @crs L.CRS.Simple
+ *
+ * A simple CRS that maps longitude and latitude into `x` and `y` directly.
+ * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
+ * axis should still be inverted (going from bottom to top). `distance()` returns
+ * simple euclidean distance.
+ */
+
+var Simple = extend({}, CRS, {
+ projection: LonLat,
+ transformation: toTransformation(1, 0, -1, 0),
+
+ scale: function (zoom) {
+ return Math.pow(2, zoom);
+ },
+
+ zoom: function (scale) {
+ return Math.log(scale) / Math.LN2;
+ },
+
+ distance: function (latlng1, latlng2) {
+ var dx = latlng2.lng - latlng1.lng,
+ dy = latlng2.lat - latlng1.lat;
+
+ return Math.sqrt(dx * dx + dy * dy);
+ },
+
+ infinite: true
+});
+
+CRS.Earth = Earth;
+CRS.EPSG3395 = EPSG3395;
+CRS.EPSG3857 = EPSG3857;
+CRS.EPSG900913 = EPSG900913;
+CRS.EPSG4326 = EPSG4326;
+CRS.Simple = Simple;
+
+/*
+ * @class Layer
+ * @inherits Evented
+ * @aka L.Layer
+ * @aka ILayer
+ *
+ * A set of methods from the Layer base class that all Leaflet layers use.
+ * Inherits all methods, options and events from `L.Evented`.
+ *
+ * @example
+ *
+ * ```js
+ * var layer = L.marker(latlng).addTo(map);
+ * layer.addTo(map);
+ * layer.remove();
+ * ```
+ *
+ * @event add: Event
+ * Fired after the layer is added to a map
+ *
+ * @event remove: Event
+ * Fired after the layer is removed from a map
+ */
+
+
+var Layer = Evented.extend({
+
+ // Classes extending `L.Layer` will inherit the following options:
+ options: {
+ // @option pane: String = 'overlayPane'
+ // By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
+ pane: 'overlayPane',
+
+ // @option attribution: String = null
+ // String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers.
+ attribution: null,
+
+ bubblingMouseEvents: true
+ },
+
+ /* @section
+ * Classes extending `L.Layer` will inherit the following methods:
+ *
+ * @method addTo(map: Map|LayerGroup): this
+ * Adds the layer to the given map or layer group.
+ */
+ addTo: function (map) {
+ map.addLayer(this);
+ return this;
+ },
+
+ // @method remove: this
+ // Removes the layer from the map it is currently active on.
+ remove: function () {
+ return this.removeFrom(this._map || this._mapToAdd);
+ },
+
+ // @method removeFrom(map: Map): this
+ // Removes the layer from the given map
+ removeFrom: function (obj) {
+ if (obj) {
+ obj.removeLayer(this);
+ }
+ return this;
+ },
+
+ // @method getPane(name? : String): HTMLElement
+ // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
+ getPane: function (name) {
+ return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
+ },
+
+ addInteractiveTarget: function (targetEl) {
+ this._map._targets[stamp(targetEl)] = this;
+ return this;
+ },
+
+ removeInteractiveTarget: function (targetEl) {
+ delete this._map._targets[stamp(targetEl)];
+ return this;
+ },
+
+ // @method getAttribution: String
+ // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
+ getAttribution: function () {
+ return this.options.attribution;
+ },
+
+ _layerAdd: function (e) {
+ var map = e.target;
+
+ // check in case layer gets added and then removed before the map is ready
+ if (!map.hasLayer(this)) { return; }
+
+ this._map = map;
+ this._zoomAnimated = map._zoomAnimated;
+
+ if (this.getEvents) {
+ var events = this.getEvents();
+ map.on(events, this);
+ this.once('remove', function () {
+ map.off(events, this);
+ }, this);
+ }
+
+ this.onAdd(map);
+
+ if (this.getAttribution && map.attributionControl) {
+ map.attributionControl.addAttribution(this.getAttribution());
+ }
+
+ this.fire('add');
+ map.fire('layeradd', {layer: this});
+ }
+});
+
+/* @section Extension methods
+ * @uninheritable
+ *
+ * Every layer should extend from `L.Layer` and (re-)implement the following methods.
+ *
+ * @method onAdd(map: Map): this
+ * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
+ *
+ * @method onRemove(map: Map): this
+ * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
+ *
+ * @method getEvents(): Object
+ * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
+ *
+ * @method getAttribution(): String
+ * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
+ *
+ * @method beforeAdd(map: Map): this
+ * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
+ */
+
+
+/* @namespace Map
+ * @section Layer events
+ *
+ * @event layeradd: LayerEvent
+ * Fired when a new layer is added to the map.
+ *
+ * @event layerremove: LayerEvent
+ * Fired when some layer is removed from the map
+ *
+ * @section Methods for Layers and Controls
+ */
+Map.include({
+ // @method addLayer(layer: Layer): this
+ // Adds the given layer to the map
+ addLayer: function (layer) {
+ if (!layer._layerAdd) {
+ throw new Error('The provided object is not a Layer.');
+ }
+
+ var id = stamp(layer);
+ if (this._layers[id]) { return this; }
+ this._layers[id] = layer;
+
+ layer._mapToAdd = this;
+
+ if (layer.beforeAdd) {
+ layer.beforeAdd(this);
+ }
+
+ this.whenReady(layer._layerAdd, layer);
+
+ return this;
+ },
+
+ // @method removeLayer(layer: Layer): this
+ // Removes the given layer from the map.
+ removeLayer: function (layer) {
+ var id = stamp(layer);
+
+ if (!this._layers[id]) { return this; }
+
+ if (this._loaded) {
+ layer.onRemove(this);
+ }
+
+ if (layer.getAttribution && this.attributionControl) {
+ this.attributionControl.removeAttribution(layer.getAttribution());
+ }
+
+ delete this._layers[id];
+
+ if (this._loaded) {
+ this.fire('layerremove', {layer: layer});
+ layer.fire('remove');
+ }
+
+ layer._map = layer._mapToAdd = null;
+
+ return this;
+ },
+
+ // @method hasLayer(layer: Layer): Boolean
+ // Returns `true` if the given layer is currently added to the map
+ hasLayer: function (layer) {
+ return !!layer && (stamp(layer) in this._layers);
+ },
+
+ /* @method eachLayer(fn: Function, context?: Object): this
+ * Iterates over the layers of the map, optionally specifying context of the iterator function.
+ * ```
+ * map.eachLayer(function(layer){
+ * layer.bindPopup('Hello');
+ * });
+ * ```
+ */
+ eachLayer: function (method, context) {
+ for (var i in this._layers) {
+ method.call(context, this._layers[i]);
+ }
+ return this;
+ },
+
+ _addLayers: function (layers) {
+ layers = layers ? (isArray(layers) ? layers : [layers]) : [];
+
+ for (var i = 0, len = layers.length; i < len; i++) {
+ this.addLayer(layers[i]);
+ }
+ },
+
+ _addZoomLimit: function (layer) {
+ if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
+ this._zoomBoundLayers[stamp(layer)] = layer;
+ this._updateZoomLevels();
+ }
+ },
+
+ _removeZoomLimit: function (layer) {
+ var id = stamp(layer);
+
+ if (this._zoomBoundLayers[id]) {
+ delete this._zoomBoundLayers[id];
+ this._updateZoomLevels();
+ }
+ },
+
+ _updateZoomLevels: function () {
+ var minZoom = Infinity,
+ maxZoom = -Infinity,
+ oldZoomSpan = this._getZoomSpan();
+
+ for (var i in this._zoomBoundLayers) {
+ var options = this._zoomBoundLayers[i].options;
+
+ minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
+ maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
+ }
+
+ this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
+ this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
+
+ // @section Map state change events
+ // @event zoomlevelschange: Event
+ // Fired when the number of zoomlevels on the map is changed due
+ // to adding or removing a layer.
+ if (oldZoomSpan !== this._getZoomSpan()) {
+ this.fire('zoomlevelschange');
+ }
+
+ if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
+ this.setZoom(this._layersMaxZoom);
+ }
+ if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
+ this.setZoom(this._layersMinZoom);
+ }
+ }
+});
+
+/*
+ * @class LayerGroup
+ * @aka L.LayerGroup
+ * @inherits Layer
+ *
+ * Used to group several layers and handle them as one. If you add it to the map,
+ * any layers added or removed from the group will be added/removed on the map as
+ * well. Extends `Layer`.
+ *
+ * @example
+ *
+ * ```js
+ * L.layerGroup([marker1, marker2])
+ * .addLayer(polyline)
+ * .addTo(map);
+ * ```
+ */
+
+var LayerGroup = Layer.extend({
+
+ initialize: function (layers, options) {
+ setOptions(this, options);
+
+ this._layers = {};
+
+ var i, len;
+
+ if (layers) {
+ for (i = 0, len = layers.length; i < len; i++) {
+ this.addLayer(layers[i]);
+ }
+ }
+ },
+
+ // @method addLayer(layer: Layer): this
+ // Adds the given layer to the group.
+ addLayer: function (layer) {
+ var id = this.getLayerId(layer);
+
+ this._layers[id] = layer;
+
+ if (this._map) {
+ this._map.addLayer(layer);
+ }
+
+ return this;
+ },
+
+ // @method removeLayer(layer: Layer): this
+ // Removes the given layer from the group.
+ // @alternative
+ // @method removeLayer(id: Number): this
+ // Removes the layer with the given internal ID from the group.
+ removeLayer: function (layer) {
+ var id = layer in this._layers ? layer : this.getLayerId(layer);
+
+ if (this._map && this._layers[id]) {
+ this._map.removeLayer(this._layers[id]);
+ }
+
+ delete this._layers[id];
+
+ return this;
+ },
+
+ // @method hasLayer(layer: Layer): Boolean
+ // Returns `true` if the given layer is currently added to the group.
+ // @alternative
+ // @method hasLayer(id: Number): Boolean
+ // Returns `true` if the given internal ID is currently added to the group.
+ hasLayer: function (layer) {
+ return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
+ },
+
+ // @method clearLayers(): this
+ // Removes all the layers from the group.
+ clearLayers: function () {
+ return this.eachLayer(this.removeLayer, this);
+ },
+
+ // @method invoke(methodName: String, …): this
+ // Calls `methodName` on every layer contained in this group, passing any
+ // additional parameters. Has no effect if the layers contained do not
+ // implement `methodName`.
+ invoke: function (methodName) {
+ var args = Array.prototype.slice.call(arguments, 1),
+ i, layer;
+
+ for (i in this._layers) {
+ layer = this._layers[i];
+
+ if (layer[methodName]) {
+ layer[methodName].apply(layer, args);
+ }
+ }
+
+ return this;
+ },
+
+ onAdd: function (map) {
+ this.eachLayer(map.addLayer, map);
+ },
+
+ onRemove: function (map) {
+ this.eachLayer(map.removeLayer, map);
+ },
+
+ // @method eachLayer(fn: Function, context?: Object): this
+ // Iterates over the layers of the group, optionally specifying context of the iterator function.
+ // ```js
+ // group.eachLayer(function (layer) {
+ // layer.bindPopup('Hello');
+ // });
+ // ```
+ eachLayer: function (method, context) {
+ for (var i in this._layers) {
+ method.call(context, this._layers[i]);
+ }
+ return this;
+ },
+
+ // @method getLayer(id: Number): Layer
+ // Returns the layer with the given internal ID.
+ getLayer: function (id) {
+ return this._layers[id];
+ },
+
+ // @method getLayers(): Layer[]
+ // Returns an array of all the layers added to the group.
+ getLayers: function () {
+ var layers = [];
+ this.eachLayer(layers.push, layers);
+ return layers;
+ },
+
+ // @method setZIndex(zIndex: Number): this
+ // Calls `setZIndex` on every layer contained in this group, passing the z-index.
+ setZIndex: function (zIndex) {
+ return this.invoke('setZIndex', zIndex);
+ },
+
+ // @method getLayerId(layer: Layer): Number
+ // Returns the internal ID for a layer
+ getLayerId: function (layer) {
+ return stamp(layer);
+ }
+});
+
+
+// @factory L.layerGroup(layers?: Layer[], options?: Object)
+// Create a layer group, optionally given an initial set of layers and an `options` object.
+var layerGroup = function (layers, options) {
+ return new LayerGroup(layers, options);
+};
+
+/*
+ * @class FeatureGroup
+ * @aka L.FeatureGroup
+ * @inherits LayerGroup
+ *
+ * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
+ * * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
+ * * Events are propagated to the `FeatureGroup`, so if the group has an event
+ * handler, it will handle events from any of the layers. This includes mouse events
+ * and custom events.
+ * * Has `layeradd` and `layerremove` events
+ *
+ * @example
+ *
+ * ```js
+ * L.featureGroup([marker1, marker2, polyline])
+ * .bindPopup('Hello world!')
+ * .on('click', function() { alert('Clicked on a member of the group!'); })
+ * .addTo(map);
+ * ```
+ */
+
+var FeatureGroup = LayerGroup.extend({
+
+ addLayer: function (layer) {
+ if (this.hasLayer(layer)) {
+ return this;
+ }
+
+ layer.addEventParent(this);
+
+ LayerGroup.prototype.addLayer.call(this, layer);
+
+ // @event layeradd: LayerEvent
+ // Fired when a layer is added to this `FeatureGroup`
+ return this.fire('layeradd', {layer: layer});
+ },
+
+ removeLayer: function (layer) {
+ if (!this.hasLayer(layer)) {
+ return this;
+ }
+ if (layer in this._layers) {
+ layer = this._layers[layer];
+ }
+
+ layer.removeEventParent(this);
+
+ LayerGroup.prototype.removeLayer.call(this, layer);
+
+ // @event layerremove: LayerEvent
+ // Fired when a layer is removed from this `FeatureGroup`
+ return this.fire('layerremove', {layer: layer});
+ },
+
+ // @method setStyle(style: Path options): this
+ // Sets the given path options to each layer of the group that has a `setStyle` method.
+ setStyle: function (style) {
+ return this.invoke('setStyle', style);
+ },
+
+ // @method bringToFront(): this
+ // Brings the layer group to the top of all other layers
+ bringToFront: function () {
+ return this.invoke('bringToFront');
+ },
+
+ // @method bringToBack(): this
+ // Brings the layer group to the back of all other layers
+ bringToBack: function () {
+ return this.invoke('bringToBack');
+ },
+
+ // @method getBounds(): LatLngBounds
+ // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
+ getBounds: function () {
+ var bounds = new LatLngBounds();
+
+ for (var id in this._layers) {
+ var layer = this._layers[id];
+ bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
+ }
+ return bounds;
+ }
+});
+
+// @factory L.featureGroup(layers: Layer[])
+// Create a feature group, optionally given an initial set of layers.
+var featureGroup = function (layers) {
+ return new FeatureGroup(layers);
+};
+
+/*
+ * @class Icon
+ * @aka L.Icon
+ *
+ * Represents an icon to provide when creating a marker.
+ *
+ * @example
+ *
+ * ```js
+ * var myIcon = L.icon({
+ * iconUrl: 'my-icon.png',
+ * iconRetinaUrl: 'my-icon@2x.png',
+ * iconSize: [38, 95],
+ * iconAnchor: [22, 94],
+ * popupAnchor: [-3, -76],
+ * shadowUrl: 'my-icon-shadow.png',
+ * shadowRetinaUrl: 'my-icon-shadow@2x.png',
+ * shadowSize: [68, 95],
+ * shadowAnchor: [22, 94]
+ * });
+ *
+ * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
+ * ```
+ *
+ * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
+ *
+ */
+
+var Icon = Class.extend({
+
+ /* @section
+ * @aka Icon options
+ *
+ * @option iconUrl: String = null
+ * **(required)** The URL to the icon image (absolute or relative to your script path).
+ *
+ * @option iconRetinaUrl: String = null
+ * The URL to a retina sized version of the icon image (absolute or relative to your
+ * script path). Used for Retina screen devices.
+ *
+ * @option iconSize: Point = null
+ * Size of the icon image in pixels.
+ *
+ * @option iconAnchor: Point = null
+ * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
+ * will be aligned so that this point is at the marker's geographical location. Centered
+ * by default if size is specified, also can be set in CSS with negative margins.
+ *
+ * @option popupAnchor: Point = [0, 0]
+ * The coordinates of the point from which popups will "open", relative to the icon anchor.
+ *
+ * @option tooltipAnchor: Point = [0, 0]
+ * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
+ *
+ * @option shadowUrl: String = null
+ * The URL to the icon shadow image. If not specified, no shadow image will be created.
+ *
+ * @option shadowRetinaUrl: String = null
+ *
+ * @option shadowSize: Point = null
+ * Size of the shadow image in pixels.
+ *
+ * @option shadowAnchor: Point = null
+ * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
+ * as iconAnchor if not specified).
+ *
+ * @option className: String = ''
+ * A custom class name to assign to both icon and shadow images. Empty by default.
+ */
+
+ options: {
+ popupAnchor: [0, 0],
+ tooltipAnchor: [0, 0]
+ },
+
+ initialize: function (options) {
+ setOptions(this, options);
+ },
+
+ // @method createIcon(oldIcon?: HTMLElement): HTMLElement
+ // Called internally when the icon has to be shown, returns a `<img>` HTML element
+ // styled according to the options.
+ createIcon: function (oldIcon) {
+ return this._createIcon('icon', oldIcon);
+ },
+
+ // @method createShadow(oldIcon?: HTMLElement): HTMLElement
+ // As `createIcon`, but for the shadow beneath it.
+ createShadow: function (oldIcon) {
+ return this._createIcon('shadow', oldIcon);
+ },
+
+ _createIcon: function (name, oldIcon) {
+ var src = this._getIconUrl(name);
+
+ if (!src) {
+ if (name === 'icon') {
+ throw new Error('iconUrl not set in Icon options (see the docs).');
+ }
+ return null;
+ }
+
+ var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
+ this._setIconStyles(img, name);
+
+ return img;
+ },
+
+ _setIconStyles: function (img, name) {
+ var options = this.options;
+ var sizeOption = options[name + 'Size'];
+
+ if (typeof sizeOption === 'number') {
+ sizeOption = [sizeOption, sizeOption];
+ }
+
+ var size = toPoint(sizeOption),
+ anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
+ size && size.divideBy(2, true));
+
+ img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
+
+ if (anchor) {
+ img.style.marginLeft = (-anchor.x) + 'px';
+ img.style.marginTop = (-anchor.y) + 'px';
+ }
+
+ if (size) {
+ img.style.width = size.x + 'px';
+ img.style.height = size.y + 'px';
+ }
+ },
+
+ _createImg: function (src, el) {
+ el = el || document.createElement('img');
+ el.src = src;
+ return el;
+ },
+
+ _getIconUrl: function (name) {
+ return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
+ }
+});
+
+
+// @factory L.icon(options: Icon options)
+// Creates an icon instance with the given options.
+function icon(options) {
+ return new Icon(options);
+}
+
+/*
+ * @miniclass Icon.Default (Icon)
+ * @aka L.Icon.Default
+ * @section
+ *
+ * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
+ * no icon is specified. Points to the blue marker image distributed with Leaflet
+ * releases.
+ *
+ * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
+ * (which is a set of `Icon options`).
+ *
+ * If you want to _completely_ replace the default icon, override the
+ * `L.Marker.prototype.options.icon` with your own icon instead.
+ */
+
+var IconDefault = Icon.extend({
+
+ options: {
+ iconUrl: 'marker-icon.png',
+ iconRetinaUrl: 'marker-icon-2x.png',
+ shadowUrl: 'marker-shadow.png',
+ iconSize: [25, 41],
+ iconAnchor: [12, 41],
+ popupAnchor: [1, -34],
+ tooltipAnchor: [16, -28],
+ shadowSize: [41, 41]
+ },
+
+ _getIconUrl: function (name) {
+ if (!IconDefault.imagePath) { // Deprecated, backwards-compatibility only
+ IconDefault.imagePath = this._detectIconPath();
+ }
+
+ // @option imagePath: String
+ // `Icon.Default` will try to auto-detect the location of the
+ // blue icon images. If you are placing these images in a non-standard
+ // way, set this option to point to the right path.
+ return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
+ },
+
+ _detectIconPath: function () {
+ var el = create$1('div', 'leaflet-default-icon-path', document.body);
+ var path = getStyle(el, 'background-image') ||
+ getStyle(el, 'backgroundImage'); // IE8
+
+ document.body.removeChild(el);
+
+ if (path === null || path.indexOf('url') !== 0) {
+ path = '';
+ } else {
+ path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
+ }
+
+ return path;
+ }
+});
+
+/*
+ * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
+ */
+
+
+/* @namespace Marker
+ * @section Interaction handlers
+ *
+ * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example:
+ *
+ * ```js
+ * marker.dragging.disable();
+ * ```
+ *
+ * @property dragging: Handler
+ * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
+ */
+
+var MarkerDrag = Handler.extend({
+ initialize: function (marker) {
+ this._marker = marker;
+ },
+
+ addHooks: function () {
+ var icon = this._marker._icon;
+
+ if (!this._draggable) {
+ this._draggable = new Draggable(icon, icon, true);
+ }
+
+ this._draggable.on({
+ dragstart: this._onDragStart,
+ predrag: this._onPreDrag,
+ drag: this._onDrag,
+ dragend: this._onDragEnd
+ }, this).enable();
+
+ addClass(icon, 'leaflet-marker-draggable');
+ },
+
+ removeHooks: function () {
+ this._draggable.off({
+ dragstart: this._onDragStart,
+ predrag: this._onPreDrag,
+ drag: this._onDrag,
+ dragend: this._onDragEnd
+ }, this).disable();
+
+ if (this._marker._icon) {
+ removeClass(this._marker._icon, 'leaflet-marker-draggable');
+ }
+ },
+
+ moved: function () {
+ return this._draggable && this._draggable._moved;
+ },
+
+ _adjustPan: function (e) {
+ var marker = this._marker,
+ map = marker._map,
+ speed = this._marker.options.autoPanSpeed,
+ padding = this._marker.options.autoPanPadding,
+ iconPos = getPosition(marker._icon),
+ bounds = map.getPixelBounds(),
+ origin = map.getPixelOrigin();
+
+ var panBounds = toBounds(
+ bounds.min._subtract(origin).add(padding),
+ bounds.max._subtract(origin).subtract(padding)
+ );
+
+ if (!panBounds.contains(iconPos)) {
+ // Compute incremental movement
+ var movement = toPoint(
+ (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
+ (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
+
+ (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
+ (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
+ ).multiplyBy(speed);
+
+ map.panBy(movement, {animate: false});
+
+ this._draggable._newPos._add(movement);
+ this._draggable._startPos._add(movement);
+
+ setPosition(marker._icon, this._draggable._newPos);
+ this._onDrag(e);
+
+ this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
+ }
+ },
+
+ _onDragStart: function () {
+ // @section Dragging events
+ // @event dragstart: Event
+ // Fired when the user starts dragging the marker.
+
+ // @event movestart: Event
+ // Fired when the marker starts moving (because of dragging).
+
+ this._oldLatLng = this._marker.getLatLng();
+ this._marker
+ .closePopup()
+ .fire('movestart')
+ .fire('dragstart');
+ },
+
+ _onPreDrag: function (e) {
+ if (this._marker.options.autoPan) {
+ cancelAnimFrame(this._panRequest);
+ this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
+ }
+ },
+
+ _onDrag: function (e) {
+ var marker = this._marker,
+ shadow = marker._shadow,
+ iconPos = getPosition(marker._icon),
+ latlng = marker._map.layerPointToLatLng(iconPos);
+
+ // update shadow position
+ if (shadow) {
+ setPosition(shadow, iconPos);
+ }
+
+ marker._latlng = latlng;
+ e.latlng = latlng;
+ e.oldLatLng = this._oldLatLng;
+
+ // @event drag: Event
+ // Fired repeatedly while the user drags the marker.
+ marker
+ .fire('move', e)
+ .fire('drag', e);
+ },
+
+ _onDragEnd: function (e) {
+ // @event dragend: DragEndEvent
+ // Fired when the user stops dragging the marker.
+
+ cancelAnimFrame(this._panRequest);
+
+ // @event moveend: Event
+ // Fired when the marker stops moving (because of dragging).
+ delete this._oldLatLng;
+ this._marker
+ .fire('moveend')
+ .fire('dragend', e);
+ }
+});
+
+/*
+ * @class Marker
+ * @inherits Interactive layer
+ * @aka L.Marker
+ * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
+ *
+ * @example
+ *
+ * ```js
+ * L.marker([50.5, 30.5]).addTo(map);
+ * ```
+ */
+
+var Marker = Layer.extend({
+
+ // @section
+ // @aka Marker options
+ options: {
+ // @option icon: Icon = *
+ // Icon instance to use for rendering the marker.
+ // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
+ // If not specified, a common instance of `L.Icon.Default` is used.
+ icon: new IconDefault(),
+
+ // Option inherited from "Interactive layer" abstract class
+ interactive: true,
+
+ // @option keyboard: Boolean = true
+ // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
+ keyboard: true,
+
+ // @option title: String = ''
+ // Text for the browser tooltip that appear on marker hover (no tooltip by default).
+ title: '',
+
+ // @option alt: String = ''
+ // Text for the `alt` attribute of the icon image (useful for accessibility).
+ alt: '',
+
+ // @option zIndexOffset: Number = 0
+ // By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively).
+ zIndexOffset: 0,
+
+ // @option opacity: Number = 1.0
+ // The opacity of the marker.
+ opacity: 1,
+
+ // @option riseOnHover: Boolean = false
+ // If `true`, the marker will get on top of others when you hover the mouse over it.
+ riseOnHover: false,
+
+ // @option riseOffset: Number = 250
+ // The z-index offset used for the `riseOnHover` feature.
+ riseOffset: 250,
+
+ // @option pane: String = 'markerPane'
+ // `Map pane` where the markers icon will be added.
+ pane: 'markerPane',
+
+ // @option pane: String = 'shadowPane'
+ // `Map pane` where the markers shadow will be added.
+ shadowPane: 'shadowPane',
+
+ // @option bubblingMouseEvents: Boolean = false
+ // When `true`, a mouse event on this marker will trigger the same event on the map
+ // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
+ bubblingMouseEvents: false,
+
+ // @section Draggable marker options
+ // @option draggable: Boolean = false
+ // Whether the marker is draggable with mouse/touch or not.
+ draggable: false,
+
+ // @option autoPan: Boolean = false
+ // Whether to pan the map when dragging this marker near its edge or not.
+ autoPan: false,
+
+ // @option autoPanPadding: Point = Point(50, 50)
+ // Distance (in pixels to the left/right and to the top/bottom) of the
+ // map edge to start panning the map.
+ autoPanPadding: [50, 50],
+
+ // @option autoPanSpeed: Number = 10
+ // Number of pixels the map should pan by.
+ autoPanSpeed: 10
+ },
+
+ /* @section
+ *
+ * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
+ */
+
+ initialize: function (latlng, options) {
+ setOptions(this, options);
+ this._latlng = toLatLng(latlng);
+ },
+
+ onAdd: function (map) {
+ this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
+
+ if (this._zoomAnimated) {
+ map.on('zoomanim', this._animateZoom, this);
+ }
+
+ this._initIcon();
+ this.update();
+ },
+
+ onRemove: function (map) {
+ if (this.dragging && this.dragging.enabled()) {
+ this.options.draggable = true;
+ this.dragging.removeHooks();
+ }
+ delete this.dragging;
+
+ if (this._zoomAnimated) {
+ map.off('zoomanim', this._animateZoom, this);
+ }
+
+ this._removeIcon();
+ this._removeShadow();
+ },
+
+ getEvents: function () {
+ return {
+ zoom: this.update,
+ viewreset: this.update
+ };
+ },
+
+ // @method getLatLng: LatLng
+ // Returns the current geographical position of the marker.
+ getLatLng: function () {
+ return this._latlng;
+ },
+
+ // @method setLatLng(latlng: LatLng): this
+ // Changes the marker position to the given point.
+ setLatLng: function (latlng) {
+ var oldLatLng = this._latlng;
+ this._latlng = toLatLng(latlng);
+ this.update();
+
+ // @event move: Event
+ // Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
+ return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
+ },
+
+ // @method setZIndexOffset(offset: Number): this
+ // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
+ setZIndexOffset: function (offset) {
+ this.options.zIndexOffset = offset;
+ return this.update();
+ },
+
+ // @method getIcon: Icon
+ // Returns the current icon used by the marker
+ getIcon: function () {
+ return this.options.icon;
+ },
+
+ // @method setIcon(icon: Icon): this
+ // Changes the marker icon.
+ setIcon: function (icon) {
+
+ this.options.icon = icon;
+
+ if (this._map) {
+ this._initIcon();
+ this.update();
+ }
+
+ if (this._popup) {
+ this.bindPopup(this._popup, this._popup.options);
+ }
+
+ return this;
+ },
+
+ getElement: function () {
+ return this._icon;
+ },
+
+ update: function () {
+
+ if (this._icon && this._map) {
+ var pos = this._map.latLngToLayerPoint(this._latlng).round();
+ this._setPos(pos);
+ }
+
+ return this;
+ },
+
+ _initIcon: function () {
+ var options = this.options,
+ classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
+
+ var icon = options.icon.createIcon(this._icon),
+ addIcon = false;
+
+ // if we're not reusing the icon, remove the old one and init new one
+ if (icon !== this._icon) {
+ if (this._icon) {
+ this._removeIcon();
+ }
+ addIcon = true;
+
+ if (options.title) {
+ icon.title = options.title;
+ }
+
+ if (icon.tagName === 'IMG') {
+ icon.alt = options.alt || '';
+ }
+ }
+
+ addClass(icon, classToAdd);
+
+ if (options.keyboard) {
+ icon.tabIndex = '0';
+ }
+
+ this._icon = icon;
+
+ if (options.riseOnHover) {
+ this.on({
+ mouseover: this._bringToFront,
+ mouseout: this._resetZIndex
+ });
+ }
+
+ var newShadow = options.icon.createShadow(this._shadow),
+ addShadow = false;
+
+ if (newShadow !== this._shadow) {
+ this._removeShadow();
+ addShadow = true;
+ }
+
+ if (newShadow) {
+ addClass(newShadow, classToAdd);
+ newShadow.alt = '';
+ }
+ this._shadow = newShadow;
+
+
+ if (options.opacity < 1) {
+ this._updateOpacity();
+ }
+
+
+ if (addIcon) {
+ this.getPane().appendChild(this._icon);
+ }
+ this._initInteraction();
+ if (newShadow && addShadow) {
+ this.getPane(options.shadowPane).appendChild(this._shadow);
+ }
+ },
+
+ _removeIcon: function () {
+ if (this.options.riseOnHover) {
+ this.off({
+ mouseover: this._bringToFront,
+ mouseout: this._resetZIndex
+ });
+ }
+
+ remove(this._icon);
+ this.removeInteractiveTarget(this._icon);
+
+ this._icon = null;
+ },
+
+ _removeShadow: function () {
+ if (this._shadow) {
+ remove(this._shadow);
+ }
+ this._shadow = null;
+ },
+
+ _setPos: function (pos) {
+
+ if (this._icon) {
+ setPosition(this._icon, pos);
+ }
+
+ if (this._shadow) {
+ setPosition(this._shadow, pos);
+ }
+
+ this._zIndex = pos.y + this.options.zIndexOffset;
+
+ this._resetZIndex();
+ },
+
+ _updateZIndex: function (offset) {
+ if (this._icon) {
+ this._icon.style.zIndex = this._zIndex + offset;
+ }
+ },
+
+ _animateZoom: function (opt) {
+ var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
+
+ this._setPos(pos);
+ },
+
+ _initInteraction: function () {
+
+ if (!this.options.interactive) { return; }
+
+ addClass(this._icon, 'leaflet-interactive');
+
+ this.addInteractiveTarget(this._icon);
+
+ if (MarkerDrag) {
+ var draggable = this.options.draggable;
+ if (this.dragging) {
+ draggable = this.dragging.enabled();
+ this.dragging.disable();
+ }
+
+ this.dragging = new MarkerDrag(this);
+
+ if (draggable) {
+ this.dragging.enable();
+ }
+ }
+ },
+
+ // @method setOpacity(opacity: Number): this
+ // Changes the opacity of the marker.
+ setOpacity: function (opacity) {
+ this.options.opacity = opacity;
+ if (this._map) {
+ this._updateOpacity();
+ }
+
+ return this;
+ },
+
+ _updateOpacity: function () {
+ var opacity = this.options.opacity;
+
+ if (this._icon) {
+ setOpacity(this._icon, opacity);
+ }
+
+ if (this._shadow) {
+ setOpacity(this._shadow, opacity);
+ }
+ },
+
+ _bringToFront: function () {
+ this._updateZIndex(this.options.riseOffset);
+ },
+
+ _resetZIndex: function () {
+ this._updateZIndex(0);
+ },
+
+ _getPopupAnchor: function () {
+ return this.options.icon.options.popupAnchor;
+ },
+
+ _getTooltipAnchor: function () {
+ return this.options.icon.options.tooltipAnchor;
+ }
+});
+
+
+// factory L.marker(latlng: LatLng, options? : Marker options)
+
+// @factory L.marker(latlng: LatLng, options? : Marker options)
+// Instantiates a Marker object given a geographical point and optionally an options object.
+function marker(latlng, options) {
+ return new Marker(latlng, options);
+}
+
+/*
+ * @class Path
+ * @aka L.Path
+ * @inherits Interactive layer
+ *
+ * An abstract class that contains options and constants shared between vector
+ * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
+ */
+
+var Path = Layer.extend({
+
+ // @section
+ // @aka Path options
+ options: {
+ // @option stroke: Boolean = true
+ // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
+ stroke: true,
+
+ // @option color: String = '#3388ff'
+ // Stroke color
+ color: '#3388ff',
+
+ // @option weight: Number = 3
+ // Stroke width in pixels
+ weight: 3,
+
+ // @option opacity: Number = 1.0
+ // Stroke opacity
+ opacity: 1,
+
+ // @option lineCap: String= 'round'
+ // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
+ lineCap: 'round',
+
+ // @option lineJoin: String = 'round'
+ // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
+ lineJoin: 'round',
+
+ // @option dashArray: String = null
+ // A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
+ dashArray: null,
+
+ // @option dashOffset: String = null
+ // A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
+ dashOffset: null,
+
+ // @option fill: Boolean = depends
+ // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
+ fill: false,
+
+ // @option fillColor: String = *
+ // Fill color. Defaults to the value of the [`color`](#path-color) option
+ fillColor: null,
+
+ // @option fillOpacity: Number = 0.2
+ // Fill opacity.
+ fillOpacity: 0.2,
+
+ // @option fillRule: String = 'evenodd'
+ // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
+ fillRule: 'evenodd',
+
+ // className: '',
+
+ // Option inherited from "Interactive layer" abstract class
+ interactive: true,
+
+ // @option bubblingMouseEvents: Boolean = true
+ // When `true`, a mouse event on this path will trigger the same event on the map
+ // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
+ bubblingMouseEvents: true
+ },
+
+ beforeAdd: function (map) {
+ // Renderer is set here because we need to call renderer.getEvents
+ // before this.getEvents.
+ this._renderer = map.getRenderer(this);
+ },
+
+ onAdd: function () {
+ this._renderer._initPath(this);
+ this._reset();
+ this._renderer._addPath(this);
+ },
+
+ onRemove: function () {
+ this._renderer._removePath(this);
+ },
+
+ // @method redraw(): this
+ // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
+ redraw: function () {
+ if (this._map) {
+ this._renderer._updatePath(this);
+ }
+ return this;
+ },
+
+ // @method setStyle(style: Path options): this
+ // Changes the appearance of a Path based on the options in the `Path options` object.
+ setStyle: function (style) {
+ setOptions(this, style);
+ if (this._renderer) {
+ this._renderer._updateStyle(this);
+ if (this.options.stroke && style && style.hasOwnProperty('weight')) {
+ this._updateBounds();
+ }
+ }
+ return this;
+ },
+
+ // @method bringToFront(): this
+ // Brings the layer to the top of all path layers.
+ bringToFront: function () {
+ if (this._renderer) {
+ this._renderer._bringToFront(this);
+ }
+ return this;
+ },
+
+ // @method bringToBack(): this
+ // Brings the layer to the bottom of all path layers.
+ bringToBack: function () {
+ if (this._renderer) {
+ this._renderer._bringToBack(this);
+ }
+ return this;
+ },
+
+ getElement: function () {
+ return this._path;
+ },
+
+ _reset: function () {
+ // defined in child classes
+ this._project();
+ this._update();
+ },
+
+ _clickTolerance: function () {
+ // used when doing hit detection for Canvas layers
+ return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
+ }
+});
+
+/*
+ * @class CircleMarker
+ * @aka L.CircleMarker
+ * @inherits Path
+ *
+ * A circle of a fixed size with radius specified in pixels. Extends `Path`.
+ */
+
+var CircleMarker = Path.extend({
+
+ // @section
+ // @aka CircleMarker options
+ options: {
+ fill: true,
+
+ // @option radius: Number = 10
+ // Radius of the circle marker, in pixels
+ radius: 10
+ },
+
+ initialize: function (latlng, options) {
+ setOptions(this, options);
+ this._latlng = toLatLng(latlng);
+ this._radius = this.options.radius;
+ },
+
+ // @method setLatLng(latLng: LatLng): this
+ // Sets the position of a circle marker to a new location.
+ setLatLng: function (latlng) {
+ var oldLatLng = this._latlng;
+ this._latlng = toLatLng(latlng);
+ this.redraw();
+
+ // @event move: Event
+ // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
+ return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
+ },
+
+ // @method getLatLng(): LatLng
+ // Returns the current geographical position of the circle marker
+ getLatLng: function () {
+ return this._latlng;
+ },
+
+ // @method setRadius(radius: Number): this
+ // Sets the radius of a circle marker. Units are in pixels.
+ setRadius: function (radius) {
+ this.options.radius = this._radius = radius;
+ return this.redraw();
+ },
+
+ // @method getRadius(): Number
+ // Returns the current radius of the circle
+ getRadius: function () {
+ return this._radius;
+ },
+
+ setStyle : function (options) {
+ var radius = options && options.radius || this._radius;
+ Path.prototype.setStyle.call(this, options);
+ this.setRadius(radius);
+ return this;
+ },
+
+ _project: function () {
+ this._point = this._map.latLngToLayerPoint(this._latlng);
+ this._updateBounds();
+ },
+
+ _updateBounds: function () {
+ var r = this._radius,
+ r2 = this._radiusY || r,
+ w = this._clickTolerance(),
+ p = [r + w, r2 + w];
+ this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
+ },
+
+ _update: function () {
+ if (this._map) {
+ this._updatePath();
+ }
+ },
+
+ _updatePath: function () {
+ this._renderer._updateCircle(this);
+ },
+
+ _empty: function () {
+ return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
+ },
+
+ // Needed by the `Canvas` renderer for interactivity
+ _containsPoint: function (p) {
+ return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
+ }
+});
+
+
+// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
+// Instantiates a circle marker object given a geographical point, and an optional options object.
+function circleMarker(latlng, options) {
+ return new CircleMarker(latlng, options);
+}
+
+/*
+ * @class Circle
+ * @aka L.Circle
+ * @inherits CircleMarker
+ *
+ * A class for drawing circle overlays on a map. Extends `CircleMarker`.
+ *
+ * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
+ *
+ * @example
+ *
+ * ```js
+ * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
+ * ```
+ */
+
+var Circle = CircleMarker.extend({
+
+ initialize: function (latlng, options, legacyOptions) {
+ if (typeof options === 'number') {
+ // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
+ options = extend({}, legacyOptions, {radius: options});
+ }
+ setOptions(this, options);
+ this._latlng = toLatLng(latlng);
+
+ if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
+
+ // @section
+ // @aka Circle options
+ // @option radius: Number; Radius of the circle, in meters.
+ this._mRadius = this.options.radius;
+ },
+
+ // @method setRadius(radius: Number): this
+ // Sets the radius of a circle. Units are in meters.
+ setRadius: function (radius) {
+ this._mRadius = radius;
+ return this.redraw();
+ },
+
+ // @method getRadius(): Number
+ // Returns the current radius of a circle. Units are in meters.
+ getRadius: function () {
+ return this._mRadius;
+ },
+
+ // @method getBounds(): LatLngBounds
+ // Returns the `LatLngBounds` of the path.
+ getBounds: function () {
+ var half = [this._radius, this._radiusY || this._radius];
+
+ return new LatLngBounds(
+ this._map.layerPointToLatLng(this._point.subtract(half)),
+ this._map.layerPointToLatLng(this._point.add(half)));
+ },
+
+ setStyle: Path.prototype.setStyle,
+
+ _project: function () {
+
+ var lng = this._latlng.lng,
+ lat = this._latlng.lat,
+ map = this._map,
+ crs = map.options.crs;
+
+ if (crs.distance === Earth.distance) {
+ var d = Math.PI / 180,
+ latR = (this._mRadius / Earth.R) / d,
+ top = map.project([lat + latR, lng]),
+ bottom = map.project([lat - latR, lng]),
+ p = top.add(bottom).divideBy(2),
+ lat2 = map.unproject(p).lat,
+ lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
+ (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
+
+ if (isNaN(lngR) || lngR === 0) {
+ lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
+ }
+
+ this._point = p.subtract(map.getPixelOrigin());
+ this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
+ this._radiusY = p.y - top.y;
+
+ } else {
+ var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
+
+ this._point = map.latLngToLayerPoint(this._latlng);
+ this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
+ }
+
+ this._updateBounds();
+ }
+});
+
+// @factory L.circle(latlng: LatLng, options?: Circle options)
+// Instantiates a circle object given a geographical point, and an options object
+// which contains the circle radius.
+// @alternative
+// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
+// Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
+// Do not use in new applications or plugins.
+function circle(latlng, options, legacyOptions) {
+ return new Circle(latlng, options, legacyOptions);
+}
+
+/*
+ * @class Polyline
+ * @aka L.Polyline
+ * @inherits Path
+ *
+ * A class for drawing polyline overlays on a map. Extends `Path`.
+ *
+ * @example
+ *
+ * ```js
+ * // create a red polyline from an array of LatLng points
+ * var latlngs = [
+ * [45.51, -122.68],
+ * [37.77, -122.43],
+ * [34.04, -118.2]
+ * ];
+ *
+ * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
+ *
+ * // zoom the map to the polyline
+ * map.fitBounds(polyline.getBounds());
+ * ```
+ *
+ * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
+ *
+ * ```js
+ * // create a red polyline from an array of arrays of LatLng points
+ * var latlngs = [
+ * [[45.51, -122.68],
+ * [37.77, -122.43],
+ * [34.04, -118.2]],
+ * [[40.78, -73.91],
+ * [41.83, -87.62],
+ * [32.76, -96.72]]
+ * ];
+ * ```
+ */
+
+
+var Polyline = Path.extend({
+
+ // @section
+ // @aka Polyline options
+ options: {
+ // @option smoothFactor: Number = 1.0
+ // How much to simplify the polyline on each zoom level. More means
+ // better performance and smoother look, and less means more accurate representation.
+ smoothFactor: 1.0,
+
+ // @option noClip: Boolean = false
+ // Disable polyline clipping.
+ noClip: false
+ },
+
+ initialize: function (latlngs, options) {
+ setOptions(this, options);
+ this._setLatLngs(latlngs);
+ },
+
+ // @method getLatLngs(): LatLng[]
+ // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
+ getLatLngs: function () {
+ return this._latlngs;
+ },
+
+ // @method setLatLngs(latlngs: LatLng[]): this
+ // Replaces all the points in the polyline with the given array of geographical points.
+ setLatLngs: function (latlngs) {
+ this._setLatLngs(latlngs);
+ return this.redraw();
+ },
+
+ // @method isEmpty(): Boolean
+ // Returns `true` if the Polyline has no LatLngs.
+ isEmpty: function () {
+ return !this._latlngs.length;
+ },
+
+ // @method closestLayerPoint(p: Point): Point
+ // Returns the point closest to `p` on the Polyline.
+ closestLayerPoint: function (p) {
+ var minDistance = Infinity,
+ minPoint = null,
+ closest = _sqClosestPointOnSegment,
+ p1, p2;
+
+ for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
+ var points = this._parts[j];
+
+ for (var i = 1, len = points.length; i < len; i++) {
+ p1 = points[i - 1];
+ p2 = points[i];
+
+ var sqDist = closest(p, p1, p2, true);
+
+ if (sqDist < minDistance) {
+ minDistance = sqDist;
+ minPoint = closest(p, p1, p2);
+ }
+ }
+ }
+ if (minPoint) {
+ minPoint.distance = Math.sqrt(minDistance);
+ }
+ return minPoint;
+ },
+
+ // @method getCenter(): LatLng
+ // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
+ getCenter: function () {
+ // throws error when not yet added to map as this center calculation requires projected coordinates
+ if (!this._map) {
+ throw new Error('Must add layer to map before using getCenter()');
+ }
+
+ var i, halfDist, segDist, dist, p1, p2, ratio,
+ points = this._rings[0],
+ len = points.length;
+
+ if (!len) { return null; }
+
+ // polyline centroid algorithm; only uses the first ring if there are multiple
+
+ for (i = 0, halfDist = 0; i < len - 1; i++) {
+ halfDist += points[i].distanceTo(points[i + 1]) / 2;
+ }
+
+ // The line is so small in the current view that all points are on the same pixel.
+ if (halfDist === 0) {
+ return this._map.layerPointToLatLng(points[0]);
+ }
+
+ for (i = 0, dist = 0; i < len - 1; i++) {
+ p1 = points[i];
+ p2 = points[i + 1];
+ segDist = p1.distanceTo(p2);
+ dist += segDist;
+
+ if (dist > halfDist) {
+ ratio = (dist - halfDist) / segDist;
+ return this._map.layerPointToLatLng([
+ p2.x - ratio * (p2.x - p1.x),
+ p2.y - ratio * (p2.y - p1.y)
+ ]);
+ }
+ }
+ },
+
+ // @method getBounds(): LatLngBounds
+ // Returns the `LatLngBounds` of the path.
+ getBounds: function () {
+ return this._bounds;
+ },
+
+ // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
+ // Adds a given point to the polyline. By default, adds to the first ring of
+ // the polyline in case of a multi-polyline, but can be overridden by passing
+ // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
+ addLatLng: function (latlng, latlngs) {
+ latlngs = latlngs || this._defaultShape();
+ latlng = toLatLng(latlng);
+ latlngs.push(latlng);
+ this._bounds.extend(latlng);
+ return this.redraw();
+ },
+
+ _setLatLngs: function (latlngs) {
+ this._bounds = new LatLngBounds();
+ this._latlngs = this._convertLatLngs(latlngs);
+ },
+
+ _defaultShape: function () {
+ return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
+ },
+
+ // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
+ _convertLatLngs: function (latlngs) {
+ var result = [],
+ flat = isFlat(latlngs);
+
+ for (var i = 0, len = latlngs.length; i < len; i++) {
+ if (flat) {
+ result[i] = toLatLng(latlngs[i]);
+ this._bounds.extend(result[i]);
+ } else {
+ result[i] = this._convertLatLngs(latlngs[i]);
+ }
+ }
+
+ return result;
+ },
+
+ _project: function () {
+ var pxBounds = new Bounds();
+ this._rings = [];
+ this._projectLatlngs(this._latlngs, this._rings, pxBounds);
+
+ if (this._bounds.isValid() && pxBounds.isValid()) {
+ this._rawPxBounds = pxBounds;
+ this._updateBounds();
+ }
+ },
+
+ _updateBounds: function () {
+ var w = this._clickTolerance(),
+ p = new Point(w, w);
+ this._pxBounds = new Bounds([
+ this._rawPxBounds.min.subtract(p),
+ this._rawPxBounds.max.add(p)
+ ]);
+ },
+
+ // recursively turns latlngs into a set of rings with projected coordinates
+ _projectLatlngs: function (latlngs, result, projectedBounds) {
+ var flat = latlngs[0] instanceof LatLng,
+ len = latlngs.length,
+ i, ring;
+
+ if (flat) {
+ ring = [];
+ for (i = 0; i < len; i++) {
+ ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
+ projectedBounds.extend(ring[i]);
+ }
+ result.push(ring);
+ } else {
+ for (i = 0; i < len; i++) {
+ this._projectLatlngs(latlngs[i], result, projectedBounds);
+ }
+ }
+ },
+
+ // clip polyline by renderer bounds so that we have less to render for performance
+ _clipPoints: function () {
+ var bounds = this._renderer._bounds;
+
+ this._parts = [];
+ if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
+ return;
+ }
+
+ if (this.options.noClip) {
+ this._parts = this._rings;
+ return;
+ }
+
+ var parts = this._parts,
+ i, j, k, len, len2, segment, points;
+
+ for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
+ points = this._rings[i];
+
+ for (j = 0, len2 = points.length; j < len2 - 1; j++) {
+ segment = clipSegment(points[j], points[j + 1], bounds, j, true);
+
+ if (!segment) { continue; }
+
+ parts[k] = parts[k] || [];
+ parts[k].push(segment[0]);
+
+ // if segment goes out of screen, or it's the last one, it's the end of the line part
+ if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
+ parts[k].push(segment[1]);
+ k++;
+ }
+ }
+ }
+ },
+
+ // simplify each clipped part of the polyline for performance
+ _simplifyPoints: function () {
+ var parts = this._parts,
+ tolerance = this.options.smoothFactor;
+
+ for (var i = 0, len = parts.length; i < len; i++) {
+ parts[i] = simplify(parts[i], tolerance);
+ }
+ },
+
+ _update: function () {
+ if (!this._map) { return; }
+
+ this._clipPoints();
+ this._simplifyPoints();
+ this._updatePath();
+ },
+
+ _updatePath: function () {
+ this._renderer._updatePoly(this);
+ },
+
+ // Needed by the `Canvas` renderer for interactivity
+ _containsPoint: function (p, closed) {
+ var i, j, k, len, len2, part,
+ w = this._clickTolerance();
+
+ if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
+
+ // hit detection for polylines
+ for (i = 0, len = this._parts.length; i < len; i++) {
+ part = this._parts[i];
+
+ for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
+ if (!closed && (j === 0)) { continue; }
+
+ if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+});
+
+// @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
+// Instantiates a polyline object given an array of geographical points and
+// optionally an options object. You can create a `Polyline` object with
+// multiple separate lines (`MultiPolyline`) by passing an array of arrays
+// of geographic points.
+function polyline(latlngs, options) {
+ return new Polyline(latlngs, options);
+}
+
+// Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
+Polyline._flat = _flat;
+
+/*
+ * @class Polygon
+ * @aka L.Polygon
+ * @inherits Polyline
+ *
+ * A class for drawing polygon overlays on a map. Extends `Polyline`.
+ *
+ * Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one — it's better to filter out such points.
+ *
+ *
+ * @example
+ *
+ * ```js
+ * // create a red polygon from an array of LatLng points
+ * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
+ *
+ * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
+ *
+ * // zoom the map to the polygon
+ * map.fitBounds(polygon.getBounds());
+ * ```
+ *
+ * You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape:
+ *
+ * ```js
+ * var latlngs = [
+ * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
+ * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
+ * ];
+ * ```
+ *
+ * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
+ *
+ * ```js
+ * var latlngs = [
+ * [ // first polygon
+ * [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
+ * [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
+ * ],
+ * [ // second polygon
+ * [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
+ * ]
+ * ];
+ * ```
+ */
+
+var Polygon = Polyline.extend({
+
+ options: {
+ fill: true
+ },
+
+ isEmpty: function () {
+ return !this._latlngs.length || !this._latlngs[0].length;
+ },
+
+ getCenter: function () {
+ // throws error when not yet added to map as this center calculation requires projected coordinates
+ if (!this._map) {
+ throw new Error('Must add layer to map before using getCenter()');
+ }
+
+ var i, j, p1, p2, f, area, x, y, center,
+ points = this._rings[0],
+ len = points.length;
+
+ if (!len) { return null; }
+
+ // polygon centroid algorithm; only uses the first ring if there are multiple
+
+ area = x = y = 0;
+
+ for (i = 0, j = len - 1; i < len; j = i++) {
+ p1 = points[i];
+ p2 = points[j];
+
+ f = p1.y * p2.x - p2.y * p1.x;
+ x += (p1.x + p2.x) * f;
+ y += (p1.y + p2.y) * f;
+ area += f * 3;
+ }
+
+ if (area === 0) {
+ // Polygon is so small that all points are on same pixel.
+ center = points[0];
+ } else {
+ center = [x / area, y / area];
+ }
+ return this._map.layerPointToLatLng(center);
+ },
+
+ _convertLatLngs: function (latlngs) {
+ var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
+ len = result.length;
+
+ // remove last point if it equals first one
+ if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
+ result.pop();
+ }
+ return result;
+ },
+
+ _setLatLngs: function (latlngs) {
+ Polyline.prototype._setLatLngs.call(this, latlngs);
+ if (isFlat(this._latlngs)) {
+ this._latlngs = [this._latlngs];
+ }
+ },
+
+ _defaultShape: function () {
+ return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
+ },
+
+ _clipPoints: function () {
+ // polygons need a different clipping algorithm so we redefine that
+
+ var bounds = this._renderer._bounds,
+ w = this.options.weight,
+ p = new Point(w, w);
+
+ // increase clip padding by stroke width to avoid stroke on clip edges
+ bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
+
+ this._parts = [];
+ if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
+ return;
+ }
+
+ if (this.options.noClip) {
+ this._parts = this._rings;
+ return;
+ }
+
+ for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
+ clipped = clipPolygon(this._rings[i], bounds, true);
+ if (clipped.length) {
+ this._parts.push(clipped);
+ }
+ }
+ },
+
+ _updatePath: function () {
+ this._renderer._updatePoly(this, true);
+ },
+
+ // Needed by the `Canvas` renderer for interactivity
+ _containsPoint: function (p) {
+ var inside = false,
+ part, p1, p2, i, j, k, len, len2;
+
+ if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
+
+ // ray casting algorithm for detecting if point is in polygon
+ for (i = 0, len = this._parts.length; i < len; i++) {
+ part = this._parts[i];
+
+ for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
+ p1 = part[j];
+ p2 = part[k];
+
+ if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
+ inside = !inside;
+ }
+ }
+ }
+
+ // also check if it's on polygon stroke
+ return inside || Polyline.prototype._containsPoint.call(this, p, true);
+ }
+
+});
+
+
+// @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
+function polygon(latlngs, options) {
+ return new Polygon(latlngs, options);
+}
+
+/*
+ * @class GeoJSON
+ * @aka L.GeoJSON
+ * @inherits FeatureGroup
+ *
+ * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
+ * GeoJSON data and display it on the map. Extends `FeatureGroup`.
+ *
+ * @example
+ *
+ * ```js
+ * L.geoJSON(data, {
+ * style: function (feature) {
+ * return {color: feature.properties.color};
+ * }
+ * }).bindPopup(function (layer) {
+ * return layer.feature.properties.description;
+ * }).addTo(map);
+ * ```
+ */
+
+var GeoJSON = FeatureGroup.extend({
+
+ /* @section
+ * @aka GeoJSON options
+ *
+ * @option pointToLayer: Function = *
+ * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
+ * called when data is added, passing the GeoJSON point feature and its `LatLng`.
+ * The default is to spawn a default `Marker`:
+ * ```js
+ * function(geoJsonPoint, latlng) {
+ * return L.marker(latlng);
+ * }
+ * ```
+ *
+ * @option style: Function = *
+ * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
+ * called internally when data is added.
+ * The default value is to not override any defaults:
+ * ```js
+ * function (geoJsonFeature) {
+ * return {}
+ * }
+ * ```
+ *
+ * @option onEachFeature: Function = *
+ * A `Function` that will be called once for each created `Feature`, after it has
+ * been created and styled. Useful for attaching events and popups to features.
+ * The default is to do nothing with the newly created layers:
+ * ```js
+ * function (feature, layer) {}
+ * ```
+ *
+ * @option filter: Function = *
+ * A `Function` that will be used to decide whether to include a feature or not.
+ * The default is to include all features:
+ * ```js
+ * function (geoJsonFeature) {
+ * return true;
+ * }
+ * ```
+ * Note: dynamically changing the `filter` option will have effect only on newly
+ * added data. It will _not_ re-evaluate already included features.
+ *
+ * @option coordsToLatLng: Function = *
+ * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
+ * The default is the `coordsToLatLng` static method.
+ *
+ * @option markersInheritOptions: Boolean = false
+ * Whether default Markers for "Point" type Features inherit from group options.
+ */
+
+ initialize: function (geojson, options) {
+ setOptions(this, options);
+
+ this._layers = {};
+
+ if (geojson) {
+ this.addData(geojson);
+ }
+ },
+
+ // @method addData( <GeoJSON> data ): this
+ // Adds a GeoJSON object to the layer.
+ addData: function (geojson) {
+ var features = isArray(geojson) ? geojson : geojson.features,
+ i, len, feature;
+
+ if (features) {
+ for (i = 0, len = features.length; i < len; i++) {
+ // only add this if geometry or geometries are set and not null
+ feature = features[i];
+ if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
+ this.addData(feature);
+ }
+ }
+ return this;
+ }
+
+ var options = this.options;
+
+ if (options.filter && !options.filter(geojson)) { return this; }
+
+ var layer = geometryToLayer(geojson, options);
+ if (!layer) {
+ return this;
+ }
+ layer.feature = asFeature(geojson);
+
+ layer.defaultOptions = layer.options;
+ this.resetStyle(layer);
+
+ if (options.onEachFeature) {
+ options.onEachFeature(geojson, layer);
+ }
+
+ return this.addLayer(layer);
+ },
+
+ // @method resetStyle( <Path> layer? ): this
+ // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
+ // If `layer` is omitted, the style of all features in the current layer is reset.
+ resetStyle: function (layer) {
+ if (layer === undefined) {
+ return this.eachLayer(this.resetStyle, this);
+ }
+ // reset any custom styles
+ layer.options = extend({}, layer.defaultOptions);
+ this._setLayerStyle(layer, this.options.style);
+ return this;
+ },
+
+ // @method setStyle( <Function> style ): this
+ // Changes styles of GeoJSON vector layers with the given style function.
+ setStyle: function (style) {
+ return this.eachLayer(function (layer) {
+ this._setLayerStyle(layer, style);
+ }, this);
+ },
+
+ _setLayerStyle: function (layer, style) {
+ if (layer.setStyle) {
+ if (typeof style === 'function') {
+ style = style(layer.feature);
+ }
+ layer.setStyle(style);
+ }
+ }
+});
+
+// @section
+// There are several static functions which can be called without instantiating L.GeoJSON:
+
+// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
+// Creates a `Layer` from a given GeoJSON feature. Can use a custom
+// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
+// functions if provided as options.
+function geometryToLayer(geojson, options) {
+
+ var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
+ coords = geometry ? geometry.coordinates : null,
+ layers = [],
+ pointToLayer = options && options.pointToLayer,
+ _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
+ latlng, latlngs, i, len;
+
+ if (!coords && !geometry) {
+ return null;
+ }
+
+ switch (geometry.type) {
+ case 'Point':
+ latlng = _coordsToLatLng(coords);
+ return _pointToLayer(pointToLayer, geojson, latlng, options);
+
+ case 'MultiPoint':
+ for (i = 0, len = coords.length; i < len; i++) {
+ latlng = _coordsToLatLng(coords[i]);
+ layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
+ }
+ return new FeatureGroup(layers);
+
+ case 'LineString':
+ case 'MultiLineString':
+ latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
+ return new Polyline(latlngs, options);
+
+ case 'Polygon':
+ case 'MultiPolygon':
+ latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
+ return new Polygon(latlngs, options);
+
+ case 'GeometryCollection':
+ for (i = 0, len = geometry.geometries.length; i < len; i++) {
+ var layer = geometryToLayer({
+ geometry: geometry.geometries[i],
+ type: 'Feature',
+ properties: geojson.properties
+ }, options);
+
+ if (layer) {
+ layers.push(layer);
+ }
+ }
+ return new FeatureGroup(layers);
+
+ default:
+ throw new Error('Invalid GeoJSON object.');
+ }
+}
+
+function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
+ return pointToLayerFn ?
+ pointToLayerFn(geojson, latlng) :
+ new Marker(latlng, options && options.markersInheritOptions && options);
+}
+
+// @function coordsToLatLng(coords: Array): LatLng
+// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
+// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
+function coordsToLatLng(coords) {
+ return new LatLng(coords[1], coords[0], coords[2]);
+}
+
+// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
+// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
+// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
+// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
+function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
+ var latlngs = [];
+
+ for (var i = 0, len = coords.length, latlng; i < len; i++) {
+ latlng = levelsDeep ?
+ coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
+ (_coordsToLatLng || coordsToLatLng)(coords[i]);
+
+ latlngs.push(latlng);
+ }
+
+ return latlngs;
+}
+
+// @function latLngToCoords(latlng: LatLng, precision?: Number): Array
+// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
+function latLngToCoords(latlng, precision) {
+ precision = typeof precision === 'number' ? precision : 6;
+ return latlng.alt !== undefined ?
+ [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
+ [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
+}
+
+// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
+// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
+// `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
+function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
+ var coords = [];
+
+ for (var i = 0, len = latlngs.length; i < len; i++) {
+ coords.push(levelsDeep ?
+ latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
+ latLngToCoords(latlngs[i], precision));
+ }
+
+ if (!levelsDeep && closed) {
+ coords.push(coords[0]);
+ }
+
+ return coords;
+}
+
+function getFeature(layer, newGeometry) {
+ return layer.feature ?
+ extend({}, layer.feature, {geometry: newGeometry}) :
+ asFeature(newGeometry);
+}
+
+// @function asFeature(geojson: Object): Object
+// Normalize GeoJSON geometries/features into GeoJSON features.
+function asFeature(geojson) {
+ if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
+ return geojson;
+ }
+
+ return {
+ type: 'Feature',
+ properties: {},
+ geometry: geojson
+ };
+}
+
+var PointToGeoJSON = {
+ toGeoJSON: function (precision) {
+ return getFeature(this, {
+ type: 'Point',
+ coordinates: latLngToCoords(this.getLatLng(), precision)
+ });
+ }
+};
+
+// @namespace Marker
+// @section Other methods
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
+// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
+Marker.include(PointToGeoJSON);
+
+// @namespace CircleMarker
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
+// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
+Circle.include(PointToGeoJSON);
+CircleMarker.include(PointToGeoJSON);
+
+
+// @namespace Polyline
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
+// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
+Polyline.include({
+ toGeoJSON: function (precision) {
+ var multi = !isFlat(this._latlngs);
+
+ var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
+
+ return getFeature(this, {
+ type: (multi ? 'Multi' : '') + 'LineString',
+ coordinates: coords
+ });
+ }
+});
+
+// @namespace Polygon
+// @method toGeoJSON(precision?: Number): Object
+// `precision` is the number of decimal places for coordinates.
+// The default value is 6 places.
+// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
+Polygon.include({
+ toGeoJSON: function (precision) {
+ var holes = !isFlat(this._latlngs),
+ multi = holes && !isFlat(this._latlngs[0]);
+
+ var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
+
+ if (!holes) {
+ coords = [coords];
+ }
+
+ return getFeature(this, {
+ type: (multi ? 'Multi' : '') + 'Polygon',
+ coordinates: coords
+ });
+ }
+});
+
+
+// @namespace LayerGroup
+LayerGroup.include({
+ toMultiPoint: function (precision) {
+ var coords = [];
+
+ this.eachLayer(function (layer) {
+ coords.push(layer.toGeoJSON(precision).geometry.coordinates);
+ });
+
+ return getFeature(this, {
+ type: 'MultiPoint',
+ coordinates: coords
+ });
+ },
+
+ // @method toGeoJSON(precision?: Number): Object
+ // `precision` is the number of decimal places for coordinates.
+ // The default value is 6 places.
+ // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
+ toGeoJSON: function (precision) {
+
+ var type = this.feature && this.feature.geometry && this.feature.geometry.type;
+
+ if (type === 'MultiPoint') {
+ return this.toMultiPoint(precision);
+ }
+
+ var isGeometryCollection = type === 'GeometryCollection',
+ jsons = [];
+
+ this.eachLayer(function (layer) {
+ if (layer.toGeoJSON) {
+ var json = layer.toGeoJSON(precision);
+ if (isGeometryCollection) {
+ jsons.push(json.geometry);
+ } else {
+ var feature = asFeature(json);
+ // Squash nested feature collections
+ if (feature.type === 'FeatureCollection') {
+ jsons.push.apply(jsons, feature.features);
+ } else {
+ jsons.push(feature);
+ }
+ }
+ }
+ });
+
+ if (isGeometryCollection) {
+ return getFeature(this, {
+ geometries: jsons,
+ type: 'GeometryCollection'
+ });
+ }
+
+ return {
+ type: 'FeatureCollection',
+ features: jsons
+ };
+ }
+});
+
+// @namespace GeoJSON
+// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
+// Creates a GeoJSON layer. Optionally accepts an object in
+// [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
+// (you can alternatively add it later with `addData` method) and an `options` object.
+function geoJSON(geojson, options) {
+ return new GeoJSON(geojson, options);
+}
+
+// Backward compatibility.
+var geoJson = geoJSON;
+
+/*
+ * @class ImageOverlay
+ * @aka L.ImageOverlay
+ * @inherits Interactive layer
+ *
+ * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
+ *
+ * @example
+ *
+ * ```js
+ * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
+ * imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
+ * L.imageOverlay(imageUrl, imageBounds).addTo(map);
+ * ```
+ */
+
+var ImageOverlay = Layer.extend({
+
+ // @section
+ // @aka ImageOverlay options
+ options: {
+ // @option opacity: Number = 1.0
+ // The opacity of the image overlay.
+ opacity: 1,
+
+ // @option alt: String = ''
+ // Text for the `alt` attribute of the image (useful for accessibility).
+ alt: '',
+
+ // @option interactive: Boolean = false
+ // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
+ interactive: false,
+
+ // @option crossOrigin: Boolean|String = false
+ // Whether the crossOrigin attribute will be added to the image.
+ // If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data.
+ // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
+ crossOrigin: false,
+
+ // @option errorOverlayUrl: String = ''
+ // URL to the overlay image to show in place of the overlay that failed to load.
+ errorOverlayUrl: '',
+
+ // @option zIndex: Number = 1
+ // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
+ zIndex: 1,
+
+ // @option className: String = ''
+ // A custom class name to assign to the image. Empty by default.
+ className: ''
+ },
+
+ initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
+ this._url = url;
+ this._bounds = toLatLngBounds(bounds);
+
+ setOptions(this, options);
+ },
+
+ onAdd: function () {
+ if (!this._image) {
+ this._initImage();
+
+ if (this.options.opacity < 1) {
+ this._updateOpacity();
+ }
+ }
+
+ if (this.options.interactive) {
+ addClass(this._image, 'leaflet-interactive');
+ this.addInteractiveTarget(this._image);
+ }
+
+ this.getPane().appendChild(this._image);
+ this._reset();
+ },
+
+ onRemove: function () {
+ remove(this._image);
+ if (this.options.interactive) {
+ this.removeInteractiveTarget(this._image);
+ }
+ },
+
+ // @method setOpacity(opacity: Number): this
+ // Sets the opacity of the overlay.
+ setOpacity: function (opacity) {
+ this.options.opacity = opacity;
+
+ if (this._image) {
+ this._updateOpacity();
+ }
+ return this;
+ },
+
+ setStyle: function (styleOpts) {
+ if (styleOpts.opacity) {
+ this.setOpacity(styleOpts.opacity);
+ }
+ return this;
+ },
+
+ // @method bringToFront(): this
+ // Brings the layer to the top of all overlays.
+ bringToFront: function () {
+ if (this._map) {
+ toFront(this._image);
+ }
+ return this;
+ },
+
+ // @method bringToBack(): this
+ // Brings the layer to the bottom of all overlays.
+ bringToBack: function () {
+ if (this._map) {
+ toBack(this._image);
+ }
+ return this;
+ },
+
+ // @method setUrl(url: String): this
+ // Changes the URL of the image.
+ setUrl: function (url) {
+ this._url = url;
+
+ if (this._image) {
+ this._image.src = url;
+ }
+ return this;
+ },
+
+ // @method setBounds(bounds: LatLngBounds): this
+ // Update the bounds that this ImageOverlay covers
+ setBounds: function (bounds) {
+ this._bounds = toLatLngBounds(bounds);
+
+ if (this._map) {
+ this._reset();
+ }
+ return this;
+ },
+
+ getEvents: function () {
+ var events = {
+ zoom: this._reset,
+ viewreset: this._reset
+ };
+
+ if (this._zoomAnimated) {
+ events.zoomanim = this._animateZoom;
+ }
+
+ return events;
+ },
+
+ // @method setZIndex(value: Number): this
+ // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
+ setZIndex: function (value) {
+ this.options.zIndex = value;
+ this._updateZIndex();
+ return this;
+ },
+
+ // @method getBounds(): LatLngBounds
+ // Get the bounds that this ImageOverlay covers
+ getBounds: function () {
+ return this._bounds;
+ },
+
+ // @method getElement(): HTMLElement
+ // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
+ // used by this overlay.
+ getElement: function () {
+ return this._image;
+ },
+
+ _initImage: function () {
+ var wasElementSupplied = this._url.tagName === 'IMG';
+ var img = this._image = wasElementSupplied ? this._url : create$1('img');
+
+ addClass(img, 'leaflet-image-layer');
+ if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
+ if (this.options.className) { addClass(img, this.options.className); }
+
+ img.onselectstart = falseFn;
+ img.onmousemove = falseFn;
+
+ // @event load: Event
+ // Fired when the ImageOverlay layer has loaded its image
+ img.onload = bind(this.fire, this, 'load');
+ img.onerror = bind(this._overlayOnError, this, 'error');
+
+ if (this.options.crossOrigin || this.options.crossOrigin === '') {
+ img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
+ }
+
+ if (this.options.zIndex) {
+ this._updateZIndex();
+ }
+
+ if (wasElementSupplied) {
+ this._url = img.src;
+ return;
+ }
+
+ img.src = this._url;
+ img.alt = this.options.alt;
+ },
+
+ _animateZoom: function (e) {
+ var scale = this._map.getZoomScale(e.zoom),
+ offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
+
+ setTransform(this._image, offset, scale);
+ },
+
+ _reset: function () {
+ var image = this._image,
+ bounds = new Bounds(
+ this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
+ this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
+ size = bounds.getSize();
+
+ setPosition(image, bounds.min);
+
+ image.style.width = size.x + 'px';
+ image.style.height = size.y + 'px';
+ },
+
+ _updateOpacity: function () {
+ setOpacity(this._image, this.options.opacity);
+ },
+
+ _updateZIndex: function () {
+ if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
+ this._image.style.zIndex = this.options.zIndex;
+ }
+ },
+
+ _overlayOnError: function () {
+ // @event error: Event
+ // Fired when the ImageOverlay layer fails to load its image
+ this.fire('error');
+
+ var errorUrl = this.options.errorOverlayUrl;
+ if (errorUrl && this._url !== errorUrl) {
+ this._url = errorUrl;
+ this._image.src = errorUrl;
+ }
+ }
+});
+
+// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
+// Instantiates an image overlay object given the URL of the image and the
+// geographical bounds it is tied to.
+var imageOverlay = function (url, bounds, options) {
+ return new ImageOverlay(url, bounds, options);
+};
+
+/*
+ * @class VideoOverlay
+ * @aka L.VideoOverlay
+ * @inherits ImageOverlay
+ *
+ * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
+ *
+ * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
+ * HTML5 element.
+ *
+ * @example
+ *
+ * ```js
+ * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
+ * videoBounds = [[ 32, -130], [ 13, -100]];
+ * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
+ * ```
+ */
+
+var VideoOverlay = ImageOverlay.extend({
+
+ // @section
+ // @aka VideoOverlay options
+ options: {
+ // @option autoplay: Boolean = true
+ // Whether the video starts playing automatically when loaded.
+ autoplay: true,
+
+ // @option loop: Boolean = true
+ // Whether the video will loop back to the beginning when played.
+ loop: true,
+
+ // @option keepAspectRatio: Boolean = true
+ // Whether the video will save aspect ratio after the projection.
+ // Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
+ keepAspectRatio: true
+ },
+
+ _initImage: function () {
+ var wasElementSupplied = this._url.tagName === 'VIDEO';
+ var vid = this._image = wasElementSupplied ? this._url : create$1('video');
+
+ addClass(vid, 'leaflet-image-layer');
+ if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
+ if (this.options.className) { addClass(vid, this.options.className); }
+
+ vid.onselectstart = falseFn;
+ vid.onmousemove = falseFn;
+
+ // @event load: Event
+ // Fired when the video has finished loading the first frame
+ vid.onloadeddata = bind(this.fire, this, 'load');
+
+ if (wasElementSupplied) {
+ var sourceElements = vid.getElementsByTagName('source');
+ var sources = [];
+ for (var j = 0; j < sourceElements.length; j++) {
+ sources.push(sourceElements[j].src);
+ }
+
+ this._url = (sourceElements.length > 0) ? sources : [vid.src];
+ return;
+ }
+
+ if (!isArray(this._url)) { this._url = [this._url]; }
+
+ if (!this.options.keepAspectRatio && vid.style.hasOwnProperty('objectFit')) { vid.style['objectFit'] = 'fill'; }
+ vid.autoplay = !!this.options.autoplay;
+ vid.loop = !!this.options.loop;
+ for (var i = 0; i < this._url.length; i++) {
+ var source = create$1('source');
+ source.src = this._url[i];
+ vid.appendChild(source);
+ }
+ }
+
+ // @method getElement(): HTMLVideoElement
+ // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
+ // used by this overlay.
+});
+
+
+// @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
+// Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
+// geographical bounds it is tied to.
+
+function videoOverlay(video, bounds, options) {
+ return new VideoOverlay(video, bounds, options);
+}
+
+/*
+ * @class SVGOverlay
+ * @aka L.SVGOverlay
+ * @inherits ImageOverlay
+ *
+ * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
+ *
+ * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
+ *
+ * @example
+ *
+ * ```js
+ * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
+ * svgElement.setAttribute('viewBox', "0 0 200 200");
+ * svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>';
+ * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
+ * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
+ * ```
+ */
+
+var SVGOverlay = ImageOverlay.extend({
+ _initImage: function () {
+ var el = this._image = this._url;
+
+ addClass(el, 'leaflet-image-layer');
+ if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
+ if (this.options.className) { addClass(el, this.options.className); }
+
+ el.onselectstart = falseFn;
+ el.onmousemove = falseFn;
+ }
+
+ // @method getElement(): SVGElement
+ // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
+ // used by this overlay.
+});
+
+
+// @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
+// Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
+// A viewBox attribute is required on the SVG element to zoom in and out properly.
+
+function svgOverlay(el, bounds, options) {
+ return new SVGOverlay(el, bounds, options);
+}
+
+/*
+ * @class DivOverlay
+ * @inherits Layer
+ * @aka L.DivOverlay
+ * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
+ */
+
+// @namespace DivOverlay
+var DivOverlay = Layer.extend({
+
+ // @section
+ // @aka DivOverlay options
+ options: {
+ // @option offset: Point = Point(0, 7)
+ // The offset of the popup position. Useful to control the anchor
+ // of the popup when opening it on some overlays.
+ offset: [0, 7],
+
+ // @option className: String = ''
+ // A custom CSS class name to assign to the popup.
+ className: '',
+
+ // @option pane: String = 'popupPane'
+ // `Map pane` where the popup will be added.
+ pane: 'popupPane'
+ },
+
+ initialize: function (options, source) {
+ setOptions(this, options);
+
+ this._source = source;
+ },
+
+ onAdd: function (map) {
+ this._zoomAnimated = map._zoomAnimated;
+
+ if (!this._container) {
+ this._initLayout();
+ }
+
+ if (map._fadeAnimated) {
+ setOpacity(this._container, 0);
+ }
+
+ clearTimeout(this._removeTimeout);
+ this.getPane().appendChild(this._container);
+ this.update();
+
+ if (map._fadeAnimated) {
+ setOpacity(this._container, 1);
+ }
+
+ this.bringToFront();
+ },
+
+ onRemove: function (map) {
+ if (map._fadeAnimated) {
+ setOpacity(this._container, 0);
+ this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
+ } else {
+ remove(this._container);
+ }
+ },
+
+ // @namespace Popup
+ // @method getLatLng: LatLng
+ // Returns the geographical point of popup.
+ getLatLng: function () {
+ return this._latlng;
+ },
+
+ // @method setLatLng(latlng: LatLng): this
+ // Sets the geographical point where the popup will open.
+ setLatLng: function (latlng) {
+ this._latlng = toLatLng(latlng);
+ if (this._map) {
+ this._updatePosition();
+ this._adjustPan();
+ }
+ return this;
+ },
+
+ // @method getContent: String|HTMLElement
+ // Returns the content of the popup.
+ getContent: function () {
+ return this._content;
+ },
+
+ // @method setContent(htmlContent: String|HTMLElement|Function): this
+ // Sets the HTML content of the popup. If a function is passed the source layer will be passed to the function. The function should return a `String` or `HTMLElement` to be used in the popup.
+ setContent: function (content) {
+ this._content = content;
+ this.update();
+ return this;
+ },
+
+ // @method getElement: String|HTMLElement
+ // Alias for [getContent()](#popup-getcontent)
+ getElement: function () {
+ return this._container;
+ },
+
+ // @method update: null
+ // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
+ update: function () {
+ if (!this._map) { return; }
+
+ this._container.style.visibility = 'hidden';
+
+ this._updateContent();
+ this._updateLayout();
+ this._updatePosition();
+
+ this._container.style.visibility = '';
+
+ this._adjustPan();
+ },
+
+ getEvents: function () {
+ var events = {
+ zoom: this._updatePosition,
+ viewreset: this._updatePosition
+ };
+
+ if (this._zoomAnimated) {
+ events.zoomanim = this._animateZoom;
+ }
+ return events;
+ },
+
+ // @method isOpen: Boolean
+ // Returns `true` when the popup is visible on the map.
+ isOpen: function () {
+ return !!this._map && this._map.hasLayer(this);
+ },
+
+ // @method bringToFront: this
+ // Brings this popup in front of other popups (in the same map pane).
+ bringToFront: function () {
+ if (this._map) {
+ toFront(this._container);
+ }
+ return this;
+ },
+
+ // @method bringToBack: this
+ // Brings this popup to the back of other popups (in the same map pane).
+ bringToBack: function () {
+ if (this._map) {
+ toBack(this._container);
+ }
+ return this;
+ },
+
+ _prepareOpen: function (parent, layer, latlng) {
+ if (!(layer instanceof Layer)) {
+ latlng = layer;
+ layer = parent;
+ }
+
+ if (layer instanceof FeatureGroup) {
+ for (var id in parent._layers) {
+ layer = parent._layers[id];
+ break;
+ }
+ }
+
+ if (!latlng) {
+ if (layer.getCenter) {
+ latlng = layer.getCenter();
+ } else if (layer.getLatLng) {
+ latlng = layer.getLatLng();
+ } else {
+ throw new Error('Unable to get source layer LatLng.');
+ }
+ }
+
+ // set overlay source to this layer
+ this._source = layer;
+
+ // update the overlay (content, layout, ect...)
+ this.update();
+
+ return latlng;
+ },
+
+ _updateContent: function () {
+ if (!this._content) { return; }
+
+ var node = this._contentNode;
+ var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
+
+ if (typeof content === 'string') {
+ node.innerHTML = content;
+ } else {
+ while (node.hasChildNodes()) {
+ node.removeChild(node.firstChild);
+ }
+ node.appendChild(content);
+ }
+ this.fire('contentupdate');
+ },
+
+ _updatePosition: function () {
+ if (!this._map) { return; }
+
+ var pos = this._map.latLngToLayerPoint(this._latlng),
+ offset = toPoint(this.options.offset),
+ anchor = this._getAnchor();
+
+ if (this._zoomAnimated) {
+ setPosition(this._container, pos.add(anchor));
+ } else {
+ offset = offset.add(pos).add(anchor);
+ }
+
+ var bottom = this._containerBottom = -offset.y,
+ left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
+
+ // bottom position the popup in case the height of the popup changes (images loading etc)
+ this._container.style.bottom = bottom + 'px';
+ this._container.style.left = left + 'px';
+ },
+
+ _getAnchor: function () {
+ return [0, 0];
+ }
+
+});
+
+/*
+ * @class Popup
+ * @inherits DivOverlay
+ * @aka L.Popup
+ * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
+ * open popups while making sure that only one popup is open at one time
+ * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
+ *
+ * @example
+ *
+ * If you want to just bind a popup to marker click and then open it, it's really easy:
+ *
+ * ```js
+ * marker.bindPopup(popupContent).openPopup();
+ * ```
+ * Path overlays like polylines also have a `bindPopup` method.
+ * Here's a more complicated way to open a popup on a map:
+ *
+ * ```js
+ * var popup = L.popup()
+ * .setLatLng(latlng)
+ * .setContent('<p>Hello world!<br />This is a nice popup.</p>')
+ * .openOn(map);
+ * ```
+ */
+
+
+// @namespace Popup
+var Popup = DivOverlay.extend({
+
+ // @section
+ // @aka Popup options
+ options: {
+ // @option maxWidth: Number = 300
+ // Max width of the popup, in pixels.
+ maxWidth: 300,
+
+ // @option minWidth: Number = 50
+ // Min width of the popup, in pixels.
+ minWidth: 50,
+
+ // @option maxHeight: Number = null
+ // If set, creates a scrollable container of the given height
+ // inside a popup if its content exceeds it.
+ maxHeight: null,
+
+ // @option autoPan: Boolean = true
+ // Set it to `false` if you don't want the map to do panning animation
+ // to fit the opened popup.
+ autoPan: true,
+
+ // @option autoPanPaddingTopLeft: Point = null
+ // The margin between the popup and the top left corner of the map
+ // view after autopanning was performed.
+ autoPanPaddingTopLeft: null,
+
+ // @option autoPanPaddingBottomRight: Point = null
+ // The margin between the popup and the bottom right corner of the map
+ // view after autopanning was performed.
+ autoPanPaddingBottomRight: null,
+
+ // @option autoPanPadding: Point = Point(5, 5)
+ // Equivalent of setting both top left and bottom right autopan padding to the same value.
+ autoPanPadding: [5, 5],
+
+ // @option keepInView: Boolean = false
+ // Set it to `true` if you want to prevent users from panning the popup
+ // off of the screen while it is open.
+ keepInView: false,
+
+ // @option closeButton: Boolean = true
+ // Controls the presence of a close button in the popup.
+ closeButton: true,
+
+ // @option autoClose: Boolean = true
+ // Set it to `false` if you want to override the default behavior of
+ // the popup closing when another popup is opened.
+ autoClose: true,
+
+ // @option closeOnEscapeKey: Boolean = true
+ // Set it to `false` if you want to override the default behavior of
+ // the ESC key for closing of the popup.
+ closeOnEscapeKey: true,
+
+ // @option closeOnClick: Boolean = *
+ // Set it if you want to override the default behavior of the popup closing when user clicks
+ // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
+
+ // @option className: String = ''
+ // A custom CSS class name to assign to the popup.
+ className: ''
+ },
+
+ // @namespace Popup
+ // @method openOn(map: Map): this
+ // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
+ openOn: function (map) {
+ map.openPopup(this);
+ return this;
+ },
+
+ onAdd: function (map) {
+ DivOverlay.prototype.onAdd.call(this, map);
+
+ // @namespace Map
+ // @section Popup events
+ // @event popupopen: PopupEvent
+ // Fired when a popup is opened in the map
+ map.fire('popupopen', {popup: this});
+
+ if (this._source) {
+ // @namespace Layer
+ // @section Popup events
+ // @event popupopen: PopupEvent
+ // Fired when a popup bound to this layer is opened
+ this._source.fire('popupopen', {popup: this}, true);
+ // For non-path layers, we toggle the popup when clicking
+ // again the layer, so prevent the map to reopen it.
+ if (!(this._source instanceof Path)) {
+ this._source.on('preclick', stopPropagation);
+ }
+ }
+ },
+
+ onRemove: function (map) {
+ DivOverlay.prototype.onRemove.call(this, map);
+
+ // @namespace Map
+ // @section Popup events
+ // @event popupclose: PopupEvent
+ // Fired when a popup in the map is closed
+ map.fire('popupclose', {popup: this});
+
+ if (this._source) {
+ // @namespace Layer
+ // @section Popup events
+ // @event popupclose: PopupEvent
+ // Fired when a popup bound to this layer is closed
+ this._source.fire('popupclose', {popup: this}, true);
+ if (!(this._source instanceof Path)) {
+ this._source.off('preclick', stopPropagation);
+ }
+ }
+ },
+
+ getEvents: function () {
+ var events = DivOverlay.prototype.getEvents.call(this);
+
+ if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
+ events.preclick = this._close;
+ }
+
+ if (this.options.keepInView) {
+ events.moveend = this._adjustPan;
+ }
+
+ return events;
+ },
+
+ _close: function () {
+ if (this._map) {
+ this._map.closePopup(this);
+ }
+ },
+
+ _initLayout: function () {
+ var prefix = 'leaflet-popup',
+ container = this._container = create$1('div',
+ prefix + ' ' + (this.options.className || '') +
+ ' leaflet-zoom-animated');
+
+ var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
+ this._contentNode = create$1('div', prefix + '-content', wrapper);
+
+ disableClickPropagation(wrapper);
+ disableScrollPropagation(this._contentNode);
+ on(wrapper, 'contextmenu', stopPropagation);
+
+ this._tipContainer = create$1('div', prefix + '-tip-container', container);
+ this._tip = create$1('div', prefix + '-tip', this._tipContainer);
+
+ if (this.options.closeButton) {
+ var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
+ closeButton.href = '#close';
+ closeButton.innerHTML = '&#215;';
+
+ on(closeButton, 'click', this._onCloseButtonClick, this);
+ }
+ },
+
+ _updateLayout: function () {
+ var container = this._contentNode,
+ style = container.style;
+
+ style.width = '';
+ style.whiteSpace = 'nowrap';
+
+ var width = container.offsetWidth;
+ width = Math.min(width, this.options.maxWidth);
+ width = Math.max(width, this.options.minWidth);
+
+ style.width = (width + 1) + 'px';
+ style.whiteSpace = '';
+
+ style.height = '';
+
+ var height = container.offsetHeight,
+ maxHeight = this.options.maxHeight,
+ scrolledClass = 'leaflet-popup-scrolled';
+
+ if (maxHeight && height > maxHeight) {
+ style.height = maxHeight + 'px';
+ addClass(container, scrolledClass);
+ } else {
+ removeClass(container, scrolledClass);
+ }
+
+ this._containerWidth = this._container.offsetWidth;
+ },
+
+ _animateZoom: function (e) {
+ var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
+ anchor = this._getAnchor();
+ setPosition(this._container, pos.add(anchor));
+ },
+
+ _adjustPan: function () {
+ if (!this.options.autoPan) { return; }
+ if (this._map._panAnim) { this._map._panAnim.stop(); }
+
+ var map = this._map,
+ marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
+ containerHeight = this._container.offsetHeight + marginBottom,
+ containerWidth = this._containerWidth,
+ layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
+
+ layerPos._add(getPosition(this._container));
+
+ var containerPos = map.layerPointToContainerPoint(layerPos),
+ padding = toPoint(this.options.autoPanPadding),
+ paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
+ paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
+ size = map.getSize(),
+ dx = 0,
+ dy = 0;
+
+ if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
+ dx = containerPos.x + containerWidth - size.x + paddingBR.x;
+ }
+ if (containerPos.x - dx - paddingTL.x < 0) { // left
+ dx = containerPos.x - paddingTL.x;
+ }
+ if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
+ dy = containerPos.y + containerHeight - size.y + paddingBR.y;
+ }
+ if (containerPos.y - dy - paddingTL.y < 0) { // top
+ dy = containerPos.y - paddingTL.y;
+ }
+
+ // @namespace Map
+ // @section Popup events
+ // @event autopanstart: Event
+ // Fired when the map starts autopanning when opening a popup.
+ if (dx || dy) {
+ map
+ .fire('autopanstart')
+ .panBy([dx, dy]);
+ }
+ },
+
+ _onCloseButtonClick: function (e) {
+ this._close();
+ stop(e);
+ },
+
+ _getAnchor: function () {
+ // Where should we anchor the popup on the source layer?
+ return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
+ }
+
+});
+
+// @namespace Popup
+// @factory L.popup(options?: Popup options, source?: Layer)
+// Instantiates a `Popup` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers.
+var popup = function (options, source) {
+ return new Popup(options, source);
+};
+
+
+/* @namespace Map
+ * @section Interaction Options
+ * @option closePopupOnClick: Boolean = true
+ * Set it to `false` if you don't want popups to close when user clicks the map.
+ */
+Map.mergeOptions({
+ closePopupOnClick: true
+});
+
+
+// @namespace Map
+// @section Methods for Layers and Controls
+Map.include({
+ // @method openPopup(popup: Popup): this
+ // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
+ // @alternative
+ // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
+ // Creates a popup with the specified content and options and opens it in the given point on a map.
+ openPopup: function (popup, latlng, options) {
+ if (!(popup instanceof Popup)) {
+ popup = new Popup(options).setContent(popup);
+ }
+
+ if (latlng) {
+ popup.setLatLng(latlng);
+ }
+
+ if (this.hasLayer(popup)) {
+ return this;
+ }
+
+ if (this._popup && this._popup.options.autoClose) {
+ this.closePopup();
+ }
+
+ this._popup = popup;
+ return this.addLayer(popup);
+ },
+
+ // @method closePopup(popup?: Popup): this
+ // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
+ closePopup: function (popup) {
+ if (!popup || popup === this._popup) {
+ popup = this._popup;
+ this._popup = null;
+ }
+ if (popup) {
+ this.removeLayer(popup);
+ }
+ return this;
+ }
+});
+
+/*
+ * @namespace Layer
+ * @section Popup methods example
+ *
+ * All layers share a set of methods convenient for binding popups to it.
+ *
+ * ```js
+ * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
+ * layer.openPopup();
+ * layer.closePopup();
+ * ```
+ *
+ * Popups will also be automatically opened when the layer is clicked on and closed when the layer is removed from the map or another popup is opened.
+ */
+
+// @section Popup methods
+Layer.include({
+
+ // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
+ // Binds a popup to the layer with the passed `content` and sets up the
+ // necessary event listeners. If a `Function` is passed it will receive
+ // the layer as the first argument and should return a `String` or `HTMLElement`.
+ bindPopup: function (content, options) {
+
+ if (content instanceof Popup) {
+ setOptions(content, options);
+ this._popup = content;
+ content._source = this;
+ } else {
+ if (!this._popup || options) {
+ this._popup = new Popup(options, this);
+ }
+ this._popup.setContent(content);
+ }
+
+ if (!this._popupHandlersAdded) {
+ this.on({
+ click: this._openPopup,
+ keypress: this._onKeyPress,
+ remove: this.closePopup,
+ move: this._movePopup
+ });
+ this._popupHandlersAdded = true;
+ }
+
+ return this;
+ },
+
+ // @method unbindPopup(): this
+ // Removes the popup previously bound with `bindPopup`.
+ unbindPopup: function () {
+ if (this._popup) {
+ this.off({
+ click: this._openPopup,
+ keypress: this._onKeyPress,
+ remove: this.closePopup,
+ move: this._movePopup
+ });
+ this._popupHandlersAdded = false;
+ this._popup = null;
+ }
+ return this;
+ },
+
+ // @method openPopup(latlng?: LatLng): this
+ // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
+ openPopup: function (layer, latlng) {
+ if (this._popup && this._map) {
+ latlng = this._popup._prepareOpen(this, layer, latlng);
+
+ // open the popup on the map
+ this._map.openPopup(this._popup, latlng);
+ }
+
+ return this;
+ },
+
+ // @method closePopup(): this
+ // Closes the popup bound to this layer if it is open.
+ closePopup: function () {
+ if (this._popup) {
+ this._popup._close();
+ }
+ return this;
+ },
+
+ // @method togglePopup(): this
+ // Opens or closes the popup bound to this layer depending on its current state.
+ togglePopup: function (target) {
+ if (this._popup) {
+ if (this._popup._map) {
+ this.closePopup();
+ } else {
+ this.openPopup(target);
+ }
+ }
+ return this;
+ },
+
+ // @method isPopupOpen(): boolean
+ // Returns `true` if the popup bound to this layer is currently open.
+ isPopupOpen: function () {
+ return (this._popup ? this._popup.isOpen() : false);
+ },
+
+ // @method setPopupContent(content: String|HTMLElement|Popup): this
+ // Sets the content of the popup bound to this layer.
+ setPopupContent: function (content) {
+ if (this._popup) {
+ this._popup.setContent(content);
+ }
+ return this;
+ },
+
+ // @method getPopup(): Popup
+ // Returns the popup bound to this layer.
+ getPopup: function () {
+ return this._popup;
+ },
+
+ _openPopup: function (e) {
+ var layer = e.layer || e.target;
+
+ if (!this._popup) {
+ return;
+ }
+
+ if (!this._map) {
+ return;
+ }
+
+ // prevent map click
+ stop(e);
+
+ // if this inherits from Path its a vector and we can just
+ // open the popup at the new location
+ if (layer instanceof Path) {
+ this.openPopup(e.layer || e.target, e.latlng);
+ return;
+ }
+
+ // otherwise treat it like a marker and figure out
+ // if we should toggle it open/closed
+ if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
+ this.closePopup();
+ } else {
+ this.openPopup(layer, e.latlng);
+ }
+ },
+
+ _movePopup: function (e) {
+ this._popup.setLatLng(e.latlng);
+ },
+
+ _onKeyPress: function (e) {
+ if (e.originalEvent.keyCode === 13) {
+ this._openPopup(e);
+ }
+ }
+});
+
+/*
+ * @class Tooltip
+ * @inherits DivOverlay
+ * @aka L.Tooltip
+ * Used to display small texts on top of map layers.
+ *
+ * @example
+ *
+ * ```js
+ * marker.bindTooltip("my tooltip text").openTooltip();
+ * ```
+ * Note about tooltip offset. Leaflet takes two options in consideration
+ * for computing tooltip offsetting:
+ * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
+ * Add a positive x offset to move the tooltip to the right, and a positive y offset to
+ * move it to the bottom. Negatives will move to the left and top.
+ * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
+ * should adapt this value if you use a custom icon.
+ */
+
+
+// @namespace Tooltip
+var Tooltip = DivOverlay.extend({
+
+ // @section
+ // @aka Tooltip options
+ options: {
+ // @option pane: String = 'tooltipPane'
+ // `Map pane` where the tooltip will be added.
+ pane: 'tooltipPane',
+
+ // @option offset: Point = Point(0, 0)
+ // Optional offset of the tooltip position.
+ offset: [0, 0],
+
+ // @option direction: String = 'auto'
+ // Direction where to open the tooltip. Possible values are: `right`, `left`,
+ // `top`, `bottom`, `center`, `auto`.
+ // `auto` will dynamically switch between `right` and `left` according to the tooltip
+ // position on the map.
+ direction: 'auto',
+
+ // @option permanent: Boolean = false
+ // Whether to open the tooltip permanently or only on mouseover.
+ permanent: false,
+
+ // @option sticky: Boolean = false
+ // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
+ sticky: false,
+
+ // @option interactive: Boolean = false
+ // If true, the tooltip will listen to the feature events.
+ interactive: false,
+
+ // @option opacity: Number = 0.9
+ // Tooltip container opacity.
+ opacity: 0.9
+ },
+
+ onAdd: function (map) {
+ DivOverlay.prototype.onAdd.call(this, map);
+ this.setOpacity(this.options.opacity);
+
+ // @namespace Map
+ // @section Tooltip events
+ // @event tooltipopen: TooltipEvent
+ // Fired when a tooltip is opened in the map.
+ map.fire('tooltipopen', {tooltip: this});
+
+ if (this._source) {
+ // @namespace Layer
+ // @section Tooltip events
+ // @event tooltipopen: TooltipEvent
+ // Fired when a tooltip bound to this layer is opened.
+ this._source.fire('tooltipopen', {tooltip: this}, true);
+ }
+ },
+
+ onRemove: function (map) {
+ DivOverlay.prototype.onRemove.call(this, map);
+
+ // @namespace Map
+ // @section Tooltip events
+ // @event tooltipclose: TooltipEvent
+ // Fired when a tooltip in the map is closed.
+ map.fire('tooltipclose', {tooltip: this});
+
+ if (this._source) {
+ // @namespace Layer
+ // @section Tooltip events
+ // @event tooltipclose: TooltipEvent
+ // Fired when a tooltip bound to this layer is closed.
+ this._source.fire('tooltipclose', {tooltip: this}, true);
+ }
+ },
+
+ getEvents: function () {
+ var events = DivOverlay.prototype.getEvents.call(this);
+
+ if (touch && !this.options.permanent) {
+ events.preclick = this._close;
+ }
+
+ return events;
+ },
+
+ _close: function () {
+ if (this._map) {
+ this._map.closeTooltip(this);
+ }
+ },
+
+ _initLayout: function () {
+ var prefix = 'leaflet-tooltip',
+ className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
+
+ this._contentNode = this._container = create$1('div', className);
+ },
+
+ _updateLayout: function () {},
+
+ _adjustPan: function () {},
+
+ _setPosition: function (pos) {
+ var map = this._map,
+ container = this._container,
+ centerPoint = map.latLngToContainerPoint(map.getCenter()),
+ tooltipPoint = map.layerPointToContainerPoint(pos),
+ direction = this.options.direction,
+ tooltipWidth = container.offsetWidth,
+ tooltipHeight = container.offsetHeight,
+ offset = toPoint(this.options.offset),
+ anchor = this._getAnchor();
+
+ if (direction === 'top') {
+ pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
+ } else if (direction === 'bottom') {
+ pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
+ } else if (direction === 'center') {
+ pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
+ } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
+ direction = 'right';
+ pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
+ } else {
+ direction = 'left';
+ pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
+ }
+
+ removeClass(container, 'leaflet-tooltip-right');
+ removeClass(container, 'leaflet-tooltip-left');
+ removeClass(container, 'leaflet-tooltip-top');
+ removeClass(container, 'leaflet-tooltip-bottom');
+ addClass(container, 'leaflet-tooltip-' + direction);
+ setPosition(container, pos);
+ },
+
+ _updatePosition: function () {
+ var pos = this._map.latLngToLayerPoint(this._latlng);
+ this._setPosition(pos);
+ },
+
+ setOpacity: function (opacity) {
+ this.options.opacity = opacity;
+
+ if (this._container) {
+ setOpacity(this._container, opacity);
+ }
+ },
+
+ _animateZoom: function (e) {
+ var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
+ this._setPosition(pos);
+ },
+
+ _getAnchor: function () {
+ // Where should we anchor the tooltip on the source layer?
+ return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
+ }
+
+});
+
+// @namespace Tooltip
+// @factory L.tooltip(options?: Tooltip options, source?: Layer)
+// Instantiates a Tooltip object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers.
+var tooltip = function (options, source) {
+ return new Tooltip(options, source);
+};
+
+// @namespace Map
+// @section Methods for Layers and Controls
+Map.include({
+
+ // @method openTooltip(tooltip: Tooltip): this
+ // Opens the specified tooltip.
+ // @alternative
+ // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
+ // Creates a tooltip with the specified content and options and open it.
+ openTooltip: function (tooltip, latlng, options) {
+ if (!(tooltip instanceof Tooltip)) {
+ tooltip = new Tooltip(options).setContent(tooltip);
+ }
+
+ if (latlng) {
+ tooltip.setLatLng(latlng);
+ }
+
+ if (this.hasLayer(tooltip)) {
+ return this;
+ }
+
+ return this.addLayer(tooltip);
+ },
+
+ // @method closeTooltip(tooltip?: Tooltip): this
+ // Closes the tooltip given as parameter.
+ closeTooltip: function (tooltip) {
+ if (tooltip) {
+ this.removeLayer(tooltip);
+ }
+ return this;
+ }
+
+});
+
+/*
+ * @namespace Layer
+ * @section Tooltip methods example
+ *
+ * All layers share a set of methods convenient for binding tooltips to it.
+ *
+ * ```js
+ * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
+ * layer.openTooltip();
+ * layer.closeTooltip();
+ * ```
+ */
+
+// @section Tooltip methods
+Layer.include({
+
+ // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
+ // Binds a tooltip to the layer with the passed `content` and sets up the
+ // necessary event listeners. If a `Function` is passed it will receive
+ // the layer as the first argument and should return a `String` or `HTMLElement`.
+ bindTooltip: function (content, options) {
+
+ if (content instanceof Tooltip) {
+ setOptions(content, options);
+ this._tooltip = content;
+ content._source = this;
+ } else {
+ if (!this._tooltip || options) {
+ this._tooltip = new Tooltip(options, this);
+ }
+ this._tooltip.setContent(content);
+
+ }
+
+ this._initTooltipInteractions();
+
+ if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
+ this.openTooltip();
+ }
+
+ return this;
+ },
+
+ // @method unbindTooltip(): this
+ // Removes the tooltip previously bound with `bindTooltip`.
+ unbindTooltip: function () {
+ if (this._tooltip) {
+ this._initTooltipInteractions(true);
+ this.closeTooltip();
+ this._tooltip = null;
+ }
+ return this;
+ },
+
+ _initTooltipInteractions: function (remove$$1) {
+ if (!remove$$1 && this._tooltipHandlersAdded) { return; }
+ var onOff = remove$$1 ? 'off' : 'on',
+ events = {
+ remove: this.closeTooltip,
+ move: this._moveTooltip
+ };
+ if (!this._tooltip.options.permanent) {
+ events.mouseover = this._openTooltip;
+ events.mouseout = this.closeTooltip;
+ if (this._tooltip.options.sticky) {
+ events.mousemove = this._moveTooltip;
+ }
+ if (touch) {
+ events.click = this._openTooltip;
+ }
+ } else {
+ events.add = this._openTooltip;
+ }
+ this[onOff](events);
+ this._tooltipHandlersAdded = !remove$$1;
+ },
+
+ // @method openTooltip(latlng?: LatLng): this
+ // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
+ openTooltip: function (layer, latlng) {
+ if (this._tooltip && this._map) {
+ latlng = this._tooltip._prepareOpen(this, layer, latlng);
+
+ // open the tooltip on the map
+ this._map.openTooltip(this._tooltip, latlng);
+
+ // Tooltip container may not be defined if not permanent and never
+ // opened.
+ if (this._tooltip.options.interactive && this._tooltip._container) {
+ addClass(this._tooltip._container, 'leaflet-clickable');
+ this.addInteractiveTarget(this._tooltip._container);
+ }
+ }
+
+ return this;
+ },
+
+ // @method closeTooltip(): this
+ // Closes the tooltip bound to this layer if it is open.
+ closeTooltip: function () {
+ if (this._tooltip) {
+ this._tooltip._close();
+ if (this._tooltip.options.interactive && this._tooltip._container) {
+ removeClass(this._tooltip._container, 'leaflet-clickable');
+ this.removeInteractiveTarget(this._tooltip._container);
+ }
+ }
+ return this;
+ },
+
+ // @method toggleTooltip(): this
+ // Opens or closes the tooltip bound to this layer depending on its current state.
+ toggleTooltip: function (target) {
+ if (this._tooltip) {
+ if (this._tooltip._map) {
+ this.closeTooltip();
+ } else {
+ this.openTooltip(target);
+ }
+ }
+ return this;
+ },
+
+ // @method isTooltipOpen(): boolean
+ // Returns `true` if the tooltip bound to this layer is currently open.
+ isTooltipOpen: function () {
+ return this._tooltip.isOpen();
+ },
+
+ // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
+ // Sets the content of the tooltip bound to this layer.
+ setTooltipContent: function (content) {
+ if (this._tooltip) {
+ this._tooltip.setContent(content);
+ }
+ return this;
+ },
+
+ // @method getTooltip(): Tooltip
+ // Returns the tooltip bound to this layer.
+ getTooltip: function () {
+ return this._tooltip;
+ },
+
+ _openTooltip: function (e) {
+ var layer = e.layer || e.target;
+
+ if (!this._tooltip || !this._map) {
+ return;
+ }
+ this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
+ },
+
+ _moveTooltip: function (e) {
+ var latlng = e.latlng, containerPoint, layerPoint;
+ if (this._tooltip.options.sticky && e.originalEvent) {
+ containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
+ layerPoint = this._map.containerPointToLayerPoint(containerPoint);
+ latlng = this._map.layerPointToLatLng(layerPoint);
+ }
+ this._tooltip.setLatLng(latlng);
+ }
+});
+
+/*
+ * @class DivIcon
+ * @aka L.DivIcon
+ * @inherits Icon
+ *
+ * Represents a lightweight icon for markers that uses a simple `<div>`
+ * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
+ *
+ * @example
+ * ```js
+ * var myIcon = L.divIcon({className: 'my-div-icon'});
+ * // you can set .my-div-icon styles in CSS
+ *
+ * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
+ * ```
+ *
+ * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
+ */
+
+var DivIcon = Icon.extend({
+ options: {
+ // @section
+ // @aka DivIcon options
+ iconSize: [12, 12], // also can be set through CSS
+
+ // iconAnchor: (Point),
+ // popupAnchor: (Point),
+
+ // @option html: String|HTMLElement = ''
+ // Custom HTML code to put inside the div element, empty by default. Alternatively,
+ // an instance of `HTMLElement`.
+ html: false,
+
+ // @option bgPos: Point = [0, 0]
+ // Optional relative position of the background, in pixels
+ bgPos: null,
+
+ className: 'leaflet-div-icon'
+ },
+
+ createIcon: function (oldIcon) {
+ var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
+ options = this.options;
+
+ if (options.html instanceof Element) {
+ empty(div);
+ div.appendChild(options.html);
+ } else {
+ div.innerHTML = options.html !== false ? options.html : '';
+ }
+
+ if (options.bgPos) {
+ var bgPos = toPoint(options.bgPos);
+ div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
+ }
+ this._setIconStyles(div, 'icon');
+
+ return div;
+ },
+
+ createShadow: function () {
+ return null;
+ }
+});
+
+// @factory L.divIcon(options: DivIcon options)
+// Creates a `DivIcon` instance with the given options.
+function divIcon(options) {
+ return new DivIcon(options);
+}
+
+Icon.Default = IconDefault;
+
+/*
+ * @class GridLayer
+ * @inherits Layer
+ * @aka L.GridLayer
+ *
+ * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
+ * GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you.
+ *
+ *
+ * @section Synchronous usage
+ * @example
+ *
+ * To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile.
+ *
+ * ```js
+ * var CanvasLayer = L.GridLayer.extend({
+ * createTile: function(coords){
+ * // create a <canvas> element for drawing
+ * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
+ *
+ * // setup tile width and height according to the options
+ * var size = this.getTileSize();
+ * tile.width = size.x;
+ * tile.height = size.y;
+ *
+ * // get a canvas context and draw something on it using coords.x, coords.y and coords.z
+ * var ctx = tile.getContext('2d');
+ *
+ * // return the tile so it can be rendered on screen
+ * return tile;
+ * }
+ * });
+ * ```
+ *
+ * @section Asynchronous usage
+ * @example
+ *
+ * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback.
+ *
+ * ```js
+ * var CanvasLayer = L.GridLayer.extend({
+ * createTile: function(coords, done){
+ * var error;
+ *
+ * // create a <canvas> element for drawing
+ * var tile = L.DomUtil.create('canvas', 'leaflet-tile');
+ *
+ * // setup tile width and height according to the options
+ * var size = this.getTileSize();
+ * tile.width = size.x;
+ * tile.height = size.y;
+ *
+ * // draw something asynchronously and pass the tile to the done() callback
+ * setTimeout(function() {
+ * done(error, tile);
+ * }, 1000);
+ *
+ * return tile;
+ * }
+ * });
+ * ```
+ *
+ * @section
+ */
+
+
+var GridLayer = Layer.extend({
+
+ // @section
+ // @aka GridLayer options
+ options: {
+ // @option tileSize: Number|Point = 256
+ // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
+ tileSize: 256,
+
+ // @option opacity: Number = 1.0
+ // Opacity of the tiles. Can be used in the `createTile()` function.
+ opacity: 1,
+
+ // @option updateWhenIdle: Boolean = (depends)
+ // Load new tiles only when panning ends.
+ // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
+ // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
+ // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
+ updateWhenIdle: mobile,
+
+ // @option updateWhenZooming: Boolean = true
+ // By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends.
+ updateWhenZooming: true,
+
+ // @option updateInterval: Number = 200
+ // Tiles will not update more than once every `updateInterval` milliseconds when panning.
+ updateInterval: 200,
+
+ // @option zIndex: Number = 1
+ // The explicit zIndex of the tile layer.
+ zIndex: 1,
+
+ // @option bounds: LatLngBounds = undefined
+ // If set, tiles will only be loaded inside the set `LatLngBounds`.
+ bounds: null,
+
+ // @option minZoom: Number = 0
+ // The minimum zoom level down to which this layer will be displayed (inclusive).
+ minZoom: 0,
+
+ // @option maxZoom: Number = undefined
+ // The maximum zoom level up to which this layer will be displayed (inclusive).
+ maxZoom: undefined,
+
+ // @option maxNativeZoom: Number = undefined
+ // Maximum zoom number the tile source has available. If it is specified,
+ // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
+ // from `maxNativeZoom` level and auto-scaled.
+ maxNativeZoom: undefined,
+
+ // @option minNativeZoom: Number = undefined
+ // Minimum zoom number the tile source has available. If it is specified,
+ // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
+ // from `minNativeZoom` level and auto-scaled.
+ minNativeZoom: undefined,
+
+ // @option noWrap: Boolean = false
+ // Whether the layer is wrapped around the antimeridian. If `true`, the
+ // GridLayer will only be displayed once at low zoom levels. Has no
+ // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
+ // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
+ // tiles outside the CRS limits.
+ noWrap: false,
+
+ // @option pane: String = 'tilePane'
+ // `Map pane` where the grid layer will be added.
+ pane: 'tilePane',
+
+ // @option className: String = ''
+ // A custom class name to assign to the tile layer. Empty by default.
+ className: '',
+
+ // @option keepBuffer: Number = 2
+ // When panning the map, keep this many rows and columns of tiles before unloading them.
+ keepBuffer: 2
+ },
+
+ initialize: function (options) {
+ setOptions(this, options);
+ },
+
+ onAdd: function () {
+ this._initContainer();
+
+ this._levels = {};
+ this._tiles = {};
+
+ this._resetView();
+ this._update();
+ },
+
+ beforeAdd: function (map) {
+ map._addZoomLimit(this);
+ },
+
+ onRemove: function (map) {
+ this._removeAllTiles();
+ remove(this._container);
+ map._removeZoomLimit(this);
+ this._container = null;
+ this._tileZoom = undefined;
+ },
+
+ // @method bringToFront: this
+ // Brings the tile layer to the top of all tile layers.
+ bringToFront: function () {
+ if (this._map) {
+ toFront(this._container);
+ this._setAutoZIndex(Math.max);
+ }
+ return this;
+ },
+
+ // @method bringToBack: this
+ // Brings the tile layer to the bottom of all tile layers.
+ bringToBack: function () {
+ if (this._map) {
+ toBack(this._container);
+ this._setAutoZIndex(Math.min);
+ }
+ return this;
+ },
+
+ // @method getContainer: HTMLElement
+ // Returns the HTML element that contains the tiles for this layer.
+ getContainer: function () {
+ return this._container;
+ },
+
+ // @method setOpacity(opacity: Number): this
+ // Changes the [opacity](#gridlayer-opacity) of the grid layer.
+ setOpacity: function (opacity) {
+ this.options.opacity = opacity;
+ this._updateOpacity();
+ return this;
+ },
+
+ // @method setZIndex(zIndex: Number): this
+ // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
+ setZIndex: function (zIndex) {
+ this.options.zIndex = zIndex;
+ this._updateZIndex();
+
+ return this;
+ },
+
+ // @method isLoading: Boolean
+ // Returns `true` if any tile in the grid layer has not finished loading.
+ isLoading: function () {
+ return this._loading;
+ },
+
+ // @method redraw: this
+ // Causes the layer to clear all the tiles and request them again.
+ redraw: function () {
+ if (this._map) {
+ this._removeAllTiles();
+ this._update();
+ }
+ return this;
+ },
+
+ getEvents: function () {
+ var events = {
+ viewprereset: this._invalidateAll,
+ viewreset: this._resetView,
+ zoom: this._resetView,
+ moveend: this._onMoveEnd
+ };
+
+ if (!this.options.updateWhenIdle) {
+ // update tiles on move, but not more often than once per given interval
+ if (!this._onMove) {
+ this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
+ }
+
+ events.move = this._onMove;
+ }
+
+ if (this._zoomAnimated) {
+ events.zoomanim = this._animateZoom;
+ }
+
+ return events;
+ },
+
+ // @section Extension methods
+ // Layers extending `GridLayer` shall reimplement the following method.
+ // @method createTile(coords: Object, done?: Function): HTMLElement
+ // Called only internally, must be overridden by classes extending `GridLayer`.
+ // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
+ // is specified, it must be called when the tile has finished loading and drawing.
+ createTile: function () {
+ return document.createElement('div');
+ },
+
+ // @section
+ // @method getTileSize: Point
+ // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
+ getTileSize: function () {
+ var s = this.options.tileSize;
+ return s instanceof Point ? s : new Point(s, s);
+ },
+
+ _updateZIndex: function () {
+ if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
+ this._container.style.zIndex = this.options.zIndex;
+ }
+ },
+
+ _setAutoZIndex: function (compare) {
+ // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
+
+ var layers = this.getPane().children,
+ edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
+
+ for (var i = 0, len = layers.length, zIndex; i < len; i++) {
+
+ zIndex = layers[i].style.zIndex;
+
+ if (layers[i] !== this._container && zIndex) {
+ edgeZIndex = compare(edgeZIndex, +zIndex);
+ }
+ }
+
+ if (isFinite(edgeZIndex)) {
+ this.options.zIndex = edgeZIndex + compare(-1, 1);
+ this._updateZIndex();
+ }
+ },
+
+ _updateOpacity: function () {
+ if (!this._map) { return; }
+
+ // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
+ if (ielt9) { return; }
+
+ setOpacity(this._container, this.options.opacity);
+
+ var now = +new Date(),
+ nextFrame = false,
+ willPrune = false;
+
+ for (var key in this._tiles) {
+ var tile = this._tiles[key];
+ if (!tile.current || !tile.loaded) { continue; }
+
+ var fade = Math.min(1, (now - tile.loaded) / 200);
+
+ setOpacity(tile.el, fade);
+ if (fade < 1) {
+ nextFrame = true;
+ } else {
+ if (tile.active) {
+ willPrune = true;
+ } else {
+ this._onOpaqueTile(tile);
+ }
+ tile.active = true;
+ }
+ }
+
+ if (willPrune && !this._noPrune) { this._pruneTiles(); }
+
+ if (nextFrame) {
+ cancelAnimFrame(this._fadeFrame);
+ this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
+ }
+ },
+
+ _onOpaqueTile: falseFn,
+
+ _initContainer: function () {
+ if (this._container) { return; }
+
+ this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
+ this._updateZIndex();
+
+ if (this.options.opacity < 1) {
+ this._updateOpacity();
+ }
+
+ this.getPane().appendChild(this._container);
+ },
+
+ _updateLevels: function () {
+
+ var zoom = this._tileZoom,
+ maxZoom = this.options.maxZoom;
+
+ if (zoom === undefined) { return undefined; }
+
+ for (var z in this._levels) {
+ if (this._levels[z].el.children.length || z === zoom) {
+ this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
+ this._onUpdateLevel(z);
+ } else {
+ remove(this._levels[z].el);
+ this._removeTilesAtZoom(z);
+ this._onRemoveLevel(z);
+ delete this._levels[z];
+ }
+ }
+
+ var level = this._levels[zoom],
+ map = this._map;
+
+ if (!level) {
+ level = this._levels[zoom] = {};
+
+ level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
+ level.el.style.zIndex = maxZoom;
+
+ level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
+ level.zoom = zoom;
+
+ this._setZoomTransform(level, map.getCenter(), map.getZoom());
+
+ // force the browser to consider the newly added element for transition
+ falseFn(level.el.offsetWidth);
+
+ this._onCreateLevel(level);
+ }
+
+ this._level = level;
+
+ return level;
+ },
+
+ _onUpdateLevel: falseFn,
+
+ _onRemoveLevel: falseFn,
+
+ _onCreateLevel: falseFn,
+
+ _pruneTiles: function () {
+ if (!this._map) {
+ return;
+ }
+
+ var key, tile;
+
+ var zoom = this._map.getZoom();
+ if (zoom > this.options.maxZoom ||
+ zoom < this.options.minZoom) {
+ this._removeAllTiles();
+ return;
+ }
+
+ for (key in this._tiles) {
+ tile = this._tiles[key];
+ tile.retain = tile.current;
+ }
+
+ for (key in this._tiles) {
+ tile = this._tiles[key];
+ if (tile.current && !tile.active) {
+ var coords = tile.coords;
+ if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
+ this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
+ }
+ }
+ }
+
+ for (key in this._tiles) {
+ if (!this._tiles[key].retain) {
+ this._removeTile(key);
+ }
+ }
+ },
+
+ _removeTilesAtZoom: function (zoom) {
+ for (var key in this._tiles) {
+ if (this._tiles[key].coords.z !== zoom) {
+ continue;
+ }
+ this._removeTile(key);
+ }
+ },
+
+ _removeAllTiles: function () {
+ for (var key in this._tiles) {
+ this._removeTile(key);
+ }
+ },
+
+ _invalidateAll: function () {
+ for (var z in this._levels) {
+ remove(this._levels[z].el);
+ this._onRemoveLevel(z);
+ delete this._levels[z];
+ }
+ this._removeAllTiles();
+
+ this._tileZoom = undefined;
+ },
+
+ _retainParent: function (x, y, z, minZoom) {
+ var x2 = Math.floor(x / 2),
+ y2 = Math.floor(y / 2),
+ z2 = z - 1,
+ coords2 = new Point(+x2, +y2);
+ coords2.z = +z2;
+
+ var key = this._tileCoordsToKey(coords2),
+ tile = this._tiles[key];
+
+ if (tile && tile.active) {
+ tile.retain = true;
+ return true;
+
+ } else if (tile && tile.loaded) {
+ tile.retain = true;
+ }
+
+ if (z2 > minZoom) {
+ return this._retainParent(x2, y2, z2, minZoom);
+ }
+
+ return false;
+ },
+
+ _retainChildren: function (x, y, z, maxZoom) {
+
+ for (var i = 2 * x; i < 2 * x + 2; i++) {
+ for (var j = 2 * y; j < 2 * y + 2; j++) {
+
+ var coords = new Point(i, j);
+ coords.z = z + 1;
+
+ var key = this._tileCoordsToKey(coords),
+ tile = this._tiles[key];
+
+ if (tile && tile.active) {
+ tile.retain = true;
+ continue;
+
+ } else if (tile && tile.loaded) {
+ tile.retain = true;
+ }
+
+ if (z + 1 < maxZoom) {
+ this._retainChildren(i, j, z + 1, maxZoom);
+ }
+ }
+ }
+ },
+
+ _resetView: function (e) {
+ var animating = e && (e.pinch || e.flyTo);
+ this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
+ },
+
+ _animateZoom: function (e) {
+ this._setView(e.center, e.zoom, true, e.noUpdate);
+ },
+
+ _clampZoom: function (zoom) {
+ var options = this.options;
+
+ if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
+ return options.minNativeZoom;
+ }
+
+ if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
+ return options.maxNativeZoom;
+ }
+
+ return zoom;
+ },
+
+ _setView: function (center, zoom, noPrune, noUpdate) {
+ var tileZoom = this._clampZoom(Math.round(zoom));
+ if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
+ (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
+ tileZoom = undefined;
+ }
+
+ var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
+
+ if (!noUpdate || tileZoomChanged) {
+
+ this._tileZoom = tileZoom;
+
+ if (this._abortLoading) {
+ this._abortLoading();
+ }
+
+ this._updateLevels();
+ this._resetGrid();
+
+ if (tileZoom !== undefined) {
+ this._update(center);
+ }
+
+ if (!noPrune) {
+ this._pruneTiles();
+ }
+
+ // Flag to prevent _updateOpacity from pruning tiles during
+ // a zoom anim or a pinch gesture
+ this._noPrune = !!noPrune;
+ }
+
+ this._setZoomTransforms(center, zoom);
+ },
+
+ _setZoomTransforms: function (center, zoom) {
+ for (var i in this._levels) {
+ this._setZoomTransform(this._levels[i], center, zoom);
+ }
+ },
+
+ _setZoomTransform: function (level, center, zoom) {
+ var scale = this._map.getZoomScale(zoom, level.zoom),
+ translate = level.origin.multiplyBy(scale)
+ .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
+
+ if (any3d) {
+ setTransform(level.el, translate, scale);
+ } else {
+ setPosition(level.el, translate);
+ }
+ },
+
+ _resetGrid: function () {
+ var map = this._map,
+ crs = map.options.crs,
+ tileSize = this._tileSize = this.getTileSize(),
+ tileZoom = this._tileZoom;
+
+ var bounds = this._map.getPixelWorldBounds(this._tileZoom);
+ if (bounds) {
+ this._globalTileRange = this._pxBoundsToTileRange(bounds);
+ }
+
+ this._wrapX = crs.wrapLng && !this.options.noWrap && [
+ Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
+ Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
+ ];
+ this._wrapY = crs.wrapLat && !this.options.noWrap && [
+ Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
+ Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
+ ];
+ },
+
+ _onMoveEnd: function () {
+ if (!this._map || this._map._animatingZoom) { return; }
+
+ this._update();
+ },
+
+ _getTiledPixelBounds: function (center) {
+ var map = this._map,
+ mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
+ scale = map.getZoomScale(mapZoom, this._tileZoom),
+ pixelCenter = map.project(center, this._tileZoom).floor(),
+ halfSize = map.getSize().divideBy(scale * 2);
+
+ return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
+ },
+
+ // Private method to load tiles in the grid's active zoom level according to map bounds
+ _update: function (center) {
+ var map = this._map;
+ if (!map) { return; }
+ var zoom = this._clampZoom(map.getZoom());
+
+ if (center === undefined) { center = map.getCenter(); }
+ if (this._tileZoom === undefined) { return; } // if out of minzoom/maxzoom
+
+ var pixelBounds = this._getTiledPixelBounds(center),
+ tileRange = this._pxBoundsToTileRange(pixelBounds),
+ tileCenter = tileRange.getCenter(),
+ queue = [],
+ margin = this.options.keepBuffer,
+ noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
+ tileRange.getTopRight().add([margin, -margin]));
+
+ // Sanity check: panic if the tile range contains Infinity somewhere.
+ if (!(isFinite(tileRange.min.x) &&
+ isFinite(tileRange.min.y) &&
+ isFinite(tileRange.max.x) &&
+ isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
+
+ for (var key in this._tiles) {
+ var c = this._tiles[key].coords;
+ if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
+ this._tiles[key].current = false;
+ }
+ }
+
+ // _update just loads more tiles. If the tile zoom level differs too much
+ // from the map's, let _setView reset levels and prune old tiles.
+ if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
+
+ // create a queue of coordinates to load tiles from
+ for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
+ for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
+ var coords = new Point(i, j);
+ coords.z = this._tileZoom;
+
+ if (!this._isValidTile(coords)) { continue; }
+
+ var tile = this._tiles[this._tileCoordsToKey(coords)];
+ if (tile) {
+ tile.current = true;
+ } else {
+ queue.push(coords);
+ }
+ }
+ }
+
+ // sort tile queue to load tiles in order of their distance to center
+ queue.sort(function (a, b) {
+ return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
+ });
+
+ if (queue.length !== 0) {
+ // if it's the first batch of tiles to load
+ if (!this._loading) {
+ this._loading = true;
+ // @event loading: Event
+ // Fired when the grid layer starts loading tiles.
+ this.fire('loading');
+ }
+
+ // create DOM fragment to append tiles in one batch
+ var fragment = document.createDocumentFragment();
+
+ for (i = 0; i < queue.length; i++) {
+ this._addTile(queue[i], fragment);
+ }
+
+ this._level.el.appendChild(fragment);
+ }
+ },
+
+ _isValidTile: function (coords) {
+ var crs = this._map.options.crs;
+
+ if (!crs.infinite) {
+ // don't load tile if it's out of bounds and not wrapped
+ var bounds = this._globalTileRange;
+ if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
+ (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
+ }
+
+ if (!this.options.bounds) { return true; }
+
+ // don't load tile if it doesn't intersect the bounds in options
+ var tileBounds = this._tileCoordsToBounds(coords);
+ return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
+ },
+
+ _keyToBounds: function (key) {
+ return this._tileCoordsToBounds(this._keyToTileCoords(key));
+ },
+
+ _tileCoordsToNwSe: function (coords) {
+ var map = this._map,
+ tileSize = this.getTileSize(),
+ nwPoint = coords.scaleBy(tileSize),
+ sePoint = nwPoint.add(tileSize),
+ nw = map.unproject(nwPoint, coords.z),
+ se = map.unproject(sePoint, coords.z);
+ return [nw, se];
+ },
+
+ // converts tile coordinates to its geographical bounds
+ _tileCoordsToBounds: function (coords) {
+ var bp = this._tileCoordsToNwSe(coords),
+ bounds = new LatLngBounds(bp[0], bp[1]);
+
+ if (!this.options.noWrap) {
+ bounds = this._map.wrapLatLngBounds(bounds);
+ }
+ return bounds;
+ },
+ // converts tile coordinates to key for the tile cache
+ _tileCoordsToKey: function (coords) {
+ return coords.x + ':' + coords.y + ':' + coords.z;
+ },
+
+ // converts tile cache key to coordinates
+ _keyToTileCoords: function (key) {
+ var k = key.split(':'),
+ coords = new Point(+k[0], +k[1]);
+ coords.z = +k[2];
+ return coords;
+ },
+
+ _removeTile: function (key) {
+ var tile = this._tiles[key];
+ if (!tile) { return; }
+
+ remove(tile.el);
+
+ delete this._tiles[key];
+
+ // @event tileunload: TileEvent
+ // Fired when a tile is removed (e.g. when a tile goes off the screen).
+ this.fire('tileunload', {
+ tile: tile.el,
+ coords: this._keyToTileCoords(key)
+ });
+ },
+
+ _initTile: function (tile) {
+ addClass(tile, 'leaflet-tile');
+
+ var tileSize = this.getTileSize();
+ tile.style.width = tileSize.x + 'px';
+ tile.style.height = tileSize.y + 'px';
+
+ tile.onselectstart = falseFn;
+ tile.onmousemove = falseFn;
+
+ // update opacity on tiles in IE7-8 because of filter inheritance problems
+ if (ielt9 && this.options.opacity < 1) {
+ setOpacity(tile, this.options.opacity);
+ }
+
+ // without this hack, tiles disappear after zoom on Chrome for Android
+ // https://github.com/Leaflet/Leaflet/issues/2078
+ if (android && !android23) {
+ tile.style.WebkitBackfaceVisibility = 'hidden';
+ }
+ },
+
+ _addTile: function (coords, container) {
+ var tilePos = this._getTilePos(coords),
+ key = this._tileCoordsToKey(coords);
+
+ var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
+
+ this._initTile(tile);
+
+ // if createTile is defined with a second argument ("done" callback),
+ // we know that tile is async and will be ready later; otherwise
+ if (this.createTile.length < 2) {
+ // mark tile as ready, but delay one frame for opacity animation to happen
+ requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
+ }
+
+ setPosition(tile, tilePos);
+
+ // save tile in cache
+ this._tiles[key] = {
+ el: tile,
+ coords: coords,
+ current: true
+ };
+
+ container.appendChild(tile);
+ // @event tileloadstart: TileEvent
+ // Fired when a tile is requested and starts loading.
+ this.fire('tileloadstart', {
+ tile: tile,
+ coords: coords
+ });
+ },
+
+ _tileReady: function (coords, err, tile) {
+ if (err) {
+ // @event tileerror: TileErrorEvent
+ // Fired when there is an error loading a tile.
+ this.fire('tileerror', {
+ error: err,
+ tile: tile,
+ coords: coords
+ });
+ }
+
+ var key = this._tileCoordsToKey(coords);
+
+ tile = this._tiles[key];
+ if (!tile) { return; }
+
+ tile.loaded = +new Date();
+ if (this._map._fadeAnimated) {
+ setOpacity(tile.el, 0);
+ cancelAnimFrame(this._fadeFrame);
+ this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
+ } else {
+ tile.active = true;
+ this._pruneTiles();
+ }
+
+ if (!err) {
+ addClass(tile.el, 'leaflet-tile-loaded');
+
+ // @event tileload: TileEvent
+ // Fired when a tile loads.
+ this.fire('tileload', {
+ tile: tile.el,
+ coords: coords
+ });
+ }
+
+ if (this._noTilesToLoad()) {
+ this._loading = false;
+ // @event load: Event
+ // Fired when the grid layer loaded all visible tiles.
+ this.fire('load');
+
+ if (ielt9 || !this._map._fadeAnimated) {
+ requestAnimFrame(this._pruneTiles, this);
+ } else {
+ // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
+ // to trigger a pruning.
+ setTimeout(bind(this._pruneTiles, this), 250);
+ }
+ }
+ },
+
+ _getTilePos: function (coords) {
+ return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
+ },
+
+ _wrapCoords: function (coords) {
+ var newCoords = new Point(
+ this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
+ this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
+ newCoords.z = coords.z;
+ return newCoords;
+ },
+
+ _pxBoundsToTileRange: function (bounds) {
+ var tileSize = this.getTileSize();
+ return new Bounds(
+ bounds.min.unscaleBy(tileSize).floor(),
+ bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
+ },
+
+ _noTilesToLoad: function () {
+ for (var key in this._tiles) {
+ if (!this._tiles[key].loaded) { return false; }
+ }
+ return true;
+ }
+});
+
+// @factory L.gridLayer(options?: GridLayer options)
+// Creates a new instance of GridLayer with the supplied options.
+function gridLayer(options) {
+ return new GridLayer(options);
+}
+
+/*
+ * @class TileLayer
+ * @inherits GridLayer
+ * @aka L.TileLayer
+ * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`.
+ *
+ * @example
+ *
+ * ```js
+ * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'}).addTo(map);
+ * ```
+ *
+ * @section URL template
+ * @example
+ *
+ * A string of the following form:
+ *
+ * ```
+ * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
+ * ```
+ *
+ * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` — zoom level, `{x}` and `{y}` — tile coordinates. `{r}` can be used to add "&commat;2x" to the URL to load retina tiles.
+ *
+ * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
+ *
+ * ```
+ * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
+ * ```
+ */
+
+
+var TileLayer = GridLayer.extend({
+
+ // @section
+ // @aka TileLayer options
+ options: {
+ // @option minZoom: Number = 0
+ // The minimum zoom level down to which this layer will be displayed (inclusive).
+ minZoom: 0,
+
+ // @option maxZoom: Number = 18
+ // The maximum zoom level up to which this layer will be displayed (inclusive).
+ maxZoom: 18,
+
+ // @option subdomains: String|String[] = 'abc'
+ // Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings.
+ subdomains: 'abc',
+
+ // @option errorTileUrl: String = ''
+ // URL to the tile image to show in place of the tile that failed to load.
+ errorTileUrl: '',
+
+ // @option zoomOffset: Number = 0
+ // The zoom number used in tile URLs will be offset with this value.
+ zoomOffset: 0,
+
+ // @option tms: Boolean = false
+ // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
+ tms: false,
+
+ // @option zoomReverse: Boolean = false
+ // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
+ zoomReverse: false,
+
+ // @option detectRetina: Boolean = false
+ // If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution.
+ detectRetina: false,
+
+ // @option crossOrigin: Boolean|String = false
+ // Whether the crossOrigin attribute will be added to the tiles.
+ // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
+ // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
+ crossOrigin: false
+ },
+
+ initialize: function (url, options) {
+
+ this._url = url;
+
+ options = setOptions(this, options);
+
+ // detecting retina displays, adjusting tileSize and zoom levels
+ if (options.detectRetina && retina && options.maxZoom > 0) {
+
+ options.tileSize = Math.floor(options.tileSize / 2);
+
+ if (!options.zoomReverse) {
+ options.zoomOffset++;
+ options.maxZoom--;
+ } else {
+ options.zoomOffset--;
+ options.minZoom++;
+ }
+
+ options.minZoom = Math.max(0, options.minZoom);
+ }
+
+ if (typeof options.subdomains === 'string') {
+ options.subdomains = options.subdomains.split('');
+ }
+
+ // for https://github.com/Leaflet/Leaflet/issues/137
+ if (!android) {
+ this.on('tileunload', this._onTileRemove);
+ }
+ },
+
+ // @method setUrl(url: String, noRedraw?: Boolean): this
+ // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
+ // If the URL does not change, the layer will not be redrawn unless
+ // the noRedraw parameter is set to false.
+ setUrl: function (url, noRedraw) {
+ if (this._url === url && noRedraw === undefined) {
+ noRedraw = true;
+ }
+
+ this._url = url;
+
+ if (!noRedraw) {
+ this.redraw();
+ }
+ return this;
+ },
+
+ // @method createTile(coords: Object, done?: Function): HTMLElement
+ // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
+ // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
+ // callback is called when the tile has been loaded.
+ createTile: function (coords, done) {
+ var tile = document.createElement('img');
+
+ on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
+ on(tile, 'error', bind(this._tileOnError, this, done, tile));
+
+ if (this.options.crossOrigin || this.options.crossOrigin === '') {
+ tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
+ }
+
+ /*
+ Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
+ http://www.w3.org/TR/WCAG20-TECHS/H67
+ */
+ tile.alt = '';
+
+ /*
+ Set role="presentation" to force screen readers to ignore this
+ https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
+ */
+ tile.setAttribute('role', 'presentation');
+
+ tile.src = this.getTileUrl(coords);
+
+ return tile;
+ },
+
+ // @section Extension methods
+ // @uninheritable
+ // Layers extending `TileLayer` might reimplement the following method.
+ // @method getTileUrl(coords: Object): String
+ // Called only internally, returns the URL for a tile given its coordinates.
+ // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
+ getTileUrl: function (coords) {
+ var data = {
+ r: retina ? '@2x' : '',
+ s: this._getSubdomain(coords),
+ x: coords.x,
+ y: coords.y,
+ z: this._getZoomForUrl()
+ };
+ if (this._map && !this._map.options.crs.infinite) {
+ var invertedY = this._globalTileRange.max.y - coords.y;
+ if (this.options.tms) {
+ data['y'] = invertedY;
+ }
+ data['-y'] = invertedY;
+ }
+
+ return template(this._url, extend(data, this.options));
+ },
+
+ _tileOnLoad: function (done, tile) {
+ // For https://github.com/Leaflet/Leaflet/issues/3332
+ if (ielt9) {
+ setTimeout(bind(done, this, null, tile), 0);
+ } else {
+ done(null, tile);
+ }
+ },
+
+ _tileOnError: function (done, tile, e) {
+ var errorUrl = this.options.errorTileUrl;
+ if (errorUrl && tile.getAttribute('src') !== errorUrl) {
+ tile.src = errorUrl;
+ }
+ done(e, tile);
+ },
+
+ _onTileRemove: function (e) {
+ e.tile.onload = null;
+ },
+
+ _getZoomForUrl: function () {
+ var zoom = this._tileZoom,
+ maxZoom = this.options.maxZoom,
+ zoomReverse = this.options.zoomReverse,
+ zoomOffset = this.options.zoomOffset;
+
+ if (zoomReverse) {
+ zoom = maxZoom - zoom;
+ }
+
+ return zoom + zoomOffset;
+ },
+
+ _getSubdomain: function (tilePoint) {
+ var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
+ return this.options.subdomains[index];
+ },
+
+ // stops loading all tiles in the background layer
+ _abortLoading: function () {
+ var i, tile;
+ for (i in this._tiles) {
+ if (this._tiles[i].coords.z !== this._tileZoom) {
+ tile = this._tiles[i].el;
+
+ tile.onload = falseFn;
+ tile.onerror = falseFn;
+
+ if (!tile.complete) {
+ tile.src = emptyImageUrl;
+ remove(tile);
+ delete this._tiles[i];
+ }
+ }
+ }
+ },
+
+ _removeTile: function (key) {
+ var tile = this._tiles[key];
+ if (!tile) { return; }
+
+ // Cancels any pending http requests associated with the tile
+ // unless we're on Android's stock browser,
+ // see https://github.com/Leaflet/Leaflet/issues/137
+ if (!androidStock) {
+ tile.el.setAttribute('src', emptyImageUrl);
+ }
+
+ return GridLayer.prototype._removeTile.call(this, key);
+ },
+
+ _tileReady: function (coords, err, tile) {
+ if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
+ return;
+ }
+
+ return GridLayer.prototype._tileReady.call(this, coords, err, tile);
+ }
+});
+
+
+// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
+// Instantiates a tile layer object given a `URL template` and optionally an options object.
+
+function tileLayer(url, options) {
+ return new TileLayer(url, options);
+}
+
+/*
+ * @class TileLayer.WMS
+ * @inherits TileLayer
+ * @aka L.TileLayer.WMS
+ * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
+ *
+ * @example
+ *
+ * ```js
+ * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
+ * layers: 'nexrad-n0r-900913',
+ * format: 'image/png',
+ * transparent: true,
+ * attribution: "Weather data © 2012 IEM Nexrad"
+ * });
+ * ```
+ */
+
+var TileLayerWMS = TileLayer.extend({
+
+ // @section
+ // @aka TileLayer.WMS options
+ // If any custom options not documented here are used, they will be sent to the
+ // WMS server as extra parameters in each request URL. This can be useful for
+ // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
+ defaultWmsParams: {
+ service: 'WMS',
+ request: 'GetMap',
+
+ // @option layers: String = ''
+ // **(required)** Comma-separated list of WMS layers to show.
+ layers: '',
+
+ // @option styles: String = ''
+ // Comma-separated list of WMS styles.
+ styles: '',
+
+ // @option format: String = 'image/jpeg'
+ // WMS image format (use `'image/png'` for layers with transparency).
+ format: 'image/jpeg',
+
+ // @option transparent: Boolean = false
+ // If `true`, the WMS service will return images with transparency.
+ transparent: false,
+
+ // @option version: String = '1.1.1'
+ // Version of the WMS service to use
+ version: '1.1.1'
+ },
+
+ options: {
+ // @option crs: CRS = null
+ // Coordinate Reference System to use for the WMS requests, defaults to
+ // map CRS. Don't change this if you're not sure what it means.
+ crs: null,
+
+ // @option uppercase: Boolean = false
+ // If `true`, WMS request parameter keys will be uppercase.
+ uppercase: false
+ },
+
+ initialize: function (url, options) {
+
+ this._url = url;
+
+ var wmsParams = extend({}, this.defaultWmsParams);
+
+ // all keys that are not TileLayer options go to WMS params
+ for (var i in options) {
+ if (!(i in this.options)) {
+ wmsParams[i] = options[i];
+ }
+ }
+
+ options = setOptions(this, options);
+
+ var realRetina = options.detectRetina && retina ? 2 : 1;
+ var tileSize = this.getTileSize();
+ wmsParams.width = tileSize.x * realRetina;
+ wmsParams.height = tileSize.y * realRetina;
+
+ this.wmsParams = wmsParams;
+ },
+
+ onAdd: function (map) {
+
+ this._crs = this.options.crs || map.options.crs;
+ this._wmsVersion = parseFloat(this.wmsParams.version);
+
+ var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
+ this.wmsParams[projectionKey] = this._crs.code;
+
+ TileLayer.prototype.onAdd.call(this, map);
+ },
+
+ getTileUrl: function (coords) {
+
+ var tileBounds = this._tileCoordsToNwSe(coords),
+ crs = this._crs,
+ bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
+ min = bounds.min,
+ max = bounds.max,
+ bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
+ [min.y, min.x, max.y, max.x] :
+ [min.x, min.y, max.x, max.y]).join(','),
+ url = TileLayer.prototype.getTileUrl.call(this, coords);
+ return url +
+ getParamString(this.wmsParams, url, this.options.uppercase) +
+ (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
+ },
+
+ // @method setParams(params: Object, noRedraw?: Boolean): this
+ // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
+ setParams: function (params, noRedraw) {
+
+ extend(this.wmsParams, params);
+
+ if (!noRedraw) {
+ this.redraw();
+ }
+
+ return this;
+ }
+});
+
+
+// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
+// Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
+function tileLayerWMS(url, options) {
+ return new TileLayerWMS(url, options);
+}
+
+TileLayer.WMS = TileLayerWMS;
+tileLayer.wms = tileLayerWMS;
+
+/*
+ * @class Renderer
+ * @inherits Layer
+ * @aka L.Renderer
+ *
+ * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
+ * DOM container of the renderer, its bounds, and its zoom animation.
+ *
+ * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
+ * itself can be added or removed to the map. All paths use a renderer, which can
+ * be implicit (the map will decide the type of renderer and use it automatically)
+ * or explicit (using the [`renderer`](#path-renderer) option of the path).
+ *
+ * Do not use this class directly, use `SVG` and `Canvas` instead.
+ *
+ * @event update: Event
+ * Fired when the renderer updates its bounds, center and zoom, for example when
+ * its map has moved
+ */
+
+var Renderer = Layer.extend({
+
+ // @section
+ // @aka Renderer options
+ options: {
+ // @option padding: Number = 0.1
+ // How much to extend the clip area around the map view (relative to its size)
+ // e.g. 0.1 would be 10% of map view in each direction
+ padding: 0.1,
+
+ // @option tolerance: Number = 0
+ // How much to extend click tolerance round a path/object on the map
+ tolerance : 0
+ },
+
+ initialize: function (options) {
+ setOptions(this, options);
+ stamp(this);
+ this._layers = this._layers || {};
+ },
+
+ onAdd: function () {
+ if (!this._container) {
+ this._initContainer(); // defined by renderer implementations
+
+ if (this._zoomAnimated) {
+ addClass(this._container, 'leaflet-zoom-animated');
+ }
+ }
+
+ this.getPane().appendChild(this._container);
+ this._update();
+ this.on('update', this._updatePaths, this);
+ },
+
+ onRemove: function () {
+ this.off('update', this._updatePaths, this);
+ this._destroyContainer();
+ },
+
+ getEvents: function () {
+ var events = {
+ viewreset: this._reset,
+ zoom: this._onZoom,
+ moveend: this._update,
+ zoomend: this._onZoomEnd
+ };
+ if (this._zoomAnimated) {
+ events.zoomanim = this._onAnimZoom;
+ }
+ return events;
+ },
+
+ _onAnimZoom: function (ev) {
+ this._updateTransform(ev.center, ev.zoom);
+ },
+
+ _onZoom: function () {
+ this._updateTransform(this._map.getCenter(), this._map.getZoom());
+ },
+
+ _updateTransform: function (center, zoom) {
+ var scale = this._map.getZoomScale(zoom, this._zoom),
+ position = getPosition(this._container),
+ viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
+ currentCenterPoint = this._map.project(this._center, zoom),
+ destCenterPoint = this._map.project(center, zoom),
+ centerOffset = destCenterPoint.subtract(currentCenterPoint),
+
+ topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
+
+ if (any3d) {
+ setTransform(this._container, topLeftOffset, scale);
+ } else {
+ setPosition(this._container, topLeftOffset);
+ }
+ },
+
+ _reset: function () {
+ this._update();
+ this._updateTransform(this._center, this._zoom);
+
+ for (var id in this._layers) {
+ this._layers[id]._reset();
+ }
+ },
+
+ _onZoomEnd: function () {
+ for (var id in this._layers) {
+ this._layers[id]._project();
+ }
+ },
+
+ _updatePaths: function () {
+ for (var id in this._layers) {
+ this._layers[id]._update();
+ }
+ },
+
+ _update: function () {
+ // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
+ // Subclasses are responsible of firing the 'update' event.
+ var p = this.options.padding,
+ size = this._map.getSize(),
+ min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
+
+ this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
+
+ this._center = this._map.getCenter();
+ this._zoom = this._map.getZoom();
+ }
+});
+
+/*
+ * @class Canvas
+ * @inherits Renderer
+ * @aka L.Canvas
+ *
+ * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
+ * Inherits `Renderer`.
+ *
+ * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
+ * available in all web browsers, notably IE8, and overlapping geometries might
+ * not display properly in some edge cases.
+ *
+ * @example
+ *
+ * Use Canvas by default for all paths in the map:
+ *
+ * ```js
+ * var map = L.map('map', {
+ * renderer: L.canvas()
+ * });
+ * ```
+ *
+ * Use a Canvas renderer with extra padding for specific vector geometries:
+ *
+ * ```js
+ * var map = L.map('map');
+ * var myRenderer = L.canvas({ padding: 0.5 });
+ * var line = L.polyline( coordinates, { renderer: myRenderer } );
+ * var circle = L.circle( center, { renderer: myRenderer } );
+ * ```
+ */
+
+var Canvas = Renderer.extend({
+ getEvents: function () {
+ var events = Renderer.prototype.getEvents.call(this);
+ events.viewprereset = this._onViewPreReset;
+ return events;
+ },
+
+ _onViewPreReset: function () {
+ // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
+ this._postponeUpdatePaths = true;
+ },
+
+ onAdd: function () {
+ Renderer.prototype.onAdd.call(this);
+
+ // Redraw vectors since canvas is cleared upon removal,
+ // in case of removing the renderer itself from the map.
+ this._draw();
+ },
+
+ _initContainer: function () {
+ var container = this._container = document.createElement('canvas');
+
+ on(container, 'mousemove', this._onMouseMove, this);
+ on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
+ on(container, 'mouseout', this._handleMouseOut, this);
+
+ this._ctx = container.getContext('2d');
+ },
+
+ _destroyContainer: function () {
+ cancelAnimFrame(this._redrawRequest);
+ delete this._ctx;
+ remove(this._container);
+ off(this._container);
+ delete this._container;
+ },
+
+ _updatePaths: function () {
+ if (this._postponeUpdatePaths) { return; }
+
+ var layer;
+ this._redrawBounds = null;
+ for (var id in this._layers) {
+ layer = this._layers[id];
+ layer._update();
+ }
+ this._redraw();
+ },
+
+ _update: function () {
+ if (this._map._animatingZoom && this._bounds) { return; }
+
+ Renderer.prototype._update.call(this);
+
+ var b = this._bounds,
+ container = this._container,
+ size = b.getSize(),
+ m = retina ? 2 : 1;
+
+ setPosition(container, b.min);
+
+ // set canvas size (also clearing it); use double size on retina
+ container.width = m * size.x;
+ container.height = m * size.y;
+ container.style.width = size.x + 'px';
+ container.style.height = size.y + 'px';
+
+ if (retina) {
+ this._ctx.scale(2, 2);
+ }
+
+ // translate so we use the same path coordinates after canvas element moves
+ this._ctx.translate(-b.min.x, -b.min.y);
+
+ // Tell paths to redraw themselves
+ this.fire('update');
+ },
+
+ _reset: function () {
+ Renderer.prototype._reset.call(this);
+
+ if (this._postponeUpdatePaths) {
+ this._postponeUpdatePaths = false;
+ this._updatePaths();
+ }
+ },
+
+ _initPath: function (layer) {
+ this._updateDashArray(layer);
+ this._layers[stamp(layer)] = layer;
+
+ var order = layer._order = {
+ layer: layer,
+ prev: this._drawLast,
+ next: null
+ };
+ if (this._drawLast) { this._drawLast.next = order; }
+ this._drawLast = order;
+ this._drawFirst = this._drawFirst || this._drawLast;
+ },
+
+ _addPath: function (layer) {
+ this._requestRedraw(layer);
+ },
+
+ _removePath: function (layer) {
+ var order = layer._order;
+ var next = order.next;
+ var prev = order.prev;
+
+ if (next) {
+ next.prev = prev;
+ } else {
+ this._drawLast = prev;
+ }
+ if (prev) {
+ prev.next = next;
+ } else {
+ this._drawFirst = next;
+ }
+
+ delete layer._order;
+
+ delete this._layers[stamp(layer)];
+
+ this._requestRedraw(layer);
+ },
+
+ _updatePath: function (layer) {
+ // Redraw the union of the layer's old pixel
+ // bounds and the new pixel bounds.
+ this._extendRedrawBounds(layer);
+ layer._project();
+ layer._update();
+ // The redraw will extend the redraw bounds
+ // with the new pixel bounds.
+ this._requestRedraw(layer);
+ },
+
+ _updateStyle: function (layer) {
+ this._updateDashArray(layer);
+ this._requestRedraw(layer);
+ },
+
+ _updateDashArray: function (layer) {
+ if (typeof layer.options.dashArray === 'string') {
+ var parts = layer.options.dashArray.split(/[, ]+/),
+ dashArray = [],
+ dashValue,
+ i;
+ for (i = 0; i < parts.length; i++) {
+ dashValue = Number(parts[i]);
+ // Ignore dash array containing invalid lengths
+ if (isNaN(dashValue)) { return; }
+ dashArray.push(dashValue);
+ }
+ layer.options._dashArray = dashArray;
+ } else {
+ layer.options._dashArray = layer.options.dashArray;
+ }
+ },
+
+ _requestRedraw: function (layer) {
+ if (!this._map) { return; }
+
+ this._extendRedrawBounds(layer);
+ this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
+ },
+
+ _extendRedrawBounds: function (layer) {
+ if (layer._pxBounds) {
+ var padding = (layer.options.weight || 0) + 1;
+ this._redrawBounds = this._redrawBounds || new Bounds();
+ this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
+ this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
+ }
+ },
+
+ _redraw: function () {
+ this._redrawRequest = null;
+
+ if (this._redrawBounds) {
+ this._redrawBounds.min._floor();
+ this._redrawBounds.max._ceil();
+ }
+
+ this._clear(); // clear layers in redraw bounds
+ this._draw(); // draw layers
+
+ this._redrawBounds = null;
+ },
+
+ _clear: function () {
+ var bounds = this._redrawBounds;
+ if (bounds) {
+ var size = bounds.getSize();
+ this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
+ } else {
+ this._ctx.clearRect(0, 0, this._container.width, this._container.height);
+ }
+ },
+
+ _draw: function () {
+ var layer, bounds = this._redrawBounds;
+ this._ctx.save();
+ if (bounds) {
+ var size = bounds.getSize();
+ this._ctx.beginPath();
+ this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
+ this._ctx.clip();
+ }
+
+ this._drawing = true;
+
+ for (var order = this._drawFirst; order; order = order.next) {
+ layer = order.layer;
+ if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
+ layer._updatePath();
+ }
+ }
+
+ this._drawing = false;
+
+ this._ctx.restore(); // Restore state before clipping.
+ },
+
+ _updatePoly: function (layer, closed) {
+ if (!this._drawing) { return; }
+
+ var i, j, len2, p,
+ parts = layer._parts,
+ len = parts.length,
+ ctx = this._ctx;
+
+ if (!len) { return; }
+
+ ctx.beginPath();
+
+ for (i = 0; i < len; i++) {
+ for (j = 0, len2 = parts[i].length; j < len2; j++) {
+ p = parts[i][j];
+ ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
+ }
+ if (closed) {
+ ctx.closePath();
+ }
+ }
+
+ this._fillStroke(ctx, layer);
+
+ // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
+ },
+
+ _updateCircle: function (layer) {
+
+ if (!this._drawing || layer._empty()) { return; }
+
+ var p = layer._point,
+ ctx = this._ctx,
+ r = Math.max(Math.round(layer._radius), 1),
+ s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
+
+ if (s !== 1) {
+ ctx.save();
+ ctx.scale(1, s);
+ }
+
+ ctx.beginPath();
+ ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
+
+ if (s !== 1) {
+ ctx.restore();
+ }
+
+ this._fillStroke(ctx, layer);
+ },
+
+ _fillStroke: function (ctx, layer) {
+ var options = layer.options;
+
+ if (options.fill) {
+ ctx.globalAlpha = options.fillOpacity;
+ ctx.fillStyle = options.fillColor || options.color;
+ ctx.fill(options.fillRule || 'evenodd');
+ }
+
+ if (options.stroke && options.weight !== 0) {
+ if (ctx.setLineDash) {
+ ctx.setLineDash(layer.options && layer.options._dashArray || []);
+ }
+ ctx.globalAlpha = options.opacity;
+ ctx.lineWidth = options.weight;
+ ctx.strokeStyle = options.color;
+ ctx.lineCap = options.lineCap;
+ ctx.lineJoin = options.lineJoin;
+ ctx.stroke();
+ }
+ },
+
+ // Canvas obviously doesn't have mouse events for individual drawn objects,
+ // so we emulate that by calculating what's under the mouse on mousemove/click manually
+
+ _onClick: function (e) {
+ var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
+
+ for (var order = this._drawFirst; order; order = order.next) {
+ layer = order.layer;
+ if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
+ clickedLayer = layer;
+ }
+ }
+ if (clickedLayer) {
+ fakeStop(e);
+ this._fireEvent([clickedLayer], e);
+ }
+ },
+
+ _onMouseMove: function (e) {
+ if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
+
+ var point = this._map.mouseEventToLayerPoint(e);
+ this._handleMouseHover(e, point);
+ },
+
+
+ _handleMouseOut: function (e) {
+ var layer = this._hoveredLayer;
+ if (layer) {
+ // if we're leaving the layer, fire mouseout
+ removeClass(this._container, 'leaflet-interactive');
+ this._fireEvent([layer], e, 'mouseout');
+ this._hoveredLayer = null;
+ this._mouseHoverThrottled = false;
+ }
+ },
+
+ _handleMouseHover: function (e, point) {
+ if (this._mouseHoverThrottled) {
+ return;
+ }
+
+ var layer, candidateHoveredLayer;
+
+ for (var order = this._drawFirst; order; order = order.next) {
+ layer = order.layer;
+ if (layer.options.interactive && layer._containsPoint(point)) {
+ candidateHoveredLayer = layer;
+ }
+ }
+
+ if (candidateHoveredLayer !== this._hoveredLayer) {
+ this._handleMouseOut(e);
+
+ if (candidateHoveredLayer) {
+ addClass(this._container, 'leaflet-interactive'); // change cursor
+ this._fireEvent([candidateHoveredLayer], e, 'mouseover');
+ this._hoveredLayer = candidateHoveredLayer;
+ }
+ }
+
+ if (this._hoveredLayer) {
+ this._fireEvent([this._hoveredLayer], e);
+ }
+
+ this._mouseHoverThrottled = true;
+ setTimeout(L.bind(function () {
+ this._mouseHoverThrottled = false;
+ }, this), 32);
+ },
+
+ _fireEvent: function (layers, e, type) {
+ this._map._fireDOMEvent(e, type || e.type, layers);
+ },
+
+ _bringToFront: function (layer) {
+ var order = layer._order;
+
+ if (!order) { return; }
+
+ var next = order.next;
+ var prev = order.prev;
+
+ if (next) {
+ next.prev = prev;
+ } else {
+ // Already last
+ return;
+ }
+ if (prev) {
+ prev.next = next;
+ } else if (next) {
+ // Update first entry unless this is the
+ // single entry
+ this._drawFirst = next;
+ }
+
+ order.prev = this._drawLast;
+ this._drawLast.next = order;
+
+ order.next = null;
+ this._drawLast = order;
+
+ this._requestRedraw(layer);
+ },
+
+ _bringToBack: function (layer) {
+ var order = layer._order;
+
+ if (!order) { return; }
+
+ var next = order.next;
+ var prev = order.prev;
+
+ if (prev) {
+ prev.next = next;
+ } else {
+ // Already first
+ return;
+ }
+ if (next) {
+ next.prev = prev;
+ } else if (prev) {
+ // Update last entry unless this is the
+ // single entry
+ this._drawLast = prev;
+ }
+
+ order.prev = null;
+
+ order.next = this._drawFirst;
+ this._drawFirst.prev = order;
+ this._drawFirst = order;
+
+ this._requestRedraw(layer);
+ }
+});
+
+// @factory L.canvas(options?: Renderer options)
+// Creates a Canvas renderer with the given options.
+function canvas$1(options) {
+ return canvas ? new Canvas(options) : null;
+}
+
+/*
+ * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
+ */
+
+
+var vmlCreate = (function () {
+ try {
+ document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
+ return function (name) {
+ return document.createElement('<lvml:' + name + ' class="lvml">');
+ };
+ } catch (e) {
+ return function (name) {
+ return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
+ };
+ }
+})();
+
+
+/*
+ * @class SVG
+ *
+ *
+ * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
+ * with old versions of Internet Explorer.
+ */
+
+// mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
+var vmlMixin = {
+
+ _initContainer: function () {
+ this._container = create$1('div', 'leaflet-vml-container');
+ },
+
+ _update: function () {
+ if (this._map._animatingZoom) { return; }
+ Renderer.prototype._update.call(this);
+ this.fire('update');
+ },
+
+ _initPath: function (layer) {
+ var container = layer._container = vmlCreate('shape');
+
+ addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
+
+ container.coordsize = '1 1';
+
+ layer._path = vmlCreate('path');
+ container.appendChild(layer._path);
+
+ this._updateStyle(layer);
+ this._layers[stamp(layer)] = layer;
+ },
+
+ _addPath: function (layer) {
+ var container = layer._container;
+ this._container.appendChild(container);
+
+ if (layer.options.interactive) {
+ layer.addInteractiveTarget(container);
+ }
+ },
+
+ _removePath: function (layer) {
+ var container = layer._container;
+ remove(container);
+ layer.removeInteractiveTarget(container);
+ delete this._layers[stamp(layer)];
+ },
+
+ _updateStyle: function (layer) {
+ var stroke = layer._stroke,
+ fill = layer._fill,
+ options = layer.options,
+ container = layer._container;
+
+ container.stroked = !!options.stroke;
+ container.filled = !!options.fill;
+
+ if (options.stroke) {
+ if (!stroke) {
+ stroke = layer._stroke = vmlCreate('stroke');
+ }
+ container.appendChild(stroke);
+ stroke.weight = options.weight + 'px';
+ stroke.color = options.color;
+ stroke.opacity = options.opacity;
+
+ if (options.dashArray) {
+ stroke.dashStyle = isArray(options.dashArray) ?
+ options.dashArray.join(' ') :
+ options.dashArray.replace(/( *, *)/g, ' ');
+ } else {
+ stroke.dashStyle = '';
+ }
+ stroke.endcap = options.lineCap.replace('butt', 'flat');
+ stroke.joinstyle = options.lineJoin;
+
+ } else if (stroke) {
+ container.removeChild(stroke);
+ layer._stroke = null;
+ }
+
+ if (options.fill) {
+ if (!fill) {
+ fill = layer._fill = vmlCreate('fill');
+ }
+ container.appendChild(fill);
+ fill.color = options.fillColor || options.color;
+ fill.opacity = options.fillOpacity;
+
+ } else if (fill) {
+ container.removeChild(fill);
+ layer._fill = null;
+ }
+ },
+
+ _updateCircle: function (layer) {
+ var p = layer._point.round(),
+ r = Math.round(layer._radius),
+ r2 = Math.round(layer._radiusY || r);
+
+ this._setPath(layer, layer._empty() ? 'M0 0' :
+ 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
+ },
+
+ _setPath: function (layer, path) {
+ layer._path.v = path;
+ },
+
+ _bringToFront: function (layer) {
+ toFront(layer._container);
+ },
+
+ _bringToBack: function (layer) {
+ toBack(layer._container);
+ }
+};
+
+var create$2 = vml ? vmlCreate : svgCreate;
+
+/*
+ * @class SVG
+ * @inherits Renderer
+ * @aka L.SVG
+ *
+ * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
+ * Inherits `Renderer`.
+ *
+ * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
+ * available in all web browsers, notably Android 2.x and 3.x.
+ *
+ * Although SVG is not available on IE7 and IE8, these browsers support
+ * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
+ * (a now deprecated technology), and the SVG renderer will fall back to VML in
+ * this case.
+ *
+ * @example
+ *
+ * Use SVG by default for all paths in the map:
+ *
+ * ```js
+ * var map = L.map('map', {
+ * renderer: L.svg()
+ * });
+ * ```
+ *
+ * Use a SVG renderer with extra padding for specific vector geometries:
+ *
+ * ```js
+ * var map = L.map('map');
+ * var myRenderer = L.svg({ padding: 0.5 });
+ * var line = L.polyline( coordinates, { renderer: myRenderer } );
+ * var circle = L.circle( center, { renderer: myRenderer } );
+ * ```
+ */
+
+var SVG = Renderer.extend({
+
+ getEvents: function () {
+ var events = Renderer.prototype.getEvents.call(this);
+ events.zoomstart = this._onZoomStart;
+ return events;
+ },
+
+ _initContainer: function () {
+ this._container = create$2('svg');
+
+ // makes it possible to click through svg root; we'll reset it back in individual paths
+ this._container.setAttribute('pointer-events', 'none');
+
+ this._rootGroup = create$2('g');
+ this._container.appendChild(this._rootGroup);
+ },
+
+ _destroyContainer: function () {
+ remove(this._container);
+ off(this._container);
+ delete this._container;
+ delete this._rootGroup;
+ delete this._svgSize;
+ },
+
+ _onZoomStart: function () {
+ // Drag-then-pinch interactions might mess up the center and zoom.
+ // In this case, the easiest way to prevent this is re-do the renderer
+ // bounds and padding when the zooming starts.
+ this._update();
+ },
+
+ _update: function () {
+ if (this._map._animatingZoom && this._bounds) { return; }
+
+ Renderer.prototype._update.call(this);
+
+ var b = this._bounds,
+ size = b.getSize(),
+ container = this._container;
+
+ // set size of svg-container if changed
+ if (!this._svgSize || !this._svgSize.equals(size)) {
+ this._svgSize = size;
+ container.setAttribute('width', size.x);
+ container.setAttribute('height', size.y);
+ }
+
+ // movement: update container viewBox so that we don't have to change coordinates of individual layers
+ setPosition(container, b.min);
+ container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
+
+ this.fire('update');
+ },
+
+ // methods below are called by vector layers implementations
+
+ _initPath: function (layer) {
+ var path = layer._path = create$2('path');
+
+ // @namespace Path
+ // @option className: String = null
+ // Custom class name set on an element. Only for SVG renderer.
+ if (layer.options.className) {
+ addClass(path, layer.options.className);
+ }
+
+ if (layer.options.interactive) {
+ addClass(path, 'leaflet-interactive');
+ }
+
+ this._updateStyle(layer);
+ this._layers[stamp(layer)] = layer;
+ },
+
+ _addPath: function (layer) {
+ if (!this._rootGroup) { this._initContainer(); }
+ this._rootGroup.appendChild(layer._path);
+ layer.addInteractiveTarget(layer._path);
+ },
+
+ _removePath: function (layer) {
+ remove(layer._path);
+ layer.removeInteractiveTarget(layer._path);
+ delete this._layers[stamp(layer)];
+ },
+
+ _updatePath: function (layer) {
+ layer._project();
+ layer._update();
+ },
+
+ _updateStyle: function (layer) {
+ var path = layer._path,
+ options = layer.options;
+
+ if (!path) { return; }
+
+ if (options.stroke) {
+ path.setAttribute('stroke', options.color);
+ path.setAttribute('stroke-opacity', options.opacity);
+ path.setAttribute('stroke-width', options.weight);
+ path.setAttribute('stroke-linecap', options.lineCap);
+ path.setAttribute('stroke-linejoin', options.lineJoin);
+
+ if (options.dashArray) {
+ path.setAttribute('stroke-dasharray', options.dashArray);
+ } else {
+ path.removeAttribute('stroke-dasharray');
+ }
+
+ if (options.dashOffset) {
+ path.setAttribute('stroke-dashoffset', options.dashOffset);
+ } else {
+ path.removeAttribute('stroke-dashoffset');
+ }
+ } else {
+ path.setAttribute('stroke', 'none');
+ }
+
+ if (options.fill) {
+ path.setAttribute('fill', options.fillColor || options.color);
+ path.setAttribute('fill-opacity', options.fillOpacity);
+ path.setAttribute('fill-rule', options.fillRule || 'evenodd');
+ } else {
+ path.setAttribute('fill', 'none');
+ }
+ },
+
+ _updatePoly: function (layer, closed) {
+ this._setPath(layer, pointsToPath(layer._parts, closed));
+ },
+
+ _updateCircle: function (layer) {
+ var p = layer._point,
+ r = Math.max(Math.round(layer._radius), 1),
+ r2 = Math.max(Math.round(layer._radiusY), 1) || r,
+ arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
+
+ // drawing a circle with two half-arcs
+ var d = layer._empty() ? 'M0 0' :
+ 'M' + (p.x - r) + ',' + p.y +
+ arc + (r * 2) + ',0 ' +
+ arc + (-r * 2) + ',0 ';
+
+ this._setPath(layer, d);
+ },
+
+ _setPath: function (layer, path) {
+ layer._path.setAttribute('d', path);
+ },
+
+ // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
+ _bringToFront: function (layer) {
+ toFront(layer._path);
+ },
+
+ _bringToBack: function (layer) {
+ toBack(layer._path);
+ }
+});
+
+if (vml) {
+ SVG.include(vmlMixin);
+}
+
+// @namespace SVG
+// @factory L.svg(options?: Renderer options)
+// Creates a SVG renderer with the given options.
+function svg$1(options) {
+ return svg || vml ? new SVG(options) : null;
+}
+
+Map.include({
+ // @namespace Map; @method getRenderer(layer: Path): Renderer
+ // Returns the instance of `Renderer` that should be used to render the given
+ // `Path`. It will ensure that the `renderer` options of the map and paths
+ // are respected, and that the renderers do exist on the map.
+ getRenderer: function (layer) {
+ // @namespace Path; @option renderer: Renderer
+ // Use this specific instance of `Renderer` for this path. Takes
+ // precedence over the map's [default renderer](#map-renderer).
+ var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
+
+ if (!renderer) {
+ renderer = this._renderer = this._createRenderer();
+ }
+
+ if (!this.hasLayer(renderer)) {
+ this.addLayer(renderer);
+ }
+ return renderer;
+ },
+
+ _getPaneRenderer: function (name) {
+ if (name === 'overlayPane' || name === undefined) {
+ return false;
+ }
+
+ var renderer = this._paneRenderers[name];
+ if (renderer === undefined) {
+ renderer = this._createRenderer({pane: name});
+ this._paneRenderers[name] = renderer;
+ }
+ return renderer;
+ },
+
+ _createRenderer: function (options) {
+ // @namespace Map; @option preferCanvas: Boolean = false
+ // Whether `Path`s should be rendered on a `Canvas` renderer.
+ // By default, all `Path`s are rendered in a `SVG` renderer.
+ return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
+ }
+});
+
+/*
+ * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
+ */
+
+/*
+ * @class Rectangle
+ * @aka L.Rectangle
+ * @inherits Polygon
+ *
+ * A class for drawing rectangle overlays on a map. Extends `Polygon`.
+ *
+ * @example
+ *
+ * ```js
+ * // define rectangle geographical bounds
+ * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
+ *
+ * // create an orange rectangle
+ * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
+ *
+ * // zoom the map to the rectangle bounds
+ * map.fitBounds(bounds);
+ * ```
+ *
+ */
+
+
+var Rectangle = Polygon.extend({
+ initialize: function (latLngBounds, options) {
+ Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
+ },
+
+ // @method setBounds(latLngBounds: LatLngBounds): this
+ // Redraws the rectangle with the passed bounds.
+ setBounds: function (latLngBounds) {
+ return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
+ },
+
+ _boundsToLatLngs: function (latLngBounds) {
+ latLngBounds = toLatLngBounds(latLngBounds);
+ return [
+ latLngBounds.getSouthWest(),
+ latLngBounds.getNorthWest(),
+ latLngBounds.getNorthEast(),
+ latLngBounds.getSouthEast()
+ ];
+ }
+});
+
+
+// @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
+function rectangle(latLngBounds, options) {
+ return new Rectangle(latLngBounds, options);
+}
+
+SVG.create = create$2;
+SVG.pointsToPath = pointsToPath;
+
+GeoJSON.geometryToLayer = geometryToLayer;
+GeoJSON.coordsToLatLng = coordsToLatLng;
+GeoJSON.coordsToLatLngs = coordsToLatLngs;
+GeoJSON.latLngToCoords = latLngToCoords;
+GeoJSON.latLngsToCoords = latLngsToCoords;
+GeoJSON.getFeature = getFeature;
+GeoJSON.asFeature = asFeature;
+
+/*
+ * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
+ * (zoom to a selected bounding box), enabled by default.
+ */
+
+// @namespace Map
+// @section Interaction Options
+Map.mergeOptions({
+ // @option boxZoom: Boolean = true
+ // Whether the map can be zoomed to a rectangular area specified by
+ // dragging the mouse while pressing the shift key.
+ boxZoom: true
+});
+
+var BoxZoom = Handler.extend({
+ initialize: function (map) {
+ this._map = map;
+ this._container = map._container;
+ this._pane = map._panes.overlayPane;
+ this._resetStateTimeout = 0;
+ map.on('unload', this._destroy, this);
+ },
+
+ addHooks: function () {
+ on(this._container, 'mousedown', this._onMouseDown, this);
+ },
+
+ removeHooks: function () {
+ off(this._container, 'mousedown', this._onMouseDown, this);
+ },
+
+ moved: function () {
+ return this._moved;
+ },
+
+ _destroy: function () {
+ remove(this._pane);
+ delete this._pane;
+ },
+
+ _resetState: function () {
+ this._resetStateTimeout = 0;
+ this._moved = false;
+ },
+
+ _clearDeferredResetState: function () {
+ if (this._resetStateTimeout !== 0) {
+ clearTimeout(this._resetStateTimeout);
+ this._resetStateTimeout = 0;
+ }
+ },
+
+ _onMouseDown: function (e) {
+ if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
+
+ // Clear the deferred resetState if it hasn't executed yet, otherwise it
+ // will interrupt the interaction and orphan a box element in the container.
+ this._clearDeferredResetState();
+ this._resetState();
+
+ disableTextSelection();
+ disableImageDrag();
+
+ this._startPoint = this._map.mouseEventToContainerPoint(e);
+
+ on(document, {
+ contextmenu: stop,
+ mousemove: this._onMouseMove,
+ mouseup: this._onMouseUp,
+ keydown: this._onKeyDown
+ }, this);
+ },
+
+ _onMouseMove: function (e) {
+ if (!this._moved) {
+ this._moved = true;
+
+ this._box = create$1('div', 'leaflet-zoom-box', this._container);
+ addClass(this._container, 'leaflet-crosshair');
+
+ this._map.fire('boxzoomstart');
+ }
+
+ this._point = this._map.mouseEventToContainerPoint(e);
+
+ var bounds = new Bounds(this._point, this._startPoint),
+ size = bounds.getSize();
+
+ setPosition(this._box, bounds.min);
+
+ this._box.style.width = size.x + 'px';
+ this._box.style.height = size.y + 'px';
+ },
+
+ _finish: function () {
+ if (this._moved) {
+ remove(this._box);
+ removeClass(this._container, 'leaflet-crosshair');
+ }
+
+ enableTextSelection();
+ enableImageDrag();
+
+ off(document, {
+ contextmenu: stop,
+ mousemove: this._onMouseMove,
+ mouseup: this._onMouseUp,
+ keydown: this._onKeyDown
+ }, this);
+ },
+
+ _onMouseUp: function (e) {
+ if ((e.which !== 1) && (e.button !== 1)) { return; }
+
+ this._finish();
+
+ if (!this._moved) { return; }
+ // Postpone to next JS tick so internal click event handling
+ // still see it as "moved".
+ this._clearDeferredResetState();
+ this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
+
+ var bounds = new LatLngBounds(
+ this._map.containerPointToLatLng(this._startPoint),
+ this._map.containerPointToLatLng(this._point));
+
+ this._map
+ .fitBounds(bounds)
+ .fire('boxzoomend', {boxZoomBounds: bounds});
+ },
+
+ _onKeyDown: function (e) {
+ if (e.keyCode === 27) {
+ this._finish();
+ }
+ }
+});
+
+// @section Handlers
+// @property boxZoom: Handler
+// Box (shift-drag with mouse) zoom handler.
+Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
+
+/*
+ * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
+ */
+
+// @namespace Map
+// @section Interaction Options
+
+Map.mergeOptions({
+ // @option doubleClickZoom: Boolean|String = true
+ // Whether the map can be zoomed in by double clicking on it and
+ // zoomed out by double clicking while holding shift. If passed
+ // `'center'`, double-click zoom will zoom to the center of the
+ // view regardless of where the mouse was.
+ doubleClickZoom: true
+});
+
+var DoubleClickZoom = Handler.extend({
+ addHooks: function () {
+ this._map.on('dblclick', this._onDoubleClick, this);
+ },
+
+ removeHooks: function () {
+ this._map.off('dblclick', this._onDoubleClick, this);
+ },
+
+ _onDoubleClick: function (e) {
+ var map = this._map,
+ oldZoom = map.getZoom(),
+ delta = map.options.zoomDelta,
+ zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
+
+ if (map.options.doubleClickZoom === 'center') {
+ map.setZoom(zoom);
+ } else {
+ map.setZoomAround(e.containerPoint, zoom);
+ }
+ }
+});
+
+// @section Handlers
+//
+// Map properties include interaction handlers that allow you to control
+// interaction behavior in runtime, enabling or disabling certain features such
+// as dragging or touch zoom (see `Handler` methods). For example:
+//
+// ```js
+// map.doubleClickZoom.disable();
+// ```
+//
+// @property doubleClickZoom: Handler
+// Double click zoom handler.
+Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
+
+/*
+ * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
+ */
+
+// @namespace Map
+// @section Interaction Options
+Map.mergeOptions({
+ // @option dragging: Boolean = true
+ // Whether the map be draggable with mouse/touch or not.
+ dragging: true,
+
+ // @section Panning Inertia Options
+ // @option inertia: Boolean = *
+ // If enabled, panning of the map will have an inertia effect where
+ // the map builds momentum while dragging and continues moving in
+ // the same direction for some time. Feels especially nice on touch
+ // devices. Enabled by default unless running on old Android devices.
+ inertia: !android23,
+
+ // @option inertiaDeceleration: Number = 3000
+ // The rate with which the inertial movement slows down, in pixels/second².
+ inertiaDeceleration: 3400, // px/s^2
+
+ // @option inertiaMaxSpeed: Number = Infinity
+ // Max speed of the inertial movement, in pixels/second.
+ inertiaMaxSpeed: Infinity, // px/s
+
+ // @option easeLinearity: Number = 0.2
+ easeLinearity: 0.2,
+
+ // TODO refactor, move to CRS
+ // @option worldCopyJump: Boolean = false
+ // With this option enabled, the map tracks when you pan to another "copy"
+ // of the world and seamlessly jumps to the original one so that all overlays
+ // like markers and vector layers are still visible.
+ worldCopyJump: false,
+
+ // @option maxBoundsViscosity: Number = 0.0
+ // If `maxBounds` is set, this option will control how solid the bounds
+ // are when dragging the map around. The default value of `0.0` allows the
+ // user to drag outside the bounds at normal speed, higher values will
+ // slow down map dragging outside bounds, and `1.0` makes the bounds fully
+ // solid, preventing the user from dragging outside the bounds.
+ maxBoundsViscosity: 0.0
+});
+
+var Drag = Handler.extend({
+ addHooks: function () {
+ if (!this._draggable) {
+ var map = this._map;
+
+ this._draggable = new Draggable(map._mapPane, map._container);
+
+ this._draggable.on({
+ dragstart: this._onDragStart,
+ drag: this._onDrag,
+ dragend: this._onDragEnd
+ }, this);
+
+ this._draggable.on('predrag', this._onPreDragLimit, this);
+ if (map.options.worldCopyJump) {
+ this._draggable.on('predrag', this._onPreDragWrap, this);
+ map.on('zoomend', this._onZoomEnd, this);
+
+ map.whenReady(this._onZoomEnd, this);
+ }
+ }
+ addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
+ this._draggable.enable();
+ this._positions = [];
+ this._times = [];
+ },
+
+ removeHooks: function () {
+ removeClass(this._map._container, 'leaflet-grab');
+ removeClass(this._map._container, 'leaflet-touch-drag');
+ this._draggable.disable();
+ },
+
+ moved: function () {
+ return this._draggable && this._draggable._moved;
+ },
+
+ moving: function () {
+ return this._draggable && this._draggable._moving;
+ },
+
+ _onDragStart: function () {
+ var map = this._map;
+
+ map._stop();
+ if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
+ var bounds = toLatLngBounds(this._map.options.maxBounds);
+
+ this._offsetLimit = toBounds(
+ this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
+ this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
+ .add(this._map.getSize()));
+
+ this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
+ } else {
+ this._offsetLimit = null;
+ }
+
+ map
+ .fire('movestart')
+ .fire('dragstart');
+
+ if (map.options.inertia) {
+ this._positions = [];
+ this._times = [];
+ }
+ },
+
+ _onDrag: function (e) {
+ if (this._map.options.inertia) {
+ var time = this._lastTime = +new Date(),
+ pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
+
+ this._positions.push(pos);
+ this._times.push(time);
+
+ this._prunePositions(time);
+ }
+
+ this._map
+ .fire('move', e)
+ .fire('drag', e);
+ },
+
+ _prunePositions: function (time) {
+ while (this._positions.length > 1 && time - this._times[0] > 50) {
+ this._positions.shift();
+ this._times.shift();
+ }
+ },
+
+ _onZoomEnd: function () {
+ var pxCenter = this._map.getSize().divideBy(2),
+ pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
+
+ this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
+ this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
+ },
+
+ _viscousLimit: function (value, threshold) {
+ return value - (value - threshold) * this._viscosity;
+ },
+
+ _onPreDragLimit: function () {
+ if (!this._viscosity || !this._offsetLimit) { return; }
+
+ var offset = this._draggable._newPos.subtract(this._draggable._startPos);
+
+ var limit = this._offsetLimit;
+ if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
+ if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
+ if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
+ if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
+
+ this._draggable._newPos = this._draggable._startPos.add(offset);
+ },
+
+ _onPreDragWrap: function () {
+ // TODO refactor to be able to adjust map pane position after zoom
+ var worldWidth = this._worldWidth,
+ halfWidth = Math.round(worldWidth / 2),
+ dx = this._initialWorldOffset,
+ x = this._draggable._newPos.x,
+ newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
+ newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
+ newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
+
+ this._draggable._absPos = this._draggable._newPos.clone();
+ this._draggable._newPos.x = newX;
+ },
+
+ _onDragEnd: function (e) {
+ var map = this._map,
+ options = map.options,
+
+ noInertia = !options.inertia || this._times.length < 2;
+
+ map.fire('dragend', e);
+
+ if (noInertia) {
+ map.fire('moveend');
+
+ } else {
+ this._prunePositions(+new Date());
+
+ var direction = this._lastPos.subtract(this._positions[0]),
+ duration = (this._lastTime - this._times[0]) / 1000,
+ ease = options.easeLinearity,
+
+ speedVector = direction.multiplyBy(ease / duration),
+ speed = speedVector.distanceTo([0, 0]),
+
+ limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
+ limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
+
+ decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
+ offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
+
+ if (!offset.x && !offset.y) {
+ map.fire('moveend');
+
+ } else {
+ offset = map._limitOffset(offset, map.options.maxBounds);
+
+ requestAnimFrame(function () {
+ map.panBy(offset, {
+ duration: decelerationDuration,
+ easeLinearity: ease,
+ noMoveStart: true,
+ animate: true
+ });
+ });
+ }
+ }
+ }
+});
+
+// @section Handlers
+// @property dragging: Handler
+// Map dragging handler (by both mouse and touch).
+Map.addInitHook('addHandler', 'dragging', Drag);
+
+/*
+ * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
+ */
+
+// @namespace Map
+// @section Keyboard Navigation Options
+Map.mergeOptions({
+ // @option keyboard: Boolean = true
+ // Makes the map focusable and allows users to navigate the map with keyboard
+ // arrows and `+`/`-` keys.
+ keyboard: true,
+
+ // @option keyboardPanDelta: Number = 80
+ // Amount of pixels to pan when pressing an arrow key.
+ keyboardPanDelta: 80
+});
+
+var Keyboard = Handler.extend({
+
+ keyCodes: {
+ left: [37],
+ right: [39],
+ down: [40],
+ up: [38],
+ zoomIn: [187, 107, 61, 171],
+ zoomOut: [189, 109, 54, 173]
+ },
+
+ initialize: function (map) {
+ this._map = map;
+
+ this._setPanDelta(map.options.keyboardPanDelta);
+ this._setZoomDelta(map.options.zoomDelta);
+ },
+
+ addHooks: function () {
+ var container = this._map._container;
+
+ // make the container focusable by tabbing
+ if (container.tabIndex <= 0) {
+ container.tabIndex = '0';
+ }
+
+ on(container, {
+ focus: this._onFocus,
+ blur: this._onBlur,
+ mousedown: this._onMouseDown
+ }, this);
+
+ this._map.on({
+ focus: this._addHooks,
+ blur: this._removeHooks
+ }, this);
+ },
+
+ removeHooks: function () {
+ this._removeHooks();
+
+ off(this._map._container, {
+ focus: this._onFocus,
+ blur: this._onBlur,
+ mousedown: this._onMouseDown
+ }, this);
+
+ this._map.off({
+ focus: this._addHooks,
+ blur: this._removeHooks
+ }, this);
+ },
+
+ _onMouseDown: function () {
+ if (this._focused) { return; }
+
+ var body = document.body,
+ docEl = document.documentElement,
+ top = body.scrollTop || docEl.scrollTop,
+ left = body.scrollLeft || docEl.scrollLeft;
+
+ this._map._container.focus();
+
+ window.scrollTo(left, top);
+ },
+
+ _onFocus: function () {
+ this._focused = true;
+ this._map.fire('focus');
+ },
+
+ _onBlur: function () {
+ this._focused = false;
+ this._map.fire('blur');
+ },
+
+ _setPanDelta: function (panDelta) {
+ var keys = this._panKeys = {},
+ codes = this.keyCodes,
+ i, len;
+
+ for (i = 0, len = codes.left.length; i < len; i++) {
+ keys[codes.left[i]] = [-1 * panDelta, 0];
+ }
+ for (i = 0, len = codes.right.length; i < len; i++) {
+ keys[codes.right[i]] = [panDelta, 0];
+ }
+ for (i = 0, len = codes.down.length; i < len; i++) {
+ keys[codes.down[i]] = [0, panDelta];
+ }
+ for (i = 0, len = codes.up.length; i < len; i++) {
+ keys[codes.up[i]] = [0, -1 * panDelta];
+ }
+ },
+
+ _setZoomDelta: function (zoomDelta) {
+ var keys = this._zoomKeys = {},
+ codes = this.keyCodes,
+ i, len;
+
+ for (i = 0, len = codes.zoomIn.length; i < len; i++) {
+ keys[codes.zoomIn[i]] = zoomDelta;
+ }
+ for (i = 0, len = codes.zoomOut.length; i < len; i++) {
+ keys[codes.zoomOut[i]] = -zoomDelta;
+ }
+ },
+
+ _addHooks: function () {
+ on(document, 'keydown', this._onKeyDown, this);
+ },
+
+ _removeHooks: function () {
+ off(document, 'keydown', this._onKeyDown, this);
+ },
+
+ _onKeyDown: function (e) {
+ if (e.altKey || e.ctrlKey || e.metaKey) { return; }
+
+ var key = e.keyCode,
+ map = this._map,
+ offset;
+
+ if (key in this._panKeys) {
+ if (!map._panAnim || !map._panAnim._inProgress) {
+ offset = this._panKeys[key];
+ if (e.shiftKey) {
+ offset = toPoint(offset).multiplyBy(3);
+ }
+
+ map.panBy(offset);
+
+ if (map.options.maxBounds) {
+ map.panInsideBounds(map.options.maxBounds);
+ }
+ }
+ } else if (key in this._zoomKeys) {
+ map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
+
+ } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
+ map.closePopup();
+
+ } else {
+ return;
+ }
+
+ stop(e);
+ }
+});
+
+// @section Handlers
+// @section Handlers
+// @property keyboard: Handler
+// Keyboard navigation handler.
+Map.addInitHook('addHandler', 'keyboard', Keyboard);
+
+/*
+ * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
+ */
+
+// @namespace Map
+// @section Interaction Options
+Map.mergeOptions({
+ // @section Mousewheel options
+ // @option scrollWheelZoom: Boolean|String = true
+ // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
+ // it will zoom to the center of the view regardless of where the mouse was.
+ scrollWheelZoom: true,
+
+ // @option wheelDebounceTime: Number = 40
+ // Limits the rate at which a wheel can fire (in milliseconds). By default
+ // user can't zoom via wheel more often than once per 40 ms.
+ wheelDebounceTime: 40,
+
+ // @option wheelPxPerZoomLevel: Number = 60
+ // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
+ // mean a change of one full zoom level. Smaller values will make wheel-zooming
+ // faster (and vice versa).
+ wheelPxPerZoomLevel: 60
+});
+
+var ScrollWheelZoom = Handler.extend({
+ addHooks: function () {
+ on(this._map._container, 'mousewheel', this._onWheelScroll, this);
+
+ this._delta = 0;
+ },
+
+ removeHooks: function () {
+ off(this._map._container, 'mousewheel', this._onWheelScroll, this);
+ },
+
+ _onWheelScroll: function (e) {
+ var delta = getWheelDelta(e);
+
+ var debounce = this._map.options.wheelDebounceTime;
+
+ this._delta += delta;
+ this._lastMousePos = this._map.mouseEventToContainerPoint(e);
+
+ if (!this._startTime) {
+ this._startTime = +new Date();
+ }
+
+ var left = Math.max(debounce - (+new Date() - this._startTime), 0);
+
+ clearTimeout(this._timer);
+ this._timer = setTimeout(bind(this._performZoom, this), left);
+
+ stop(e);
+ },
+
+ _performZoom: function () {
+ var map = this._map,
+ zoom = map.getZoom(),
+ snap = this._map.options.zoomSnap || 0;
+
+ map._stop(); // stop panning and fly animations if any
+
+ // map the delta with a sigmoid function to -4..4 range leaning on -1..1
+ var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
+ d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
+ d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
+ delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
+
+ this._delta = 0;
+ this._startTime = null;
+
+ if (!delta) { return; }
+
+ if (map.options.scrollWheelZoom === 'center') {
+ map.setZoom(zoom + delta);
+ } else {
+ map.setZoomAround(this._lastMousePos, zoom + delta);
+ }
+ }
+});
+
+// @section Handlers
+// @property scrollWheelZoom: Handler
+// Scroll wheel zoom handler.
+Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
+
+/*
+ * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
+ */
+
+// @namespace Map
+// @section Interaction Options
+Map.mergeOptions({
+ // @section Touch interaction options
+ // @option tap: Boolean = true
+ // Enables mobile hacks for supporting instant taps (fixing 200ms click
+ // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
+ tap: true,
+
+ // @option tapTolerance: Number = 15
+ // The max number of pixels a user can shift his finger during touch
+ // for it to be considered a valid tap.
+ tapTolerance: 15
+});
+
+var Tap = Handler.extend({
+ addHooks: function () {
+ on(this._map._container, 'touchstart', this._onDown, this);
+ },
+
+ removeHooks: function () {
+ off(this._map._container, 'touchstart', this._onDown, this);
+ },
+
+ _onDown: function (e) {
+ if (!e.touches) { return; }
+
+ preventDefault(e);
+
+ this._fireClick = true;
+
+ // don't simulate click or track longpress if more than 1 touch
+ if (e.touches.length > 1) {
+ this._fireClick = false;
+ clearTimeout(this._holdTimeout);
+ return;
+ }
+
+ var first = e.touches[0],
+ el = first.target;
+
+ this._startPos = this._newPos = new Point(first.clientX, first.clientY);
+
+ // if touching a link, highlight it
+ if (el.tagName && el.tagName.toLowerCase() === 'a') {
+ addClass(el, 'leaflet-active');
+ }
+
+ // simulate long hold but setting a timeout
+ this._holdTimeout = setTimeout(bind(function () {
+ if (this._isTapValid()) {
+ this._fireClick = false;
+ this._onUp();
+ this._simulateEvent('contextmenu', first);
+ }
+ }, this), 1000);
+
+ this._simulateEvent('mousedown', first);
+
+ on(document, {
+ touchmove: this._onMove,
+ touchend: this._onUp
+ }, this);
+ },
+
+ _onUp: function (e) {
+ clearTimeout(this._holdTimeout);
+
+ off(document, {
+ touchmove: this._onMove,
+ touchend: this._onUp
+ }, this);
+
+ if (this._fireClick && e && e.changedTouches) {
+
+ var first = e.changedTouches[0],
+ el = first.target;
+
+ if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
+ removeClass(el, 'leaflet-active');
+ }
+
+ this._simulateEvent('mouseup', first);
+
+ // simulate click if the touch didn't move too much
+ if (this._isTapValid()) {
+ this._simulateEvent('click', first);
+ }
+ }
+ },
+
+ _isTapValid: function () {
+ return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
+ },
+
+ _onMove: function (e) {
+ var first = e.touches[0];
+ this._newPos = new Point(first.clientX, first.clientY);
+ this._simulateEvent('mousemove', first);
+ },
+
+ _simulateEvent: function (type, e) {
+ var simulatedEvent = document.createEvent('MouseEvents');
+
+ simulatedEvent._simulated = true;
+ e.target._simulatedClick = true;
+
+ simulatedEvent.initMouseEvent(
+ type, true, true, window, 1,
+ e.screenX, e.screenY,
+ e.clientX, e.clientY,
+ false, false, false, false, 0, null);
+
+ e.target.dispatchEvent(simulatedEvent);
+ }
+});
+
+// @section Handlers
+// @property tap: Handler
+// Mobile touch hacks (quick tap and touch hold) handler.
+if (touch && !pointer) {
+ Map.addInitHook('addHandler', 'tap', Tap);
+}
+
+/*
+ * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
+ */
+
+// @namespace Map
+// @section Interaction Options
+Map.mergeOptions({
+ // @section Touch interaction options
+ // @option touchZoom: Boolean|String = *
+ // Whether the map can be zoomed by touch-dragging with two fingers. If
+ // passed `'center'`, it will zoom to the center of the view regardless of
+ // where the touch events (fingers) were. Enabled for touch-capable web
+ // browsers except for old Androids.
+ touchZoom: touch && !android23,
+
+ // @option bounceAtZoomLimits: Boolean = true
+ // Set it to false if you don't want the map to zoom beyond min/max zoom
+ // and then bounce back when pinch-zooming.
+ bounceAtZoomLimits: true
+});
+
+var TouchZoom = Handler.extend({
+ addHooks: function () {
+ addClass(this._map._container, 'leaflet-touch-zoom');
+ on(this._map._container, 'touchstart', this._onTouchStart, this);
+ },
+
+ removeHooks: function () {
+ removeClass(this._map._container, 'leaflet-touch-zoom');
+ off(this._map._container, 'touchstart', this._onTouchStart, this);
+ },
+
+ _onTouchStart: function (e) {
+ var map = this._map;
+ if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
+
+ var p1 = map.mouseEventToContainerPoint(e.touches[0]),
+ p2 = map.mouseEventToContainerPoint(e.touches[1]);
+
+ this._centerPoint = map.getSize()._divideBy(2);
+ this._startLatLng = map.containerPointToLatLng(this._centerPoint);
+ if (map.options.touchZoom !== 'center') {
+ this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
+ }
+
+ this._startDist = p1.distanceTo(p2);
+ this._startZoom = map.getZoom();
+
+ this._moved = false;
+ this._zooming = true;
+
+ map._stop();
+
+ on(document, 'touchmove', this._onTouchMove, this);
+ on(document, 'touchend', this._onTouchEnd, this);
+
+ preventDefault(e);
+ },
+
+ _onTouchMove: function (e) {
+ if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
+
+ var map = this._map,
+ p1 = map.mouseEventToContainerPoint(e.touches[0]),
+ p2 = map.mouseEventToContainerPoint(e.touches[1]),
+ scale = p1.distanceTo(p2) / this._startDist;
+
+ this._zoom = map.getScaleZoom(scale, this._startZoom);
+
+ if (!map.options.bounceAtZoomLimits && (
+ (this._zoom < map.getMinZoom() && scale < 1) ||
+ (this._zoom > map.getMaxZoom() && scale > 1))) {
+ this._zoom = map._limitZoom(this._zoom);
+ }
+
+ if (map.options.touchZoom === 'center') {
+ this._center = this._startLatLng;
+ if (scale === 1) { return; }
+ } else {
+ // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
+ var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
+ if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
+ this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
+ }
+
+ if (!this._moved) {
+ map._moveStart(true, false);
+ this._moved = true;
+ }
+
+ cancelAnimFrame(this._animRequest);
+
+ var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
+ this._animRequest = requestAnimFrame(moveFn, this, true);
+
+ preventDefault(e);
+ },
+
+ _onTouchEnd: function () {
+ if (!this._moved || !this._zooming) {
+ this._zooming = false;
+ return;
+ }
+
+ this._zooming = false;
+ cancelAnimFrame(this._animRequest);
+
+ off(document, 'touchmove', this._onTouchMove);
+ off(document, 'touchend', this._onTouchEnd);
+
+ // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
+ if (this._map.options.zoomAnimation) {
+ this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
+ } else {
+ this._map._resetView(this._center, this._map._limitZoom(this._zoom));
+ }
+ }
+});
+
+// @section Handlers
+// @property touchZoom: Handler
+// Touch zoom handler.
+Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
+
+Map.BoxZoom = BoxZoom;
+Map.DoubleClickZoom = DoubleClickZoom;
+Map.Drag = Drag;
+Map.Keyboard = Keyboard;
+Map.ScrollWheelZoom = ScrollWheelZoom;
+Map.Tap = Tap;
+Map.TouchZoom = TouchZoom;
+
+Object.freeze = freeze;
+
+exports.version = version;
+exports.Control = Control;
+exports.control = control;
+exports.Browser = Browser;
+exports.Evented = Evented;
+exports.Mixin = Mixin;
+exports.Util = Util;
+exports.Class = Class;
+exports.Handler = Handler;
+exports.extend = extend;
+exports.bind = bind;
+exports.stamp = stamp;
+exports.setOptions = setOptions;
+exports.DomEvent = DomEvent;
+exports.DomUtil = DomUtil;
+exports.PosAnimation = PosAnimation;
+exports.Draggable = Draggable;
+exports.LineUtil = LineUtil;
+exports.PolyUtil = PolyUtil;
+exports.Point = Point;
+exports.point = toPoint;
+exports.Bounds = Bounds;
+exports.bounds = toBounds;
+exports.Transformation = Transformation;
+exports.transformation = toTransformation;
+exports.Projection = index;
+exports.LatLng = LatLng;
+exports.latLng = toLatLng;
+exports.LatLngBounds = LatLngBounds;
+exports.latLngBounds = toLatLngBounds;
+exports.CRS = CRS;
+exports.GeoJSON = GeoJSON;
+exports.geoJSON = geoJSON;
+exports.geoJson = geoJson;
+exports.Layer = Layer;
+exports.LayerGroup = LayerGroup;
+exports.layerGroup = layerGroup;
+exports.FeatureGroup = FeatureGroup;
+exports.featureGroup = featureGroup;
+exports.ImageOverlay = ImageOverlay;
+exports.imageOverlay = imageOverlay;
+exports.VideoOverlay = VideoOverlay;
+exports.videoOverlay = videoOverlay;
+exports.SVGOverlay = SVGOverlay;
+exports.svgOverlay = svgOverlay;
+exports.DivOverlay = DivOverlay;
+exports.Popup = Popup;
+exports.popup = popup;
+exports.Tooltip = Tooltip;
+exports.tooltip = tooltip;
+exports.Icon = Icon;
+exports.icon = icon;
+exports.DivIcon = DivIcon;
+exports.divIcon = divIcon;
+exports.Marker = Marker;
+exports.marker = marker;
+exports.TileLayer = TileLayer;
+exports.tileLayer = tileLayer;
+exports.GridLayer = GridLayer;
+exports.gridLayer = gridLayer;
+exports.SVG = SVG;
+exports.svg = svg$1;
+exports.Renderer = Renderer;
+exports.Canvas = Canvas;
+exports.canvas = canvas$1;
+exports.Path = Path;
+exports.CircleMarker = CircleMarker;
+exports.circleMarker = circleMarker;
+exports.Circle = Circle;
+exports.circle = circle;
+exports.Polyline = Polyline;
+exports.polyline = polyline;
+exports.Polygon = Polygon;
+exports.polygon = polygon;
+exports.Rectangle = Rectangle;
+exports.rectangle = rectangle;
+exports.Map = Map;
+exports.map = createMap;
+
+var oldL = window.L;
+exports.noConflict = function() {
+ window.L = oldL;
+ return this;
+}
+
+// Always export us to window global (see #2364)
+window.L = exports;
+
+})));
diff --git a/www/wiki/extensions/Maps/resources/maps.common.js b/www/wiki/extensions/Maps/resources/maps.common.js
deleted file mode 100644
index 8e0180b4..00000000
--- a/www/wiki/extensions/Maps/resources/maps.common.js
+++ /dev/null
@@ -1,4 +0,0 @@
-window.maps = new ( function() {
- this.googlemapsList = [];
- this.leafletList = [];
-} )();
diff --git a/www/wiki/extensions/Maps/resources/maps.services.js b/www/wiki/extensions/Maps/resources/maps.services.js
deleted file mode 100644
index a23ff0c8..00000000
--- a/www/wiki/extensions/Maps/resources/maps.services.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/*global jQuery, mediaWiki, maps */
-/*global confirm */
-( function ( $, mw, maps ) {
- 'use strict';
-
- /**
- * @since 3.5
- *
- * @param {object} container
- * @return {this}
- */
- var services = function ( container ) {
-
- if ( $.type( container ) !== 'object' ) {
- throw new Error( 'The container is not of the correct type ' + $.type( container ) );
- }
-
- this.container = container;
-
- return this;
- };
-
- /* Public methods */
-
- services.prototype = {
-
- constructor: services,
-
- /**
- * @since 3.5
- *
- * @param {string} service
- */
- render: function( service ) {
- if ( service === 'googlemaps' || service === 'maps' || service === 'googlemaps3' ) {
- this.google();
- }
-
- if ( service === 'leaflet' || service === 'leafletmaps' ) {
- this.leaflet();
- }
- },
-
- /**
- * Google service
- *
- * @since 3.5
- */
- google: function() {
-
- var self = this;
-
- // https://www.mediawiki.org/wiki/ResourceLoader/Modules#mw.loader.using
- mw.loader.using( 'ext.maps.googlemaps3' ).done( function () {
-
- if ( typeof google === 'undefined' ) {
- throw new Error( 'The google map service is unknown, please ensure that the API or module is loaded correctly.' );
- }
-
- self.container.find( '.maps-googlemaps3' ).each( function() {
- var $this = $( this );
- $this.googlemaps( $.parseJSON( $this.find( 'div').text() ) );
- } );
- } );
- },
-
- /**
- * Leaflet service
- *
- * @since 3.5
- */
- leaflet: function() {
- mw.loader.using( 'ext.maps.leaflet' ).done( function () {
- $( '.maps-leaflet' ).each( function() {
- var $this = $( this );
- maps.leafletList.push(
- $this.leafletmaps( $.parseJSON( $this.find( 'div').text() ) )
- );
- } );
- } );
- }
- };
-
- maps.services = services;
-
-}( jQuery, mediaWiki, maps ) );
diff --git a/www/wiki/extensions/Maps/resources/sm.common.js b/www/wiki/extensions/Maps/resources/semanticMaps.js
index 373c1dd9..b3db7e5e 100644
--- a/www/wiki/extensions/Maps/resources/sm.common.js
+++ b/www/wiki/extensions/Maps/resources/semanticMaps.js
@@ -1,7 +1,4 @@
/**
- * JavaScript the Semantic Maps extension.
- * @see https://www.mediawiki.org/wiki/Extension:Semantic_Maps
- *
* @licence GNU GPL v2++
* @author Peter Grassberger < petertheone@gmail.com >
*/
@@ -9,18 +6,20 @@ window.sm = new ( function( $, mw ) {
this.buildQueryString = function( query, ajaxcoordproperty, top, right, bottom, left ) {
var isCompoundQuery = query.indexOf( '|' ) > -1;
- var query = query.split( '|' );
- $.each( query, function( index ) {
- query[index] += ' [[' + ajaxcoordproperty + '::+]] ';
- query[index] += '[[' + ajaxcoordproperty + '::>' + bottom + '°, ' + left + '°]] ';
- query[index] += '[[' + ajaxcoordproperty + '::<' + top + '°, ' + right + '°]]';
+ var queryParts = query.split( '|' );
+
+ $.each( queryParts, function( index ) {
+ queryParts[index] += ' [[' + ajaxcoordproperty + '::+]] ';
+ queryParts[index] += '[[' + ajaxcoordproperty + '::>' + bottom + '°, ' + left + '°]] ';
+ queryParts[index] += '[[' + ajaxcoordproperty + '::<' + top + '°, ' + right + '°]]';
if( !isCompoundQuery ) {
- query[index] += '|?' + ajaxcoordproperty;
+ queryParts[index] += '|?' + ajaxcoordproperty;
} else {
- query[index] += ';?' + ajaxcoordproperty;
+ queryParts[index] += ';?' + ajaxcoordproperty;
}
} );
- return query.join( ' | ' );
+
+ return queryParts.join( ' | ' );
};
/**
@@ -47,19 +46,19 @@ window.sm = new ( function( $, mw ) {
} );
};
- this.ajaxUpdateMarker = function( map, query, icon ) {
+ this.ajaxUpdateMarker = function( jqueryMap, query, icon ) {
return this.sendQuery( query ).done( function( data ) {
if( !data.hasOwnProperty( 'query' ) ||
!data.query.hasOwnProperty( 'results' ) ) {
return;
}
- // todo: don't remove and recreate all markers..
- // only add new ones.
- map.removeMarkers();
+
+ jqueryMap.removeMarkers();
+
for( var property in data.query.results ) {
if( data.query.results.hasOwnProperty( property ) ) {
var location = data.query.results[property];
- var coordinates = location.printouts[map.options.ajaxcoordproperty][0];
+ var coordinates = location.printouts[jqueryMap.options.ajaxcoordproperty][0];
var markerOptions = {
lat: coordinates.lat,
lon: coordinates.lon,
@@ -67,7 +66,8 @@ window.sm = new ( function( $, mw ) {
text: '<b><a href="' + location.fullurl + '">' + location.fulltext + '</a></b>',
icon: icon
};
- map.addMarker( markerOptions );
+
+ jqueryMap.addMarker( markerOptions );
}
}
} );
diff --git a/www/wiki/extensions/Maps/src/DataAccess/GeoJsonFetcher.php b/www/wiki/extensions/Maps/src/DataAccess/GeoJsonFetcher.php
new file mode 100644
index 00000000..ea7724d5
--- /dev/null
+++ b/www/wiki/extensions/Maps/src/DataAccess/GeoJsonFetcher.php
@@ -0,0 +1,93 @@
+<?php
+
+declare( strict_types = 1 );
+
+namespace Maps\DataAccess;
+
+use FileFetcher\FileFetcher;
+use FileFetcher\FileFetchingException;
+use MediaWiki\Storage\RevisionLookup;
+
+/**
+ * Returns the content of the JSON file at the specified location as array.
+ * Empty array is returned on failure.
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ */
+class GeoJsonFetcher {
+
+ private $fileFetcher;
+ private $titleParser;
+ private $revisionLookup;
+
+ public function __construct( FileFetcher $fileFetcher, \TitleParser $titleParser, RevisionLookup $revisionLookup ) {
+ $this->fileFetcher = $fileFetcher;
+ $this->titleParser = $titleParser;
+ $this->revisionLookup = $revisionLookup;
+ }
+
+ public function parse( string $fileLocation ): array {
+ return $this->fetch( $fileLocation )->getContent();
+ }
+
+ public function fetch( string $fileLocation ) {
+ try {
+ $title = $this->titleParser->parseTitle( $fileLocation, NS_GEO_JSON );
+ $revision = $this->revisionLookup->getRevisionByTitle( $title );
+
+ if ( $revision !== null ) {
+ $content = $revision->getContent( 'main' );
+
+ if ( $content instanceof \JsonContent ) {
+ return new GeoJsonFetcherResult(
+ $this->normalizeJson( $content->getNativeData() ),
+ $revision->getId(),
+ $title
+ );
+ }
+ }
+ }
+ catch ( \MalformedTitleException $e ) {
+ }
+
+ // Prevent reading JSON files on the server
+ if( !filter_var( $fileLocation, FILTER_VALIDATE_URL) ) {
+ return $this->newEmptyResult();
+ }
+
+ try {
+ return new GeoJsonFetcherResult(
+ $this->normalizeJson( $this->fileFetcher->fetchFile( $fileLocation ) ),
+ null,
+ null
+ );
+ }
+ catch ( FileFetchingException $ex ) {
+ return $this->newEmptyResult();
+ }
+ }
+
+ private function newEmptyResult(): GeoJsonFetcherResult {
+ return new GeoJsonFetcherResult(
+ [],
+ null,
+ null
+ );
+ }
+
+ private function normalizeJson( ?string $jsonString ): array {
+ if ( $jsonString === null ) {
+ return [];
+ }
+
+ $json = json_decode( $jsonString, true );
+
+ if ( $json === null ) {
+ return [];
+ }
+
+ return $json;
+ }
+
+}
diff --git a/www/wiki/extensions/Maps/src/DataAccess/GeoJsonFetcherResult.php b/www/wiki/extensions/Maps/src/DataAccess/GeoJsonFetcherResult.php
new file mode 100644
index 00000000..7345b649
--- /dev/null
+++ b/www/wiki/extensions/Maps/src/DataAccess/GeoJsonFetcherResult.php
@@ -0,0 +1,31 @@
+<?php
+
+declare( strict_types = 1 );
+
+namespace Maps\DataAccess;
+
+class GeoJsonFetcherResult {
+
+ private $content;
+ private $revisionId;
+ private $source;
+
+ public function __construct( array $content, ?int $revisionId, ?\TitleValue $source ) {
+ $this->content = $content;
+ $this->revisionId = $revisionId;
+ $this->source = $source;
+ }
+
+ public function getContent(): array {
+ return $this->content;
+ }
+
+ public function getTitleValue(): ?\TitleValue {
+ return $this->source;
+ }
+
+ public function getRevisionId(): ?int {
+ return $this->revisionId;
+ }
+
+}
diff --git a/www/wiki/extensions/Maps/src/DataAccess/JsonFileParser.php b/www/wiki/extensions/Maps/src/DataAccess/JsonFileParser.php
deleted file mode 100644
index 81d6cfa0..00000000
--- a/www/wiki/extensions/Maps/src/DataAccess/JsonFileParser.php
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-
-declare( strict_types = 1 );
-
-namespace Maps\DataAccess;
-
-use FileFetcher\FileFetcher;
-use FileFetcher\FileFetchingException;
-use Maps\MapsFactory;
-use ValueParsers\ParseException;
-use ValueParsers\ValueParser;
-
-/**
- * Returns the content of the JSON file at the specified location as array.
- * Empty array is returned on failure.
- *
- * @licence GNU GPL v2+
- * @author Jeroen De Dauw < jeroendedauw@gmail.com >
- */
-class JsonFileParser implements ValueParser {
-
- private $fileFetcher;
- private $pageContentFetcher;
- private $defaultNamespace;
-
- public function __construct( $fileFetcher = null, PageContentFetcher $pageContentFetcher = null ) {
- $this->fileFetcher = $fileFetcher instanceof FileFetcher
- ? $fileFetcher : MapsFactory::newDefault()->getGeoJsonFileFetcher();
-
- $this->pageContentFetcher = $pageContentFetcher instanceof PageContentFetcher
- ? $pageContentFetcher : MapsFactory::newDefault()->getPageContentFetcher();
-
- $this->defaultNamespace = NS_GEO_JSON;
- }
-
- /**
- * @param string $fileLocation
- *
- * @return array
- * @throws ParseException
- */
- public function parse( $fileLocation ) {
- $jsonString = $this->getJsonString( $fileLocation );
-
- if ( $jsonString === null ) {
- return [];
- }
-
- $json = json_decode( $jsonString, true );
-
- if ( $json === null ) {
- return [];
- }
-
- return $json;
- }
-
- private function getJsonString( string $fileLocation ): ?string {
- $content = $this->pageContentFetcher->getPageContent( $fileLocation, $this->defaultNamespace );
-
- if ( $content instanceof \JsonContent ) {
- return $content->getNativeData();
- }
-
- // Prevent reading JSON files on the server
- if( !filter_var( $fileLocation, FILTER_VALIDATE_URL) ) {
- return null;
- }
-
- try {
- return $this->fileFetcher->fetchFile( $fileLocation );
- }
- catch ( FileFetchingException $ex ) {
- return null;
- }
- }
-
-
-}
diff --git a/www/wiki/extensions/Maps/src/GoogleMapsService.php b/www/wiki/extensions/Maps/src/GoogleMapsService.php
index 9c1b91b9..b1ccba34 100644
--- a/www/wiki/extensions/Maps/src/GoogleMapsService.php
+++ b/www/wiki/extensions/Maps/src/GoogleMapsService.php
@@ -3,6 +3,8 @@
namespace Maps;
use Html;
+use ParamProcessor\ProcessedParam;
+use ParamProcessor\ProcessingResult;
/**
* @licence GNU GPL v2+
@@ -49,7 +51,19 @@ class GoogleMapsService implements MappingService {
global $egMapsGMaps3DefTypeStyle, $egMapsGMaps3DefZoomStyle, $egMapsGMaps3AutoInfoWindows;
global $egMapsResizableByDefault;
- $params = [];
+ $params = MapsFunctions::getCommonParameters();
+
+ $params['visitedicon'] = [
+ 'default' => '',
+ 'message' => 'maps-displaymap-par-visitedicon',
+ ];
+
+ $params['wmsoverlay'] = [
+ 'type' => 'wmsoverlay',
+ 'default' => false,
+ 'delimiter' => ' ',
+ 'message' => 'maps-displaymap-par-wmsoverlay',
+ ];
$params['zoom'] = [
'type' => 'integer',
@@ -150,7 +164,8 @@ class GoogleMapsService implements MappingService {
'message' => 'maps-googlemaps3-par-poi',
];
- $params['markercluster'] = [
+ $params['cluster'] = [
+ 'aliases' => [ 'markercluster' ],
'type' => 'boolean',
'default' => false,
'message' => 'maps-par-markercluster',
@@ -199,11 +214,18 @@ class GoogleMapsService implements MappingService {
'message' => 'maps-par-kml',
'islist' => true,
'post-format' => function( array $kmlFileNames ) {
- return array_map(
- function( string $fileName ) {
- return wfExpandUrl( MapsFunctions::getFileUrl( $fileName ) );
- },
- $kmlFileNames
+ return array_values(
+ array_filter(
+ array_map(
+ function( string $fileName ) {
+ return wfExpandUrl( MapsFunctions::getFileUrl( $fileName ) );
+ },
+ $kmlFileNames
+ ),
+ function( string $fileName ) {
+ return $fileName !== '';
+ }
+ )
);
}
];
@@ -220,13 +242,15 @@ class GoogleMapsService implements MappingService {
// new CriterionSearchMarkers() FIXME
];
- $params['enablefullscreen'] = [
+ $params['fullscreen'] = [
+ 'aliases' => [ 'enablefullscreen' ],
'type' => 'boolean',
'default' => false,
'message' => 'maps-par-enable-fullscreen',
];
$params['scrollwheelzoom'] = [
+ 'aliases' => [ 'scrollzoom' ],
'type' => 'boolean',
'default' => false,
'message' => 'maps-par-scrollwheelzoom',
@@ -251,7 +275,7 @@ class GoogleMapsService implements MappingService {
}
public function getResourceModules(): array {
- return [ 'ext.maps.googlemaps3', 'ext.sm.googlemaps3ajax' ];
+ return [ 'ext.maps.googlemaps3', 'ext.maps.googlemaps3ajax' ];
}
public static function getApiScript( $langCode, array $urlArgs = [] ) {
@@ -311,4 +335,33 @@ class GoogleMapsService implements MappingService {
)
];
}
+
+ public function processingResultToMapParams( ProcessingResult $processingResult ): array {
+ $parameters = $processingResult->getParameters();
+
+ if ( array_key_exists( 'zoom', $parameters ) && $parameters['zoom']->wasSetToDefault() && count(
+ $parameters['coordinates']->getValue()
+ ) > 1 ) {
+ $parameters['zoom'] = $this->getParameterWithValue( $parameters['zoom'], false );
+ }
+
+ $mapParams = [];
+
+ foreach ( $parameters as $parameter ) {
+ $mapParams[$parameter->getName()] = $parameter->getValue();
+ }
+
+ return $mapParams;
+ }
+
+ private function getParameterWithValue( ProcessedParam $param, $value ) {
+ return new ProcessedParam(
+ $param->getName(),
+ $value,
+ $param->wasSetToDefault(),
+ $param->getOriginalName(),
+ $param->getOriginalValue()
+ );
+ }
+
}
diff --git a/www/wiki/extensions/Maps/src/LeafletService.php b/www/wiki/extensions/Maps/src/LeafletService.php
index 0e41d670..6d189fdb 100644
--- a/www/wiki/extensions/Maps/src/LeafletService.php
+++ b/www/wiki/extensions/Maps/src/LeafletService.php
@@ -3,6 +3,8 @@
namespace Maps;
use Html;
+use ParamProcessor\ParameterTypes;
+use ParamProcessor\ProcessingResult;
/**
* @licence GNU GPL v2+
@@ -24,19 +26,17 @@ class LeafletService implements MappingService {
}
public function getParameterInfo(): array {
- global $GLOBALS;
-
- $params = [];
+ $params = MapsFunctions::getCommonParameters();
$params['zoom'] = [
- 'type' => 'integer',
+ 'type' => ParameterTypes::INTEGER,
'range' => [ 0, 20 ],
'default' => false,
'message' => 'maps-par-zoom'
];
$params['defzoom'] = [
- 'type' => 'integer',
+ 'type' => ParameterTypes::INTEGER,
'range' => [ 0, 20 ],
'default' => self::getDefaultZoom(),
'message' => 'maps-leaflet-par-defzoom'
@@ -51,8 +51,9 @@ class LeafletService implements MappingService {
'islist' => true,
];
- $params['overlaylayers'] = [
- 'type' => 'string',
+ $params['overlays'] = [
+ 'aliases' => [ 'overlaylayers' ],
+ 'type' => ParameterTypes::STRING,
'values' => array_keys( $GLOBALS['egMapsLeafletAvailableOverlayLayers'], true, true ),
'default' => $GLOBALS['egMapsLeafletOverlayLayers'],
'message' => 'maps-leaflet-par-overlaylayers',
@@ -60,59 +61,68 @@ class LeafletService implements MappingService {
];
$params['resizable'] = [
- 'type' => 'boolean',
+ 'type' => ParameterTypes::BOOLEAN,
'default' => $GLOBALS['egMapsResizableByDefault'],
'message' => 'maps-par-resizable'
];
- $params['enablefullscreen'] = [
- 'type' => 'boolean',
+ $params['fullscreen'] = [
+ 'aliases' => [ 'enablefullscreen' ],
+ 'type' => ParameterTypes::BOOLEAN,
'default' => false,
'message' => 'maps-par-enable-fullscreen',
];
$params['scrollwheelzoom'] = [
- 'type' => 'boolean',
+ 'aliases' => [ 'scrollzoom' ],
+ 'type' => ParameterTypes::BOOLEAN,
'default' => true,
'message' => 'maps-par-scrollwheelzoom',
];
- $params['markercluster'] = [
- 'type' => 'boolean',
+ $params['cluster'] = [
+ 'aliases' => [ 'markercluster' ],
+ 'type' => ParameterTypes::BOOLEAN,
'default' => false,
'message' => 'maps-par-markercluster',
];
$params['clustermaxzoom'] = [
- 'type' => 'integer',
+ 'type' => ParameterTypes::INTEGER,
'default' => 20,
'message' => 'maps-par-clustermaxzoom',
];
$params['clusterzoomonclick'] = [
- 'type' => 'boolean',
+ 'type' => ParameterTypes::BOOLEAN,
'default' => true,
'message' => 'maps-par-clusterzoomonclick',
];
$params['clustermaxradius'] = [
- 'type' => 'integer',
+ 'type' => ParameterTypes::INTEGER,
'default' => 80,
'message' => 'maps-par-maxclusterradius',
];
$params['clusterspiderfy'] = [
- 'type' => 'boolean',
+ 'type' => ParameterTypes::BOOLEAN,
'default' => true,
'message' => 'maps-leaflet-par-clusterspiderfy',
];
$params['geojson'] = [
- 'type' => 'jsonfile',
+ 'type' => ParameterTypes::STRING,
'default' => '',
'message' => 'maps-displaymap-par-geojson',
];
+ $params['clicktarget'] = [
+ 'type' => ParameterTypes::STRING,
+ 'default' => '',
+ 'message' => 'maps-leaflet-par-clicktarget',
+ ];
+
return $params;
}
@@ -132,7 +142,7 @@ class LeafletService implements MappingService {
}
public function getResourceModules(): array {
- return [ 'ext.maps.leaflet', 'ext.sm.leafletajax' ];
+ return [ 'ext.maps.leaflet.loader', 'ext.maps.leaflet.leafletajax' ];
}
public function getDependencyHtml( array $params ): string {
@@ -151,7 +161,7 @@ class LeafletService implements MappingService {
}
private function getDependencies( array $params ): array {
- $leafletPath = $GLOBALS['wgScriptPath'] . '/extensions/Maps/resources/leaflet/leaflet';
+ $leafletPath = $GLOBALS['wgScriptPath'] . '/extensions/Maps/resources/lib/leaflet';
return array_merge(
[
@@ -181,4 +191,20 @@ class LeafletService implements MappingService {
return array_unique( $layerDependencies );
}
+ public function processingResultToMapParams( ProcessingResult $processingResult ): array {
+ $mapParams = $processingResult->getParameterArray();
+
+ if ( $mapParams['geojson'] !== '' ) {
+ $fetcher = MapsFactory::newDefault()->newGeoJsonFetcher();
+
+ $result = $fetcher->fetch( $mapParams['geojson'] );
+
+ $mapParams['geojson'] = $result->getContent();
+ $mapParams['GeoJsonSource'] = $result->getTitleValue() === null ? null : $result->getTitleValue()->getText();
+ $mapParams['GeoJsonRevisionId'] = $result->getRevisionId();
+ }
+
+ return $mapParams;
+ }
+
}
diff --git a/www/wiki/extensions/Maps/src/MappingService.php b/www/wiki/extensions/Maps/src/MappingService.php
index 184ef712..960315d1 100644
--- a/www/wiki/extensions/Maps/src/MappingService.php
+++ b/www/wiki/extensions/Maps/src/MappingService.php
@@ -3,6 +3,8 @@
namespace Maps;
use ParamProcessor\ParamDefinition;
+use ParamProcessor\ProcessedParam;
+use ParamProcessor\ProcessingResult;
/**
* @licence GNU GPL v2+
@@ -28,6 +30,8 @@ interface MappingService {
*/
public function getResourceModules(): array;
- public function newMapId();
+ public function newMapId(): string;
+
+ public function processingResultToMapParams( ProcessingResult $processingResult ): array;
}
diff --git a/www/wiki/extensions/Maps/src/MapsFactory.php b/www/wiki/extensions/Maps/src/MapsFactory.php
index 6ab23d5d..a8fd1338 100644
--- a/www/wiki/extensions/Maps/src/MapsFactory.php
+++ b/www/wiki/extensions/Maps/src/MapsFactory.php
@@ -4,6 +4,7 @@ declare( strict_types = 1 );
namespace Maps;
+use DataValues\Geo\Parsers\LatLongParser;
use FileFetcher\Cache\Factory as CacheFactory;
use FileFetcher\FileFetcher;
use Jeroen\SimpleGeocoder\Geocoder;
@@ -13,13 +14,22 @@ use Jeroen\SimpleGeocoder\Geocoders\FileFetchers\GoogleGeocoder;
use Jeroen\SimpleGeocoder\Geocoders\FileFetchers\NominatimGeocoder;
use Jeroen\SimpleGeocoder\Geocoders\NullGeocoder;
use Maps\DataAccess\CachingGeocoder;
+use Maps\DataAccess\GeoJsonFetcher;
use Maps\DataAccess\MapsFileFetcher;
use Maps\DataAccess\MediaWikiFileUrlFinder;
use Maps\DataAccess\PageContentFetcher;
use Maps\MediaWiki\ParserHooks\DisplayMapFunction;
use Maps\Presentation\CoordinateFormatter;
+use Maps\Presentation\WikitextParsers\CircleParser;
+use Maps\Presentation\WikitextParsers\DistanceParser;
+use Maps\Presentation\WikitextParsers\ImageOverlayParser;
+use Maps\Presentation\WikitextParsers\LineParser;
use Maps\Presentation\WikitextParsers\LocationParser;
+use Maps\Presentation\WikitextParsers\PolygonParser;
+use Maps\Presentation\WikitextParsers\RectangleParser;
+use Maps\Presentation\WikitextParsers\WmsOverlayParser;
use MediaWiki\MediaWikiServices;
+use ParamProcessor\ParamDefinitionFactory;
use SimpleCache\Cache\Cache;
use SimpleCache\Cache\MediaWikiCache;
@@ -165,4 +175,28 @@ class MapsFactory {
);
}
+ public function getParamDefinitionFactory(): ParamDefinitionFactory {
+ $factory = ParamDefinitionFactory::newDefault();
+
+ $factory->registerType( 'coordinate', [ 'string-parser' => LatLongParser::class ] );
+ $factory->registerType( 'mapslocation', [ 'string-parser' => LocationParser::class ] );
+ $factory->registerType( 'mapsline', [ 'string-parser' => LineParser::class ] );
+ $factory->registerType( 'mapscircle', [ 'string-parser' => CircleParser::class ] );
+ $factory->registerType( 'mapsrectangle', [ 'string-parser' => RectangleParser::class ] );
+ $factory->registerType( 'mapspolygon', [ 'string-parser' => PolygonParser::class ] );
+ $factory->registerType( 'distance', [ 'string-parser' => DistanceParser::class ] );
+ $factory->registerType( 'wmsoverlay', [ 'string-parser' => WmsOverlayParser::class ] );
+ $factory->registerType( 'mapsimageoverlay', [ 'string-parser' => ImageOverlayParser::class ] );
+
+ return $factory;
+ }
+
+ public function newGeoJsonFetcher( FileFetcher $fileFetcher = null ): GeoJsonFetcher {
+ return new GeoJsonFetcher(
+ $fileFetcher ?? $this->getGeoJsonFileFetcher(),
+ $this->mediaWikiServices->getTitleParser(),
+ $this->mediaWikiServices->getRevisionLookup()
+ );
+ }
+
}
diff --git a/www/wiki/extensions/Maps/src/MapsFunctions.php b/www/wiki/extensions/Maps/src/MapsFunctions.php
index 47cd1358..06274cc1 100644
--- a/www/wiki/extensions/Maps/src/MapsFunctions.php
+++ b/www/wiki/extensions/Maps/src/MapsFunctions.php
@@ -75,24 +75,19 @@ final class MapsFunctions {
public static function getCommonParameters() {
$params = [];
- $params['mappingservice'] = [
- 'type' => 'string',
- 'aliases' => 'service',
- 'default' => $GLOBALS['egMapsDefaultService'],
- 'values' => MapsFactory::globalInstance()->getMappingServices()->getAllNames(),
- ];
-
$params['width'] = [
'type' => 'dimension',
'allowauto' => true,
'units' => [ 'px', 'ex', 'em', '%', '' ],
'default' => $GLOBALS['egMapsMapWidth'],
+ 'message' => 'maps-par-width',
];
$params['height'] = [
'type' => 'dimension',
'units' => [ 'px', 'ex', 'em', '' ],
'default' => $GLOBALS['egMapsMapHeight'],
+ 'message' => 'maps-par-height',
];
$params['centre'] = [
@@ -100,16 +95,9 @@ final class MapsFunctions {
'aliases' => [ 'center' ],
'default' => false,
'manipulatedefault' => false,
+ 'message' => 'maps-par-centre',
];
- // Give grep a chance to find the usages:
- // maps-par-mappingservice, maps-par-geoservice, maps-par-width,
- // maps-par-height, maps-par-centre
- foreach ( $params as $name => &$data ) {
- $data['name'] = $name;
- $data['message'] = 'maps-par-' . $name;
- }
-
$params['title'] = [
'name' => 'title',
'default' => $GLOBALS['egMapsDefaultTitle'],
@@ -124,10 +112,6 @@ final class MapsFunctions {
'default' => '',
];
- $params['visitedicon'] = [
- 'default' => '',
- ];
-
$params['lines'] = [
'type' => 'mapsline',
'default' => [],
@@ -156,12 +140,6 @@ final class MapsFunctions {
'islist' => true,
];
- $params['wmsoverlay'] = [
- 'type' => 'wmsoverlay',
- 'default' => false,
- 'delimiter' => ' ',
- ];
-
$params['maxzoom'] = [
'type' => 'integer',
'default' => false,
@@ -188,8 +166,8 @@ final class MapsFunctions {
// Give grep a chance to find the usages:
// maps-displaymap-par-title, maps-displaymap-par-label, maps-displaymap-par-icon,
- // maps-displaymap-par-visitedicon, aps-displaymap-par-lines, maps-displaymap-par-polygons,
- // maps-displaymap-par-circles, maps-displaymap-par-rectangles, maps-displaymap-par-wmsoverlay,
+ // aps-displaymap-par-lines, maps-displaymap-par-polygons,
+ // maps-displaymap-par-circles, maps-displaymap-par-rectangles,
// maps-displaymap-par-maxzoom, maps-displaymap-par-minzoom, maps-displaymap-par-copycoords,
// maps-displaymap-par-static
foreach ( $params as $name => &$param ) {
diff --git a/www/wiki/extensions/Maps/src/MapsSetup.php b/www/wiki/extensions/Maps/src/MapsSetup.php
index 245d3915..d682339b 100644
--- a/www/wiki/extensions/Maps/src/MapsSetup.php
+++ b/www/wiki/extensions/Maps/src/MapsSetup.php
@@ -5,7 +5,7 @@ declare( strict_types = 1 );
namespace Maps;
use DataValues\Geo\Parsers\LatLongParser;
-use Maps\DataAccess\JsonFileParser;
+use Maps\DataAccess\GeoJsonFetcher;
use Maps\MediaWiki\Content\GeoJsonContent;
use Maps\MediaWiki\Content\GeoJsonContentHandler;
use Maps\MediaWiki\ParserHooks\CoordinatesFunction;
@@ -47,15 +47,6 @@ class MapsSetup {
}
}
- private function registerAllTheThings() {
- $this->registerParserHooks();
- $this->registerPermissions();
- $this->registerParameterTypes();
- $this->registerHooks();
-
- $this->mwGlobals['wgContentHandlers'][GeoJsonContent::CONTENT_MODEL_ID] = GeoJsonContentHandler::class;
- }
-
private function defaultSettings() {
if ( $this->mwGlobals['egMapsGMaps3Language'] === '' ) {
$this->mwGlobals['egMapsGMaps3Language'] = $this->mwGlobals['wgLang'];
@@ -74,6 +65,15 @@ class MapsSetup {
}
}
+ private function registerAllTheThings() {
+ $this->registerParserHooks();
+ $this->registerPermissions();
+ $this->registerParameterTypes();
+ $this->registerHooks();
+ $this->registerGeoJsonContentModel();
+ $this->registerEditApiModuleFallbacks();
+ }
+
private function registerParserHooks() {
if ( $this->mwGlobals['egMapsEnableCoordinateFunction'] ) {
$this->mwGlobals['wgHooks']['ParserFirstCallInit'][] = function ( Parser &$parser ) {
@@ -109,13 +109,7 @@ class MapsSetup {
$hookName,
function ( $text, array $arguments, Parser $parser ) {
if ( $text !== null ) {
- $defaultParameters = DisplayMapFunction::getHookDefinition( "\n" )->getDefaultParameters();
- $defaultParam = array_shift( $defaultParameters );
-
- // If there is a first default parameter, set the tag contents as its value.
- if ( $defaultParam !== null ) {
- $arguments[$defaultParam] = $text;
- }
+ $arguments[DisplayMapFunction::getDefaultParameters()[0]] = $text;
}
return MapsFactory::newDefault()->getDisplayMapFunction()->getMapHtmlForParameterList( $parser, $arguments );
@@ -192,15 +186,66 @@ class MapsSetup {
$this->mwGlobals['wgParamDefinitions']['mapsimageoverlay'] = [
'string-parser' => ImageOverlayParser::class,
];
-
- $this->mwGlobals['wgParamDefinitions']['jsonfile'] = [
- 'string-parser' => JsonFileParser::class,
- ];
}
private function registerHooks() {
$this->mwGlobals['wgHooks']['AdminLinks'][] = 'Maps\MediaWiki\MapsHooks::addToAdminLinks';
$this->mwGlobals['wgHooks']['MakeGlobalVariablesScript'][] = 'Maps\MediaWiki\MapsHooks::onMakeGlobalVariablesScript';
+ $this->mwGlobals['wgHooks']['SkinTemplateNavigation'][] = 'Maps\MediaWiki\MapsHooks::onSkinTemplateNavigation';
+ $this->mwGlobals['wgHooks']['BeforeDisplayNoArticleText'][] = 'Maps\MediaWiki\MapsHooks::onBeforeDisplayNoArticleText';
+ $this->mwGlobals['wgHooks']['ShowMissingArticle'][] = 'Maps\MediaWiki\MapsHooks::onShowMissingArticle';
+ $this->mwGlobals['wgHooks']['ListDefinedTags'][] = 'Maps\MediaWiki\MapsHooks::onRegisterTags';
+ $this->mwGlobals['wgHooks']['ChangeTagsListActive'][] = 'Maps\MediaWiki\MapsHooks::onRegisterTags';
+ $this->mwGlobals['wgHooks']['ChangeTagsAllowedAdd'][] = 'Maps\MediaWiki\MapsHooks::onChangeTagsAllowedAdd';
+ $this->mwGlobals['wgHooks']['ResourceLoaderTestModules'][] = 'Maps\MediaWiki\MapsHooks::onResourceLoaderTestModules';
+ }
+
+ private function registerGeoJsonContentModel() {
+ $this->mwGlobals['wgContentHandlers'][GeoJsonContent::CONTENT_MODEL_ID] = GeoJsonContentHandler::class;
+ }
+
+ private function registerEditApiModuleFallbacks() {
+ // mediawiki.api.edit is present in 1.31 but not 1.32
+ // Once Maps requires MW 1.32+, this can be removed after replacing usage of mediawiki.api.edit
+ if ( version_compare( $this->mwGlobals['wgVersion'], '1.32', '>=' ) ) {
+ $this->mwGlobals['wgResourceModules']['mediawiki.api.edit'] = [
+ 'dependencies' => [
+ 'mediawiki.api'
+ ],
+ 'targets' => [ 'desktop', 'mobile' ]
+ ];
+ }
+
+ // 1.35 combines the jquery.ui modules into one
+ if ( version_compare( $this->mwGlobals['wgVersion'], '1.35', '>=' ) ) {
+ $this->mwGlobals['wgResourceModules']['jquery.ui.resizable'] = [
+ 'dependencies' => [
+ 'jquery.ui'
+ ],
+ 'targets' => [ 'desktop', 'mobile' ]
+ ];
+
+ $this->mwGlobals['wgResourceModules']['jquery.ui.autocomplete'] = [
+ 'dependencies' => [
+ 'jquery.ui'
+ ],
+ 'targets' => [ 'desktop', 'mobile' ]
+ ];
+
+ $this->mwGlobals['wgResourceModules']['jquery.ui.slider'] = [
+ 'dependencies' => [
+ 'jquery.ui'
+ ],
+ 'targets' => [ 'desktop', 'mobile' ]
+ ];
+
+ $this->mwGlobals['wgResourceModules']['jquery.ui.dialog'] = [
+ 'dependencies' => [
+ 'jquery.ui'
+ ],
+ 'targets' => [ 'desktop', 'mobile' ]
+ ];
+ }
}
}
diff --git a/www/wiki/extensions/Maps/src/MediaWiki/Content/GeoJsonContent.php b/www/wiki/extensions/Maps/src/MediaWiki/Content/GeoJsonContent.php
index 72a89b04..c6eaae23 100644
--- a/www/wiki/extensions/Maps/src/MediaWiki/Content/GeoJsonContent.php
+++ b/www/wiki/extensions/Maps/src/MediaWiki/Content/GeoJsonContent.php
@@ -2,48 +2,55 @@
namespace Maps\MediaWiki\Content;
-use Html;
+use FormatJson;
+use Maps\Presentation\GeoJsonMapPageUi;
+use Maps\Presentation\OutputFacade;
use ParserOptions;
use ParserOutput;
+use Status;
use Title;
class GeoJsonContent extends \JsonContent {
public const CONTENT_MODEL_ID = 'GeoJSON';
+ public static function newEmptyContentString(): string {
+ $text = '{"type": "FeatureCollection", "features": []}';
+ return FormatJson::encode( FormatJson::parse( $text )->getValue(), true, FormatJson::UTF8_OK );
+ }
+
public function __construct( string $text, string $modelId = self::CONTENT_MODEL_ID ) {
- parent::__construct( $text, $modelId );
+ parent::__construct(
+ $text,
+ $modelId
+ );
+ }
+
+ public function getData(): Status {
+ $status = parent::getData();
+
+ if ( $status->isGood() && !$this->isGeoJson( $status->getValue() ) ) {
+ return Status::newFatal( 'Invalid GeoJson' );
+ }
+
+ return $status;
+ }
+
+ private function isGeoJson( $json ): bool {
+ return property_exists( $json, 'type' )
+ && $json->type === 'FeatureCollection'
+ && property_exists( $json, 'features' )
+ && is_array( $json->features );
}
protected function fillParserOutput( Title $title, $revId, ParserOptions $options,
$generateHtml, ParserOutput &$output ) {
if ( $generateHtml && $this->isValid() ) {
- $output->setText( $this->getMapHtml( $this->beautifyJSON() ) );
- $output->addModules( 'ext.maps.leaflet.editor' );
+ ( GeoJsonMapPageUi::forExistingPage( $this->beautifyJSON() ) )->addToOutput( OutputFacade::newFromParserOutput( $output ) );
} else {
$output->setText( '' );
}
}
- private function getMapHtml( string $jsonString ): string {
- return
- Html::element(
- 'div',
- [
- 'id' => 'GeoJsonMap',
- 'class' => 'GeoJsonMap',
- ]
- )
- . '<style>'
- . '.GeoJsonMap {width: "100%"; height: 600px; display: "inline-block"}'
- . '</style>'
- .
- Html::element(
- 'script',
- [],
- 'var GeoJson =' . $jsonString . ';'
- );
- }
-
-} \ No newline at end of file
+}
diff --git a/www/wiki/extensions/Maps/src/MediaWiki/Content/GeoJsonContentHandler.php b/www/wiki/extensions/Maps/src/MediaWiki/Content/GeoJsonContentHandler.php
index b192a9c3..de6c38b1 100644
--- a/www/wiki/extensions/Maps/src/MediaWiki/Content/GeoJsonContentHandler.php
+++ b/www/wiki/extensions/Maps/src/MediaWiki/Content/GeoJsonContentHandler.php
@@ -12,4 +12,8 @@ class GeoJsonContentHandler extends \JsonContentHandler {
return GeoJsonContent::class;
}
-} \ No newline at end of file
+ public function makeEmptyContent() {
+ return new GeoJsonContent( GeoJsonContent::newEmptyContentString() );
+ }
+
+}
diff --git a/www/wiki/extensions/Maps/src/MediaWiki/MapsHooks.php b/www/wiki/extensions/Maps/src/MediaWiki/MapsHooks.php
index e2a8ad95..9cefac2d 100644
--- a/www/wiki/extensions/Maps/src/MediaWiki/MapsHooks.php
+++ b/www/wiki/extensions/Maps/src/MediaWiki/MapsHooks.php
@@ -1,9 +1,13 @@
<?php
+
namespace Maps\MediaWiki;
use AlItem;
use ALTree;
+use Maps\Presentation\GeoJsonNewPageUi;
+use Maps\Presentation\OutputFacade;
+use SkinTemplate;
/**
* Static class for hooks handled by the Maps extension.
@@ -59,10 +63,67 @@ final class MapsHooks {
$vars['egMapsDebugJS'] = $GLOBALS['egMapsDebugJS'];
$vars['egMapsAvailableServices'] = $GLOBALS['egMapsAvailableServices'];
$vars['egMapsLeafletLayersApiKeys'] = $GLOBALS['egMapsLeafletLayersApiKeys'];
+ $vars['egMapsLeafletLayersDark'] = $GLOBALS['egMapsLeafletLayersDark'];
$vars += $GLOBALS['egMapsGlobalJSVars'];
return true;
}
+ public static function onSkinTemplateNavigation( SkinTemplate $skinTemplate, array &$links ) {
+ if ( $skinTemplate->getTitle() === null ) {
+ return true;
+ }
+
+ if ( $skinTemplate->getTitle()->getNamespace() === NS_GEO_JSON ) {
+ if ( array_key_exists( 'edit', $links['views'] ) ) {
+ $links['views']['edit']['text'] = wfMessage(
+ $skinTemplate->getTitle()->exists() ? 'maps-geo-json-edit-source': 'maps-geo-json-create-source'
+ );
+ }
+ }
+
+ return true;
+ }
+
+ public static function onBeforeDisplayNoArticleText( \Article $article ) {
+ return !self::shouldShowGeoJsonCreatePageUi( $article );
+ }
+
+ public static function onShowMissingArticle( \Article $article ) {
+ if ( self::shouldShowGeoJsonCreatePageUi( $article ) ) {
+ $ui = new GeoJsonNewPageUi( OutputFacade::newFromOutputPage( $article->getContext()->getOutput() ) );
+ $ui->addToOutput();
+ }
+
+ return true;
+ }
+
+ private static function shouldShowGeoJsonCreatePageUi( \Article $article ): bool {
+ return $article->getTitle()->getNamespace() === NS_GEO_JSON
+ && $article->getContext()->getUser()->isAllowed( 'createpage' );
+ }
+
+ public static function onRegisterTags( array &$tags ) {
+ $tags[] = 'maps-visual-edit';
+ return true;
+ }
+
+ public static function onChangeTagsAllowedAdd( array &$allowedTags, array $tags, \User $user = null ) {
+ $allowedTags[] = 'maps-visual-edit';
+ }
+
+ public static function onResourceLoaderTestModules( array &$modules, $resourceLoader ) {
+ $modules['qunit']['ext.maps.test'] = [
+ 'scripts' => [
+ 'tests/js/leaflet/GeoJsonTest.js',
+ ],
+ 'dependencies' => [
+ 'ext.maps.leaflet.geojson',
+ ],
+ 'localBasePath' => __DIR__ . '/../../',
+ 'remoteExtPath' => 'Maps'
+ ];
+ }
+
}
diff --git a/www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/DisplayMapFunction.php b/www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/DisplayMapFunction.php
index bad0d842..5d64b52e 100644
--- a/www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/DisplayMapFunction.php
+++ b/www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/DisplayMapFunction.php
@@ -3,12 +3,12 @@
namespace Maps\MediaWiki\ParserHooks;
use Maps;
-use Maps\MapsFunctions;
+use Maps\MappingService;
use Maps\MappingServices;
+use Maps\MapsFactory;
use Maps\Presentation\ParameterExtractor;
use MWException;
-use ParamProcessor;
-use ParamProcessor\ProcessedParam;
+use ParamProcessor\Processor;
use Parser;
/**
@@ -38,7 +38,7 @@ class DisplayMapFunction {
* @throws MWException
*/
public function getMapHtmlForKeyValueStrings( Parser $parser, array $parameters ): string {
- $processor = new \ParamProcessor\Processor( new \ParamProcessor\Options() );
+ $processor = new Processor( new \ParamProcessor\Options() );
$service = $this->services->getServiceOrDefault(
$this->extractServiceName(
@@ -50,14 +50,20 @@ class DisplayMapFunction {
$processor->setFunctionParams(
$parameters,
- array_merge(
- self::getHookDefinition( ';' )->getParameters(),
- $service->getParameterInfo()
- ),
- self::getHookDefinition( ';' )->getDefaultParameters()
+ [],
+ self::getDefaultParameters()
+ );
+
+ $processor->setParameterDefinitions(
+ $this->getAllParameterDefinitions( $service, ';' )
);
- return $this->getMapHtmlFromProcessor( $parser, $processor );
+ $this->trackMap( $parser );
+
+ return $this->renderer->renderMap(
+ $service->processingResultToMapParams( $processor->processParameters() ),
+ $parser
+ );
}
/**
@@ -69,65 +75,35 @@ class DisplayMapFunction {
* @throws MWException
*/
public function getMapHtmlForParameterList( Parser $parser, array $parameters ) {
- $processor = new \ParamProcessor\Processor( new \ParamProcessor\Options() );
+ $processor = new Processor( new \ParamProcessor\Options() );
$service = $this->services->getServiceOrDefault( $this->extractServiceName( $parameters ) );
$this->renderer->service = $service;
- $processor->setParameters(
- $parameters,
- array_merge(
- self::getHookDefinition( "\n" )->getParameters(),
- $service->getParameterInfo()
- )
+ $processor->setParameters( $parameters );
+ $processor->setParameterDefinitions(
+ $this->getAllParameterDefinitions( $service, "\n" )
);
- return $this->getMapHtmlFromProcessor( $parser, $processor );
- }
-
- private function getMapHtmlFromProcessor( Parser $parser, ParamProcessor\Processor $processor ) {
- $params = $processor->processParameters()->getParameters();
-
- $this->defaultMapZoom( $params );
-
$this->trackMap( $parser );
return $this->renderer->renderMap(
- $this->processedParametersToKeyValueArray( $params ),
+ $service->processingResultToMapParams( $processor->processParameters() ),
$parser
);
}
- private function extractServiceName( array $parameters ): string {
- $service = ( new ParameterExtractor() )->extract(
- [ 'mappingservice', 'service' ],
- $parameters
- );
-
- return $service ?? '';
- }
-
- private function processedParametersToKeyValueArray( array $params ): array {
- $parameters = [];
-
- foreach ( $params as $parameter ) {
- $parameters[$parameter->getName()] = $parameter->getValue();
- }
+ private function getAllParameterDefinitions( MappingService $service, string $locationDelimiter ) {
+ $params = [];
- return $parameters;
- }
-
- public static function getHookDefinition( string $locationDelimiter ): \ParserHooks\HookDefinition {
- return new \ParserHooks\HookDefinition(
- [ 'display_map', 'display_point', 'display_points', 'display_line' ],
- self::getParameterDefinitions( $locationDelimiter ),
- [ 'coordinates' ]
- );
- }
-
- private static function getParameterDefinitions( $locationDelimiter ): array {
- $params = MapsFunctions::getCommonParameters();
+ $params['mappingservice'] = [
+ 'type' => 'string',
+ 'aliases' => 'service',
+ 'default' => $GLOBALS['egMapsDefaultService'],
+ 'values' => MapsFactory::globalInstance()->getMappingServices()->getAllNames(),
+ 'message' => 'maps-par-mappingservice'
+ ];
$params['coordinates'] = [
'type' => 'string',
@@ -138,28 +114,25 @@ class DisplayMapFunction {
'message' => 'maps-displaymap-par-coordinates',
];
- return $params;
+ return MapsFactory::globalInstance()->getParamDefinitionFactory()->newDefinitionsFromArrays(
+ array_merge(
+ $params,
+ $service->getParameterInfo()
+ )
+ );
}
- /**
- * @param ProcessedParam[] $parameters
- */
- private function defaultMapZoom( array &$parameters ) {
- if ( array_key_exists( 'zoom', $parameters ) && $parameters['zoom']->wasSetToDefault() && count(
- $parameters['coordinates']->getValue()
- ) > 1 ) {
- $parameters['zoom'] = $this->getParameterWithValue( $parameters['zoom'], false );
- }
+ private function extractServiceName( array $parameters ): string {
+ $service = ( new ParameterExtractor() )->extract(
+ [ 'mappingservice', 'service' ],
+ $parameters
+ );
+
+ return $service ?? '';
}
- private function getParameterWithValue( ProcessedParam $param, $value ) {
- return new ProcessedParam(
- $param->getName(),
- $value,
- $param->wasSetToDefault(),
- $param->getOriginalName(),
- $param->getOriginalValue()
- );
+ public static function getDefaultParameters(): array {
+ return [ 'coordinates' ];
}
private function trackMap( Parser $parser ) {
diff --git a/www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/DisplayMapRenderer.php b/www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/DisplayMapRenderer.php
index 8c757acd..4fcb93ba 100644
--- a/www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/DisplayMapRenderer.php
+++ b/www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/DisplayMapRenderer.php
@@ -2,8 +2,6 @@
namespace Maps\MediaWiki\ParserHooks;
-use FormatJson;
-use Html;
use Maps\DataAccess\MediaWikiFileUrlFinder;
use Maps\Elements\Location;
use Maps\MappingService;
@@ -92,7 +90,8 @@ class DisplayMapRenderer {
private function handleMarkerData( array &$params ) {
$params['centre'] = $this->getCenter( $params['centre'] );
- if ( is_object( $params['wmsoverlay'] ) ) {
+ // FIXME: this parameter is google maps service specific
+ if ( array_key_exists( 'wmsoverlay', $params ) && is_object( $params['wmsoverlay'] ) ) {
$params['wmsoverlay'] = $params['wmsoverlay']->getJSONObject();
}
@@ -122,7 +121,7 @@ class DisplayMapRenderer {
private function getLocationJson( array $params ) {
$iconUrl = $this->fileUrlFinder->getUrlForFileName( $params['icon'] );
- $visitedIconUrl = $this->fileUrlFinder->getUrlForFileName( $params['visitedicon'] );
+ $visitedIconUrl = $this->fileUrlFinder->getUrlForFileName( $params['visitedicon'] ?? '' );
$locationJsonObjects = [];
diff --git a/www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/FindDestinationFunction.php b/www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/FindDestinationFunction.php
index e7cb319c..05859ca0 100644
--- a/www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/FindDestinationFunction.php
+++ b/www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/FindDestinationFunction.php
@@ -3,8 +3,8 @@
namespace Maps\MediaWiki\ParserHooks;
use DataValues\Geo\Values\LatLongValue;
-use Maps\MapsFactory;
use Maps\GeoFunctions;
+use Maps\MapsFactory;
use ParserHook;
/**
@@ -117,4 +117,4 @@ class FindDestinationFunction extends ParserHook {
return [ 'location', 'bearing', 'distance' ];
}
-} \ No newline at end of file
+}
diff --git a/www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/MapsDocFunction.php b/www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/MapsDocFunction.php
index 6a365378..e294736b 100644
--- a/www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/MapsDocFunction.php
+++ b/www/wiki/extensions/Maps/src/MediaWiki/ParserHooks/MapsDocFunction.php
@@ -2,7 +2,6 @@
namespace Maps\MediaWiki\ParserHooks;
-use Maps\MappingServices;
use Maps\MapsFactory;
use ParamProcessor\ParamDefinition;
use ParserHook;
@@ -35,12 +34,14 @@ class MapsDocFunction extends ParserHook {
public function render( array $parameters ) {
$this->language = $parameters['language'];
- $params = $this->getServiceParameters( $parameters['service'] );
+ $factory = MapsFactory::globalInstance();
- return $this->getParameterTable( $params );
+ $params = $this->getServiceParameters( $factory, $parameters['service'] );
+
+ return $this->getParameterTable( $factory, $params );
}
- private function getServiceParameters( $service ) {
+ private function getServiceParameters( MapsFactory $factory, string $service ) {
return array_merge(
[
'zoom' => [
@@ -48,21 +49,17 @@ class MapsDocFunction extends ParserHook {
'message' => 'maps-par-zoom',
]
],
- MapsFactory::globalInstance()->getMappingServices()->getService( $service )->getParameterInfo()
+ $factory->getMappingServices()->getService( $service )->getParameterInfo()
);
}
/**
* Returns the wikitext for a table listing the provided parameters.
- *
- * @param array $parameters
- *
- * @return string
*/
- private function getParameterTable( array $parameters ) {
+ private function getParameterTable( MapsFactory $factory, array $parameters ): string {
$tableRows = [];
- $parameters = ParamDefinition::getCleanDefinitions( $parameters );
+ $parameters = $factory->getParamDefinitionFactory()->newDefinitionsFromArrays( $parameters );
foreach ( $parameters as $parameter ) {
$tableRows[] = $this->getDescriptionRow( $parameter );
diff --git a/www/wiki/extensions/Maps/src/MediaWiki/Specials/SpecialMapEditor.php b/www/wiki/extensions/Maps/src/MediaWiki/Specials/SpecialMapEditor.php
index 76b5dad5..0226113e 100644
--- a/www/wiki/extensions/Maps/src/MediaWiki/Specials/SpecialMapEditor.php
+++ b/www/wiki/extensions/Maps/src/MediaWiki/Specials/SpecialMapEditor.php
@@ -3,7 +3,6 @@
namespace Maps\MediaWiki\Specials;
use Maps\GoogleMapsService;
-use Maps\MediaWiki\Specials\MapEditorHtml;
use SpecialPage;
/**
@@ -45,7 +44,7 @@ class SpecialMapEditor extends SpecialPage {
)
);
- $outputPage->addModules( 'mapeditor' );
+ $outputPage->addModules( 'ext.maps.wikitext.editor' );
$editorHtml = new MapEditorHtml( $this->getAttribs() );
$html = $editorHtml->getEditorHtml();
$outputPage->addHTML( $html );
diff --git a/www/wiki/extensions/Maps/src/Presentation/GeoJsonMapPageUi.php b/www/wiki/extensions/Maps/src/Presentation/GeoJsonMapPageUi.php
new file mode 100644
index 00000000..89440f1f
--- /dev/null
+++ b/www/wiki/extensions/Maps/src/Presentation/GeoJsonMapPageUi.php
@@ -0,0 +1,83 @@
+<?php
+
+declare( strict_types = 1 );
+
+namespace Maps\Presentation;
+
+use Html;
+
+class GeoJsonMapPageUi {
+
+ private $json;
+
+ public static function forExistingPage( string $mapJson ): self {
+ return new self( $mapJson );
+ }
+
+ private function __construct( ?string $json ) {
+ $this->json = $json;
+ }
+
+ public function addToOutput( OutputFacade $output ) {
+ $leafletPath = $GLOBALS['wgScriptPath'] . '/extensions/Maps/resources/lib/leaflet';
+
+ $output->addHeadItem(
+ 'MapsGeoJsonHeadItem',
+ Html::linkedStyle( "$leafletPath/leaflet.css" ) . Html::linkedScript( "$leafletPath/leaflet.js" )
+ );
+
+ $output->addHTML( $this->getJavascript() . $this->getHtml() );
+ $output->addModules( 'ext.maps.geojson.page' );
+ }
+
+ private function getJavascript(): string {
+ return Html::element(
+ 'script',
+ [],
+ $this->getJsonJs()
+ );
+ }
+
+ private function getJsonJs(): string {
+ return 'var GeoJson ='
+ . $this->json
+ . ';';
+ }
+
+ private function getHtml(): string {
+ return $this->wrapHtmlInThumbDivs(
+ Html::rawElement(
+ 'div',
+ [
+ 'id' => 'GeoJsonMap',
+ 'style' => 'width: 100%; height: 600px; background-color: #eeeeee; overflow: hidden;',
+ 'class' => 'maps-map maps-leaflet maps-geojson-editor'
+ ],
+ Html::element(
+ 'div',
+ [
+ 'class' => 'maps-loading-message'
+ ],
+ wfMessage( 'maps-loading-map' )->inContentLanguage()->text()
+ )
+ )
+ );
+ }
+
+ private function wrapHtmlInThumbDivs( string $html ): string {
+ return Html::rawElement(
+ 'div',
+ [
+ 'class' => 'thumb'
+ ],
+ Html::rawElement(
+ 'div',
+ [
+ 'class' => 'thumbinner'
+ ],
+ $html
+ )
+ );
+ }
+
+}
diff --git a/www/wiki/extensions/Maps/src/Presentation/GeoJsonNewPageUi.php b/www/wiki/extensions/Maps/src/Presentation/GeoJsonNewPageUi.php
new file mode 100644
index 00000000..5de1df71
--- /dev/null
+++ b/www/wiki/extensions/Maps/src/Presentation/GeoJsonNewPageUi.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Maps\Presentation;
+
+class GeoJsonNewPageUi {
+
+ private $output;
+
+ public function __construct( OutputFacade $output ) {
+ $this->output = $output;
+ }
+
+ public function addToOutput() {
+ $this->output->addModules( 'ext.maps.geojson.new.page' );
+
+ $this->output->addHtml(
+ \Html::element(
+ 'button',
+ [
+ 'id' => 'maps-geojson-new'
+ ],
+ wfMessage( 'maps-geo-json-create-page-button' )->inContentLanguage()->text()
+ )
+ );
+ }
+
+}
diff --git a/www/wiki/extensions/Maps/src/Presentation/MapHtmlBuilder.php b/www/wiki/extensions/Maps/src/Presentation/MapHtmlBuilder.php
index aa47e558..7d74ad34 100644
--- a/www/wiki/extensions/Maps/src/Presentation/MapHtmlBuilder.php
+++ b/www/wiki/extensions/Maps/src/Presentation/MapHtmlBuilder.php
@@ -22,12 +22,18 @@ class MapHtmlBuilder {
'div',
[
'id' => $mapName,
- 'style' => "width: {$params['width']}; height: {$params['height']}; background-color: #cccccc; overflow: hidden;",
+ 'style' => "width: {$params['width']}; height: {$params['height']}; background-color: #eeeeee; overflow: hidden;",
'class' => 'maps-map maps-' . $serviceName
],
- wfMessage( 'maps-loading-map' )->inContentLanguage()->escaped() .
Html::element(
'div',
+ [
+ 'class' => 'maps-loading-message'
+ ],
+ wfMessage( 'maps-loading-map' )->inContentLanguage()->text()
+ )
+ . Html::element(
+ 'div',
[ 'style' => 'display:none', 'class' => 'mapdata' ],
FormatJson::encode( $params )
)
diff --git a/www/wiki/extensions/Maps/src/Presentation/MapsDistanceParser.php b/www/wiki/extensions/Maps/src/Presentation/MapsDistanceParser.php
index c8fb0ef1..7c0d595a 100644
--- a/www/wiki/extensions/Maps/src/Presentation/MapsDistanceParser.php
+++ b/www/wiki/extensions/Maps/src/Presentation/MapsDistanceParser.php
@@ -99,7 +99,7 @@ class MapsDistanceParser {
$strlen = strlen( $distance );
for ( $i = 0; $i < $strlen; $i++ ) {
- if ( !ctype_digit( $distance{$i} ) && !in_array( $distance{$i}, [ ',', '.' ] ) ) {
+ if ( !ctype_digit( $distance[$i] ) && !in_array( $distance[$i], [ ',', '.' ] ) ) {
$value = substr( $distance, 0, $i );
$unit = substr( $distance, $i );
break;
diff --git a/www/wiki/extensions/Maps/src/Presentation/OutputFacade.php b/www/wiki/extensions/Maps/src/Presentation/OutputFacade.php
new file mode 100644
index 00000000..119f3b1d
--- /dev/null
+++ b/www/wiki/extensions/Maps/src/Presentation/OutputFacade.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Maps\Presentation;
+
+use OutputPage;
+use ParserOutput;
+
+class OutputFacade {
+
+ /**
+ * @var OutputPage
+ */
+ private $outputPage;
+
+ /**
+ * @var ParserOutput
+ */
+ private $parserOutput;
+
+ public static function newFromOutputPage( OutputPage $outputPage ) {
+ $instance = new self();
+ $instance->outputPage = $outputPage;
+ return $instance;
+ }
+
+ public static function newFromParserOutput( ParserOutput $parserOutput ) {
+ $instance = new self();
+ $instance->parserOutput = $parserOutput;
+ return $instance;
+ }
+
+ public function addHtml( string $html ) {
+ if ( $this->outputPage !== null ) {
+ $this->outputPage->addHTML( $html );
+ }
+
+ if ( $this->parserOutput !== null ) {
+ $this->parserOutput->setText( $this->parserOutput->getRawText() . $html );
+ }
+ }
+
+ public function addModules( string ...$modules ) {
+ if ( $this->outputPage !== null ) {
+ $this->outputPage->addModules( $modules );
+ }
+
+ if ( $this->parserOutput !== null ) {
+ $this->parserOutput->addModules( $modules );
+ }
+ }
+
+ public function addHeadItem( string $name, string $html ) {
+ if ( $this->outputPage !== null ) {
+ $this->outputPage->addHeadItem( $name, $html );
+ }
+
+ if ( $this->parserOutput !== null ) {
+ $this->parserOutput->addHeadItem( $html, $name );
+ }
+ }
+
+}
diff --git a/www/wiki/extensions/Maps/src/SemanticMW/ResultPrinters/MapPrinter.php b/www/wiki/extensions/Maps/src/SemanticMW/ResultPrinters/MapPrinter.php
index 6ab945f6..7a5f64b5 100644
--- a/www/wiki/extensions/Maps/src/SemanticMW/ResultPrinters/MapPrinter.php
+++ b/www/wiki/extensions/Maps/src/SemanticMW/ResultPrinters/MapPrinter.php
@@ -2,19 +2,15 @@
namespace Maps\SemanticMW\ResultPrinters;
-use FormatJson;
-use Html;
use Linker;
use Maps\Elements\BaseElement;
use Maps\Elements\Location;
use Maps\FileUrlFinder;
use Maps\MappingService;
-use Maps\MapsFunctions;
use Maps\Presentation\ElementJsonSerializer;
use Maps\Presentation\MapHtmlBuilder;
use Maps\Presentation\WikitextParser;
use Maps\Presentation\WikitextParsers\LocationParser;
-use ParamProcessor\ParamDefinition;
use Parser;
use SMW\Query\ResultPrinters\ResultPrinter;
use SMWOutputs;
@@ -113,6 +109,8 @@ class MapPrinter extends ResultPrinter {
return $this->fatalErrorMsg;
}
+ $this->isHTML = true;
+
$factory = \Maps\MapsFactory::newDefault();
$this->locationParser = $factory->newLocationParser();
$this->fileUrlFinder = $factory->getFileUrlFinder();
@@ -205,7 +203,7 @@ class MapPrinter extends ResultPrinter {
$params['centre'] = $this->getCenter( $params['centre'] );
$iconUrl = $this->fileUrlFinder->getUrlForFileName( $params['icon'] );
- $visitedIconUrl = $this->fileUrlFinder->getUrlForFileName( $params['visitedicon'] );
+ $visitedIconUrl = $this->fileUrlFinder->getUrlForFileName( $params['visitedicon'] ?? '' );
$params['locations'] = $this->getJsonForStaticLocations(
$params['staticlocations'],
@@ -318,10 +316,6 @@ class MapPrinter extends ResultPrinter {
$params = parent::getParameters();
$paramInfo = $this->getParameterInfo();
- // Do not display this as an option, as the format already determines it
- // TODO: this can probably be done cleaner with some changes in Maps
- unset( $paramInfo['mappingservice'] );
-
$params = array_merge( $params, $paramInfo );
return $params;
@@ -335,10 +329,7 @@ class MapPrinter extends ResultPrinter {
private function getParameterInfo() {
global $smgQPShowTitle, $smgQPTemplate, $smgQPHideNamespace;
- $params = array_merge(
- ParamDefinition::getCleanDefinitions( MapsFunctions::getCommonParameters() ),
- $this->service->getParameterInfo()
- );
+ $params = $this->service->getParameterInfo();
$params['staticlocations'] = [
'type' => 'mapslocation',
diff --git a/www/wiki/extensions/Maps/tests/Integration/parsers/JsonFileParserTest.php b/www/wiki/extensions/Maps/tests/Integration/DataAccess/GeoJsonFetcherTest.php
index a169beb1..a3edbb87 100644
--- a/www/wiki/extensions/Maps/tests/Integration/parsers/JsonFileParserTest.php
+++ b/www/wiki/extensions/Maps/tests/Integration/DataAccess/GeoJsonFetcherTest.php
@@ -1,45 +1,40 @@
<?php
-namespace Maps\Tests\Integration\parsers;
+namespace Maps\Tests\Integration\DataAccess;
use FileFetcher\FileFetcher;
use FileFetcher\NullFileFetcher;
use FileFetcher\SimpleFileFetcher;
use FileFetcher\StubFileFetcher;
use FileFetcher\ThrowingFileFetcher;
-use Maps\DataAccess\JsonFileParser;
+use Maps\DataAccess\GeoJsonFetcher;
+use Maps\MapsFactory;
use Maps\MediaWiki\Content\GeoJsonContent;
use PHPUnit\Framework\TestCase;
use PHPUnit4And6Compat;
use Title;
/**
- * @covers \Maps\DataAccess\JsonFileParser
+ * @covers \Maps\DataAccess\GeoJsonFetcher
* @licence GNU GPL v2+
* @author Jeroen De Dauw < jeroendedauw@gmail.com >
*/
-class JsonFileParserTest extends TestCase {
+class GeoJsonFetcherTest extends TestCase {
use PHPUnit4And6Compat;
private const VALID_FILE_JSON = [
- 'such' => 'string',
- 42 => 13.37,
- 'array' => [
- '~[,,_,,]:3'
- ]
+ 'type' => 'FeatureCollection',
+ 'features' => []
];
private const VALID_PAGE_JSON = [
- 'foo' => 'bar',
- 1 => 2.3,
- 'array' => [
- '~[,,_,,]:3'
- ]
+ 'type' => 'FeatureCollection',
+ 'features' => []
];
- private const EXISTING_GEO_JSON_PAGE = 'Such';
- private const EXISTING_GEO_JSON_PAGE_WITH_PREFIX = 'GeoJson:Such';
- private const NON_EXISTING_GEO_JSON_PAGE = 'GeoJson:Nope';
+ private const EXISTING_GEO_JSON_PAGE = 'Test Such';
+ private const EXISTING_GEO_JSON_PAGE_WITH_PREFIX = 'GeoJson:Test Such';
+ private const NON_EXISTING_GEO_JSON_PAGE = 'GeoJson:Test Nope';
/**
* @var FileFetcher
@@ -53,11 +48,8 @@ class JsonFileParserTest extends TestCase {
$page->doEditContent( new GeoJsonContent( json_encode( self::VALID_PAGE_JSON ) ), '' );
}
- private function newJsonFileParser(): JsonFileParser {
- return new JsonFileParser(
- $this->fileFetcher,
- null
- );
+ private function newJsonFileParser(): GeoJsonFetcher {
+ return MapsFactory::newDefault()->newGeoJsonFetcher( $this->fileFetcher );
}
public function testWhenFileRetrievalFails_emptyJsonIsReturned() {
@@ -117,4 +109,11 @@ class JsonFileParserTest extends TestCase {
);
}
+ public function testPageIsReturnedAsSource() {
+ $this->assertSame(
+ self::EXISTING_GEO_JSON_PAGE,
+ $this->newJsonFileParser()->fetch( self::EXISTING_GEO_JSON_PAGE_WITH_PREFIX )->getTitleValue()->getText()
+ );
+ }
+
}
diff --git a/www/wiki/extensions/Maps/tests/Integration/MediaWiki/ParserHooks/CoordinatesTest.php b/www/wiki/extensions/Maps/tests/Integration/MediaWiki/ParserHooks/CoordinatesTest.php
index 9ee324fe..9dffefae 100644
--- a/www/wiki/extensions/Maps/tests/Integration/MediaWiki/ParserHooks/CoordinatesTest.php
+++ b/www/wiki/extensions/Maps/tests/Integration/MediaWiki/ParserHooks/CoordinatesTest.php
@@ -3,8 +3,8 @@
namespace Maps\Tests\Integration\MediaWiki\ParserHooks;
use DataValues\Geo\Values\LatLongValue;
+use Maps\MapsFactory;
use Maps\MediaWiki\ParserHooks\CoordinatesFunction;
-use ParamProcessor\ParamDefinition;
/**
* @covers CoordinatesFunction
@@ -71,7 +71,9 @@ class CoordinatesTest extends ParserHookTest {
* @see ParserHookTest::processingProvider
*/
public function processingProvider() {
- $definitions = ParamDefinition::getCleanDefinitions( $this->getInstance()->getParamDefinitions() );
+ $definitions = MapsFactory::globalInstance()->getParamDefinitionFactory()->newDefinitionsFromArrays(
+ $this->getInstance()->getParamDefinitions()
+ );
$argLists = [];
$values = [
@@ -122,4 +124,4 @@ class CoordinatesTest extends ParserHookTest {
return new \Maps\MediaWiki\ParserHooks\CoordinatesFunction();
}
-} \ No newline at end of file
+}
diff --git a/www/wiki/extensions/Maps/tests/Integration/MediaWiki/ParserHooks/ParserHookTest.php b/www/wiki/extensions/Maps/tests/Integration/MediaWiki/ParserHooks/ParserHookTest.php
index eeb19d61..791f5d44 100644
--- a/www/wiki/extensions/Maps/tests/Integration/MediaWiki/ParserHooks/ParserHookTest.php
+++ b/www/wiki/extensions/Maps/tests/Integration/MediaWiki/ParserHooks/ParserHookTest.php
@@ -2,7 +2,7 @@
namespace Maps\Tests\Integration\MediaWiki\ParserHooks;
-use ParamProcessor\ParamDefinition;
+use Maps\MapsFactory;
use ParamProcessor\Processor;
use PHPUnit\Framework\TestCase;
@@ -77,7 +77,10 @@ abstract class ParserHookTest extends TestCase {
$definitions = $this->getInstance()->getParamDefinitions();
$processor = Processor::newDefault();
- $processor->setParameters( $parameters, $definitions );
+ $processor->setParameters( $parameters );
+ $processor->setParameterDefinitions(
+ MapsFactory::globalInstance()->getParamDefinitionFactory()->newDefinitionsFromArrays( $definitions )
+ );
$result = $processor->processParameters();
@@ -108,7 +111,9 @@ abstract class ParserHookTest extends TestCase {
* Returns an array with the default values of the parameters.
*/
private function getDefaultValues() {
- $definitions = ParamDefinition::getCleanDefinitions( $this->getInstance()->getParamDefinitions() );
+ $definitions = MapsFactory::globalInstance()->getParamDefinitionFactory()->newDefinitionsFromArrays(
+ $this->getInstance()->getParamDefinitions()
+ );
$defaults = [];
@@ -130,4 +135,4 @@ abstract class ParserHookTest extends TestCase {
);
}
-} \ No newline at end of file
+}
diff --git a/www/wiki/extensions/Maps/tests/Integration/Parser/DisplayMapTest.php b/www/wiki/extensions/Maps/tests/Integration/Parser/DisplayMapTest.php
index 9bd64bcf..4ef7c791 100644
--- a/www/wiki/extensions/Maps/tests/Integration/Parser/DisplayMapTest.php
+++ b/www/wiki/extensions/Maps/tests/Integration/Parser/DisplayMapTest.php
@@ -2,6 +2,7 @@
namespace Maps\Tests\Integration\Parser;
+use Maps\MediaWiki\Content\GeoJsonContent;
use PHPUnit\Framework\TestCase;
/**
@@ -217,4 +218,40 @@ class DisplayMapTest extends TestCase {
);
}
-} \ No newline at end of file
+ public function testGeoJsonSourceForFile() {
+ $this->assertContains(
+ '"GeoJsonSource":null,',
+ $this->parse(
+ "{{#display_map:geojson=404}}"
+ )
+ );
+ }
+
+ public function testGeoJsonSourceForPage() {
+ $page = new \WikiPage( \Title::newFromText( 'GeoJson:TestPageSource' ) );
+ $page->doEditContent(
+ new GeoJsonContent( json_encode( [
+ 'type' => 'FeatureCollection',
+ 'features' => []
+ ] ) ),
+ ''
+ );
+
+ $this->assertContains(
+ '"GeoJsonSource":"TestPageSource",',
+ $this->parse(
+ "{{#display_map:geojson=TestPageSource}}"
+ )
+ );
+ }
+
+ public function testGoogleMapsKmlFiltersInvalidFileNames() {
+ $this->assertContains(
+ '"kml":["ValidFile.kml"],',
+ $this->parse(
+ "{{#display_map:service=google|kml=, ,ValidFile.kml ,}}"
+ )
+ );
+ }
+
+}
diff --git a/www/wiki/extensions/Maps/tests/Integration/ResourceModulesTest.php b/www/wiki/extensions/Maps/tests/Integration/ResourceModulesTest.php
new file mode 100644
index 00000000..d6f2a6e5
--- /dev/null
+++ b/www/wiki/extensions/Maps/tests/Integration/ResourceModulesTest.php
@@ -0,0 +1,56 @@
+<?php
+
+declare( strict_types = 1 );
+
+namespace Maps\Tests\Integration;
+
+use MediaWiki\MediaWikiServices;
+use PHPUnit\Framework\TestCase;
+use ResourceLoader;
+
+class ResourceModulesTest extends TestCase {
+
+ /**
+ * @dataProvider mapsModuleNameProvider
+ */
+ public function testDependenciesExist( string $moduleName ) {
+ $resourceLoader = $this->getResourceLoader();
+ $module = $resourceLoader->getModule( $moduleName );
+
+ if ( $module->getDependencies() === [] ) {
+ $this->assertTrue( true );
+ }
+ else {
+ foreach ( $module->getDependencies() as $dependency ) {
+ $this->assertNotNull(
+ $resourceLoader->getModule( $dependency ),
+ 'Dependency ' . $dependency . ' should exist'
+ );
+ }
+ }
+ }
+
+ public function mapsModuleNameProvider() {
+ foreach ( $this->getResourceLoader()->getModuleNames() as $name ) {
+ if ( $this->isMapsModule( $name ) ) {
+ yield $name => [ $name ];
+ }
+ }
+ }
+
+ private function isMapsModule( string $name ): bool {
+ $modulePrefix = 'ext.maps.';
+ return substr( $name, 0, strlen( $modulePrefix ) ) === $modulePrefix;
+ }
+
+ private function getResourceLoader(): ResourceLoader {
+ $mwServices = MediaWikiServices::getInstance();
+
+ if ( method_exists( $mwServices, 'getResourceLoader' ) ) {
+ return $mwServices->getResourceLoader();
+ }
+
+ return new ResourceLoader();
+ }
+
+}
diff --git a/www/wiki/extensions/Maps/tests/Unit/MediaWiki/GeoJsonContentTest.php b/www/wiki/extensions/Maps/tests/Unit/MediaWiki/GeoJsonContentTest.php
new file mode 100644
index 00000000..df2cc759
--- /dev/null
+++ b/www/wiki/extensions/Maps/tests/Unit/MediaWiki/GeoJsonContentTest.php
@@ -0,0 +1,22 @@
+<?php
+
+declare( strict_types = 1 );
+
+namespace Maps\Tests\Unit\MediaWiki;
+
+use Maps\MediaWiki\Content\GeoJsonContent;
+use PHPUnit\Framework\TestCase;
+
+class GeoJsonContentTest extends TestCase {
+
+ public function testEmptyJsonIsNotValidContent() {
+ $this->assertFalse( ( new GeoJsonContent( '{}' ) )->isValid() );
+ }
+
+ public function testMinimalGeoJsonIsValid() {
+ $this->assertTrue( ( new GeoJsonContent(
+ '{"type": "FeatureCollection", "features": []}'
+ ) )->isValid() );
+ }
+
+}
diff --git a/www/wiki/extensions/Maps/tests/js/leaflet/GeoJsonTest.js b/www/wiki/extensions/Maps/tests/js/leaflet/GeoJsonTest.js
new file mode 100644
index 00000000..768db788
--- /dev/null
+++ b/www/wiki/extensions/Maps/tests/js/leaflet/GeoJsonTest.js
@@ -0,0 +1,112 @@
+( function () {
+ QUnit.module( 'Maps' );
+
+ let GeoJSON = window.maps.leaflet.GeoJson;
+
+ QUnit.test( 'GeoJSON.simpleStyleToLeafletPathOptions', function ( assert ) {
+ assert.deepEqual(
+ GeoJSON.simpleStyleToLeafletPathOptions( {} ),
+ {},
+ 'Empty properties results in empty path'
+ );
+
+ let pathOptions = GeoJSON.simpleStyleToLeafletPathOptions( {
+ "stroke": "#a92c2c",
+ "stroke-width": 5.1,
+ "stroke-opacity": 1,
+ "fill": "#ffff00",
+ "fill-opacity": 0.5,
+ "title": "hi"
+ } );
+
+ assert.equal(
+ pathOptions.color,
+ '#a92c2c',
+ 'color is set (based on stroke)'
+ );
+
+ assert.equal(
+ pathOptions.weight,
+ 5.1,
+ 'weight is set (based on stroke-width)'
+ );
+
+ assert.equal(
+ pathOptions.opacity,
+ 1,
+ 'opacity is set (based on stroke-opacity)'
+ );
+
+ assert.equal(
+ pathOptions.fillColor,
+ "#ffff00",
+ 'fillColor is set'
+ );
+
+ assert.equal(
+ pathOptions.fillOpacity,
+ 0.5,
+ 'fillOpacity is set'
+ );
+ } );
+
+ QUnit.test( 'GeoJSON.popupContentFromProperties', function ( assert ) {
+ assert.equal(
+ GeoJSON.popupContentFromProperties({
+ title: 'Hello World',
+ description: 'pew pew'
+ }),
+ '<strong>Hello World</strong><br>pew pew',
+ 'Title and description: title is made bold and description is on a new line'
+ );
+
+ assert.equal(
+ GeoJSON.popupContentFromProperties({
+ title: 'Hello World'
+ }),
+ 'Hello World',
+ 'Only title'
+ );
+
+ assert.equal(
+ GeoJSON.popupContentFromProperties({
+ description: 'pew pew'
+ }),
+ 'pew pew',
+ 'Only description'
+ );
+
+ assert.equal(
+ GeoJSON.popupContentFromProperties({
+ }),
+ '',
+ 'No content'
+ );
+
+ assert.equal(
+ GeoJSON.popupContentFromProperties({
+ title: 'Hello <a href="#">link</a>'
+ }),
+ 'Hello &lt;a href=\"#\"&gt;link&lt;/a&gt;',
+ 'Title escaping'
+ );
+
+ assert.equal(
+ GeoJSON.popupContentFromProperties({
+ description: 'Hello <a href="#">link</a>'
+ }),
+ 'Hello &lt;a href=\"#\"&gt;link&lt;/a&gt;',
+ 'Description escaping'
+ );
+
+ assert.equal(
+ GeoJSON.popupContentFromProperties({
+ title: 'Hello <a href="#">link</a>',
+ description: "<script>alert('evil')</script>"
+ }),
+ '<strong>Hello &lt;a href=\"#\"&gt;link&lt;/a&gt;</strong><br>&lt;script&gt;alert(\'evil\')&lt;/script&gt;',
+ 'Title and description escaping'
+ );
+ } );
+
+}() );