diff --git a/sandbox/lines-polygons/README.md b/sandbox/lines-polygons/README.md new file mode 100644 index 0000000..7c435a5 --- /dev/null +++ b/sandbox/lines-polygons/README.md @@ -0,0 +1,3 @@ +How to draw lines and polygons in Leaflet: sandbox for testing +================================= + diff --git a/sandbox/lines-polygons/index.html b/sandbox/lines-polygons/index.html new file mode 100644 index 0000000..73a5157 --- /dev/null +++ b/sandbox/lines-polygons/index.html @@ -0,0 +1,91 @@ + + +
+
';
+
+L.tileLayer(
+ 'https://otile{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.png',
+ {
+ attribution: 'Data by OpenStreetMap contributors'
+ }
+)
+.addTo(map);
+
+// GeoJSON layer (UTM15)
+proj4.defs('EPSG:26915', '+proj=utm +zone=15 +ellps=GRS80 +datum=NAD83 +units=m +no_defs');
+
+var geojson = {
+ 'type': 'Feature',
+ 'geometry': {
+ 'type': 'Point',
+ 'coordinates': [481650, 4980105],
+ },
+ 'properties': {
+ 'name': 'University of Minnesota'
+ },
+ 'crs': {
+ 'type': 'name',
+ 'properties': {
+ 'name': 'urn:ogc:def:crs:EPSG::26915'
+ }
+ }
+ };
+
+L.Proj.geoJson(geojson, {
+ 'pointToLayer': function(feature, latlng) {
+ return L.marker(latlng).bindPopup(feature.properties.name);
+ }
+}).addTo(map);
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/geojson-crs/style.css b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/geojson-crs/style.css
new file mode 100644
index 0000000..01d5be9
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/geojson-crs/style.css
@@ -0,0 +1,10 @@
+html, body {
+ height: 100%;
+ padding: 0;
+ margin: 0;
+}
+
+#map {
+ width: 100%;
+ height: 100%;
+}
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/image-overlay/index.html b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/image-overlay/index.html
new file mode 100644
index 0000000..0e03bd3
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/image-overlay/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/image-overlay/script.js b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/image-overlay/script.js
new file mode 100644
index 0000000..c4c5344
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/image-overlay/script.js
@@ -0,0 +1,20 @@
+var crs = new L.Proj.CRS('EPSG:25833', '+proj=utm +zone=33 +ellps=GRS80 +units=m +no_defs', {
+ resolutions: [21674.7100160867, 10837.35500804335, 5418.677504021675, 2709.3387520108377, 1354.6693760054188, 677.3346880027094,
+ 338.6673440013547, 169.33367200067735, 84.66683600033868, 42.33341800016934, 21.16670900008467, 10.583354500042335,
+ 5.291677250021167, 2.6458386250105836, 1.3229193125052918, 0.6614596562526459, 0.33072982812632296, 0.16536491406316148],
+ origin: [-2500000, 9045984]
+ }
+);
+
+var map = L.map('map', {
+ crs: crs,
+ center: [60, 10],
+ zoom: 14
+});
+
+L.tileLayer('https://services.geodataonline.no/arcgis/rest/services/Geocache_UTM33_EUREF89/GeocacheBasis/MapServer/tile/{z}/{y}/{x}').addTo(map);
+
+L.Proj.imageOverlay('https://services.geodataonline.no/arcgis/rest/services/Geocache_UTM33_EUREF89/GeocacheGraatone/MapServer/export' +
+ '?bbox=220461.84450009145,6661489.40861154,222115.49415195954,6662415.4521056535' +
+ '&size=1250,700&dpi=96&format=png24&transparent=true&bboxSR=25833&imageSR=25833&f=image',
+ L.bounds([220461.84450009145, 6661489.40861154], [222115.49415195954, 6662415.4521056535])).addTo(map);
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/image-overlay/style.css b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/image-overlay/style.css
new file mode 100644
index 0000000..5896443
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/image-overlay/style.css
@@ -0,0 +1,10 @@
+html, body {
+ height: 100%;
+ padding: 0;
+ margin: 0;
+}
+
+#map {
+ width: 100%;
+ height: 100%;
+}
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/europe/index_austria.html b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/europe/index_austria.html
new file mode 100644
index 0000000..7591547
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/europe/index_austria.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/europe/script_austria.js b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/europe/script_austria.js
new file mode 100644
index 0000000..508796b
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/europe/script_austria.js
@@ -0,0 +1,36 @@
+var crs31258 = new L.Proj.CRS('EPSG::31258',
+ "+proj=tmerc +lat_0=0 +lon_0=13.33333333333333 +k=1 +x_0=450000 +y_0=-5000000 +ellps=bessel +towgs84=577.326,90.129,463.919,5.137,1.474,5.297,2.4232 +units=m +no_defs",
+ {
+ origin: [-5172500.0, 5001000.0],
+ resolutions: [
+ 400.00079375158754,
+ 200.000529167725,
+ 100.0002645838625,
+ 50,
+ 25,
+ 15.000052916772502,
+ 9.9999470832275,
+ 5.000105833545001,
+ 3.0001164168995005,
+ 2.5000529167725003,
+ 1.9999894166455001,
+ 1.4999259165184997,
+ 1.0001270002540006,
+ 0.5000635001270003,
+ 0.25003175006350015],
+ });
+
+var map = L.map('map', {
+ crs: crs31258,
+});
+var attrib = "© KAGIS http://www.kagis.ktn.gv.at";
+var basemap = new L.TileLayer("http://gis.ktn.gv.at/arcgis/rest/services/tilecache/Ortho_2010_2012/MapServer/tile/{z}/{y}/{x}", {
+ tileSize: 512,
+ maxZoom: 14,
+ minZoom: 0,
+ attribution: attrib
+});
+
+map.addLayer(basemap);
+map.setView([46.66411, 14.63602], 12);
+
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/nz/index_auckland.html b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/nz/index_auckland.html
new file mode 100644
index 0000000..4a604e6
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/nz/index_auckland.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/nz/index_wellington.html b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/nz/index_wellington.html
new file mode 100644
index 0000000..525ef5e
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/nz/index_wellington.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/nz/script_auckland.js b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/nz/script_auckland.js
new file mode 100644
index 0000000..6fc2e18
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/nz/script_auckland.js
@@ -0,0 +1,35 @@
+var crs = new L.Proj.CRS('EPSG:2193',
+ '+proj=tmerc +lat_0=0 +lon_0=173 +k=0.9996 +x_0=1600000 +y_0=10000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs',
+ {
+ origin: [1565000, 6150000],
+ resolutions: [
+ 264.583862501058,
+ 201.083735500804,
+ 132.291931250529,
+ 66.1459656252646,
+ 26.4583862501058,
+ 13.2291931250529,
+ 6.61459656252646,
+ 3.96875793751588,
+ 2.11667090000847,
+ 1.32291931250529,
+ 0.661459656252646,
+ 0.264583862501058,
+ 0.132291931250529
+ ]
+ });
+
+var map = new L.Map('map', {
+ crs: crs,
+});
+
+var tileUrl = 'https://maps.aucklandcouncil.govt.nz/ArcGIS/rest/services/Aerials/MapServer/tile/{z}/{y}/{x}',
+ attrib = 'Auckland City Council © 2012',
+ tilelayer = new L.TileLayer(tileUrl, {
+ maxZoom: 12,
+ minZoom: 0,
+ attribution: attrib,
+ });
+
+map.addLayer(tilelayer);
+map.setView([-36.852931, 174.762057], 10);
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/nz/script_wellington.js b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/nz/script_wellington.js
new file mode 100644
index 0000000..62b5e82
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/nz/script_wellington.js
@@ -0,0 +1,45 @@
+var crs = new L.Proj.CRS('EPSG:2193',
+ '+proj=tmerc +lat_0=0 +lon_0=173 +k=0.9996 +x_0=1600000 +y_0=10000000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs',
+ {
+ origin: [-5099531.19635, 57089446.18],
+ resolutions: [
+ 66.1459656252646,
+ 33.0729828126323,
+ 16.933367200067735,
+ 8.466683600033868,
+ 4.233341800016934,
+ 2.116670900008467,
+ 1.0583354500042335,
+ 0.5291677250021167,
+ 0.26458386250105836,
+ 0.13229193125052918,
+ 0.06614596562526459
+ ]
+ });
+
+var map = new L.Map('map', {
+ crs: crs,
+ continuousWorld: true,
+ worldCopyJump: false
+});
+
+/*
+Wellington City Council's GIS web services are available under the following terms and conditions:
+https://wellington.govt.nz/about-wellington/maps/gis-data-terms-and-conditions
+
+Aerial Imagery: Creative Commons Attribution 3.0 New Zealand Licence, https://creativecommons.org/licenses/by/3.0/nz/
+Additional services listed at https://wellington.govt.nz/~/media/maps/gis/ogc-services-list.pdf
+*/
+
+var tileUrl = 'https://gis.wcc.govt.nz/arcgis/rest/services/Basemap/Aerial_Photo/MapServer/tile/{z}/{y}/{x}',
+ attrib = 'Wellington City Council © 2012',
+ tilelayer = new L.TileLayer(tileUrl, {
+ maxZoom: 10,
+ minZoom: 0,
+ continuousWorld: true,
+ attribution: attrib,
+ tileSize: 512
+ });
+
+map.addLayer(tilelayer);
+map.setView([-41.288889, 174.777222], 5);
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/style.css b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/style.css
new file mode 100644
index 0000000..5896443
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/local-projections/style.css
@@ -0,0 +1,10 @@
+html, body {
+ height: 100%;
+ padding: 0;
+ margin: 0;
+}
+
+#map {
+ width: 100%;
+ height: 100%;
+}
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/osm-tiled/index.html b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/osm-tiled/index.html
new file mode 100644
index 0000000..661c90c
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/osm-tiled/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/osm-tiled/script.js b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/osm-tiled/script.js
new file mode 100644
index 0000000..115e5fa
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/osm-tiled/script.js
@@ -0,0 +1,21 @@
+var crs = new L.Proj.CRS('EPSG:3006',
+ '+proj=utm +zone=33 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs',
+ {
+ resolutions: [
+ 8192, 4096, 2048, 1024, 512, 256, 128,
+ 64, 32, 16, 8, 4, 2, 1, 0.5
+ ],
+ origin: [0, 0],
+ bounds: L.bounds([218128.7031, 6126002.9379], [1083427.2970, 7692850.9468])
+ }),
+ map = new L.Map('map', {
+ crs: crs,
+ });
+
+L.tileLayer('https://api.geosition.com/tile/osm-bright-3006/{z}/{x}/{y}.png', {
+ maxZoom: 14,
+ minZoom: 0,
+ attribution: 'Map data © OpenStreetMap contributors, Imagery © Kartena'
+}).addTo(map);
+
+map.setView([57.704, 11.965], 13);
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/osm-tiled/style.css b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/osm-tiled/style.css
new file mode 100644
index 0000000..01d5be9
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/osm-tiled/style.css
@@ -0,0 +1,10 @@
+html, body {
+ height: 100%;
+ padding: 0;
+ margin: 0;
+}
+
+#map {
+ width: 100%;
+ height: 100%;
+}
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/tms-tiled/index.html b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/tms-tiled/index.html
new file mode 100644
index 0000000..0f108ba
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/tms-tiled/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/tms-tiled/script.js b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/tms-tiled/script.js
new file mode 100644
index 0000000..d51d5f6
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/tms-tiled/script.js
@@ -0,0 +1,27 @@
+var crs = new L.Proj.CRS('EPSG:5181',
+ '+proj=tmerc +lat_0=38 +lon_0=127 +k=1 +x_0=200000 +y_0=500000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs',
+ {
+ resolutions: [2048, 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1, 0.5, 0.25],
+ origin: [-30000, -60000],
+ bounds: L.bounds([-30000, -60000], [494288, 464288])
+ }),
+ map = L.map('map', {
+ crs: crs,
+ continuousWorld: true,
+ worldCopyJump: false,
+ });
+
+new L.TileLayer('http://i{s}.maps.daum-img.net/map/image/G03/i/1.20/L{z}/{y}/{x}.png', {
+ maxZoom: 14,
+ minZoom: 0,
+ zoomReverse: true,
+ subdomains: '0123',
+ continuousWorld: true,
+ attribution: 'ⓒ Daum',
+ tms: true
+}).addTo(map);
+
+//Gunsan Airport
+new L.marker([35.925937, 126.615810]).addTo(map);
+
+map.setView([36.0, 127.0], 0);
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/tms-tiled/style.css b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/tms-tiled/style.css
new file mode 100644
index 0000000..01d5be9
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/tms-tiled/style.css
@@ -0,0 +1,10 @@
+html, body {
+ height: 100%;
+ padding: 0;
+ margin: 0;
+}
+
+#map {
+ width: 100%;
+ height: 100%;
+}
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wms/index.html b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wms/index.html
new file mode 100644
index 0000000..0f108ba
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wms/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wms/script.js b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wms/script.js
new file mode 100644
index 0000000..5cd0663
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wms/script.js
@@ -0,0 +1,22 @@
+var crs = new L.Proj.CRS('EPSG:3006',
+ '+proj=utm +zone=33 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs',
+ {
+ resolutions: [
+ 8192, 4096, 2048, 1024, 512, 256, 128,
+ 64, 32, 16, 8, 4, 2, 1, 0.5
+ ],
+ origin: [0, 0]
+ }),
+ map = new L.Map('map', {
+ crs: crs,
+ });
+
+L.tileLayer.wms('https://geodatatest.havochvatten.se/geoservices/ows', {
+ layers: 'hav-bakgrundskartor:hav-grundkarta',
+ format: 'image/png',
+ maxZoom: 14,
+ minZoom: 0,
+ attribution: '© OpenStreetMap contributors Havs- och vattenmyndigheten (Swedish Agency for Marine and Water Management)'
+}).addTo(map);
+
+map.setView([55.8, 14.3], 3);
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wms/style.css b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wms/style.css
new file mode 100644
index 0000000..01d5be9
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wms/style.css
@@ -0,0 +1,10 @@
+html, body {
+ height: 100%;
+ padding: 0;
+ margin: 0;
+}
+
+#map {
+ width: 100%;
+ height: 100%;
+}
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wmts/index.html b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wmts/index.html
new file mode 100644
index 0000000..0e03bd3
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wmts/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wmts/script.js b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wmts/script.js
new file mode 100644
index 0000000..b2ca4e8
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wmts/script.js
@@ -0,0 +1,30 @@
+/*
+Example using Sweden Lantmäteriet Topografisk Webbkarta
+https://opendata.lantmateriet.se/#apis
+*/
+
+/*** INSERT YOUR LANTMÄTERIET API TOKEN BELOW ***/
+var token = '';
+
+var crs = new L.Proj.CRS('EPSG:3006',
+ '+proj=utm +zone=33 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs',
+ {
+ resolutions: [
+ 4096, 2048, 1024, 512, 256, 128, 64, 32, 16, 8
+ ],
+ origin: [-1200000.000000, 8500000.000000 ],
+ bounds: L.bounds( [-1200000.000000, 8500000.000000], [4305696.000000, 2994304.000000])
+ }),
+ map = new L.Map('map', {
+ crs: crs,
+ continuousWorld: true,
+ });
+
+new L.TileLayer('https://api.lantmateriet.se/open/topowebb-ccby/v1/wmts/token/'+ token +'/?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=topowebb&STYLE=default&TILEMATRIXSET=3006&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&FORMAT=image%2Fpng', {
+ maxZoom: 9,
+ minZoom: 0,
+ continuousWorld: true,
+ attribution: '© Lantmäteriet Topografisk Webbkarta Visning, CCB',
+}).addTo(map);
+//Set view over Stockholm Södermalm
+map.setView([59.3167, 18.0667], 7);
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wmts/style.css b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wmts/style.css
new file mode 100644
index 0000000..5896443
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/examples/wmts/style.css
@@ -0,0 +1,10 @@
+html, body {
+ height: 100%;
+ padding: 0;
+ margin: 0;
+}
+
+#map {
+ width: 100%;
+ height: 100%;
+}
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/images/layers-2x.png b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/images/layers-2x.png
new file mode 100644
index 0000000..200c333
Binary files /dev/null and b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/images/layers-2x.png differ
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/images/layers.png b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/images/layers.png
new file mode 100644
index 0000000..1a72e57
Binary files /dev/null and b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/images/layers.png differ
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/images/marker-icon-2x.png b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/images/marker-icon-2x.png
new file mode 100644
index 0000000..e4abba3
Binary files /dev/null and b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/images/marker-icon-2x.png differ
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/images/marker-icon.png b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/images/marker-icon.png
new file mode 100644
index 0000000..950edf2
Binary files /dev/null and b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/images/marker-icon.png differ
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/images/marker-shadow.png b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/images/marker-shadow.png
new file mode 100644
index 0000000..9fd2979
Binary files /dev/null and b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/images/marker-shadow.png differ
diff --git a/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/leaflet-src.js b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/leaflet-src.js
new file mode 100644
index 0000000..41003c0
--- /dev/null
+++ b/sandbox/lines-polygons/leaflet/Proj4Leaflet-1.0.1/lib/leaflet/leaflet-src.js
@@ -0,0 +1,13251 @@
+/*
+ Leaflet 1.0.3, a JS library for interactive maps. http://leafletjs.com
+ (c) 2010-2016 Vladimir Agafonkin, (c) 2010-2011 CloudMade
+*/
+(function (window, document, undefined) {
+var L = {
+ version: "1.0.3"
+};
+
+function expose() {
+ var oldL = window.L;
+
+ L.noConflict = function () {
+ window.L = oldL;
+ return this;
+ };
+
+ window.L = L;
+}
+
+// define Leaflet for Node module pattern loaders, including Browserify
+if (typeof module === 'object' && typeof module.exports === 'object') {
+ module.exports = L;
+
+// define Leaflet as an AMD module
+} else if (typeof define === 'function' && define.amd) {
+ define(L);
+}
+
+// define Leaflet as a global L variable, saving the original L to restore later if needed
+if (typeof window !== 'undefined') {
+ expose();
+}
+
+
+
+/*
+ * @namespace Util
+ *
+ * Various utility functions, used by Leaflet internally.
+ */
+
+L.Util = {
+
+ // @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.
+ extend: function (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)
+ 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.
+ bind: function (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);
+ };
+ },
+
+ // @function stamp(obj: Object): Number
+ // Returns the unique ID of an object, assiging it one if it doesn't have it.
+ stamp: function (obj) {
+ /*eslint-disable */
+ obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId;
+ return obj._leaflet_id;
+ /*eslint-enable */
+ },
+
+ // @property lastId: Number
+ // Last unique ID used by [`stamp()`](#util-stamp)
+ lastId: 0,
+
+ // @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.bind` shortcut.
+ throttle: function (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`.
+ wrapNum: function (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`.
+ falseFn: function () { return false; },
+
+ // @function formatNum(num: Number, digits?: Number): Number
+ // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
+ formatNum: function (num, digits) {
+ var pow = Math.pow(10, digits || 5);
+ 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)
+ trim: function (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.
+ splitWords: function (str) {
+ return L.Util.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.
+ setOptions: function (obj, options) {
+ if (!obj.hasOwnProperty('options')) {
+ obj.options = obj.options ? L.Util.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'`)
+ getParamString: function (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('&');
+ },
+
+ // @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.
+ template: function (str, data) {
+ return str.replace(L.Util.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;
+ });
+ },
+
+ templateRe: /\{ *([\w_\-]+) *\}/g,
+
+ // @function isArray(obj): Boolean
+ // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
+ 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)
+ indexOf: function (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).
+ emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
+};
+
+(function () {
+ // 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,
+ 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.
+ L.Util.requestAnimFrame = function (fn, context, immediate) {
+ if (immediate && requestFn === timeoutDefer) {
+ fn.call(context);
+ } else {
+ return requestFn.call(window, L.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).
+ L.Util.cancelAnimFrame = function (id) {
+ if (id) {
+ cancelFn.call(window, id);
+ }
+ };
+})();
+
+// shortcuts for most used utility functions
+L.extend = L.Util.extend;
+L.bind = L.Util.bind;
+L.stamp = L.Util.stamp;
+L.setOptions = L.Util.setOptions;
+
+
+
+
+// @class Class
+// @aka L.Class
+
+// @section
+// @uninheritable
+
+// Thanks to John Resig and Dean Edwards for inspiration!
+
+L.Class = function () {};
+
+L.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 = L.Util.create(parentProto);
+ proto.constructor = NewClass;
+
+ NewClass.prototype = proto;
+
+ // inherit parent's statics
+ for (var i in this) {
+ if (this.hasOwnProperty(i) && i !== 'prototype') {
+ NewClass[i] = this[i];
+ }
+ }
+
+ // mix static properties into the class
+ if (props.statics) {
+ L.extend(NewClass, props.statics);
+ delete props.statics;
+ }
+
+ // mix includes into the prototype
+ if (props.includes) {
+ L.Util.extend.apply(null, [proto].concat(props.includes));
+ delete props.includes;
+ }
+
+ // merge options
+ if (proto.options) {
+ props.options = L.Util.extend(L.Util.create(proto.options), props.options);
+ }
+
+ // mix given properties into the prototype
+ L.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.
+L.Class.include = function (props) {
+ L.extend(this.prototype, props);
+ return this;
+};
+
+// @function mergeOptions(options: Object): this
+// [Merges `options`](#class-options) into the defaults of the class.
+L.Class.mergeOptions = function (options) {
+ L.extend(this.prototype.options, options);
+ return this;
+};
+
+// @function addInitHook(fn: Function): this
+// Adds a [constructor hook](#class-constructor-hooks) to the class.
+L.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;
+};
+
+
+
+/*
+ * @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);
+ * ```
+ */
+
+
+L.Evented = L.Class.extend({
+
+ /* @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 = L.Util.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.
+ */
+ 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 = L.Util.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 = L.Util.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 = L.Util.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 = L.Util.extend({}, data, {type: type, target: 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 = L.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[L.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[L.stamp(obj)];
+ }
+ return this;
+ },
+
+ _propagateEvent: function (e) {
+ for (var id in this._eventParents) {
+ this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true);
+ }
+ }
+});
+
+var proto = L.Evented.prototype;
+
+// aliases; we should ditch those eventually
+
+// @method addEventListener(…): this
+// Alias to [`on(…)`](#evented-on)
+proto.addEventListener = proto.on;
+
+// @method removeEventListener(…): this
+// Alias to [`off(…)`](#evented-off)
+
+// @method clearAllEventListeners(…): this
+// Alias to [`off()`](#evented-off)
+proto.removeEventListener = proto.clearAllEventListeners = proto.off;
+
+// @method addOneTimeEventListener(…): this
+// Alias to [`once(…)`](#evented-once)
+proto.addOneTimeEventListener = proto.once;
+
+// @method fireEvent(…): this
+// Alias to [`fire(…)`](#evented-fire)
+proto.fireEvent = proto.fire;
+
+// @method hasEventListeners(…): Boolean
+// Alias to [`listens(…)`](#evented-listens)
+proto.hasEventListeners = proto.listens;
+
+L.Mixin = {Events: proto};
+
+
+
+/*
+ * @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!');
+ * }
+ * ```
+ */
+
+(function () {
+
+ var ua = navigator.userAgent.toLowerCase(),
+ doc = document.documentElement,
+
+ ie = 'ActiveXObject' in window,
+
+ webkit = ua.indexOf('webkit') !== -1,
+ phantomjs = ua.indexOf('phantom') !== -1,
+ android23 = ua.search('android [23]') !== -1,
+ chrome = ua.indexOf('chrome') !== -1,
+ gecko = ua.indexOf('gecko') !== -1 && !webkit && !window.opera && !ie,
+
+ win = navigator.platform.indexOf('Win') === 0,
+
+ mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1,
+ msPointer = !window.PointerEvent && window.MSPointerEvent,
+ pointer = window.PointerEvent || msPointer,
+
+ ie3d = ie && ('transition' in doc.style),
+ webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
+ gecko3d = 'MozPerspective' in doc.style,
+ opera12 = 'OTransition' in doc.style;
+
+
+ var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
+ (window.DocumentTouch && document instanceof window.DocumentTouch));
+
+ L.Browser = {
+
+ // @property ie: Boolean
+ // `true` for all Internet Explorer versions (not Edge).
+ ie: ie,
+
+ // @property ielt9: Boolean
+ // `true` for Internet Explorer versions less than 9.
+ ielt9: ie && !document.addEventListener,
+
+ // @property edge: Boolean
+ // `true` for the Edge web browser.
+ edge: 'msLaunchUri' in navigator && !('documentMode' in document),
+
+ // @property webkit: Boolean
+ // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
+ webkit: webkit,
+
+ // @property gecko: Boolean
+ // `true` for gecko-based browsers like Firefox.
+ gecko: gecko,
+
+ // @property android: Boolean
+ // `true` for any browser running on an Android platform.
+ android: ua.indexOf('android') !== -1,
+
+ // @property android23: Boolean
+ // `true` for browsers running on Android 2 or Android 3.
+ android23: android23,
+
+ // @property chrome: Boolean
+ // `true` for the Chrome browser.
+ chrome: chrome,
+
+ // @property safari: Boolean
+ // `true` for the Safari browser.
+ safari: !chrome && ua.indexOf('safari') !== -1,
+
+
+ // @property win: Boolean
+ // `true` when the browser is running in a Windows platform
+ win: win,
+
+
+ // @property ie3d: Boolean
+ // `true` for all Internet Explorer versions supporting CSS transforms.
+ ie3d: ie3d,
+
+ // @property webkit3d: Boolean
+ // `true` for webkit-based browsers supporting CSS transforms.
+ webkit3d: webkit3d,
+
+ // @property gecko3d: Boolean
+ // `true` for gecko-based browsers supporting CSS transforms.
+ gecko3d: gecko3d,
+
+ // @property opera12: Boolean
+ // `true` for the Opera browser supporting CSS transforms (version 12 or later).
+ opera12: opera12,
+
+ // @property any3d: Boolean
+ // `true` for all browsers supporting CSS transforms.
+ any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs,
+
+
+ // @property mobile: Boolean
+ // `true` for all browsers running in a mobile device.
+ mobile: mobile,
+
+ // @property mobileWebkit: Boolean
+ // `true` for all webkit-based browsers in a mobile device.
+ mobileWebkit: mobile && webkit,
+
+ // @property mobileWebkit3d: Boolean
+ // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
+ mobileWebkit3d: mobile && webkit3d,
+
+ // @property mobileOpera: Boolean
+ // `true` for the Opera browser in a mobile device.
+ mobileOpera: mobile && window.opera,
+
+ // @property mobileGecko: Boolean
+ // `true` for gecko-based browsers running in a mobile device.
+ mobileGecko: mobile && gecko,
+
+
+ // @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.
+ touch: !!touch,
+
+ // @property msPointer: Boolean
+ // `true` for browsers implementing the Microsoft touch events model (notably IE10).
+ msPointer: !!msPointer,
+
+ // @property pointer: Boolean
+ // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
+ pointer: !!pointer,
+
+
+ // @property retina: Boolean
+ // `true` for browsers on a high-resolution "retina" screen.
+ retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1
+ };
+
+}());
+
+
+
+/*
+ * @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));
+ * ```
+ */
+
+L.Point = function (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);
+};
+
+L.Point.prototype = {
+
+ // @method clone(): Point
+ // Returns a copy of the current point.
+ clone: function () {
+ return new L.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(L.point(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(L.point(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 L.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 L.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 distanceTo(otherPoint: Point): Number
+ // Returns the cartesian distance between the current and the given points.
+ distanceTo: function (point) {
+ point = L.point(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 = L.point(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 = L.point(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(' +
+ L.Util.formatNum(this.x) + ', ' +
+ L.Util.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.
+L.point = function (x, y, round) {
+ if (x instanceof L.Point) {
+ return x;
+ }
+ if (L.Util.isArray(x)) {
+ return new L.Point(x[0], x[1]);
+ }
+ if (x === undefined || x === null) {
+ return x;
+ }
+ if (typeof x === 'object' && 'x' in x && 'y' in x) {
+ return new L.Point(x.x, x.y);
+ }
+ return new L.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]]);
+ * ```
+ */
+
+L.Bounds = function (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]);
+ }
+};
+
+L.Bounds.prototype = {
+ // @method extend(point: Point): this
+ // Extends the bounds to contain the given point.
+ extend: function (point) { // (Point)
+ point = L.point(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 L.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 L.Point(this.min.x, this.max.y);
+ },
+
+ // @method getTopRight(): Point
+ // Returns the top-right point of the bounds.
+ getTopRight: function () { // -> Point
+ return new L.Point(this.max.x, this.min.y);
+ },
+
+ // @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 L.Point) {
+ obj = L.point(obj);
+ } else {
+ obj = L.bounds(obj);
+ }
+
+ if (obj instanceof L.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 = L.bounds(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 = L.bounds(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(topLeft: Point, bottomRight: Point)
+// Creates a Bounds object from two coordinates (usually top-left and bottom-right corners).
+// @alternative
+// @factory L.bounds(points: Point[])
+// Creates a Bounds object from the points it contains
+L.bounds = function (a, b) {
+ if (!a || a instanceof L.Bounds) {
+ return a;
+ }
+ return new L.Bounds(a, b);
+};
+
+
+
+/*
+ * @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 = new 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.
+L.Transformation = function (a, b, c, d) {
+ this._a = a;
+ this._b = b;
+ this._c = c;
+ this._d = d;
+};
+
+L.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 L.Point(
+ (point.x / scale - this._b) / this._a,
+ (point.y / scale - this._d) / this._c);
+ }
+};
+
+
+
+/*
+ * @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.
+ */
+
+L.DomUtil = {
+
+ // @function get(id: String|HTMLElement): HTMLElement
+ // Returns an element given its DOM id, or returns the element itself
+ // if it was passed directly.
+ get: function (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.
+ getStyle: function (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.
+ create: function (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
+ remove: function (el) {
+ var parent = el.parentNode;
+ if (parent) {
+ parent.removeChild(el);
+ }
+ },
+
+ // @function empty(el: HTMLElement)
+ // Removes all of `el`'s children elements from `el`
+ empty: function (el) {
+ while (el.firstChild) {
+ el.removeChild(el.firstChild);
+ }
+ },
+
+ // @function toFront(el: HTMLElement)
+ // Makes `el` the last children of its parent, so it renders in front of the other children.
+ toFront: function (el) {
+ el.parentNode.appendChild(el);
+ },
+
+ // @function toBack(el: HTMLElement)
+ // Makes `el` the first children of its parent, so it renders back from the other children.
+ toBack: function (el) {
+ var parent = el.parentNode;
+ parent.insertBefore(el, parent.firstChild);
+ },
+
+ // @function hasClass(el: HTMLElement, name: String): Boolean
+ // Returns `true` if the element's class attribute contains `name`.
+ hasClass: function (el, name) {
+ if (el.classList !== undefined) {
+ return el.classList.contains(name);
+ }
+ var className = L.DomUtil.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.
+ addClass: function (el, name) {
+ if (el.classList !== undefined) {
+ var classes = L.Util.splitWords(name);
+ for (var i = 0, len = classes.length; i < len; i++) {
+ el.classList.add(classes[i]);
+ }
+ } else if (!L.DomUtil.hasClass(el, name)) {
+ var className = L.DomUtil.getClass(el);
+ L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
+ }
+ },
+
+ // @function removeClass(el: HTMLElement, name: String)
+ // Removes `name` from the element's class attribute.
+ removeClass: function (el, name) {
+ if (el.classList !== undefined) {
+ el.classList.remove(name);
+ } else {
+ L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
+ }
+ },
+
+ // @function setClass(el: HTMLElement, name: String)
+ // Sets the element's class.
+ setClass: function (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.
+ getClass: function (el) {
+ 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`.
+ setOpacity: function (el, value) {
+
+ if ('opacity' in el.style) {
+ el.style.opacity = value;
+
+ } else if ('filter' in el.style) {
+ L.DomUtil._setOpacityIE(el, value);
+ }
+ },
+
+ _setOpacityIE: function (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`.
+ testProp: function (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.
+ setTransform: function (el, offset, scale) {
+ var pos = offset || new L.Point(0, 0);
+
+ el.style[L.DomUtil.TRANSFORM] =
+ (L.Browser.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).
+ setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])
+
+ /*eslint-disable */
+ el._leaflet_pos = point;
+ /*eslint-enable */
+
+ if (L.Browser.any3d) {
+ L.DomUtil.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.
+ getPosition: function (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 L.Point(0, 0);
+ }
+};
+
+
+(function () {
+ // prefix style property names
+
+ // @property TRANSFORM: String
+ // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
+ L.DomUtil.TRANSFORM = L.DomUtil.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 transform style name.
+ var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
+ ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
+
+ L.DomUtil.TRANSITION_END =
+ transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';
+
+ // @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).
+ if ('onselectstart' in document) {
+ L.DomUtil.disableTextSelection = function () {
+ L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
+ };
+ L.DomUtil.enableTextSelection = function () {
+ L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
+ };
+
+ } else {
+ var userSelectProperty = L.DomUtil.testProp(
+ ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
+
+ L.DomUtil.disableTextSelection = function () {
+ if (userSelectProperty) {
+ var style = document.documentElement.style;
+ this._userSelect = style[userSelectProperty];
+ style[userSelectProperty] = 'none';
+ }
+ };
+ L.DomUtil.enableTextSelection = function () {
+ if (userSelectProperty) {
+ document.documentElement.style[userSelectProperty] = this._userSelect;
+ delete this._userSelect;
+ }
+ };
+ }
+
+ // @function disableImageDrag()
+ // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
+ // for `dragstart` DOM events, usually generated when the user drags an image.
+ L.DomUtil.disableImageDrag = function () {
+ L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
+ };
+
+ // @function enableImageDrag()
+ // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
+ L.DomUtil.enableImageDrag = function () {
+ L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
+ };
+
+ // @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.
+ L.DomUtil.preventOutline = function (element) {
+ while (element.tabIndex === -1) {
+ element = element.parentNode;
+ }
+ if (!element || !element.style) { return; }
+ L.DomUtil.restoreOutline();
+ this._outlineElement = element;
+ this._outlineStyle = element.style.outline;
+ element.style.outline = 'none';
+ L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
+ };
+
+ // @function restoreOutline()
+ // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
+ L.DomUtil.restoreOutline = function () {
+ if (!this._outlineElement) { return; }
+ this._outlineElement.style.outline = this._outlineStyle;
+ delete this._outlineElement;
+ delete this._outlineStyle;
+ L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
+ };
+})();
+
+
+
+/* @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));
+ * ```
+ */
+
+L.LatLng = function (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;
+ }
+};
+
+L.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 overriden by setting `maxMargin` to a small number.
+ equals: function (obj, maxMargin) {
+ if (!obj) { return false; }
+
+ obj = L.latLng(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(' +
+ L.Util.formatNum(this.lat, precision) + ', ' +
+ L.Util.formatNum(this.lng, precision) + ')';
+ },
+
+ // @method distanceTo(otherLatLng: LatLng): Number
+ // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
+ distanceTo: function (other) {
+ return L.CRS.Earth.distance(this, L.latLng(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 L.CRS.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 L.latLngBounds(
+ [this.lat - latAccuracy, this.lng - lngAccuracy],
+ [this.lat + latAccuracy, this.lng + lngAccuracy]);
+ },
+
+ clone: function () {
+ return new L.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.
+
+L.latLng = function (a, b, c) {
+ if (a instanceof L.LatLng) {
+ return a;
+ }
+ if (L.Util.isArray(a) && typeof a[0] !== 'object') {
+ if (a.length === 3) {
+ return new L.LatLng(a[0], a[1], a[2]);
+ }
+ if (a.length === 2) {
+ return new L.LatLng(a[0], a[1]);
+ }
+ return null;
+ }
+ if (a === undefined || a === null) {
+ return a;
+ }
+ if (typeof a === 'object' && 'lat' in a) {
+ return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
+ }
+ if (b === undefined) {
+ return null;
+ }
+ return new L.LatLng(a, b, c);
+};
+
+
+
+/*
+ * @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.
+ */
+
+L.LatLngBounds = function (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]);
+ }
+};
+
+L.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 L.LatLng) {
+ sw2 = obj;
+ ne2 = obj;
+
+ } else if (obj instanceof L.LatLngBounds) {
+ sw2 = obj._southWest;
+ ne2 = obj._northEast;
+
+ if (!sw2 || !ne2) { return this; }
+
+ } else {
+ return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this;
+ }
+
+ if (!sw && !ne) {
+ this._southWest = new L.LatLng(sw2.lat, sw2.lng);
+ this._northEast = new L.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 bigger bounds created by extending the current bounds by a given percentage in each direction.
+ 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 L.LatLngBounds(
+ new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
+ new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
+ },
+
+ // @method getCenter(): LatLng
+ // Returns the center point of the bounds.
+ getCenter: function () {
+ return new L.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 L.LatLng(this.getNorth(), this.getWest());
+ },
+
+ // @method getSouthEast(): LatLng
+ // Returns the south-east point of the bounds.
+ getSouthEast: function () {
+ return new L.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 L.LatLng || 'lat' in obj) {
+ obj = L.latLng(obj);
+ } else {
+ obj = L.latLngBounds(obj);
+ }
+
+ var sw = this._southWest,
+ ne = this._northEast,
+ sw2, ne2;
+
+ if (obj instanceof L.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 = L.latLngBounds(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 = L.latLngBounds(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): Boolean
+ // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds.
+ equals: function (bounds) {
+ if (!bounds) { return false; }
+
+ bounds = L.latLngBounds(bounds);
+
+ return this._southWest.equals(bounds.getSouthWest()) &&
+ this._northEast.equals(bounds.getNorthEast());
+ },
+
+ // @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).
+L.latLngBounds = function (a, b) {
+ if (a instanceof L.LatLngBounds) {
+ return a;
+ }
+ return new L.LatLngBounds(a, b);
+};
+
+
+
+/*
+ * @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:3395` and `Simple` CRS.
+ */
+
+L.Projection = {};
+
+L.Projection.LonLat = {
+ project: function (latlng) {
+ return new L.Point(latlng.lng, latlng.lat);
+ },
+
+ unproject: function (point) {
+ return new L.LatLng(point.y, point.x);
+ },
+
+ bounds: L.bounds([-180, -90], [180, 90])
+};
+
+
+
+/*
+ * @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.
+ */
+
+L.Projection.SphericalMercator = {
+
+ R: 6378137,
+ 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 L.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 L.LatLng(
+ (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
+ point.x * d / this.R);
+ },
+
+ bounds: (function () {
+ var d = 6378137 * Math.PI;
+ return L.bounds([-d, -d], [d, d]);
+ })()
+};
+
+
+
+/*
+ * @class CRS
+ * @aka L.CRS
+ * Abstract class 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.
+ */
+
+L.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 L.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.
+ // Only accepts actual `L.LatLng` instances, not arrays.
+ wrapLatLng: function (latlng) {
+ var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
+ lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
+ alt = latlng.alt;
+
+ return L.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 = L.latLng({lat: sw.lat - latShift, lng: sw.lng - lngShift}),
+ newNe = L.latLng({lat: ne.lat - latShift, lng: ne.lng - lngShift});
+
+ return new L.LatLngBounds(newSw, newNe);
+ }
+};
+
+
+
+/*
+ * @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.
+ */
+
+L.CRS.Simple = L.extend({}, L.CRS, {
+ projection: L.Projection.LonLat,
+ transformation: new L.Transformation(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
+});
+
+
+
+/*
+ * @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.
+ */
+
+L.CRS.Earth = L.extend({}, L.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,
+ a = Math.sin(lat1) * Math.sin(lat2) +
+ Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
+
+ return this.R * Math.acos(Math.min(a, 1));
+ }
+});
+
+
+
+/*
+ * @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.
+ */
+
+L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, {
+ code: 'EPSG:3857',
+ projection: L.Projection.SphericalMercator,
+
+ transformation: (function () {
+ var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R);
+ return new L.Transformation(scale, 0.5, -scale, 0.5);
+ }())
+});
+
+L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
+ code: 'EPSG:900913'
+});
+
+
+
+/*
+ * @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.
+ */
+
+L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, {
+ code: 'EPSG:4326',
+ projection: L.Projection.LonLat,
+ transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5)
+});
+
+
+
+/*
+ * @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
+ * });
+ * ```
+ *
+ */
+
+L.Map = L.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: L.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 = undefined
+ // Minimum zoom level of the map. Overrides any `minZoom` option set on map layers.
+ minZoom: undefined,
+
+ // @option maxZoom: Number = undefined
+ // Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers.
+ 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 = L.setOptions(this, options);
+
+ this._initContainer(id);
+ this._initLayout();
+
+ // hack for https://github.com/Leaflet/Leaflet/issues/1980
+ this._onResize = L.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(L.latLng(options.center), options.zoom, {reset: true});
+ }
+
+ this._handlers = [];
+ this._layers = {};
+ this._zoomBoundLayers = {};
+ this._sizeChanged = true;
+
+ this.callInitHooks();
+
+ // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
+ this._zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.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();
+ L.DomEvent.on(this._proxy, L.DomUtil.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(L.latLng(center), zoom, this.options.maxBounds);
+ options = options || {};
+
+ this._stop();
+
+ if (this._loaded && !options.reset && options !== true) {
+
+ if (options.animate !== undefined) {
+ options.zoom = L.extend({animate: options.animate}, options.zoom);
+ options.pan = L.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 || (L.Browser.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 || (L.Browser.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 L.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() : L.latLngBounds(bounds);
+
+ var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
+ paddingBR = L.point(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;
+
+ 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 = L.latLngBounds(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): this
+ // Pans the map by a given number of pixels (animated).
+ panBy: function (offset, options) {
+ offset = L.point(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 L.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) {
+ L.DomUtil.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 || !L.Browser.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 = L.latLng(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 = L.Util.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);
+
+ 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 = L.latLngBounds(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) {
+ this.options.minZoom = zoom;
+
+ if (this._loaded && 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) {
+ this.options.maxZoom = zoom;
+
+ if (this._loaded && (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, L.latLngBounds(bounds));
+
+ if (!center.equals(newCenter)) {
+ this.panTo(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 = L.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(L.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 = L.extend({
+ timeout: 10000,
+ watch: false
+ // setView: false
+ // maxZoom: