summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/Maps/resources
diff options
context:
space:
mode:
authorYaco <franco@reevo.org>2020-06-04 11:01:00 -0300
committerYaco <franco@reevo.org>2020-06-04 11:01:00 -0300
commitfc7369835258467bf97eb64f184b93691f9a9fd5 (patch)
treedaabd60089d2dd76d9f5fb416b005fbe159c799d /www/wiki/extensions/Maps/resources
first commit
Diffstat (limited to 'www/wiki/extensions/Maps/resources')
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/ext.maps.googlemaps3.js25
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/ext.sm.googlemaps3ajax.js48
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ProjectedOverlay.js139
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/README5
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ZipFile.complete.js2172
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js1855
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/README6
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/googleearth-compiled.js20
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/markerclusterer.js1637
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/markerwithlabel.css13
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/markerwithlabel.js568
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/img/blue-dot.pngbin0 -> 1274 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/GoogleMaps/jquery.googlemap.js900
-rw-r--r--www/wiki/extensions/Maps/resources/editor/css/jquery.miniColors.css70
-rw-r--r--www/wiki/extensions/Maps/resources/editor/css/mapeditor.css79
-rw-r--r--www/wiki/extensions/Maps/resources/editor/images/circle.gifbin0 -> 78 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/editor/images/gradient.pngbin0 -> 6548 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/editor/images/line.gifbin0 -> 1104 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/editor/images/rainbow.pngbin0 -> 1665 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/editor/images/trigger.pngbin0 -> 538 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/editor/js/README3
-rw-r--r--www/wiki/extensions/Maps/resources/editor/js/jquery.miniColors.js567
-rw-r--r--www/wiki/extensions/Maps/resources/editor/js/mapeditor.iefixes.js28
-rw-r--r--www/wiki/extensions/Maps/resources/editor/js/mapeditor.js919
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/cluster/m1.pngbin0 -> 3288 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/cluster/m2.pngbin0 -> 3707 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/cluster/m3.pngbin0 -> 5605 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/cluster/m4.pngbin0 -> 6515 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/cluster/m5.pngbin0 -> 7249 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/ext.maps.leaflet.js4
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/ext.sm.leafletajax.js44
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/jquery.leaflet.js434
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet-providers/leaflet-providers.js774
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.editable/Leaflet.Editable.js1945
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.editor.js15
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/.jshintrc12
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/Control.FullScreen.css4
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/Control.FullScreen.js164
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/LICENSE22
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/README.md68
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/bower.json30
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/icon-fullscreen-2x.pngbin0 -> 228 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/icon-fullscreen.pngbin0 -> 153 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/index.html48
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/package.json25
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.markercluster/MarkerCluster.css14
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet.markercluster/leaflet.markercluster.js3
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet/images/layers-2x.pngbin0 -> 1259 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet/images/layers.pngbin0 -> 696 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-icon-2x.pngbin0 -> 4230 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-icon.pngbin0 -> 1870 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-shadow.pngbin0 -> 618 bytes
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet/leaflet.css635
-rw-r--r--www/wiki/extensions/Maps/resources/leaflet/leaflet/leaflet.js5
-rw-r--r--www/wiki/extensions/Maps/resources/maps.common.js4
-rw-r--r--www/wiki/extensions/Maps/resources/maps.services.js86
-rw-r--r--www/wiki/extensions/Maps/resources/sm.common.js76
57 files changed, 13466 insertions, 0 deletions
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/ext.maps.googlemaps3.js b/www/wiki/extensions/Maps/resources/GoogleMaps/ext.maps.googlemaps3.js
new file mode 100644
index 00000000..ce5880c2
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/ext.maps.googlemaps3.js
@@ -0,0 +1,25 @@
+/**
+ * JavaScript for Google Maps v3 maps in the Maps extension.
+ * @see https://www.mediawiki.org/wiki/Extension:Maps
+ *
+ * @licence GNU GPL v2+
+ * @author Jeroen De Dauw <jeroendedauw at gmail dot com>
+ */
+(function( $, mw ) {
+
+ $( document ).ready( function() {
+
+ if ( typeof google === 'undefined' ) {
+ $( '.maps-googlemaps3' ).text( mw.msg( 'maps-googlemaps3-incompatbrowser' ) );
+ }
+ else {
+ $( '.maps-googlemaps3' ).each( function() {
+ var $this = $( this );
+ var map = $this.googlemaps( $.parseJSON( $this.find( 'div').text() ) );
+ window.maps.googlemapsList.push(map);
+ } );
+ }
+
+ } );
+
+})( window.jQuery, mediaWiki );
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/ext.sm.googlemaps3ajax.js b/www/wiki/extensions/Maps/resources/GoogleMaps/ext.sm.googlemaps3ajax.js
new file mode 100644
index 00000000..9f8c658a
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/ext.sm.googlemaps3ajax.js
@@ -0,0 +1,48 @@
+/**
+ * JavaScript for Google Maps v3 maps in the Semantic Maps extension.
+ * @see https://www.mediawiki.org/wiki/Extension:Semantic_Maps
+ *
+ * @licence GNU GPL v2+
+ * @author Peter Grassberger < petertheone@gmail.com >
+ */
+
+
+(function( $, sm ) {
+ var ajaxRequest = null;
+ var mapEvents = ['dragend', 'zoom_changed'];
+
+ $( document ).ready( function() {
+ // todo: find a way to remove setTimeout.
+ setTimeout( function() {
+ if( typeof google === 'undefined' ) {
+ return;
+ }
+ $( window.maps.googlemapsList ).each( function( index, map ) {
+ if( !map.options.ajaxquery || !map.options.ajaxcoordproperty ) {
+ return;
+ }
+ $( mapEvents ).each( function( index, event ) {
+ google.maps.event.addListener( map.map, event, function() {
+ var bounds = map.map.getBounds();
+ var query = sm.buildQueryString(
+ decodeURIComponent( map.options.ajaxquery.replace( /\+/g, ' ' ) ),
+ map.options.ajaxcoordproperty,
+ bounds.getNorthEast().lat(),
+ bounds.getNorthEast().lng(),
+ bounds.getSouthWest().lat(),
+ bounds.getSouthWest().lng()
+ );
+
+ if( ajaxRequest !== null ) {
+ ajaxRequest.abort();
+ }
+ ajaxRequest = sm.ajaxUpdateMarker( map, query, map.options.icon ).done( function() {
+ map.createMarkerCluster();
+ ajaxRequest = null;
+ } );
+ } );
+ } );
+ } );
+ }, 500 );
+ } );
+})( window.jQuery, window.sm );
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ProjectedOverlay.js b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ProjectedOverlay.js
new file mode 100644
index 00000000..4056c2c3
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ProjectedOverlay.js
@@ -0,0 +1,139 @@
+// Create an overlay on the map from a projected image - Maps v3...
+// Author. John D. Coryat 05/2009
+// USNaviguide LLC - http://www.usnaviguide.com
+// Thanks go to Mile Williams EInsert: http://econym.googlepages.com/einsert.js, Google's GOverlay Example and Bratliff's suggestion...
+// Opacity code from TPhoto: http://gmaps.tommangan.us/addtphoto.html
+// This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+//
+// Parameters:
+// map: This Map
+// imageUrl: URL of the image (Mandatory)
+// bounds: Bounds object of image destination (Mandatory)
+// Options:
+// addZoom: Added Zoom factor as a parameter to the imageUrl (include complete parameter, including separater like '?zoom='
+// percentOpacity: Default 50, percent opacity to use when the image is loaded 0-100.
+// id: Default imageUrl, ID of the div
+//
+
+function ProjectedOverlay(map, imageUrl, bounds, opts)
+{
+ google.maps.OverlayView.call(this);
+
+ this.map_ = map;
+ this.url_ = imageUrl ;
+ this.bounds_ = bounds ;
+ this.addZ_ = opts.addZoom || '' ; // Add the zoom to the image as a parameter
+ this.id_ = opts.id || this.url_ ; // Added to allow for multiple images
+ this.percentOpacity_ = opts.percentOpacity || 50 ;
+
+ this.setMap(map);
+}
+
+ProjectedOverlay.prototype = new google.maps.OverlayView();
+
+ProjectedOverlay.prototype.createElement = function()
+{
+ var panes = this.getPanes() ;
+ var div = this.div_ ;
+
+ if (!div)
+ {
+ div = this.div_ = document.createElement("div");
+ div.style.position = "absolute" ;
+ div.setAttribute('id',this.id_) ;
+ this.div_ = div ;
+ this.lastZoom_ = -1 ;
+ if( this.percentOpacity_ )
+ {
+ this.setOpacity(this.percentOpacity_) ;
+ }
+ panes.overlayLayer.appendChild(div);
+ }
+}
+
+// Remove the main DIV from the map pane
+
+ProjectedOverlay.prototype.remove = function()
+{
+ if (this.div_)
+ {
+ this.div_.parentNode.removeChild(this.div_);
+ this.div_ = null;
+ this.setMap(null);
+ }
+}
+
+// Redraw based on the current projection and zoom level...
+
+ProjectedOverlay.prototype.draw = function(firstTime)
+{
+ // Creates the element if it doesn't exist already.
+
+ this.createElement();
+
+ if (!this.div_)
+ {
+ return ;
+ }
+
+ var c1 = this.get('projection').fromLatLngToDivPixel(this.bounds_.getSouthWest());
+ var c2 = this.get('projection').fromLatLngToDivPixel(this.bounds_.getNorthEast());
+
+ if (!c1 || !c2) return;
+
+ // Now position our DIV based on the DIV coordinates of our bounds
+
+ this.div_.style.width = Math.abs(c2.x - c1.x) + "px";
+ this.div_.style.height = Math.abs(c2.y - c1.y) + "px";
+ this.div_.style.left = Math.min(c2.x, c1.x) + "px";
+ this.div_.style.top = Math.min(c2.y, c1.y) + "px";
+
+ // Do the rest only if the zoom has changed...
+
+ if ( this.lastZoom_ == this.map_.getZoom() )
+ {
+ return ;
+ }
+
+ this.lastZoom_ = this.map_.getZoom() ;
+
+ var url = this.url_ ;
+
+ if ( this.addZ_ )
+ {
+ url += this.addZ_ + this.map_.getZoom() ;
+ }
+
+ this.div_.innerHTML = '<img src="' + url + '" width=' + this.div_.style.width + ' height=' + this.div_.style.height + ' >' ;
+}
+
+ProjectedOverlay.prototype.setOpacity=function(opacity)
+{
+ if (opacity < 0)
+ {
+ opacity = 0 ;
+ }
+ if(opacity > 100)
+ {
+ opacity = 100 ;
+ }
+ var c = opacity/100 ;
+
+ if (typeof(this.div_.style.filter) =='string')
+ {
+ this.div_.style.filter = 'alpha(opacity:' + opacity + ')' ;
+ }
+ if (typeof(this.div_.style.KHTMLOpacity) == 'string' )
+ {
+ this.div_.style.KHTMLOpacity = c ;
+ }
+ if (typeof(this.div_.style.MozOpacity) == 'string')
+ {
+ this.div_.style.MozOpacity = c ;
+ }
+ if (typeof(this.div_.style.opacity) == 'string')
+ {
+ this.div_.style.opacity = c ;
+ }
+}
+
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/README b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/README
new file mode 100644
index 00000000..a82f556e
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/README
@@ -0,0 +1,5 @@
+geoxml3.js and ZipFile.complete.js has been fetched from the googlecode project : http://code.google.com/p/geoxml3/
+from the branch kmz (which seems to be the branch containing the most features)
+http://code.google.com/p/geoxml3/source/browse/#svn%2Fbranches%2Fkmz
+
+The latest version fetched when writing this was rev 79. \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ZipFile.complete.js b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ZipFile.complete.js
new file mode 100644
index 00000000..d49bb056
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/ZipFile.complete.js
@@ -0,0 +1,2172 @@
+// ZipFile.complete.js
+//
+// 2/17/2012
+//
+// =======================================================
+//
+
+// JSIO.core.js
+// ------------------------------------------------------------------
+//
+// core methods for Javascript IO.
+//
+// =======================================================
+//
+// Copyleft (c) 2010, Dino Chiesa via MS-PL
+// Copyleft (c) 2012, Brendan Byrd via GPL
+//
+// This work is licensed under the GPLv3.
+
+
+(function(){
+ if (typeof JSIO == "object"){
+ var e1 = new Error("JSIO is already defined");
+ e1.source = "JSIO.core.js";
+ throw e1;
+ }
+
+ JSIO = {};
+
+ JSIO.version = "2.0 2012Feb";
+
+ JSIO.throwError = function(msg, source, sub) {
+ var error = new Error("Error: " + msg);
+ error.source = (source || this._typename || 'JSIO') + (sub ? '.'+sub : '');
+ throw error;
+ }
+
+ // Format a number as hex. Quantities over 7ffffff will be displayed properly.
+ JSIO.decimalToHexString = function(number, digits) {
+ if (number < 0) {
+ number = 0xFFFFFFFF + number + 1;
+ }
+ var r1 = number.toString(16).toUpperCase();
+ if (digits) {
+ r1 = "00000000" + r1;
+ r1 = r1.substring(r1.length - digits);
+ }
+ return r1;
+ };
+
+ JSIO.FileType = {
+ Text : 0,
+ Binary : 1,
+ XML : 2,
+ Unknown : 3
+ };
+
+
+ JSIO.guessFileType = function(name) {
+ if (name == "makefile") { return JSIO.FileType.Text; }
+
+ var lastDot = name.lastIndexOf(".");
+ if (lastDot <= 0) { return JSIO.FileType.Unknown; }
+
+ var ext= name.substring(lastDot);
+ if (ext == ".zip") { return JSIO.FileType.Binary; }
+ if (ext == ".xlsx") { return JSIO.FileType.Binary; }
+ if (ext == ".docx") { return JSIO.FileType.Binary; }
+ if (ext == ".dll") { return JSIO.FileType.Binary; }
+ if (ext == ".obj") { return JSIO.FileType.Binary; }
+ if (ext == ".pdb") { return JSIO.FileType.Binary; }
+ if (ext == ".exe") { return JSIO.FileType.Binary; }
+ if (ext == ".kmz") { return JSIO.FileType.Binary; }
+
+ if (ext == ".xml") { return JSIO.FileType.XML; }
+ if (ext == ".xsl") { return JSIO.FileType.XML; }
+ if (ext == ".kml") { return JSIO.FileType.XML; }
+ if (ext == ".csproj") { return JSIO.FileType.XML; }
+ if (ext == ".vbproj") { return JSIO.FileType.XML; }
+ if (ext == ".shfbproj") { return JSIO.FileType.XML; }
+ if (ext == ".resx") { return JSIO.FileType.XML; }
+ if (ext == ".xslt") { return JSIO.FileType.XML; }
+
+ if (ext == ".sln") { return JSIO.FileType.Text; }
+ if (ext == ".htm") { return JSIO.FileType.Text; }
+ if (ext == ".html") { return JSIO.FileType.Text; }
+ if (ext == ".js") { return JSIO.FileType.Text; }
+ if (ext == ".vb") { return JSIO.FileType.Text; }
+ if (ext == ".txt") { return JSIO.FileType.Text; }
+ if (ext == ".rels") { return JSIO.FileType.Text; }
+ if (ext == ".css") { return JSIO.FileType.Text; }
+ if (ext == ".cs") { return JSIO.FileType.Text; }
+ if (ext == ".asp") { return JSIO.FileType.Text; }
+
+ return JSIO.FileType.Unknown;
+ };
+
+ JSIO.stringOfLength = function (charCode, length) {
+ var s3 = "";
+ for (var i = 0; i < length; i++) {
+ s3 += String.fromCharCode(charCode);
+ }
+ return s3;
+ };
+
+ JSIO.formatByteArray = function(b) {
+ var s1 = "0000 ";
+ var s2 = "";
+ for (var i = 0; i < b.length; i++) {
+ if (i !== 0 && i % 16 === 0) {
+ s1 += " " + s2 +"\n" + JSIO.decimalToHexString(i, 4) + " ";
+ s2 = "";
+ }
+ s1 += JSIO.decimalToHexString(b[i], 2) + " ";
+ if (b[i] >=32 && b[i] <= 126) {
+ s2 += String.fromCharCode(b[i]);
+ } else {
+ s2 += ".";
+ }
+ }
+ if (s2.length > 0) {
+ s1 += JSIO.stringOfLength(32, ((i%16>0)? ((16 - i%16) * 3) : 0) + 4) + s2;
+ }
+ return s1;
+ };
+
+ JSIO.htmlEscape = function(str) {
+ return str
+ .replace(new RegExp( "&", "g" ), "&amp;")
+ .replace(new RegExp( "<", "g" ), "&lt;")
+ .replace(new RegExp( ">", "g" ), "&gt;")
+ .replace(new RegExp( "\x13", "g" ), "<br/>")
+ .replace(new RegExp( "\x10", "g" ), "<br/>");
+ };
+
+ JSIO.massApply = function(func, funcThis, arr, needReturn) {
+ var arrayLimit = 99999; // Chrome has an apply/array limit of 99999; Firefox = 491519
+ if (arr.length < arrayLimit) return func.apply(funcThis, arr);
+ else {
+ var newThis = funcThis;
+ var offset = 0;
+ var end = 99999;
+
+ while (offset < arr.length) {
+ var arrSlice;
+ if (arr.subarray) arrSlice = arr.subarray(offset, end);
+ else if (arr.slice) arrSlice = arr.slice(offset, end);
+
+ if (needReturn) newThis += func.apply(newThis, arrSlice);
+ else func.apply(funcThis, arrSlice);
+
+ offset += arrayLimit;
+ end += arrayLimit;
+ end = Math.min(arr.length, end);
+ }
+ return newThis;
+ }
+ }
+
+})();
+
+/// JSIO.core.js ends
+
+
+// JSIO.BasicByteReaders.js
+// ------------------------------------------------------------------
+//
+// Part of the JSIO library. Adds a couple basic ByteReaders to JSIO.
+// ByteReaders are forward-only byte-wise readers. They read one byte at
+// a time from a source.
+//
+// =======================================================
+//
+// A ByteReader exposes an interface with these functions:
+//
+// readByte()
+// must return null when EOF is reached.
+//
+// readToEnd()
+// returns an array of all bytes read, to EOF
+//
+// beginReadToEnd(callback)
+// async version of the above
+//
+// readBytes(n)
+// returns an array of the next n bytes from the source
+//
+// beginReadBytes(n, callback)
+// async version of the above
+//
+// =======================================================
+//
+// Copyleft (c) 2010, Dino Chiesa via MS-PL
+// Copyleft (c) 2012, Brendan Byrd via GPL
+//
+// This work is licensed under the GPLv3.
+
+
+(function(){
+ var version = "2.0 2012Feb";
+
+ if (typeof JSIO !== "object") { JSIO = {}; }
+ if ((typeof JSIO.version !== "string")) {
+ JSIO.version = version;
+ }
+ else if ((JSIO.version.length < 3) ||
+ (JSIO.version.substring(0,3) !== "2.0")) {
+ JSIO.version += " " + version;
+ }
+
+ // =======================================================
+ // the base object, used as the prototype of all ByteReader objects.
+ var _byteReaderBase = function () {
+ this.position = 0;
+ // position must be incremented in .readByte() for all derived classes
+ };
+
+ _byteReaderBase.prototype._throwError = JSIO.throwError;
+
+ _byteReaderBase.prototype._limitCheck = function(len, startPos) {
+ var LOE = {
+ len: len,
+ pos: startPos,
+ end: startPos+len
+ };
+
+ if (len === 0) return {len:0, pos:0, end:0};
+ if (len < 0) this._throwError("Invalid read length");
+ if (!this.length) return {len:len, pos:this.position, end:len+this.position};
+ if (!startPos >= 0) LOE.pos = this.position;
+ if (this.length <= LOE.pos) this._throwError("EOF reached");
+
+ LOE.end = LOE.pos+len;
+ if (this.length < LOE.end) LOE.end = LOE.pos+(LOE.len = this.length-this.position);
+ return LOE;
+ }
+
+ JSIO.SeekOrigin = {
+ Begin : 0,
+ Current : 1,
+ End : 2,
+ SEEK_SET : 0,
+ SEEK_CUR : 1,
+ SEEK_END : 2
+ };
+
+ _byteReaderBase.prototype.seek = function(offset, origin) {
+ switch (origin) {
+ case JSIO.SeekOrigin.Begin:
+ if (offset == this.position) return this.position;
+ if (!this.length) {
+ if (offset < this.position) this._throwError('Uni-directional stream cannot seek backwards', null, 'seek');
+ else if (offset > this.position) return this.read(offset - this.position); // read will limit check
+ }
+ else {
+ if (this.length < offset) this._throwError('Cannot seek past reader length', null, 'seek');
+ this.position = offset;
+ }
+ break;
+ case JSIO.SeekOrigin.Current:
+ return this.seek(this.position + offset, JSIO.SeekOrigin.Begin);
+ break;
+ case JSIO.SeekOrigin.End:
+ if (!this.length) this._throwError('Uni-directional stream has no known end length for seek', null, 'seek');
+ return this.seek(this.length - 1 + offset, JSIO.SeekOrigin.Begin);
+ break;
+ default:
+ this._throwError('Invalid seek method', null, 'seek');
+ break;
+ }
+
+ return this.position;
+ };
+
+ _byteReaderBase.prototype.read = function(len, startPos) {
+ var LOE = this._limitCheck(len, startPos);
+ if (LOE.len === 0) return [];
+ if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin);
+
+ var bytesRead = [];
+
+ // Faster methods with an array or stream
+ if (this.array && this.array.subarray) bytesRead = this.array.subarray(LOE.pos, LOE.end);
+ else if (this.array && this.array.slice) bytesRead = this.array.slice(LOE.pos, LOE.end);
+ else if (this.stream) bytesRead = this.stream.read(LOE.len, LOE.pos);
+ else if (this.length) { // Random-access stream
+ for(var i=LOE.pos; i<LOE.end; i++) { bytesRead.push(this.readByteAt(i)); }
+ }
+ else { // Uni-directional stream
+ for(var i=LOE.pos; i<LOE.end; i++) {
+ var b = this.readByte();
+ if (b === null || b === undefined) break;
+ bytesRead.push(b);
+ }
+ }
+ this.position = LOE.end;
+ return bytesRead;
+ };
+
+ _byteReaderBase.prototype.beginRead = function(len, startPos, callback) {
+ var LOE = this._limitCheck(len, startPos);
+ if (LOE.len === 0) return setTimeout(function() { callback([]); }, 1);
+ if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin);
+
+ var bytesRead = [];
+ var thisReader = this;
+ var leftToRead = LOE.len;
+
+ var readBatchAsync = function() {
+ var c = 0;
+ var pos = thisReader.position;
+
+ // read a 32k batch
+ var l = (leftToRead >= 32768) ? 32768 : leftToRead;
+ var newBytes = thisReader.read(l);
+ JSIO.massApply(bytesRead.push, bytesRead, newBytes);
+ c += l;
+ leftToRead -= l;
+ if (newBytes.length < l) leftToRead = 0;
+
+ if (leftToRead>0) setTimeout(readBatchAsync, 1);
+ else callback(bytesRead);
+ };
+
+ // kickoff
+ setTimeout(readBatchAsync, 1); // always async, in ALL situations
+ return null;
+ };
+
+ _byteReaderBase.prototype.readToEnd = function() {
+ if (this.array && this.array.subarray) return this.array.subarray(this.position);
+ else if (this.array && this.array.slice) return this.array.slice(this.position);
+ else if (this.length) return this.read(this.length - this.position);
+ else return this.read(9000 * 9000); // over 9000
+ };
+
+ _byteReaderBase.prototype.beginReadToEnd = function(callback) {
+ if (this.array && this.array.subarray) setTimeout(function() { callback( this.array.subarray(this.position) ); }, 1);
+ else if (this.array && this.array.slice) setTimeout(function() { callback( this.array.slice(this.position) ); }, 1);
+ else if (this.length) return this.beginRead(this.length - this.position, this.position, callback);
+ else return this.beginRead(9000 * 9000, this.position, callback);
+ };
+
+ // Generic routines; one of these two MUST be overloaded (preferrably both)
+ _byteReaderBase.prototype.readByte = function(){
+ if (this.length && this.position >= this.length) return null; // EOF
+
+ var byte;
+ if (this.array) byte = this.array[this.position++];
+ else if (this.length) byte = this.readByteAt(this.position++);
+ else if (this.stream) byte = this.stream.read(1)[0];
+ else byte = this.read(1)[0];
+ return (byte === null || byte === undefined) ? null : byte;
+ };
+ _byteReaderBase.prototype.readByteAt = function(i) {
+ var pos = this.position; // no position changes on this one
+ if (i === null || i === undefined) i = this.position;
+
+ var byte;
+ if (this.array) byte = this.array[i];
+ else if (i === pos) byte = this.readByte();
+ else if (this.stream) byte = this.stream.read(1, i)[0];
+ else byte = this.read(1, i)[0];
+
+ this.position = pos;
+ return (byte === null || byte === undefined) ? null : byte;
+ }
+
+ _byteReaderBase.prototype.readBytes = _byteReaderBase.prototype.read;
+ _byteReaderBase.prototype.beginReadBytes = function(len, callback) { return this.beginRead(len, this.position, callback); };
+
+ _byteReaderBase.prototype.readNumber = function(len, startPos){
+ var LOE = this._limitCheck(len, startPos);
+ if (LOE.len === 0) LOE.len = 1;
+ if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin);
+
+ var result = 0;
+ var bytes = this.read(LOE.len, LOE.pos);
+ for (var i=bytes.length-1; i>=0; i--) {
+ // IE only supports 32-bit integer shifting
+ //result = result << 8 | bytes[i];
+ result = result*256 + bytes[i];
+ }
+ return result;
+ };
+
+ _byteReaderBase.prototype.readString = function(len, startPos){
+ var LOE = this._limitCheck(len, startPos);
+ if (LOE.len === 0) return '';
+ if (LOE.pos != this.position) this.seek(LOE.pos, JSIO.SeekOrigin.Begin);
+
+ var result = '';
+ var bytes = this.read(LOE.len, LOE.pos);
+ for(var i=0; i<bytes.length; i++){
+ result += String.fromCharCode(bytes[i]);
+ }
+ return result;
+ };
+
+ _byteReaderBase.prototype.readNullTerminatedString = function(startPos){
+ var pos = startPos || this.position;
+ if (this.length && this.length < pos) this._throwError('EOF reached', null, 'readNullTerminatedString');
+ if (pos != this.position) this.seek(pos, JSIO.SeekOrigin.Begin);
+
+ var slarge = "";
+ var s = "";
+ var c = 0;
+
+ // Faster method with an array
+ if (this.array && this.array.indexOf) {
+ var len = pos - this.array.indexOf(0, pos);
+ if (len > 0) return this.readString(len, pos);
+ }
+
+ var ch;
+ while(1) {
+ ch = String.fromCharCode(this.readByteAt(pos+c));
+ if (ch === null) break;
+
+ s += ch;
+ c++;
+ if(c >= 32768) {
+ slarge += s;
+ s = "";
+ pos += c;
+ this.position += c;
+ c = 0;
+ }
+ };
+ this.position = pos + c;
+ return slarge + s;
+ };
+
+ _byteReaderBase.prototype.beginReadNullTerminatedString = function(callback, startPos){
+ var pos = startPos || this.position;
+ if (this.length && this.length < pos) this._throwError('EOF reached', null, 'beginReadNullTerminatedString');
+
+ var slarge = "";
+ var s = "";
+ var thisBinStream = this;
+
+ var readBatchAsync = function() {
+ var c = 0;
+
+ var ch;
+ while(1) {
+ ch = String.fromCharCode(this.readByteAt(pos+c));
+ if (ch === null) break;
+
+ s += ch;
+ c++;
+ if(c >= 32768) {
+ slarge += s;
+ s = "";
+ pos += c;
+ this.position += c;
+ c = 0;
+ }
+ };
+
+ thisBinStream.position = pos + c;
+ if (ch!==null) setTimeout(readBatchAsync, 1);
+ else callback(slarge+s);
+ };
+
+ // Faster method with an array
+ if (this.array && this.array.indexOf) {
+ var len = pos - this.array.indexOf(0, pos);
+ if (len > 0) readBatchASync = function() { callback(thisBinStream.readString(len, pos)); };
+ }
+
+ // kickoff
+ setTimeout(readBatchAsync, 1); // always async, in ALL situations
+ return null;
+ };
+
+
+
+ JSIO._ByteReaderBase = _byteReaderBase;
+ // =======================================================
+
+
+
+
+ // =======================================================
+ // reads from an array of bytes.
+ // This basically wraps a readByte() fn onto array access.
+ var _arrayReader = function(array) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.ArrayReader', 'ctor');
+ this.position = 0;
+ this.array = array;
+ this.length = array.length;
+ this._typename = "JSIO.ArrayReader";
+ this._version = version;
+ return this;
+ };
+
+ _arrayReader.prototype = new JSIO._ByteReaderBase();
+
+ _arrayReader.prototype.readByte = function() {
+ if (this.position >= this.array.length) return null; // EOF
+ return this.array[this.position++];
+ };
+ _arrayReader.prototype.readByteAt = function(i) {
+ return this.array[i];
+ };
+
+ // =======================================================
+
+
+ // =======================================================
+ // reads bytes at a time from a defined segment of a stream.
+ var _streamSegmentReader = function(stream, offset, len) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.StreamSegmentReader', 'ctor');
+ if (!stream) this._throwError('You must pass a non-null stream', 'JSIO.StreamSegmentReader', 'ctor');
+
+ if (!(offset >= 1)) offset = 0;
+ if (!(len >= 1)) len = 0;
+
+ this.stream = stream;
+ this.array = null;
+ if (stream.array) {
+ var end = len ? offset + len : null;
+ if (stream.array.subarray) this.array = stream.array.subarray(offset, end);
+ else if (stream.array.slice) this.array = stream.array.slice(offset, end);
+ }
+ this.length = this.array ? this.array.length : (stream.length ? stream.length - offset : null);
+ this.offset = offset;
+ this.limit = len;
+ this.position = 0;
+ this._typeName = 'JSIO.StreamSegmentReader';
+ this._version = version;
+
+ if (this.array) {
+ this.readByte = _arrayReader.prototype.readByte;
+ this.readByteAt = _arrayReader.prototype.readByteAt;
+ }
+ return this;
+ };
+
+ _streamSegmentReader.prototype = new JSIO._ByteReaderBase();
+
+ _streamSegmentReader.prototype.readByte = function() {
+ if (this.limit && this.position >= this.limit) return null; // EOF
+ this.position++;
+ return this.stream.readByteAt(this.offset + this.position - 1);
+ };
+ _streamSegmentReader.prototype.readByteAt = function(i) {
+ if (this.limit && i >= this.limit) return null; // EOF
+ return this.stream.readByteAt(this.offset + i);
+ };
+
+ // =======================================================
+
+ JSIO.ArrayReader = _arrayReader;
+ JSIO.StreamReader = _streamSegmentReader;
+ JSIO.StreamSegmentReader = _streamSegmentReader;
+
+})();
+
+
+/// JSIO.BasicByteReaders.js ends
+
+// JSIO.BinaryUrlStream.js
+// ------------------------------------------------------------------
+//
+// a class that acts as a stream wrapper around binary files obtained from URLs.
+//
+// =======================================================
+//
+// Copyleft (c) 2008, Andy G.P. Na <nagoon97@naver.com> via an MIT-style license
+// Copyleft (c) 2012, Brendan Byrd via GPL
+//
+// This work is licensed under the GPLv3.
+
+(function(){
+ var version = "2.0 2012Feb";
+ var typename = "JSIO.BinaryUrlStream";
+
+ if ((typeof JSIO !== "object") ||
+ (typeof JSIO.version !== "string") ||
+ (JSIO.version.length < 3) ||
+ (JSIO.version.substring(0,3) !== "2.0"))
+ JSIO.throwError('This extension requires JSIO.core.js v2.0', typename);
+
+ if (typeof JSIO._ByteReaderBase !== "function")
+ JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
+
+ if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) {
+ var IEBinaryToArray_ByteStr_Script =
+ "<!-- IEBinaryToArray_ByteStr -->\r\n"+
+ "<script type='text/vbscript'>\r\n"+
+ "Function IEBinaryToArray_ByteStr(Binary)\r\n"+
+ " IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+
+ "End Function\r\n"+
+ "Function IEBinaryToArray_ByteAsc_Last(Binary)\r\n"+
+ " Dim lastIndex\r\n"+
+ " lastIndex = LenB(Binary)\r\n"+
+ " if lastIndex mod 2 Then\r\n"+
+ " IEBinaryToArray_ByteAsc_Last = AscB( MidB( Binary, lastIndex, 1 ) )\r\n"+
+ " Else\r\n"+
+ " IEBinaryToArray_ByteAsc_Last = -1\r\n"+
+ " End If\r\n"+
+ "End Function\r\n"+
+ "</script>\r\n";
+
+ // inject VBScript
+ document.write(IEBinaryToArray_ByteStr_Script);
+ }
+
+ JSIO.IEByteMapping = null;
+
+ var bus = function(url, callback) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.BinaryUrlStream', 'ctor');
+
+ this.callback = callback;
+ this.position = 0;
+ this.length = null;
+ this.readByte = JSIO.ArrayReader.prototype.readByte;
+ this.readByteAt = JSIO.ArrayReader.prototype.readByteAt;
+ this.req = null;
+ this._typename = typename;
+ this._version = version;
+ this.status = "-none-";
+
+ var _IeGetBinResource = function(fileURL){
+ var binStream = this;
+ // http://stackoverflow.com/questions/1919972/how-do-i-access-xhr-responsebody-for-binary-data-from-javascript-in-ie
+
+ // my helper to convert from responseBody to a byte array
+ var convertResponseBodyToArray = function (binary) {
+ var byteArray = new Array;
+
+ try {
+ // very fast; very little work involved
+ byteArray = new VBArray(binary).toArray();
+ }
+ catch(err) {
+ // use the BinaryToArray VBScript
+ if (!JSIO.IEByteMapping) {
+ JSIO.IEByteMapping = {};
+ for ( var i = 0; i < 256; i++ ) {
+ for ( var j = 0; j < 256; j++ ) {
+ JSIO.IEByteMapping[ String.fromCharCode( i + j * 256 ) ] = [ i, j ];
+ }
+ }
+ }
+ var rawBytes = IEBinaryToArray_ByteStr(binary);
+ var lastAsc = IEBinaryToArray_ByteAsc_Last(binary);
+
+ for ( var i = 0; i < rawBytes.length; i++ ) {
+ byteArray.push.apply(byteArray, JSIO.IEByteMapping[ rawBytes.substr(i,1) ]);
+ }
+ if (lastAsc >= 0) byteArray.push(lastAsc);
+ }
+
+ return byteArray;
+ };
+
+ this.req = (function() {
+ if (window.XMLHttpRequest) return new window.XMLHttpRequest();
+ else if (window.ActiveXObject) {
+ // the many versions of IE's XML fetchers
+ var AXOs = [
+ 'MSXML2.XMLHTTP.6.0',
+ 'MSXML2.XMLHTTP.5.0',
+ 'MSXML2.XMLHTTP.4.0',
+ 'MSXML2.XMLHTTP.3.0',
+ 'MSXML2.XMLHTTP',
+ 'Microsoft.XMLHTTP',
+ 'MSXML.XMLHTTP'
+ ];
+ for (var i = 0; i < AXOs.length; i++) {
+ try { return new ActiveXObject(AXOs[i]); }
+ catch(e) { continue; }
+ }
+ }
+ return null;
+ })();
+ this.req.open("GET", fileURL, true);
+ this.req.setRequestHeader("Accept-Charset", "x-user-defined");
+ this.req.onreadystatechange = function(event){
+ if (binStream.req.readyState == 4) {
+ binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText;
+ if (binStream.req.status == 200) {
+ binStream.array = convertResponseBodyToArray(binStream.req.responseBody);
+ binStream.length = binStream.array.length;
+ if (binStream.length < 0) this._throwError('Failed to load "'+ fileURL + '" after converting');
+
+ if (typeof binStream.callback == "function") binStream.callback(binStream);
+ }
+ else {
+ binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status);
+ }
+ }
+ };
+ this.req.send();
+ };
+
+ var _NormalGetBinResource = function(fileURL){
+ var binStream= this;
+ this.req = new XMLHttpRequest();
+ this.req.open('GET', fileURL, true);
+ this.req.onreadystatechange = function(aEvt) {
+ if (binStream.req.readyState == 4) {
+ binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText;
+ if(binStream.req.status == 200){
+ var fileContents = binStream.req.responseText;
+ binStream.length = fileContents.byteLength;
+ binStream.array = fileContents.split('');
+ for ( var i = 0; i < binStream.array.length; i++ ) {
+ binStream.array[i] = binStream.array[i].charCodeAt(0) & 0xff;
+ }
+
+ if (typeof binStream.callback == "function") binStream.callback(binStream);
+ }
+ else {
+ binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status);
+ }
+ }
+ };
+ //XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
+ this.req.overrideMimeType('text/plain; charset=x-user-defined');
+ this.req.send(null);
+ };
+
+ // http://stackoverflow.com/questions/327685/is-there-a-way-to-read-binary-data-into-javascript
+ var _ArrayBufferGetBinResource = function(fileURL){
+ var binStream= this;
+ this.req = new XMLHttpRequest();
+ this.req.open('GET', fileURL, true);
+ this.req.onreadystatechange = function(aEvt) {
+ if (binStream.req.readyState == 4) {
+ binStream.status = "Status: " + binStream.req.status + ' ' + binStream.req.statusText;
+ if(binStream.req.status == 200){
+ var fileContents = binStream.req.response;
+ binStream.length = fileContents.byteLength;
+ binStream.array = new Uint8Array(fileContents);
+
+ if (typeof binStream.callback == "function") binStream.callback(binStream);
+ }
+ else {
+ binStream._throwError('Failed to load "'+ fileURL + '": HTTP ' + binStream.status);
+ }
+ }
+ };
+ this.req.responseType = 'arraybuffer';
+ this.req.overrideMimeType('text/plain; charset=x-user-defined');
+ this.req.send(null);
+ };
+
+
+ if (typeof ArrayBuffer !== 'undefined') _ArrayBufferGetBinResource.apply(this, [url]);
+ else if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent)) _IeGetBinResource.apply(this, [url]);
+ else _NormalGetBinResource.apply(this, [url]);
+ };
+
+ bus.prototype = new JSIO._ByteReaderBase();
+
+ bus.prototype.readByte = function(){
+ var byte = this.readByteAt(this.position++);
+ return (byte === null || byte === undefined) ? null : byte;
+ };
+
+ JSIO.BinaryUrlStream = bus;
+
+})();
+
+/// JSIO.BinaryUrlStream.js ends
+
+// JSIO.TextDecoder.js
+// ------------------------------------------------------------------
+//
+// Part of the JSIO library. Adds text decoders, for UTF-8 and UTF-16,
+// and plain text.
+//
+// =======================================================
+//
+// Derived in part from work by notmasteryet.
+// http://www.codeproject.com/KB/scripting/Javascript_binaryenc.aspx
+
+// Copyleft (c) 2008, notmasteryet via an MIT-style license
+// Copyleft (c) 2010, Dino Chiesa via MS-PL
+// Copyleft (c) 2012, Brendan Byrd via GPL
+//
+// This work is licensed under the GPLv3.
+
+(function(){
+ var version = "2.0 2012Feb";
+ var typename = "JSIO.TextDecoder";
+
+ if ((typeof JSIO !== "object") ||
+ (typeof JSIO.version !== "string") ||
+ (JSIO.version.length < 3) ||
+ (JSIO.version.substring(0,3) !== "2.0"))
+ JSIO.throwError('This extension requires JSIO.core.js v2.0', typename);
+
+ if (typeof JSIO._ByteReaderBase !== "function")
+ JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
+
+ var _ansi = function(reader) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.ANSI', 'ctor');
+ this.byteReader = reader;
+ this.charWidth = 1;
+ this._version = version;
+ this._typename = typename + ".ANSI";
+ return this;
+ };
+
+ _ansi.prototype.readChar = function() {
+ var code = this.byteReader.readByte();
+ return (code < 0) ? null : String.fromCharCode(code);
+ };
+
+ _ansi.prototype.parseChar = function(code) {
+ return (code < 0) ? null : String.fromCharCode(code);
+ };
+
+ var _utf16 = function(reader) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.UTF16', 'ctor');
+ this.byteReader = reader;
+ this.charWidth = 2;
+ this.bomState = 0;
+ this._version = version;
+ this._typename = typename + ".UTF16";
+ return this;
+ };
+
+ _utf16.prototype.readChar = function() {
+ var b1 = this.byteReader.readByte();
+ if (b1 < 0) return null;
+ var b2 = this.byteReader.readByte();
+ if (b2 < 0) this._throwError('Incomplete UTF16 character', null, 'readChar');
+
+ if ((this.bomState === 0) && ((b1 + b2) == 509)) {
+ this.bomState = (b2 == 254) ? 1 : 2;
+
+ b1 = this.byteReader.readByte();
+ if (b1 < 0) return null;
+ b2 = this.byteReader.readByte();
+ if (b2 < 0) this._throwError('Incomplete UTF16 character', null, 'readChar');
+ }
+ else {
+ this.bomState = 1;
+ }
+ return this.parseChar(b1, b2);
+ };
+
+ _utf16.prototype.parseChar = function(b1, b2) {
+ return String.fromCharCode( this.bomState == 1 ? (b2 << 8 | b1) : (b1 << 8 | b2) );
+ }
+
+ /* RFC 3629 */
+ var _utf8 = function(reader) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.TextDecoder.UTF8', 'ctor');
+ this.byteReader = reader;
+ this.charWidth = null;
+ this.waitBom = true;
+ this.strict = false;
+ this.pendingChar = null;
+ this._version = version;
+ this._typename = typename + ".UTF8";
+ return this;
+ };
+
+ _utf8.prototype.readChar = function() {
+ var ch = null;
+ do {
+ if (this.pendingChar !== null) {
+ ch = this.pendingChar;
+ this.pendingChar = null;
+ }
+ else {
+ var b1 = this.byteReader.readByte();
+ if (b1 === null) return null;
+
+ if ((b1 & 0x80) === 0) ch = String.fromCharCode(b1);
+ else {
+ var currentPrefix = 0xC0;
+ var ttlBytes = 0;
+ do {
+ var mask = currentPrefix >> 1 | 0x80;
+ if((b1 & mask) == currentPrefix) break;
+ currentPrefix = currentPrefix >> 1 | 0x80;
+ } while(++ttlBytes < 5);
+
+ if (ttlBytes > 0) {
+ var code;
+ if (ttlBytes === 1) code = (b1 & 0x1F) << 6 | (this.byteReader.readByte() & 0x3F);
+ else {
+ code = code << 6*ttlBytes
+ var bytes = this.byteReader.read(ttlBytes);
+ for (var i = 0; i > ttlBytes; i++) {
+ var bi = bytes[i];
+ if ((bi & 0xC0) != 0x80) this._throwError('Invalid sequence character', null, 'readChar');
+ code = (code << 6) | (bi & 0x3F);
+ }
+ }
+
+ if (code <= 0xFFFF) {
+ ch = (code == 0xFEFF && this.waitBom) ? null : String.fromCharCode(code);
+ }
+ else {
+ var v = code - 0x10000;
+ var w1 = 0xD800 | ((v >> 10) & 0x3FF);
+ var w2 = 0xDC00 | (v & 0x3FF);
+ this.pendingChar = String.fromCharCode(w2);
+ ch = String.fromCharCode(w1);
+ }
+ }
+ else {
+ // a byte higher than 0x80.
+ if (this.strict) this._throwError('Invalid character', null, 'readChar');
+ // fall back to "super ascii" (eg IBM-437)
+ else ch = String.fromCharCode(b1);
+ }
+ }
+ }
+ this.waitBom = false;
+ } while(ch === null);
+ return ch;
+ };
+
+ JSIO.TextDecoder = {
+ Default : _ansi,
+ ANSI : _ansi,
+ UTF16 : _utf16,
+ UTF8 : _utf8
+ };
+
+})();
+
+
+/// JSIO.TextDecoder.js ends
+
+// JSIO.TextReader.js
+// ------------------------------------------------------------------
+//
+// A reader class that decodes text as it reads.
+//
+// =======================================================
+//
+// Methods:
+// readChar() = read 1 char
+// read(n) = read n chars
+// readLine() = read one line of data (to \n)
+// unreadChar(ch) = unread one char
+// readToEnd() = read all data in the reader;
+// return a string.
+// beginReadToEnd(cb) = asynchronously read all data.
+//
+// =======================================================
+//
+// Derived in part from work by notmasteryet.
+// http://www.codeproject.com/KB/scripting/Javascript_binaryenc.aspx
+//
+// Copyleft (c) 2008, notmasteryet via an MIT-style license
+// Copyleft (c) 2010, Dino Chiesa via MS-PL
+// Copyleft (c) 2012, Brendan Byrd via GPL
+//
+// This work is licensed under the GPLv3.
+
+
+(function(){
+ var version = "2.0 2012Feb";
+ var typename = "JSIO.TextReader";
+
+ if (typeof JSIO._ByteReaderBase !== "function")
+ JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
+
+ var tr = function(textDecoder) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor');
+ this.decoder = textDecoder;
+ this._version = version;
+ this._typename = typename;
+ this.unreads = [];
+ };
+
+ // read one char
+ tr.prototype.readChar = function() {
+ return (this.unreads.length > 0) ? this.unreads.pop() : this.decoder.readChar();
+ };
+
+ // read a length of data
+ tr.prototype.read = function(n) {
+ // ANSI makes this easy
+ if (this.decoder.charWidth === 1) return JSIO.massApply(String.fromCharCode, new String, this.decoder.byteReader.read(n), true);
+
+ var s = "";
+ for (vari=0; i<n; i++) {
+ var ch = this.readChar();
+ if (ch !== null) s+= ch;
+ else break;
+ }
+ return s;
+ };
+
+ tr.prototype.unreadChar = function(ch) {
+ this.unreads.push(ch);
+ };
+
+ tr.prototype.readToEnd = function() {
+ // ANSI makes this easy
+ if (this.decoder.charWidth === 1) return JSIO.massApply(String.fromCharCode, new String, this.decoder.byteReader.readToEnd(n), true);
+
+ var slarge = "";
+ var s = "";
+ var c = 0;
+ var ch = this.readChar();
+ while(ch !== null) {
+ s += ch;
+ c++;
+ if(c >= 32768) {
+ slarge += s;
+ s = "";
+ c = 0;
+ }
+ ch = this.readChar();
+ }
+ return slarge + s;
+ };
+
+ tr.prototype.beginReadToEnd = function(callback) {
+ // ANSI makes this easy
+ if (this.decoder.charWidth === 1) {
+ this.decoder.byteReader.beginReadToEnd(function (bytes) {
+ callback( JSIO.massApply(String.fromCharCode, new String, bytes, true) );
+ });
+ return null;
+ }
+
+ var slarge = "";
+ var s = "";
+ var txtrdr = this;
+
+ var readBatchAsync = function() {
+ var c = 0;
+ var ch = txtrdr.readChar();
+ while(ch !== null) {
+ s += ch;c++;
+ if(c >= 32768) {
+ slarge += s;
+ s = "";
+ break;
+ }
+ ch = txtrdr.readChar();
+ }
+ if (ch!==null){
+ setTimeout(readBatchAsync, 1);
+ }
+ else {
+ callback(slarge+s);
+ }
+ };
+
+ // kickoff
+ setTimeout(readBatchAsync, 1); // always async, in ALL situations
+ return null;
+ };
+
+ tr.prototype.readLine = function() {
+ var s = "";
+ var ch = this.readChar();
+ if (ch === null) return null;
+
+ while(ch != "\r" && ch != "\n") {
+ s += ch;
+ ch = this.readChar();
+ if (ch === null) return s;
+ }
+ if(ch == "\r") {
+ ch = this.readChar();
+ if(ch !== null && ch != "\n"){
+ this.unreadChar(ch);
+ }
+ }
+ return s;
+ };
+
+ JSIO.TextReader = tr;
+
+})();
+
+
+/// JSIO.TextReader.js ends
+
+// JSIO.Crc32.js
+//
+// Part of the JSIO library. This adds an CRC32-calculating
+// ByteReader to JSIO.
+//
+// =======================================================
+//
+// A ByteReader exposes an interface with these functions:
+//
+// readByte()
+// must return null when EOF is reached.
+//
+// readToEnd()
+// returns an array of all bytes read, to EOF
+//
+// beginReadToEnd(callback)
+// async version of the above
+//
+// readBytes(n)
+// returns an array of all n bytes read from the source
+//
+// beginReadBytes(n, callback)
+// async version of the above
+//
+// =======================================================
+//
+// Copyleft (c) 2010, Dino Chiesa via MS-PL
+// Copyleft (c) 2012, Brendan Byrd via GPL
+//
+// This work is licensed under the GPLv3.
+
+(function(){
+ var version = "2.0 2012Feb";
+ var typename = "JSIO.Crc32";
+
+ if (typeof JSIO._ByteReaderBase !== "function")
+ JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
+
+ JSIO.crc32Table = null;
+ JSIO.crc32Polynomial = 0xEDB88320;
+
+ var crc32TableCalc = function () {
+ // do this once only, for all instances
+ if (JSIO.crc32Table) return;
+ JSIO.crc32Table = new Array(256);
+ for (var i = 0; i < 256; i++) {
+ var c=i;
+ for (var k = 0; k < 8; k++) {
+ if ((c & 1) == 1) c = JSIO.crc32Polynomial ^ (c>>>1);
+ else c >>>= 1;
+ }
+ JSIO.crc32Table[i] = c;
+ }
+ };
+
+ JSIO.computeCrc32 = function(str) {
+ crc32TableCalc(); // once
+ var c = 0xFFFFFFFF;
+ var sL = str.length;
+ if (typeof str == "object") {
+ for (var n1=0; n1<sL; n1++) {
+ c = JSIO.crc32Table[(c&0xff) ^ str[n1]] ^ (c>>>8);
+ }
+ } else {
+ for (var n2=0; n2<sL; n2++) {
+ c = JSIO.crc32Table[(c&0xff) ^ str.charCodeAt(n2)] ^ (c>>>8);
+ }
+ }
+ c ^= 0xFFFFFFFF;
+ if (c < 0) c += 0xFFFFFFFF+1;
+ return c;
+ };
+
+ // =======================================================
+ var _crc32 = function() {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor');
+ crc32TableCalc(); // once
+ this._typename = typename;
+ this._version = version;
+ this._runningCrc32 = 0xFFFFFFFF;
+ };
+
+ _crc32.prototype.slurpByte = function(b) {
+ var r = this._runningCrc32;
+ this._runningCrc32 = r>>>8 ^ JSIO.crc32Table[b ^ (r & 0x000000FF)];
+ };
+
+ _crc32.prototype.result = function() {
+ var c = this._runningCrc32 ^ 0xFFFFFFFF;
+ if (c < 0) c += 0xFFFFFFFF+1;
+ return c;
+ };
+ // =======================================================
+
+
+
+ var _crc32CalculatingReader = function(reader) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', 'JSIO.Crc32Reader', 'ctor');
+ this._byteReader = reader;
+ this._typename = "JSIO.Crc32Reader";
+ this._version = version;
+ this._crc32 = new JSIO.Crc32();
+ };
+
+ _crc32CalculatingReader.prototype = new JSIO._ByteReaderBase();
+
+ _crc32CalculatingReader.prototype.readByte = function() {
+ var b = this._byteReader.readByte();
+ if (b !== null) this._crc32.slurpByte(b);
+ this.position++;
+ return b;
+ };
+
+ _crc32CalculatingReader.prototype.read = function(len) {
+ if (len === 0) return [];
+ var bytes = this._byteReader.read(len);
+ len = bytes.length;
+
+ var tbl = JSIO.crc32Table;
+ var r = this._crc32._runningCrc32;
+ var t;
+ for (var i = 0; i < len; i++) {
+ t = tbl[ bytes[i] ^ (r & 0x000000FF) ];
+ r = (r >>> 8) ^ t;
+ }
+ this._crc32._runningCrc32 = r;
+
+ this.position += len;
+ return bytes;
+ }
+
+ _crc32CalculatingReader.prototype.crc32 = function() {
+ return this._crc32.result();
+ };
+
+ JSIO.Crc32 = _crc32;
+ JSIO.Crc32Reader = _crc32CalculatingReader;
+
+})();
+
+/// JSIO.CRC32.js ends
+// JSIO.InflatingReader.js
+// ------------------------------------------------------------------
+//
+// Part of the JSIO library. This adds an Inflating ByteReader to
+// JSIO.
+//
+// =======================================================
+//
+// A ByteReader exposes an interface with these functions:
+//
+// readByte()
+// must return null when EOF is reached.
+//
+// readToEnd()
+// returns an array of all bytes read, to EOF
+//
+// beginReadToEnd(callback)
+// async version of the above
+//
+// readBytes(n)
+// returns an array of all n bytes read from the source
+//
+// beginReadBytes(n, callback)
+// async version of the above
+//
+// =======================================================
+//
+// Derived in part from work by notmasteryet.
+// http://www.codeproject.com/KB/scripting/Javascript_binaryenc.aspx
+//
+// Copyleft (c) 2008, notmasteryet via an MIT-style license
+// Copyleft (c) 2010, Dino Chiesa via MS-PL
+// Copyleft (c) 2012, Brendan Byrd via GPL
+//
+// This work is licensed under the GPLv3.
+
+
+(function(){
+ var version = "2.0 2012Feb";
+ var typename = "JSIO.InflatingReader";
+
+ if (typeof JSIO._ByteReaderBase !== "function")
+ JSIO.throwError('This extension requires JSIO.BasicByteReaders.js', typename);
+
+ // =======================================================
+ // _InternalBitReader is used internally in the InflatingReader class.
+ //
+
+ JSIO.bitShiftTable = null;
+
+ var bitShiftTableCalc = function () {
+ // do this once only, for all instances
+ if (JSIO.bitShiftTable) return;
+
+ var bits = 8;
+ JSIO.bitShiftTable = {
+ LSB: new Array(bits),
+ MSB: new Array(bits)
+ }
+ for (var bp = 0; bp < bits; bp++) {
+ var lim = bits - bp;
+ JSIO.bitShiftTable.LSB[bp] = new Array(lim);
+ JSIO.bitShiftTable.MSB[bp] = new Array(lim);
+
+ var maskLSB = 1 << bp;
+ var maskMSB = 1 << lim-1;
+ for (var len = 1; len <= lim; len++) {
+ JSIO.bitShiftTable.LSB[bp][len-1] = maskLSB;
+ JSIO.bitShiftTable.MSB[bp][len-1] = maskMSB;
+ maskLSB |= 1 << bp+len;
+ maskMSB |= 1 << lim-len-1;
+ }
+ }
+ };
+
+ var _InternalBitReader = function(reader) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename + '._InternalBitReader.ctor');
+ this.bitsLength = 0;
+ this.bits = 0;
+ this.byteReader = reader;
+ this._typeName = typename + "._InternalBitReader";
+ this._version = version;
+ bitShiftTableCalc();
+ };
+
+ _InternalBitReader.prototype._throwError = JSIO.throwError;
+
+ _InternalBitReader.prototype.readBit = function() {
+ if (this.bitsLength === 0) {
+ var nextByte = this.byteReader.readByte();
+ if (nextByte === null) this._throwError('Unexpected end of stream', null, 'readBit');
+ this.bits = nextByte;
+ this.bitsLength = 8;
+ }
+
+ var bit = (this.bits & 1 << 8-this.bitsLength) !== 0;
+ this.bitsLength--;
+ return bit;
+ };
+
+ _InternalBitReader.prototype.align = function() { this.bitsLength = 0; };
+
+ _InternalBitReader.prototype.readBits = function(len, type) {
+ var data = 0;
+ type = type || 'LSB';
+ var tbl = JSIO.bitShiftTable[type];
+ var dl = 0;
+ while (len > 0) {
+ if (this.bitsLength === 0) {
+ var nextByte = this.byteReader.readByte();
+ if (nextByte === null) this._throwError('Unexpected end of stream', null, 'read'+type);
+ this.bits = nextByte;
+ this.bitsLength = 8;
+ }
+
+ var maskLen = (this.bitsLength >= len) ? len : this.bitsLength;
+ var bitsPos = 8-this.bitsLength;
+ data |= (this.bits & tbl[bitsPos][maskLen-1]) >>> bitsPos << dl;
+
+ dl += maskLen;
+ len -= maskLen;
+ this.bitsLength -= maskLen;
+ };
+ return data;
+ };
+
+ _InternalBitReader.prototype.readLSB = function(len) { return this.readBits(len, 'LSB'); }
+ _InternalBitReader.prototype.readMSB = function(len) { return this.readBits(len, 'MSB'); }
+
+ //
+ // =======================================================
+
+
+ /* inflating ByteReader - RFC 1951 */
+ var _inflatingReader = function(reader) {
+ if (! (this instanceof arguments.callee) ) this._throwError('You must use new to instantiate this class', typename, 'ctor');
+ this._byteReader = reader;
+ this._bitReader = new _InternalBitReader(reader);
+ this._buffer = [];
+ this._bufferPosition = 0;
+ this._state = 0;
+ this._blockFinal = false;
+ this._typeName = typename;
+ this._version = version;
+ return this;
+ };
+
+
+ // shared fns and variables
+
+ var staticCodes = null;
+ var staticDistances = null;
+
+ var clenMap = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15];
+
+ var buildCodes = function(lengths){
+ var i=0;
+ var codes = new Array(lengths.length);
+ var maxBits = lengths[0];
+ for (i=1; i<lengths.length; i++) {
+ if (maxBits < lengths[i]) maxBits = lengths[i];
+ }
+
+ var bitLengthsCount = new Array(maxBits + 1);
+ for (i=0; i<=maxBits; i++) bitLengthsCount[i]=0;
+
+ for (i=0; i<lengths.length; i++) {
+ ++bitLengthsCount[lengths[i]];
+ }
+
+ var nextCode = new Array(maxBits + 1);
+ var code = 0;
+ bitLengthsCount[0] = 0;
+ for (var bits=1; bits<=maxBits; bits++) {
+ code = (code + bitLengthsCount[bits - 1]) << 1;
+ nextCode[bits] = code;
+ }
+
+ for (i=0; i<codes.length; i++) {
+ var len = lengths[i];
+ if (len !== 0) {
+ codes[i] = nextCode[len];
+ nextCode[len]++;
+ }
+ }
+ return codes;
+ };
+
+ var buildTree = function(codes, lengths){
+ var nonEmptyCodes = [];
+ for(var i=0; i<codes.length; ++i) {
+ if(lengths[i] > 0) {
+ var code = {};
+ code.bits = codes[i];
+ code.length = lengths[i];
+ code.index = i;
+ nonEmptyCodes.push(code);
+ }
+ }
+ return buildTreeBranch(nonEmptyCodes, 0, 0);
+ };
+
+
+ var buildTreeBranch = function(codes, prefix, prefixLength){
+ if (codes.length === 0) return null;
+
+ var zeros = [];
+ var ones = [];
+ var branch = {};
+ branch.isLeaf = false;
+ for(var i=0; i<codes.length; ++i) {
+ if (codes[i].length == prefixLength && codes[i].bits == prefix) {
+ branch.isLeaf = true;
+ branch.index = codes[i].index;
+ break;
+ }
+ else {
+ var nextBit = ((codes[i].bits >> (codes[i].length - prefixLength - 1)) & 1) > 0;
+ if (nextBit) ones.push(codes[i]);
+ else zeros.push(codes[i]);
+ }
+ }
+ if(!branch.isLeaf) {
+ branch.zero = buildTreeBranch(zeros, (prefix << 1), prefixLength + 1);
+ branch.one = buildTreeBranch(ones, (prefix << 1) | 1, prefixLength + 1);
+ }
+ return branch;
+ };
+
+
+ var encodedLengthStart = [3,4,5,6,7,8,9,10,
+ 11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,
+ 115,131,163,195,227,258];
+
+ var encodedLengthAdditionalBits = [0,0,0,0,0,0,0,0,
+ 1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0];
+
+ var encodedDistanceStart = [1,2,3,4, 5,7,9, 13,17,25, 33,49,65,
+ 97,129,193,257,385,513,769,1025,1537,2049,
+ 3073,4097,6145,8193,12289,16385,24577];
+
+ var encodedDistanceAdditionalBits = [0,0,0,0,1,1,2,2,3,3,4,4,
+ 5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];
+
+
+ var readDynamicTrees = function(bitReader){
+ var hlit = bitReader.readLSB(5) + 257;
+ var hdist = bitReader.readLSB(5) + 1;
+ var hclen = bitReader.readLSB(4) + 4;
+ var clen = new Array(19);
+ var i;
+ for (i=0; i<clen.length; i++) { clen[i] = 0; }
+ for (i=0; i<hclen; i++) { clen[clenMap[i]] = bitReader.readLSB(3); }
+
+ var clenCodes = buildCodes(clen);
+ var clenTree = buildTree(clenCodes, clen);
+
+ var lengthsSequence = [];
+ while(lengthsSequence.length < hlit + hdist) {
+ var p = clenTree;
+ while(!p.isLeaf) { p = bitReader.readBit() ? p.one : p.zero; }
+
+ var code = p.index;
+ if (code <= 15) lengthsSequence.push(code);
+ else if (code == 16) {
+ // TODO: replace with faster repeat algorythm
+ var repeat = bitReader.readLSB(2) + 3;
+ for(var q=0; q<repeat; ++q){
+ lengthsSequence.push(lengthsSequence[lengthsSequence.length - 1]);
+ }
+ }
+ else if (code == 17) {
+ var repeat1 = bitReader.readLSB(3) + 3;
+ for(var q1=0; q1<repeat1; ++q1) {
+ lengthsSequence.push(0);
+ }
+ }
+ else if (code == 18) {
+ var repeat2 = bitReader.readLSB(7) + 11;
+ for(var q2=0; q2<repeat2; ++q2){
+ lengthsSequence.push(0);
+ }
+ }
+ }
+
+ var codesLengths = lengthsSequence.slice(0, hlit);
+ var codes = buildCodes(codesLengths);
+ var distancesLengths = lengthsSequence.slice(hlit, hlit + hdist);
+ var distances = buildCodes(distancesLengths);
+
+ return {
+ codesTree : buildTree(codes, codesLengths),
+ distancesTree : buildTree(distances, distancesLengths)
+ };
+ };
+
+
+ _inflatingReader.prototype = new JSIO._ByteReaderBase();
+
+
+ // internal instance fns
+ _inflatingReader.prototype._decodeItem = function() {
+ if (this._state == 2) return null; // end-of-blocks
+
+ var item;
+ if(this._state === 0) {
+ this._blockFinal = this._bitReader.readBit();
+ var blockType = this._bitReader.readLSB(2);
+ switch(blockType) {
+ case 0:
+ this._bitReader.align();
+ var len = this._bitReader.readLSB(16); // low-byte first, as opposed to readNumber's HBF
+ var nlen = this._bitReader.readLSB(16);
+ if ((len & ~nlen) != len) this._throwError('Invalid block type 0 length', null, '_decodeItem');
+
+ item = {};
+ item.itemType = 0;
+ item.array = this._bitReader.byteReader.read(len);
+ if (item.array.length < len) this._throwError('Incomplete block', null, '_decodeItem');
+ if (this._blockFinal) this._state = 2;
+ return item;
+ case 1:
+ this._codesTree = staticCodes;
+ this._distancesTree = staticDistances;
+ this._state = 1;
+ break;
+ case 2:
+ var dTrees = readDynamicTrees(this._bitReader);
+ this._codesTree = dTrees.codesTree;
+ this._distancesTree = dTrees.distancesTree;
+ this._state = 1;
+ break;
+ default:
+ this._throwError('Invalid block type ('+ blockType +')', null, '_decodeItem');
+ }
+ }
+
+ item = {};
+
+ var p = this._codesTree;
+ while (!p.isLeaf) { p = this._bitReader.readBit() ? p.one : p.zero; }
+ if(p.index < 256) {
+ item.itemType = 2;
+ item.symbol = p.index;
+ } else if(p.index > 256) {
+ var lengthCode = p.index;
+ if(lengthCode > 285) this._throwError('Invalid length code', null, '_decodeItem');
+
+ var length = encodedLengthStart[lengthCode - 257];
+ if(encodedLengthAdditionalBits[lengthCode - 257] > 0) {
+ length += this._bitReader.readLSB(encodedLengthAdditionalBits[lengthCode - 257]);
+ }
+
+ p = this._distancesTree;
+ while (!p.isLeaf) { p = this._bitReader.readBit() ? p.one : p.zero; }
+
+ var distanceCode = p.index;
+ var distance = encodedDistanceStart[distanceCode];
+ if (encodedDistanceAdditionalBits[distanceCode] > 0)
+ distance += this._bitReader.readLSB(encodedDistanceAdditionalBits[distanceCode]);
+
+ item.itemType = 3;
+ item.distance = distance;
+ item.length = length;
+ } else {
+ item.itemType = 1;
+ this._state = this._blockFinal ? 2 : 0; // EOB
+ }
+ return item;
+ };
+
+
+
+ // public instance functions
+
+ _inflatingReader.prototype.readByte = function() {
+ var byte = this.read(1)[0];
+ return (byte === null || byte === undefined) ? null : byte;
+ };
+
+ _inflatingReader.prototype.read = function(len) {
+ var b = this._buffer; // (since we use this so much...)
+
+ // Keep reading until we get to the right length
+ while (this._bufferPosition+len > b.length) {
+ var item = this._decodeItem();
+ if (item === null) { // EOF
+ len = b.length - this._bufferPosition;
+ break;
+ }
+ switch(item.itemType) {
+ case 0:
+ JSIO.massApply(b.push, b, item.array);
+ break;
+ case 2:
+ b.push(item.symbol);
+ break;
+ case 3:
+ var j = b.length - item.distance;
+ if (item.distance >= item.length)
+ JSIO.massApply(b.push, b, b.slice(j, j+item.length));
+ // sometimes DEFLATE tries some trickery with "look-ahead" compression
+ else {
+ // this is basically just a repetition of the same string, plus some possible cutoff
+ var count = parseInt(item.length / item.distance);
+ var repArr = b.slice(j);
+ // http://stackoverflow.com/questions/202605/repeat-string-javascript/5450113#5450113
+ while (count > 0) {
+ if (count & 1) JSIO.massApply( b.push, b, repArr);
+ if (count >>= 1) JSIO.massApply(repArr.push, repArr, repArr);
+ }
+ // add any remaining cutoff
+ var r;
+ if (r = item.length % item.distance)
+ JSIO.massApply(b.push, b, b.slice(j, j+r));
+ }
+ break;
+ }
+ }
+ var bytes = b.slice(this._bufferPosition, this._bufferPosition+len);
+ this._bufferPosition += len;
+ this.position += len;
+
+ if (this._bufferPosition > 0xC000) {
+ var shift = b.length - 0x8000;
+ if (shift > this._bufferPosition) shift = this._bufferPosition;
+ b.splice(0, shift);
+ this._bufferPosition -= shift;
+ }
+
+ return bytes;
+ };
+
+ // initialization routine - once per type
+ (function(){
+
+ var codes = new Array(288);
+ var codesLengths = new Array(288);
+ var i=0;
+ for ( i = 0; i <= 143; i++) {
+ codes[i] = 0x0030 + i;
+ codesLengths[i] = 8;
+ }
+ for ( i = 144; i <= 255; i++) {
+ codes[i] = 0x0190 + i - 144;
+ codesLengths[i] = 9;
+ }
+ for ( i = 256; i <= 279; i++) {
+ codes[i] = 0x0000 + i - 256;
+ codesLengths[i] = 7;
+ }
+ for ( i = 280; i <= 287; i++) {
+ codes[i] = 0x00C0 + i - 280;
+ codesLengths[i] = 8;
+ }
+ staticCodes = buildTree(codes, codesLengths);
+
+ var distances = new Array(32);
+ var distancesLengths = new Array(32);
+ for ( i = 0; i <= 31; i++) {
+ distances[i] = i;
+ distancesLengths[i] = 5;
+ }
+ staticDistances = buildTree(distances, distancesLengths);
+ })();
+
+
+ JSIO.InflatingReader = _inflatingReader;
+
+})();
+
+
+/// JSIO.InflatingReader.js ends
+
+// Zipfile.js
+// ------------------------------------------------------------------
+//
+// A class that reads Zip files.
+// Depends on the JSIO library functions.
+//
+// =======================================================
+//
+// Copyleft (c) 2010, Dino Chiesa via MS-PL
+// Copyleft (c) 2012, Brendan Byrd via GPL
+//
+// This work is licensed under the GPLv3.
+
+(function(){
+ var version = "2.0 2012Feb";
+ var typename = "Zipfile";
+
+ if (typeof JSIO.BinaryUrlStream !== "function") JSIO.throwError('This extension requires JSIO.BinaryUrlStream.js v2.0', typename);
+ if (typeof JSIO.TextDecoder !== "object") JSIO.throwError('This extension requires JSIO.TextDecoder.js v2.0', typename);
+ if (typeof JSIO.TextReader !== "function") JSIO.throwError('This extension requires JSIO.TextReader.js v2.0', typename);
+ if (typeof JSIO.Crc32 !== "function") JSIO.throwError('This extension requires JSIO.Crc32.js v2.0', typename);
+ if (typeof JSIO.InflatingReader !== "function") JSIO.throwError('This extension requires JSIO.InflatingReader.js v2.0', typename);
+
+ // =======================================================
+ function ZipEntry(zip) {
+ this.zipfile = zip;
+ this._typename = "ZipEntry";
+ this._version = version;
+ this._crcCalculator = null;
+ }
+
+ ZipEntry.prototype._throwError = JSIO.throwError;
+
+ // return byte array or string
+ ZipEntry.prototype.extract = function(callback, asString) {
+ this.contentType = JSIO.guessFileType(this.name);
+ asString = asString || ( this.contentType == JSIO.FileType.Text ||
+ this.contentType == JSIO.FileType.XML);
+ var thisEntry = this;
+
+ if (this.compressionMethod !== 0 && this.compressionMethod != 8)
+ this._throwError('Unsupported compression method: ' + this.compressionMethod, null, 'extract');
+
+ var reader = (asString) ? this.openTextReader(thisEntry.utf8 ? JSIO.TextDecoder.UTF8 : JSIO.TextDecoder.ANSI) : this.openBinaryReader();
+
+ // diagnostic purpose only; tag the reader with the entry name
+ reader.zipEntryName = thisEntry.name;
+
+ if (typeof callback != "function") {
+ // synchronous
+ var result = reader.readToEnd();
+ this.verifyCrc32();
+ return result;
+ }
+
+ // asynchronous
+ reader.beginReadToEnd(function(result){
+ try {
+ thisEntry.verifyCrc32();
+ callback(thisEntry, result);
+ }
+ catch (exc1) {
+ callback(thisEntry, exc1);
+ }
+ });
+ return null;
+ };
+
+
+ // open a ByteReader on the entry, which will read binary
+ // content from the compressed stream.
+ ZipEntry.prototype.openBinaryReader = function() {
+ var reader =
+ new JSIO.StreamSegmentReader(this.zipfile.binaryStream,
+ this.offset + this.lengthOfHeader,
+ this.compressedSize);
+ if (this.compressionMethod === 0) {
+ this._crcCalculator = new JSIO.Crc32Reader(reader);
+ }
+ else {
+ var inflator = new JSIO.InflatingReader(reader);
+ this._crcCalculator = new JSIO.Crc32Reader(inflator);
+ }
+ // Whether compressed or not, the source ByteReader in each case
+ // is wrapped in a second ByteReader object that calculates CRC
+ // as it reads. That way, after all reading is complete, the
+ // caller can check the calcuated CRC against the expected CRC.
+ return this._crcCalculator;
+ };
+
+ // open a TextReader on the entry, to read text from the
+ // compressed stream.
+ ZipEntry.prototype.openTextReader = function(decoderKind) {
+ var reader = this.openBinaryReader();
+ decoderKind = decoderKind || JSIO.TextDecoder.UTF8;
+ var d = new decoderKind(reader);
+ var textReader = new JSIO.TextReader(d);
+ d._parent = textReader; // store a reference, for diagnostic purposes only
+ return textReader;
+ };
+
+ // verify the CRC on the entry.
+ // call this after all bytes have been read.
+ ZipEntry.prototype.verifyCrc32 = function() {
+ var computedCrc = this._crcCalculator.crc32();
+ var rc = false; // CRC FAIL
+ if (this.crc32 != computedCrc) {
+ var msg = "WARNING: CRC check failed: " +
+ "entry(" + this.name + ") " +
+ "computed(" + JSIO.decimalToHexString(computedCrc,8) + ") " +
+ "expected(" + JSIO.decimalToHexString(this.crc32,8) + ") ";
+ this.zipfile.status.push(msg);
+ } else {
+ rc = true; // OK
+ if (this.zipfile.verbose>2) {
+ this.zipfile.status.push("INFO: CRC check ok: 0x" +
+ JSIO.decimalToHexString(this.crc32,8));
+ }
+ }
+ return rc;
+ };
+
+
+ // ctor
+ ZipFile = function(fileUrl, callback, verbosity) {
+ if (! (this instanceof arguments.callee) ) JSIO.throwError('You must use new to instantiate this class', typename, 'ctor');
+
+ this.verbose = verbosity || 0;
+ this.entries = [];
+ this.entryNames = [];
+ this.status = [];
+ this._version = version;
+ this._typename = "ZipFile";
+ this._throwError = JSIO.throwError;
+
+ var thisZipFile = this;
+
+ // Could use a back-tracking reader for the central directory, but
+ // there's no point, since all the zip data is held in memory anyway.
+
+ /* function ReadCentralDirectory(){
+ var posn = thisZipFile.binaryStream.length - 64;
+ var maxSeekback = Math.Max(s.Length - 0x4000, 10);
+ var success = false;
+ var nTries = 0;
+ do
+ {
+ thisZipFile.binaryStream.Seek(posn, SeekOrigin.Begin);
+ var bytesRead = thisZipFile.binaryStream.findSignature(thisZipFile.Signatures.EndOfCentralDirectory);
+ if (bytesRead != -1)
+ success = true;
+ else
+ {
+ nTries++;
+ // increasingly larger
+ posn -= (32 * (nTries + 1) * nTries);
+ if (posn < 0) posn = 0; // BOF
+ }
+ }
+ while (!success && posn > maxSeekback);
+ if (!success) {
+ thisZipFile.status.push("cannot find End of Central Directory");
+ return;
+ }
+ } */
+
+
+ function DateFromPackedFormat(packed) {
+ if (packed == 0xFFFF || packed === 0) return new Date(1995, 0, 1, 0,0,0,0);
+
+ var packedTime = packed & 0x0000ffff;
+ var packedDate = ((packed & 0xffff0000) >> 16);
+
+ var year = 1980 + ((packedDate & 0xFE00) >> 9);
+ var month = ((packedDate & 0x01E0) >> 5) -1;
+ var day = packedDate & 0x001F;
+
+ var hour = (packedTime & 0xF800) >> 11;
+ var minute = (packedTime & 0x07E0) >> 5;
+ var second = (packedTime & 0x001F) * 2;
+
+ // Validation and error checking.
+ // This is not foolproof but will catch most errors.
+
+ // I can't believe how many different ways applications
+ // can mess up a simple date format.
+
+ if (second >= 60) { minute++; second = 0; }
+ if (minute >= 60) { hour++; minute = 0; }
+ if (hour >= 24) { day++; hour = 0; }
+ var success = false;
+ var d;
+ try {
+ d = new Date(year, month, day, hour, minute, second, 0);
+ success= true;
+ }
+ catch (exc1) {
+ if (year == 1980 && (month === 0 || day === 0)) {
+ try {
+ d = new Date(1980, 0, 1, hour, minute, second, 0);
+ success= true;
+ }
+ catch (exc2) {
+ try {
+ d = new Date(1980, 0, 1, 0, 0, 0, 0);
+ success= true;
+ }
+ catch (exc3) { } // how could this fail??
+ }
+ }
+ else {
+ try {
+ if (year < 1980) year = 1980;
+ if (year > 2030) year = 2030;
+ if (month < 1) month = 1;
+ if (month > 12) month = 12;
+ if (day < 1) day = 1;
+ if (day > 31) day = 31;
+ if (minute < 0) minute = 0;
+ if (minute > 59) minute = 59;
+ if (second < 0) second = 0;
+ if (second > 59) second = 59;
+ d = new Date(year, month-1, day, hour, minute, second, 0);
+ success= true;
+ }
+ catch (exc4){}
+ }
+ }
+ if (!success) this._throwError('Bad date/time value in this ZIP file', null, 'DateFromPackedFormat');
+ return d;
+ }
+
+
+ function ReadZipEntries () {
+ // read only once
+ if (thisZipFile.entryNames.length === 0){
+ var e;
+ while ((e = ReadZipEntry()) !== null) {
+ thisZipFile.entries.push(e);
+ thisZipFile.entryNames.push(e.name);
+ }
+ }
+ }
+
+
+ function ReadZipEntry () {
+ var offset = thisZipFile.binaryStream.position;
+ var sig = thisZipFile.binaryStream.readNumber(4);
+ if (sig == ZipFile.Signatures.DirEntry) {
+ // after all entries, comes the central directory
+ if (thisZipFile.verbose > 0) {
+ thisZipFile.status.push("INFO: at offset 0x" +
+ JSIO.decimalToHexString(offset) +
+ ", found start of Zip Directory.");
+ }
+ // all done reading
+ return null;
+ }
+ if (sig != ZipFile.Signatures.Entry) {
+ thisZipFile.status.push("WARNING: at offset 0x" +
+ JSIO.decimalToHexString(offset) +
+ ", found unexpected signature: 0x" +
+ JSIO.decimalToHexString(sig));
+ return null;
+ }
+
+ var entry = new ZipEntry(thisZipFile);
+ entry.offset = offset;
+ entry.versionNeeded = thisZipFile.binaryStream.readNumber(2);
+ entry.bitField = thisZipFile.binaryStream.readNumber(2);
+ entry.compressionMethod = thisZipFile.binaryStream.readNumber(2);
+ var timeBlob = thisZipFile.binaryStream.readNumber(4);
+ entry.lastModified = DateFromPackedFormat(timeBlob);
+ entry.crc32 = thisZipFile.binaryStream.readNumber(4);
+ entry.compressedSize = thisZipFile.binaryStream.readNumber(4);
+ entry.uncompressedSize = thisZipFile.binaryStream.readNumber(4);
+
+ if ((entry.bitField & 0x01) == 0x01){
+ thisZipFile.status.push("This zipfile uses Encryption, which is not supported by ZipFile.js.");
+ return null;
+ }
+
+ entry.utf8 = ((entry.bitField & 0x0800) == 0x0800);
+
+ if ((entry.bitField & 0x0008) == 0x0008){
+ thisZipFile.status.push("This zipfile uses a bit 3 trailing data descriptor, which is not supported by ZipFile.js.");
+ return null;
+ }
+
+ if (entry.compressedSize == 0xFFFFFFFF ||
+ entry.uncompressedSize == 0xFFFFFFFF) {
+ thisZipFile.status.push("This zipfile uses ZIP64, which is not supported by ZipFile.js");
+ return null;
+ }
+
+ var filenameLength = thisZipFile.binaryStream.readNumber(2);
+ var extraFieldLength = thisZipFile.binaryStream.readNumber(2);
+
+ thisZipFile.status.push("INFO: filename length= " + filenameLength);
+
+ // we've read 30 bytes of metadata so far
+ var bytesRead = 30 + filenameLength + extraFieldLength;
+
+ if (entry.utf8) {
+ thisZipFile.status.push("INFO: before filename, position= 0x" +
+ JSIO.decimalToHexString( thisZipFile.binaryStream.position ));
+ var binReader =
+ new JSIO.StreamSegmentReader(thisZipFile.binaryStream,
+ thisZipFile.binaryStream.position,
+ filenameLength);
+ var utf8Decoder = new JSIO.TextDecoder.UTF8(binReader);
+ var textReader = new JSIO.TextReader(utf8Decoder);
+ entry.name = textReader.readToEnd();
+
+ // advance the filepointer:
+ thisZipFile.binaryStream.seek(filenameLength,
+ JSIO.SeekOrigin.Current,
+ thisZipFile);
+
+ thisZipFile.status.push("INFO: after filename, position= 0x" +
+ JSIO.decimalToHexString( thisZipFile.binaryStream.position ));
+ }
+ else {
+ entry.name = thisZipFile.binaryStream.readString(filenameLength);
+ }
+
+ // There are a bunch of things in the "extra" header, thisZipFile we
+ // could parse, like timestamps and other things. This class
+ // only identifies and separates them.
+
+ // More info here: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
+
+ var extraPos = 0;
+ entry.extra = [];
+ while (extraPos < extraFieldLength) {
+ var extraBlock = {
+ type: thisZipFile.binaryStream.readNumber(2),
+ size: thisZipFile.binaryStream.readNumber(2)
+ };
+ extraBlock.typeDescription = ZipFile.ExtraFieldTypes[extraBlock.type];
+ extraBlock.data = thisZipFile.binaryStream.read(extraBlock.size);
+ entry.extra.push(extraBlock);
+ extraPos += 4 + extraBlock.size;
+ }
+
+ if (thisZipFile.verbose > 1) {
+ thisZipFile.status.push("INFO: at offset 0x" +
+ JSIO.decimalToHexString(entry.offset) +
+ ", found entry '" + entry.name + "' fnl(" +
+ filenameLength + ") efl(" +
+ extraFieldLength +")");
+ }
+
+ if (extraFieldLength > 0) {
+ if (thisZipFile.verbose > 0) {
+ thisZipFile.status.push("INFO: entry " + entry.name + " has " +
+ extraFieldLength + " bytes of " +
+ "extra metadata (ID'd but ignored)");
+ }
+ }
+
+ entry.lengthOfHeader = bytesRead;
+ entry.totalEntrySize = entry.lengthOfHeader + entry.compressedSize;
+
+ // seek past the data without reading it. We will read on Extract()
+ if (thisZipFile.verbose > 1) {
+ thisZipFile.status.push("INFO: seek 0x" +
+ JSIO.decimalToHexString(entry.compressedSize) +
+ " (" + entry.compressedSize + ") bytes");
+ }
+
+ thisZipFile.binaryStream.seek(entry.compressedSize,
+ JSIO.SeekOrigin.Current,
+ thisZipFile);
+
+ return entry;
+ }
+
+
+ var parseZipFile = function(bfr){
+ try {
+ if (bfr.req.status == 200) {
+ var sig = thisZipFile.binaryStream.readNumber(4);
+ if (sig != ZipFile.Signatures.Entry){
+ thisZipFile.status.push("WARNING: this file does not appear to be a zip file");
+ } else {
+ thisZipFile.binaryStream.seek(0, JSIO.SeekOrigin.Begin);
+ ReadZipEntries();
+ if (thisZipFile.verbose > 0) {
+ thisZipFile.status.push("INFO: read " + thisZipFile.entries.length + " entries");
+ }
+ }
+ }
+ else {
+ thisZipFile.status.push("ERROR: the URL could not be read (" +
+ bfr.req.status + " " + bfr.req.statusText + ")");
+ }
+ callback(thisZipFile);
+ }
+ catch (exc1)
+ {
+ thisZipFile.status.push("Exception: " + exc1.message);
+ callback(thisZipFile);
+ }
+ };
+
+ this.binaryStream = new JSIO.BinaryUrlStream(fileUrl, parseZipFile);
+
+ return this;
+ };
+
+
+ ZipFile.Signatures = {
+ Entry : 0x04034b50,
+ EndOfCentralDirectory : 0x06054b50,
+ DirEntry : 0x02014b50
+ };
+
+ ZipFile.Version = version;
+
+ ZipFile.EncryptionAlgorithm = {
+ None : 0,
+ PkzipWeak : 1,
+ WinZipAes : 2
+ };
+
+ ZipFile.ExtraFieldTypes = {};
+ ZipFile.ExtraFieldTypes[0x0001] = 'Zip64 Extended Info';
+ ZipFile.ExtraFieldTypes[0x0007] = 'AV Info';
+ ZipFile.ExtraFieldTypes[0x0008] = 'Extended Language Encoding Data (PFS)';
+ ZipFile.ExtraFieldTypes[0x0009] = 'OS/2';
+ ZipFile.ExtraFieldTypes[0x000a] = 'NTFS ';
+ ZipFile.ExtraFieldTypes[0x000c] = 'OpenVMS';
+ ZipFile.ExtraFieldTypes[0x000d] = 'UNIX';
+ ZipFile.ExtraFieldTypes[0x000e] = 'File Stream and Fork Descriptors';
+ ZipFile.ExtraFieldTypes[0x000f] = 'Patch Descriptor';
+ ZipFile.ExtraFieldTypes[0x0014] = 'PKCS#7 Store for X.509 Certificates';
+ ZipFile.ExtraFieldTypes[0x0015] = 'X.509 Certificate ID and Signature (Individual File)';
+ ZipFile.ExtraFieldTypes[0x0016] = 'X.509 Certificate ID (Central Directory)';
+ ZipFile.ExtraFieldTypes[0x0017] = 'Strong Encryption Header';
+ ZipFile.ExtraFieldTypes[0x0018] = 'Record Management Controls';
+ ZipFile.ExtraFieldTypes[0x0019] = 'PKCS#7 Encryption Recipient Certificate List';
+ ZipFile.ExtraFieldTypes[0x0065] = 'IBM S/390 (Z390), AS/400 (I400) attributes (uncompressed)';
+ ZipFile.ExtraFieldTypes[0x0066] = 'IBM S/390 (Z390), AS/400 (I400) attributes (compressed)';
+ ZipFile.ExtraFieldTypes[0x4690] = 'POSZIP 4690 (reserved) ';
+ ZipFile.ExtraFieldTypes[0x07c8] = 'Macintosh';
+ ZipFile.ExtraFieldTypes[0x2605] = 'ZipIt Macintosh';
+ ZipFile.ExtraFieldTypes[0x2705] = 'ZipIt Macintosh 1.3.5+';
+ ZipFile.ExtraFieldTypes[0x2805] = 'ZipIt Macintosh 1.3.5+';
+ ZipFile.ExtraFieldTypes[0x334d] = 'Info-ZIP Macintosh';
+ ZipFile.ExtraFieldTypes[0x4341] = 'Acorn/SparkFS ';
+ ZipFile.ExtraFieldTypes[0x4453] = 'Windows NT security descriptor (binary ACL)';
+ ZipFile.ExtraFieldTypes[0x4704] = 'VM/CMS';
+ ZipFile.ExtraFieldTypes[0x470f] = 'MVS';
+ ZipFile.ExtraFieldTypes[0x4b46] = 'FWKCS MD5';
+ ZipFile.ExtraFieldTypes[0x4c41] = 'OS/2 access control list (text ACL)';
+ ZipFile.ExtraFieldTypes[0x4d49] = 'Info-ZIP OpenVMS';
+ ZipFile.ExtraFieldTypes[0x4f4c] = 'Xceed original location extra field';
+ ZipFile.ExtraFieldTypes[0x5356] = 'AOS/VS (ACL)';
+ ZipFile.ExtraFieldTypes[0x5455] = 'extended timestamp';
+ ZipFile.ExtraFieldTypes[0x554e] = 'Xceed unicode extra field';
+ ZipFile.ExtraFieldTypes[0x5855] = 'Info-ZIP UNIX (original, also OS/2, NT, etc)';
+ ZipFile.ExtraFieldTypes[0x6375] = 'Info-ZIP Unicode Comment Extra Field';
+ ZipFile.ExtraFieldTypes[0x6542] = 'BeOS/BeBox';
+ ZipFile.ExtraFieldTypes[0x7075] = 'Info-ZIP Unicode Path Extra Field';
+ ZipFile.ExtraFieldTypes[0x756e] = 'ASi UNIX';
+ ZipFile.ExtraFieldTypes[0x7855] = 'Info-ZIP UNIX (new)';
+ ZipFile.ExtraFieldTypes[0xa220] = 'Microsoft Open Packaging Growth Hint';
+ ZipFile.ExtraFieldTypes[0xfd4a] = 'SMS/QDOS';
+
+})();
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js
new file mode 100644
index 00000000..6cc72d79
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/geoxml3/geoxml3.js
@@ -0,0 +1,1855 @@
+/**
+ * @fileOverview Renders KML on the Google Maps JavaScript API Version 3
+ * @name GeoXML3
+ * @author Sterling Udell, Larry Ross, Brendan Byrd
+ * @see http://code.google.com/p/geoxml3/
+ *
+ * geoxml3.js
+ *
+ * Renders KML on the Google Maps JavaScript API Version 3
+ * http://code.google.com/p/geoxml3/
+ *
+ * Copyright 2010 Sterling Udell, Larry Ross
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+if (!String.prototype.trim) {
+/**
+ * Remove leading and trailing whitespace.
+ *
+ * @augments String
+ * @return {String}
+ */
+ String.prototype.trim = function () {
+ return this.replace(/^\s+|\s+$/g, '');
+ };
+}
+
+/**
+ * @namespace The GeoXML3 namespace.
+ */
+geoXML3 = window.geoXML3 || {instances: []};
+
+/**
+ * Constructor for the root KML parser object.
+ *
+ * <p>All top-level objects and functions are declared under a namespace of geoXML3.
+ * The core object is geoXML3.parser; typically, you'll instantiate a one parser
+ * per map.</p>
+ *
+ * @class Main XML parser.
+ * @param {geoXML3.parserOptions} options
+ */
+geoXML3.parser = function (options) {
+ // Private variables
+ var parserOptions = new geoXML3.parserOptions(options);
+ var docs = []; // Individual KML documents
+ var docsByUrl = {}; // Same docs as an hash by cleanURL
+ var kmzMetaData = {}; // Extra files from KMZ data
+ var styles = {}; // Global list of styles
+ var lastPlacemark;
+ var parserName;
+ if (!parserOptions.infoWindow && parserOptions.singleInfoWindow)
+ parserOptions.infoWindow = new google.maps.InfoWindow();
+
+ var parseKmlString = function (kmlString, docSet) {
+ // Internal values for the set of documents as a whole
+ var internals = {
+ parser: this,
+ docSet: docSet || [],
+ remaining: 1,
+ parseOnly: !(parserOptions.afterParse || parserOptions.processStyles)
+ };
+ thisDoc = new Object();
+ thisDoc.internals = internals;
+ internals.docSet.push(thisDoc);
+ render(geoXML3.xmlParse(kmlString),thisDoc);
+ }
+
+ var parse = function (urls, docSet) {
+ // Process one or more KML documents
+ if (!parserName) {
+ parserName = 'geoXML3.instances[' + (geoXML3.instances.push(this) - 1) + ']';
+ }
+
+ if (typeof urls === 'string') {
+ // Single KML document
+ urls = [urls];
+ }
+
+ // Internal values for the set of documents as a whole
+ var internals = {
+ parser: this,
+ docSet: docSet || [],
+ remaining: urls.length,
+ parseOnly: !(parserOptions.afterParse || parserOptions.processStyles)
+ };
+ var thisDoc, j;
+ for (var i = 0; i < urls.length; i++) {
+ var baseUrl = cleanURL(defileURL(location.pathname), urls[i]);
+ if (docsByUrl[baseUrl]) {
+ // Reloading an existing document
+ thisDoc = docsByUrl[baseUrl];
+ thisDoc.reload = true;
+ }
+ else {
+ thisDoc = new Object();
+ thisDoc.baseUrl = baseUrl;
+ internals.docSet.push(thisDoc);
+ }
+ thisDoc.url = urls[i];
+ thisDoc.internals = internals;
+ fetchDoc(thisDoc.url, thisDoc);
+ }
+ };
+
+ function fetchDoc(url, doc, resFunc) {
+ resFunc = resFunc || function (responseXML) { render(responseXML, doc); };
+
+ if (typeof ZipFile === 'function' && typeof JSIO === 'object' && typeof JSIO.guessFileType === 'function') { // KMZ support requires these modules loaded
+ contentType = JSIO.guessFileType(doc.baseUrl);
+ if (contentType == JSIO.FileType.Binary || contentType == JSIO.FileType.Unknown) {
+ doc.isCompressed = true;
+ doc.baseDir = doc.baseUrl + '/';
+ geoXML3.fetchZIP(url, resFunc, doc.internals.parser);
+ return;
+ }
+ }
+ doc.isCompressed = false;
+ doc.baseDir = defileURL(doc.baseUrl);
+ geoXML3.fetchXML(url, resFunc);
+ }
+
+ var hideDocument = function (doc) {
+ if (!doc) doc = docs[0];
+ // Hide the map objects associated with a document
+ var i;
+ if (!!doc.markers) {
+ for (i = 0; i < doc.markers.length; i++) {
+ if(!!doc.markers[i].infoWindow) doc.markers[i].infoWindow.close();
+ doc.markers[i].setVisible(false);
+ }
+ }
+ if (!!doc.ggroundoverlays) {
+ for (i = 0; i < doc.ggroundoverlays.length; i++) {
+ doc.ggroundoverlays[i].setOpacity(0);
+ }
+ }
+ if (!!doc.gpolylines) {
+ for (i=0;i<doc.gpolylines.length;i++) {
+ if(!!doc.gpolylines[i].infoWindow) doc.gpolylines[i].infoWindow.close();
+ doc.gpolylines[i].setMap(null);
+ }
+ }
+ if (!!doc.gpolygons) {
+ for (i=0;i<doc.gpolygons.length;i++) {
+ if(!!doc.gpolygons[i].infoWindow) doc.gpolygons[i].infoWindow.close();
+ doc.gpolygons[i].setMap(null);
+ }
+ }
+ };
+
+ var showDocument = function (doc) {
+ if (!doc) doc = docs[0];
+ // Show the map objects associated with a document
+ var i;
+ if (!!doc.markers) {
+ for (i = 0; i < doc.markers.length; i++) {
+ doc.markers[i].setVisible(true);
+ }
+ }
+ if (!!doc.ggroundoverlays) {
+ for (i = 0; i < doc.ggroundoverlays.length; i++) {
+ doc.ggroundoverlays[i].setOpacity(doc.ggroundoverlays[i].percentOpacity_);
+ }
+ }
+ if (!!doc.gpolylines) {
+ for (i=0;i<doc.gpolylines.length;i++) {
+ doc.gpolylines[i].setMap(parserOptions.map);
+ }
+ }
+ if (!!doc.gpolygons) {
+ for (i=0;i<doc.gpolygons.length;i++) {
+ doc.gpolygons[i].setMap(parserOptions.map);
+ }
+ }
+ };
+
+ var defaultStyle = {
+ balloon: {
+ bgColor: 'ffffffff',
+ textColor: 'ff000000',
+ text: "<h3>$[name]</h3>\n<div>$[description]</div>\n<div>$[geDirections]</div>",
+ displayMode: 'default'
+ },
+ icon: {
+ scale: 1.0,
+ dim: {
+ x: 0,
+ y: 0,
+ w: -1,
+ h: -1
+ },
+ hotSpot: {
+ x: 0.5,
+ y: 0.5,
+ xunits: 'fraction',
+ yunits: 'fraction'
+ }
+ },
+ line: {
+ color: 'ffffffff', // white (KML default)
+ colorMode: 'normal',
+ width: 1.0
+ },
+ poly: {
+ color: 'ffffffff', // white (KML default)
+ colorMode: 'normal',
+ fill: true,
+ outline: true
+ }
+ };
+
+ var kmlNS = 'http://www.opengis.net/kml/2.2';
+ var gxNS = 'http://www.google.com/kml/ext/2.2';
+ var nodeValue = geoXML3.nodeValue;
+ var getBooleanValue = geoXML3.getBooleanValue;
+ var getElementsByTagNameNS = geoXML3.getElementsByTagNameNS;
+ var getElementsByTagName = geoXML3.getElementsByTagName;
+
+function processStyleUrl(node) {
+ var styleUrlStr = nodeValue(getElementsByTagName(node, 'styleUrl')[0]);
+ if (!!styleUrlStr && styleUrlStr.indexOf('#') != -1)
+ var styleUrl = styleUrlStr.split('#');
+ else var styleUrl = ["",""];
+ return styleUrl;
+}
+
+ function processStyle(thisNode, baseUrl, styleID, baseDir) {
+ var style = (baseUrl === '{inline}') ? clone(defaultStyle) : (styles[baseUrl][styleID] = styles[baseUrl][styleID] || clone(defaultStyle));
+
+ var styleNodes = getElementsByTagName(thisNode, 'BalloonStyle');
+ if (!!styleNodes && styleNodes.length > 0) {
+ style.balloon.bgColor = nodeValue(getElementsByTagName(styleNodes[0], 'bgColor')[0], style.balloon.bgColor);
+ style.balloon.textColor = nodeValue(getElementsByTagName(styleNodes[0], 'textColor')[0], style.balloon.textColor);
+ style.balloon.text = nodeValue(getElementsByTagName(styleNodes[0], 'text')[0], style.balloon.text);
+ style.balloon.displayMode = nodeValue(getElementsByTagName(styleNodes[0], 'displayMode')[0], style.balloon.displayMode);
+ }
+
+ // style.list = (unsupported; doesn't make sense in Google Maps)
+
+ var styleNodes = getElementsByTagName(thisNode, 'IconStyle');
+ if (!!styleNodes && styleNodes.length > 0) {
+ var icon = style.icon;
+
+ icon.scale = parseFloat(nodeValue(getElementsByTagName(styleNodes[0], 'scale')[0], icon.scale));
+ // style.icon.heading = (unsupported; not supported in API)
+ // style.icon.color = (unsupported; not supported in API)
+ // style.icon.colorMode = (unsupported; not supported in API)
+
+ styleNodes = getElementsByTagName(thisNode, 'Icon');
+ if (!!styleNodes && styleNodes.length > 0) {
+ icon.href = nodeValue(getElementsByTagName(styleNodes[0], 'href')[0]);
+ icon.url = cleanURL(baseDir, icon.href);
+ // Detect images buried in KMZ files (and use a base64 encoded URL)
+ if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl;
+
+ // Support for icon palettes and exact size dimensions
+ icon.dim = {
+ x: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'x')[0], icon.dim.x)),
+ y: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'y')[0], icon.dim.y)),
+ w: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'w')[0], icon.dim.w)),
+ h: parseInt(nodeValue(getElementsByTagNameNS(styleNodes[0], gxNS, 'h')[0], icon.dim.h))
+ };
+
+ styleNodes = getElementsByTagName(styleNodes[0], 'hotSpot')[0];
+ if (!!styleNodes && styleNodes.length > 0) {
+ icon.hotSpot = {
+ x: styleNodes[0].getAttribute('x'),
+ y: styleNodes[0].getAttribute('y'),
+ xunits: styleNodes[0].getAttribute('xunits'),
+ yunits: styleNodes[0].getAttribute('yunits')
+ };
+ }
+
+ // certain occasions where we need the pixel size of the image (like the default settings...)
+ // (NOTE: Scale is applied to entire image, not just the section of the icon palette. So,
+ // if we need scaling, we'll need the img dimensions no matter what.)
+ if ( (icon.dim.w < 0 || icon.dim.h < 0) && (icon.xunits != 'pixels' || icon.yunits == 'fraction') || icon.scale != 1.0) {
+ // (hopefully, this will load by the time we need it...)
+ icon.img = new Image();
+ icon.img.onload = function() {
+ if (icon.dim.w < 0 || icon.dim.h < 0) {
+ icon.dim.w = this.width;
+ icon.dim.h = this.height;
+ }
+ };
+ icon.img.src = icon.url;
+
+ // sometimes the file is already cached and it never calls onLoad
+ if (icon.img.width > 0) {
+ icon.dim.w = icon.img.width;
+ icon.dim.h = icon.img.height;
+ }
+ }
+ }
+ }
+
+ // style.label = (unsupported; may be possible but not with API)
+
+ styleNodes = getElementsByTagName(thisNode, 'LineStyle');
+ if (!!styleNodes && styleNodes.length > 0) {
+ style.line.color = nodeValue(getElementsByTagName(styleNodes[0], 'color')[0], style.line.color);
+ style.line.colorMode = nodeValue(getElementsByTagName(styleNodes[0], 'colorMode')[0], style.line.colorMode);
+ style.line.width = nodeValue(getElementsByTagName(styleNodes[0], 'width')[0], style.line.width);
+ // style.line.outerColor = (unsupported; not supported in API)
+ // style.line.outerWidth = (unsupported; not supported in API)
+ // style.line.physicalWidth = (unsupported; unnecessary in Google Maps)
+ // style.line.labelVisibility = (unsupported; possible to implement)
+ }
+
+ styleNodes = getElementsByTagName(thisNode, 'PolyStyle');
+ if (!!styleNodes && styleNodes.length > 0) {
+ style.poly.color = nodeValue( getElementsByTagName(styleNodes[0], 'color')[0], style.poly.color);
+ style.poly.colorMode = nodeValue( getElementsByTagName(styleNodes[0], 'colorMode')[0], style.poly.colorMode);
+ style.poly.outline = getBooleanValue(getElementsByTagName(styleNodes[0], 'outline')[0], style.poly.outline);
+ style.poly.fill = getBooleanValue(getElementsByTagName(styleNodes[0], 'fill')[0], style.poly.fill);
+ }
+ return style;
+ }
+
+ // from http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-a-javascript-object
+ // http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
+ function clone(obj){
+ if(obj == null || typeof(obj) != 'object') return obj;
+ if (obj.cloneNode) return obj.cloneNode(true);
+ var temp = new obj.constructor();
+ for(var key in obj) temp[key] = clone(obj[key]);
+ return temp;
+ }
+
+ function processStyleMap(thisNode, baseUrl, styleID, baseDir) {
+ var pairs = getElementsByTagName(thisNode, 'Pair');
+ var map = new Object();
+
+ // add each key to the map
+ for (var pr=0;pr<pairs.length;pr++) {
+ var pairKey = nodeValue(getElementsByTagName(pairs[pr], 'key')[0]);
+ var pairStyle = nodeValue(getElementsByTagName(pairs[pr], 'Style')[0]);
+ var pairStyleUrl = processStyleUrl(pairs[pr]);
+ var pairStyleBaseUrl = pairStyleUrl[0] ? cleanURL(baseDir, pairStyleUrl[0]) : baseUrl;
+ var pairStyleID = pairStyleUrl[1];
+
+ if (!!pairStyle) {
+ map[pairKey] = processStyle(pairStyle, pairStyleBaseUrl, pairStyleID);
+ } else if (!!pairStyleID && !!styles[pairStyleBaseUrl][pairStyleID]) {
+ map[pairKey] = clone(styles[pairStyleBaseUrl][pairStyleID]);
+ }
+ }
+ if (!!map["normal"]) {
+ styles[baseUrl][styleID] = clone(map["normal"]);
+ } else {
+ styles[baseUrl][styleID] = clone(defaultStyle);
+ }
+ if (!!map["highlight"]) {
+ processStyleID(map["highlight"]);
+ }
+ styles[baseUrl][styleID].map = clone(map);
+ }
+
+ function processPlacemarkCoords(node, tag) {
+ var parent = getElementsByTagName(node, tag);
+ var coordListA = [];
+ for (var i=0; i<parent.length; i++) {
+ var coordNodes = getElementsByTagName(parent[i], 'coordinates');
+ if (!coordNodes) {
+ if (coordListA.length > 0) {
+ break;
+ } else {
+ return [{coordinates: []}];
+ }
+ }
+
+ for (var j=0; j<coordNodes.length;j++) {
+ var coords = nodeValue(coordNodes[j]).trim();
+ coords = coords.replace(/,\s+/g, ',');
+ var path = coords.split(/\s+/g);
+ var pathLength = path.length;
+ var coordList = [];
+ for (var k = 0; k < pathLength; k++) {
+ coords = path[k].split(',');
+ if (!isNaN(coords[0]) && !isNaN(coords[1])) {
+ coordList.push({
+ lat: parseFloat(coords[1]),
+ lng: parseFloat(coords[0]),
+ alt: parseFloat(coords[2])
+ });
+ }
+ }
+ coordListA.push({coordinates: coordList});
+ }
+ }
+ return coordListA;
+ }
+
+ var render = function (responseXML, doc) {
+ // Callback for retrieving a KML document: parse the KML and display it on the map
+ if (!responseXML) {
+ // Error retrieving the data
+ geoXML3.log('Unable to retrieve ' + doc.url);
+ if (parserOptions.failedParse) parserOptions.failedParse(doc);
+ doc.failed = true;
+ return;
+ } else if (responseXML.parseError && responseXML.parseError.errorCode != 0) {
+ // IE parse error
+ var err = responseXML.parseError;
+ var msg = 'Parse error in line ' + err.line + ', col ' + err.linePos + ' (error code: ' + err.errorCode + ")\n" +
+ "\nError Reason: " + err.reason +
+ 'Error Line: ' + err.srcText;
+
+ geoXML3.log('Unable to retrieve ' + doc.url + ': ' + msg);
+ if (parserOptions.failedParse) parserOptions.failedParse(doc);
+ doc.failed = true;
+ return;
+ } else if (responseXML.documentElement && responseXML.documentElement.nodeName == 'parsererror') {
+ // Firefox parse error
+ geoXML3.log('Unable to retrieve ' + doc.url + ': ' + responseXML.documentElement.childNodes[0].nodeValue);
+ if (parserOptions.failedParse) parserOptions.failedParse(doc);
+ doc.failed = true;
+ return;
+ } else if (!doc) {
+ throw 'geoXML3 internal error: render called with null document';
+ } else { //no errors
+ var i;
+ doc.placemarks = [];
+ doc.groundoverlays = [];
+ doc.ggroundoverlays = [];
+ doc.networkLinks = [];
+ doc.gpolygons = [];
+ doc.gpolylines = [];
+
+ // Check for dependent KML files
+ var nodes = getElementsByTagName(responseXML, 'styleUrl');
+ var docSet = doc.internals.docSet;
+
+ for (var i = 0; i < nodes.length; i++) {
+ var url = nodeValue(nodes[i]).split('#')[0];
+ if (!url) continue; // #id (inside doc)
+ var rUrl = cleanURL( doc.baseDir, url );
+ if (rUrl === doc.baseUrl) continue; // self
+ if (docsByUrl[rUrl]) continue; // already loaded
+
+ var thisDoc;
+ var j = docSet.indexOfObjWithItem('baseUrl', rUrl);
+ if (j != -1) {
+ // Already listed to be loaded, but probably in the wrong order.
+ // Load it right away to immediately resolve dependency.
+ thisDoc = docSet[j];
+ if (thisDoc.failed) continue; // failed to load last time; don't retry it again
+ }
+ else {
+ // Not listed at all; add it in
+ thisDoc = new Object();
+ thisDoc.url = rUrl; // url can't be trusted inside KMZ files, since it may .. outside of the archive
+ thisDoc.baseUrl = rUrl;
+ thisDoc.internals = doc.internals;
+
+ doc.internals.docSet.push(thisDoc);
+ doc.internals.remaining++;
+ }
+
+ // render dependent KML first then re-run renderer
+ fetchDoc(rUrl, thisDoc, function (thisResXML) {
+ render(thisResXML, thisDoc);
+ render(responseXML, doc);
+ });
+
+ // to prevent cross-dependency issues, just load the one
+ // file first and re-check the rest later
+ return;
+ }
+
+ // Parse styles
+ doc.styles = styles[doc.baseUrl] = styles[doc.baseUrl] || {};
+ var styleID, styleNodes;
+ nodes = getElementsByTagName(responseXML, 'Style');
+ nodeCount = nodes.length;
+ for (i = 0; i < nodeCount; i++) {
+ thisNode = nodes[i];
+ var styleID = thisNode.getAttribute('id');
+ if (!!styleID) processStyle(thisNode, doc.baseUrl, styleID, doc.baseDir);
+ }
+ // Parse StyleMap nodes
+ nodes = getElementsByTagName(responseXML, 'StyleMap');
+ for (i = 0; i < nodes.length; i++) {
+ thisNode = nodes[i];
+ var styleID = thisNode.getAttribute('id');
+ if (!!styleID) processStyleMap(thisNode, doc.baseUrl, styleID, doc.baseDir);
+ }
+
+ if (!!parserOptions.processStyles || !parserOptions.createMarker) {
+ // Convert parsed styles into GMaps equivalents
+ processStyles(doc);
+ }
+
+ // Parse placemarks
+ if (!!doc.reload && !!doc.markers) {
+ for (i = 0; i < doc.markers.length; i++) {
+ doc.markers[i].active = false;
+ }
+ }
+ var placemark, node, coords, path, marker, poly;
+ var placemark, coords, path, pathLength, marker, polygonNodes, coordList;
+ var placemarkNodes = getElementsByTagName(responseXML, 'Placemark');
+ for (pm = 0; pm < placemarkNodes.length; pm++) {
+ // Init the placemark object
+ node = placemarkNodes[pm];
+ var styleUrl = processStyleUrl(node);
+ placemark = {
+ name: nodeValue(getElementsByTagName(node, 'name')[0]),
+ description: nodeValue(getElementsByTagName(node, 'description')[0]),
+ styleUrl: styleUrl.join('#'),
+ styleBaseUrl: styleUrl[0] ? cleanURL(doc.baseDir, styleUrl[0]) : doc.baseUrl,
+ styleID: styleUrl[1],
+ visibility: getBooleanValue(getElementsByTagName(node, 'visibility')[0], true),
+ balloonVisibility: getBooleanValue(getElementsByTagNameNS(node, gxNS, 'balloonVisibility')[0], !parserOptions.suppressInfoWindows)
+ };
+ placemark.style = (styles[placemark.styleBaseUrl] && styles[placemark.styleBaseUrl][placemark.styleID]) || clone(defaultStyle);
+ // inline style overrides shared style
+ var inlineStyles = getElementsByTagName(node, 'Style');
+ if (inlineStyles && (inlineStyles.length > 0)) {
+ var style = processStyle(node, '{inline}', '{inline}');
+ processStyleID(style);
+ if (style) placemark.style = style;
+ }
+
+ if (/^https?:\/\//.test(placemark.description)) {
+ placemark.description = ['<a href="', placemark.description, '">', placemark.description, '</a>'].join('');
+ }
+
+ // record list of variables for substitution
+ placemark.vars = {
+ display: {
+ name: 'Name',
+ description: 'Description',
+ address: 'Street Address',
+ id: 'ID',
+ Snippet: 'Snippet',
+ geDirections: 'Directions'
+ },
+ val: {
+ name: placemark.name || '',
+ description: placemark.description || '',
+ address: nodeValue(getElementsByTagName(node, 'address')[0], ''),
+ id: node.getAttribute('id') || '',
+ Snippet: nodeValue(getElementsByTagName(node, 'Snippet')[0], '')
+ },
+ directions: [
+ 'f=d',
+ 'source=GeoXML3'
+ ]
+ };
+
+ // add extended data to variables
+ var extDataNodes = getElementsByTagName(node, 'ExtendedData');
+ if (!!extDataNodes && extDataNodes.length > 0) {
+ var dataNodes = getElementsByTagName(extDataNodes[0], 'Data');
+ for (var d = 0; d < dataNodes.length; d++) {
+ var dn = dataNodes[d];
+ var name = dn.getAttribute('name');
+ if (!name) continue;
+ var dName = nodeValue(getElementsByTagName(dn, 'displayName')[0], name);
+ var val = nodeValue(getElementsByTagName(dn, 'value')[0]);
+
+ placemark.vars.val[name] = val;
+ placemark.vars.display[name] = dName;
+ }
+ }
+
+ // process MultiGeometry
+ var GeometryNodes = getElementsByTagName(node, 'coordinates');
+ var Geometry = null;
+ if (!!GeometryNodes && (GeometryNodes.length > 0)) {
+ for (var gn=0;gn<GeometryNodes.length;gn++) {
+ if (GeometryNodes[gn].parentNode &&
+ GeometryNodes[gn].parentNode.nodeName) {
+ var GeometryPN = GeometryNodes[gn].parentNode;
+ Geometry = GeometryPN.nodeName;
+
+ // Extract the coordinates
+ // What sort of placemark?
+ switch(Geometry) {
+ case "Point":
+ placemark.Point = processPlacemarkCoords(node, "Point")[0];
+ placemark.latlng = new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng);
+ pathLength = 1;
+ break;
+ case "LinearRing":
+ // Polygon/line
+ polygonNodes = getElementsByTagName(node, 'Polygon');
+ // Polygon
+ if (!placemark.Polygon)
+ placemark.Polygon = [{
+ outerBoundaryIs: {coordinates: []},
+ innerBoundaryIs: [{coordinates: []}]
+ }];
+ for (var pg=0;pg<polygonNodes.length;pg++) {
+ placemark.Polygon[pg] = {
+ outerBoundaryIs: {coordinates: []},
+ innerBoundaryIs: [{coordinates: []}]
+ }
+ placemark.Polygon[pg].outerBoundaryIs = processPlacemarkCoords(polygonNodes[pg], "outerBoundaryIs");
+ placemark.Polygon[pg].innerBoundaryIs = processPlacemarkCoords(polygonNodes[pg], "innerBoundaryIs");
+ }
+ coordList = placemark.Polygon[0].outerBoundaryIs;
+ break;
+
+ case "LineString":
+ pathLength = 0;
+ placemark.LineString = processPlacemarkCoords(node,"LineString");
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+ }
+
+ // call the custom placemark parse function if it is defined
+ if (!!parserOptions.pmParseFn) parserOptions.pmParseFn(node, placemark);
+ doc.placemarks.push(placemark);
+
+ // single marker
+ if (placemark.Point) {
+ if (!!google.maps) {
+ doc.bounds = doc.bounds || new google.maps.LatLngBounds();
+ doc.bounds.extend(placemark.latlng);
+ }
+
+ // Potential user-defined marker handler
+ var pointCreateFunc = parserOptions.createMarker || createMarker;
+ var found = false;
+ if (!parserOptions.createMarker) {
+ // Check to see if this marker was created on a previous load of this document
+ if (!!doc) {
+ doc.markers = doc.markers || [];
+ if (doc.reload) {
+ for (var j = 0; j < doc.markers.length; j++) {
+ if (doc.markers[j].getPosition().equals(placemark.latlng)) {
+ found = doc.markers[j].active = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (!found) {
+ // Call the marker creator
+ var marker = pointCreateFunc(placemark, doc);
+ if (marker) marker.active = placemark.visibility;
+ }
+ }
+ // polygon/line
+ var poly, line;
+ if (!!doc) {
+ if (placemark.Polygon) doc.gpolygons = doc.gpolygons || [];
+ if (placemark.LineString) doc.gpolylines = doc.gpolylines || [];
+ }
+
+ var polyCreateFunc = parserOptions.createPolygon || createPolygon;
+ var lineCreateFunc = parserOptions.createLineString || createPolyline;
+ if (placemark.Polygon) {
+ poly = polyCreateFunc(placemark,doc);
+ if (poly) poly.active = placemark.visibility;
+ }
+ if (placemark.LineString) {
+ line = lineCreateFunc(placemark,doc);
+ if (line) line.active = placemark.visibility;
+ }
+ if (!!google.maps) {
+ doc.bounds = doc.bounds || new google.maps.LatLngBounds();
+ if (poly) doc.bounds.union(poly.bounds);
+ if (line) doc.bounds.union(line.bounds);
+ }
+
+ } // placemark loop
+
+ if (!!doc.reload && !!doc.markers) {
+ for (i = doc.markers.length - 1; i >= 0 ; i--) {
+ if (!doc.markers[i].active) {
+ if (!!doc.markers[i].infoWindow) {
+ doc.markers[i].infoWindow.close();
+ }
+ doc.markers[i].setMap(null);
+ doc.markers.splice(i, 1);
+ }
+ }
+ }
+
+ // Parse ground overlays
+ if (!!doc.reload && !!doc.groundoverlays) {
+ for (i = 0; i < doc.groundoverlays.length; i++) {
+ doc.groundoverlays[i].active = false;
+ }
+ }
+
+ if (!!doc) {
+ doc.groundoverlays = doc.groundoverlays || [];
+ }
+ // doc.groundoverlays =[];
+ var groundOverlay, color, transparency, overlay;
+ var groundNodes = getElementsByTagName(responseXML, 'GroundOverlay');
+ for (i = 0; i < groundNodes.length; i++) {
+ node = groundNodes[i];
+
+ // Detect images buried in KMZ files (and use a base64 encoded URL)
+ var gnUrl = cleanURL( doc.baseDir, nodeValue(getElementsByTagName(node, 'href')[0]) );
+ if (kmzMetaData[gnUrl]) gnUrl = kmzMetaData[gnUrl].dataUrl;
+
+ // Init the ground overlay object
+ groundOverlay = {
+ name: nodeValue(getElementsByTagName(node, 'name')[0]),
+ description: nodeValue(getElementsByTagName(node, 'description')[0]),
+ icon: { href: gnUrl },
+ latLonBox: {
+ north: parseFloat(nodeValue(getElementsByTagName(node, 'north')[0])),
+ east: parseFloat(nodeValue(getElementsByTagName(node, 'east')[0])),
+ south: parseFloat(nodeValue(getElementsByTagName(node, 'south')[0])),
+ west: parseFloat(nodeValue(getElementsByTagName(node, 'west')[0]))
+ }
+ };
+ if (!!google.maps) {
+ doc.bounds = doc.bounds || new google.maps.LatLngBounds();
+ doc.bounds.union(new google.maps.LatLngBounds(
+ new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west),
+ new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)
+ ));
+ }
+
+ // Opacity is encoded in the color node
+ var colorNode = getElementsByTagName(node, 'color');
+ if (colorNode && colorNode.length > 0) {
+ groundOverlay.opacity = geoXML3.getOpacity(nodeValue(colorNode[0]));
+ } else {
+ groundOverlay.opacity = 1.0; // KML default
+ }
+
+ doc.groundoverlays.push(groundOverlay);
+ if (!!parserOptions.createOverlay) {
+ // User-defined overlay handler
+ parserOptions.createOverlay(groundOverlay, doc);
+ } else {
+ // Check to see if this overlay was created on a previous load of this document
+ var found = false;
+ if (!!doc) {
+ doc.groundoverlays = doc.groundoverlays || [];
+ if (doc.reload) {
+ overlayBounds = new google.maps.LatLngBounds(
+ new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west),
+ new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)
+ );
+ var overlays = doc.groundoverlays;
+ for (i = overlays.length; i--;) {
+ if ((overlays[i].bounds().equals(overlayBounds)) &&
+ (overlays.url_ === groundOverlay.icon.href)) {
+ found = overlays[i].active = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if (!found) {
+ // Call the built-in overlay creator
+ overlay = createOverlay(groundOverlay, doc);
+ overlay.active = true;
+ }
+ }
+ if (!!doc.reload && !!doc.groundoverlays && !!doc.groundoverlays.length) {
+ var overlays = doc.groundoverlays;
+ for (i = overlays.length; i--;) {
+ if (!overlays[i].active) {
+ overlays[i].remove();
+ overlays.splice(i, 1);
+ }
+ }
+ doc.groundoverlays = overlays;
+ }
+ }
+
+ // Parse network links
+ var networkLink;
+ var docPath = document.location.pathname.split('/');
+ docPath = docPath.splice(0, docPath.length - 1).join('/');
+ var linkNodes = getElementsByTagName(responseXML, 'NetworkLink');
+ for (i = 0; i < linkNodes.length; i++) {
+ node = linkNodes[i];
+
+ // Init the network link object
+ networkLink = {
+ name: nodeValue(getElementsByTagName(node, 'name')[0]),
+ link: {
+ href: nodeValue(getElementsByTagName(node, 'href')[0]),
+ refreshMode: nodeValue(getElementsByTagName(node, 'refreshMode')[0])
+ }
+ };
+
+ // Establish the specific refresh mode
+ if (!networkLink.link.refreshMode) {
+ networkLink.link.refreshMode = 'onChange';
+ }
+ if (networkLink.link.refreshMode === 'onInterval') {
+ networkLink.link.refreshInterval = parseFloat(nodeValue(getElementsByTagName(node, 'refreshInterval')[0]));
+ if (isNaN(networkLink.link.refreshInterval)) {
+ networkLink.link.refreshInterval = 0;
+ }
+ } else if (networkLink.link.refreshMode === 'onChange') {
+ networkLink.link.viewRefreshMode = nodeValue(getElementsByTagName(node, 'viewRefreshMode')[0]);
+ if (!networkLink.link.viewRefreshMode) {
+ networkLink.link.viewRefreshMode = 'never';
+ }
+ if (networkLink.link.viewRefreshMode === 'onStop') {
+ networkLink.link.viewRefreshTime = nodeValue(getElementsByTagName(node, 'refreshMode')[0]);
+ networkLink.link.viewFormat = nodeValue(getElementsByTagName(node, 'refreshMode')[0]);
+ if (!networkLink.link.viewFormat) {
+ networkLink.link.viewFormat = 'BBOX=[bboxWest],[bboxSouth],[bboxEast],[bboxNorth]';
+ }
+ }
+ }
+
+ if (!/^[\/|http]/.test(networkLink.link.href)) {
+ // Fully-qualify the HREF
+ networkLink.link.href = docPath + '/' + networkLink.link.href;
+ }
+
+ // Apply the link
+ if ((networkLink.link.refreshMode === 'onInterval') &&
+ (networkLink.link.refreshInterval > 0)) {
+ // Reload at regular intervals
+ setInterval(parserName + '.parse("' + networkLink.link.href + '")',
+ 1000 * networkLink.link.refreshInterval);
+ } else if (networkLink.link.refreshMode === 'onChange') {
+ if (networkLink.link.viewRefreshMode === 'never') {
+ // Load the link just once
+ doc.internals.parser.parse(networkLink.link.href, doc.internals.docSet);
+ } else if (networkLink.link.viewRefreshMode === 'onStop') {
+ // Reload when the map view changes
+
+ }
+ }
+ }
+ }
+
+ if (!!doc.bounds) {
+ doc.internals.bounds = doc.internals.bounds || new google.maps.LatLngBounds();
+ doc.internals.bounds.union(doc.bounds);
+ }
+ if (!!doc.markers || !!doc.groundoverlays || !!doc.gpolylines || !!doc.gpolygons) {
+ doc.internals.parseOnly = false;
+ }
+
+ if (!doc.internals.parseOnly) {
+ // geoXML3 is not being used only as a real-time parser, so keep the processed documents around
+ if (!docsByUrl[doc.baseUrl]) {
+ docs.push(doc);
+ docsByUrl[doc.baseUrl] = doc;
+ }
+ else {
+ // internal replacement, which keeps the same memory ref loc in docs and docsByUrl
+ for (var i in docsByUrl[doc.baseUrl]) {
+ docsByUrl[doc.baseUrl][i] = doc[i];
+ }
+ }
+ }
+
+ doc.internals.remaining--;
+ if (doc.internals.remaining === 0) {
+ // We're done processing this set of KML documents
+ // Options that get invoked after parsing completes
+ if (parserOptions.zoom && !!doc.internals.bounds) {
+ parserOptions.map.fitBounds(doc.internals.bounds);
+ }
+ if (parserOptions.afterParse) {
+ parserOptions.afterParse(doc.internals.docSet);
+ }
+ }
+ };
+
+ var kmlColor = function (kmlIn, colorMode) {
+ var kmlColor = {};
+ kmlIn = kmlIn || 'ffffffff'; // white (KML 2.2 default)
+
+ var aa = kmlIn.substr(0,2);
+ var bb = kmlIn.substr(2,2);
+ var gg = kmlIn.substr(4,2);
+ var rr = kmlIn.substr(6,2);
+
+ kmlColor.opacity = parseInt(aa, 16) / 256;
+ kmlColor.color = (colorMode === 'random') ? randomColor(rr, gg, bb) : '#' + rr + gg + bb;
+ return kmlColor;
+ };
+
+ // Implemented per KML 2.2 <ColorStyle> specs
+ var randomColor = function(rr, gg, bb) {
+ var col = { rr: rr, gg: gg, bb: bb };
+ for (var k in col) {
+ var v = col[k];
+ if (v == null) v = 'ff';
+
+ // RGB values are limiters for random numbers (ie: 7f would be a random value between 0 and 7f)
+ v = Math.round(Math.random() * parseInt(rr, 16)).toString(16);
+ if (v.length === 1) v = '0' + v;
+ col[k] = v;
+ }
+
+ return '#' + col.rr + col.gg + col.bb;
+ };
+
+ var processStyleID = function (style) {
+ var icon = style.icon;
+ if (!icon.href) return;
+
+ if (icon.img && !icon.img.complete) {
+ // we're still waiting on the image loading (probably because we've been blocking since the declaration)
+ // so, let's queue this function on the onload stack
+ icon.markerBacklog = [];
+ icon.img.onload = function() {
+ if (icon.dim.w < 0 || icon.dim.h < 0) {
+ icon.dim.w = this.width;
+ icon.dim.h = this.height;
+ }
+ processStyleID(style);
+
+ // we will undoubtedly get some createMarker queuing, so set this up in advance
+ for (var i = 0; i < icon.markerBacklog.length; i++) {
+ var p = icon.markerBacklog[i][0];
+ var d = icon.markerBacklog[i][1];
+ createMarker(p, d);
+ if (p.marker) p.marker.active = true;
+ }
+ delete icon.markerBacklog;
+ };
+ return;
+ }
+ else if (icon.dim.w < 0 || icon.dim.h < 0) {
+ if (icon.img && icon.img.complete) {
+ // sometimes the file is already cached and it never calls onLoad
+ icon.dim.w = icon.img.width;
+ icon.dim.h = icon.img.height;
+ }
+ else {
+ // settle for a default of 32x32
+ icon.dim.whGuess = true;
+ icon.dim.w = 32;
+ icon.dim.h = 32;
+ }
+ }
+
+ // pre-scaled variables
+ var rnd = Math.round;
+ var scaled = {
+ x: icon.dim.x * icon.scale,
+ y: icon.dim.y * icon.scale,
+ w: icon.dim.w * icon.scale,
+ h: icon.dim.h * icon.scale,
+ aX: icon.hotSpot.x * icon.scale,
+ aY: icon.hotSpot.y * icon.scale,
+ iW: (icon.img ? icon.img.width : icon.dim.w) * icon.scale,
+ iH: (icon.img ? icon.img.height : icon.dim.h) * icon.scale
+ };
+
+ // Figure out the anchor spot
+ var aX, aY;
+ switch (icon.hotSpot.xunits) {
+ case 'fraction': aX = rnd(scaled.aX * icon.dim.w); break;
+ case 'insetPixels': aX = rnd(icon.dim.w * icon.scale - scaled.aX); break;
+ default: aX = rnd(scaled.aX); break; // already pixels
+ }
+ aY = rnd( ((icon.hotSpot.yunits === 'fraction') ? icon.dim.h : 1) * scaled.aY ); // insetPixels Y = pixels Y
+ var iconAnchor = new google.maps.Point(aX, aY);
+
+ // Sizes
+ // (NOTE: Scale is applied to entire image, not just the section of the icon palette.)
+ var iconSize = icon.dim.whGuess ? null : new google.maps.Size(rnd(scaled.w), rnd(scaled.h));
+ var iconScale = icon.scale == 1.0 ? null :
+ icon.dim.whGuess ? new google.maps.Size(rnd(scaled.w), rnd(scaled.h))
+ : new google.maps.Size(rnd(scaled.iW), rnd(scaled.iH));
+ var iconOrigin = new google.maps.Point(rnd(scaled.x), rnd(scaled.y));
+
+ // Detect images buried in KMZ files (and use a base64 encoded URL)
+ if (kmzMetaData[icon.url]) icon.url = kmzMetaData[icon.url].dataUrl;
+
+ // Init the style object with the KML icon
+ icon.marker = new google.maps.MarkerImage(
+ icon.url, // url
+ iconSize, // size
+ iconOrigin, // origin
+ iconAnchor, // anchor
+ iconScale // scaledSize
+ );
+
+ // Look for a predictable shadow
+ var stdRegEx = /\/(red|blue|green|yellow|lightblue|purple|pink|orange)(-dot)?\.png/;
+ var shadowSize = new google.maps.Size(59, 32);
+ var shadowPoint = new google.maps.Point(16, 32);
+ if (stdRegEx.test(icon.href)) {
+ // A standard GMap-style marker icon
+ icon.shadow = new google.maps.MarkerImage(
+ 'http://maps.google.com/mapfiles/ms/micons/msmarker.shadow.png', // url
+ shadowSize, // size
+ null, // origin
+ shadowPoint, // anchor
+ shadowSize // scaledSize
+ );
+ } else if (icon.href.indexOf('-pushpin.png') > -1) {
+ // Pushpin marker icon
+ icon.shadow = new google.maps.MarkerImage(
+ 'http://maps.google.com/mapfiles/ms/micons/pushpin_shadow.png', // url
+ shadowSize, // size
+ null, // origin
+ shadowPoint, // anchor
+ shadowSize // scaledSize
+ );
+ } /* else {
+ // Other MyMaps KML standard icon
+ icon.shadow = new google.maps.MarkerImage(
+ icon.href.replace('.png', '.shadow.png'), // url
+ shadowSize, // size
+ null, // origin
+ anchorPoint, // anchor
+ shadowSize // scaledSize
+ );
+ } */
+ }
+
+ var processStyles = function (doc) {
+ for (var styleID in doc.styles) {
+ processStyleID(doc.styles[styleID]);
+ }
+ };
+
+ var createMarker = function (placemark, doc) {
+ // create a Marker to the map from a placemark KML object
+ var icon = placemark.style.icon;
+
+ if ( !icon.marker && icon.img ) {
+ // yay, single point of failure is holding up multiple markers...
+ icon.markerBacklog = icon.markerBacklog || [];
+ icon.markerBacklog.push([placemark, doc]);
+ return;
+ }
+
+ // Load basic marker properties
+ var markerOptions = geoXML3.combineOptions(parserOptions.markerOptions, {
+ map: parserOptions.map,
+ position: new google.maps.LatLng(placemark.Point.coordinates[0].lat, placemark.Point.coordinates[0].lng),
+ title: placemark.name,
+ zIndex: Math.round(placemark.Point.coordinates[0].lat * -100000)<<5,
+ icon: icon.marker,
+ shadow: icon.shadow,
+ flat: !icon.shadow,
+ visible: placemark.visibility
+ });
+
+ // Create the marker on the map
+ var marker = new google.maps.Marker(markerOptions);
+ if (!!doc) doc.markers.push(marker);
+
+ // Set up and create the infowindow if it is not suppressed
+ createInfoWindow(placemark, doc, marker);
+ placemark.marker = marker;
+ return marker;
+ };
+
+ var createOverlay = function (groundOverlay, doc) {
+ // Add a ProjectedOverlay to the map from a groundOverlay KML object
+
+ if (!window.ProjectedOverlay) {
+ throw 'geoXML3 error: ProjectedOverlay not found while rendering GroundOverlay from KML';
+ }
+
+ var bounds = new google.maps.LatLngBounds(
+ new google.maps.LatLng(groundOverlay.latLonBox.south, groundOverlay.latLonBox.west),
+ new google.maps.LatLng(groundOverlay.latLonBox.north, groundOverlay.latLonBox.east)
+ );
+ var overlayOptions = geoXML3.combineOptions(parserOptions.overlayOptions, {percentOpacity: groundOverlay.opacity*100});
+ var overlay = new ProjectedOverlay(parserOptions.map, groundOverlay.icon.href, bounds, overlayOptions);
+
+ if (!!doc) {
+ doc.ggroundoverlays = doc.ggroundoverlays || [];
+ doc.ggroundoverlays.push(overlay);
+ }
+
+ return overlay;
+ };
+
+ // Create Polyline
+ var createPolyline = function(placemark, doc) {
+ var path = [];
+ for (var j=0; j<placemark.LineString.length; j++) {
+ var coords = placemark.LineString[j].coordinates;
+ var bounds = new google.maps.LatLngBounds();
+ for (var i=0;i<coords.length;i++) {
+ var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
+ path.push(pt);
+ bounds.extend(pt);
+ }
+ }
+ // point to open the infowindow if triggered
+ var point = path[Math.floor(path.length/2)];
+ // Load basic polyline properties
+ var kmlStrokeColor = kmlColor(placemark.style.line.color, placemark.style.line.colorMode);
+ var polyOptions = geoXML3.combineOptions(parserOptions.polylineOptions, {
+ map: parserOptions.map,
+ path: path,
+ strokeColor: kmlStrokeColor.color,
+ strokeWeight: placemark.style.line.width,
+ strokeOpacity: kmlStrokeColor.opacity,
+ title: placemark.name,
+ visible: placemark.visibility
+ });
+ var p = new google.maps.Polyline(polyOptions);
+ p.bounds = bounds;
+
+ // setup and create the infoWindow if it is not suppressed
+ createInfoWindow(placemark, doc, p);
+ if (!!doc) doc.gpolylines.push(p);
+ placemark.polyline = p;
+ return p;
+ }
+
+ // Create Polygon
+ var createPolygon = function(placemark, doc) {
+ var bounds = new google.maps.LatLngBounds();
+ var pathsLength = 0;
+ var paths = [];
+ for (var polygonPart=0;polygonPart<placemark.Polygon.length;polygonPart++) {
+ for (var j=0; j<placemark.Polygon[polygonPart].outerBoundaryIs.length; j++) {
+ var coords = placemark.Polygon[polygonPart].outerBoundaryIs[j].coordinates;
+ var path = [];
+ for (var i=0;i<coords.length;i++) {
+ var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
+ path.push(pt);
+ bounds.extend(pt);
+ }
+ paths.push(path);
+ pathsLength += path.length;
+ }
+ for (var j=0; j<placemark.Polygon[polygonPart].innerBoundaryIs.length; j++) {
+ var coords = placemark.Polygon[polygonPart].innerBoundaryIs[j].coordinates;
+ var path = [];
+ for (var i=0;i<coords.length;i++) {
+ var pt = new google.maps.LatLng(coords[i].lat, coords[i].lng);
+ path.push(pt);
+ bounds.extend(pt);
+ }
+ paths.push(path);
+ pathsLength += path.length;
+ }
+ }
+
+ // Load basic polygon properties
+ var kmlStrokeColor = kmlColor(placemark.style.line.color, placemark.style.line.colorMode);
+ var kmlFillColor = kmlColor(placemark.style.poly.color, placemark.style.poly.colorMode);
+ if (!placemark.style.poly.fill) kmlFillColor.opacity = 0.0;
+ var strokeWeight = placemark.style.line.width;
+ if (!placemark.style.poly.outline) {
+ strokeWeight = 0;
+ kmlStrokeColor.opacity = 0.0;
+ }
+ var polyOptions = geoXML3.combineOptions(parserOptions.polygonOptions, {
+ map: parserOptions.map,
+ paths: paths,
+ title: placemark.name,
+ strokeColor: kmlStrokeColor.color,
+ strokeWeight: strokeWeight,
+ strokeOpacity: kmlStrokeColor.opacity,
+ fillColor: kmlFillColor.color,
+ fillOpacity: kmlFillColor.opacity,
+ visible: placemark.visibility
+ });
+ var p = new google.maps.Polygon(polyOptions);
+ p.bounds = bounds;
+
+ createInfoWindow(placemark, doc, p);
+ if (!!doc) doc.gpolygons.push(p);
+ placemark.polygon = p;
+ return p;
+ }
+
+ var createInfoWindow = function(placemark, doc, gObj) {
+ var bStyle = placemark.style.balloon;
+ var vars = placemark.vars;
+
+ if (!placemark.balloonVisibility || bStyle.displayMode === 'hide') return;
+
+ // define geDirections
+ if (placemark.latlng) {
+ vars.directions.push('sll=' + placemark.latlng.toUrlValue());
+
+ var url = 'http://maps.google.com/maps?' + vars.directions.join('&');
+ var address = encodeURIComponent( vars.val.address || placemark.latlng.toUrlValue() ).replace(/\%20/g, '+');
+
+ vars.val.geDirections = '<a href="' + url + '&daddr=' + address + '" target=_blank>To Here</a> - <a href="' + url + '&saddr=' + address + '" target=_blank>From Here</a>';
+ }
+ else vars.val.geDirections = '';
+
+ // add in the variables
+ var iwText = bStyle.text.replace(/\$\[(\w+(\/displayName)?)\]/g, function(txt, n, dn) { return dn ? vars.display[n] : vars.val[n]; });
+ var classTxt = 'geoxml3_infowindow geoxml3_style_' + placemark.styleID;
+
+ // color styles
+ var styleArr = [];
+ if (bStyle.bgColor != 'ffffffff') styleArr.push('background: ' + kmlColor(bStyle.bgColor ).color + ';');
+ if (bStyle.textColor != 'ff000000') styleArr.push('color: ' + kmlColor(bStyle.textColor).color + ';');
+ var styleProp = styleArr.length ? ' style="' + styleArr.join(' ') + '"' : '';
+
+ var infoWindowOptions = geoXML3.combineOptions(parserOptions.infoWindowOptions, {
+ content: '<div class="' + classTxt + '"' + styleProp + '>' + iwText + '</div>',
+ pixelOffset: new google.maps.Size(0, 2)
+ });
+
+ gObj.infoWindow = parserOptions.infoWindow || new google.maps.InfoWindow(infoWindowOptions);
+ gObj.infoWindowOptions = infoWindowOptions;
+
+ // Info Window-opening event handler
+ google.maps.event.addListener(gObj, 'click', function(e) {
+ var iW = this.infoWindow;
+ iW.close();
+ iW.setOptions(this.infoWindowOptions);
+
+ if (e && e.latLng) iW.setPosition(e.latLng);
+ else if (this.bounds) iW.setPosition(this.bounds.getCenter());
+
+ iW.open(this.map, this.bounds ? null : this);
+ });
+
+ }
+
+ return {
+ // Expose some properties and methods
+
+ options: parserOptions,
+ docs: docs,
+ docsByUrl: docsByUrl,
+ kmzMetaData: kmzMetaData,
+
+ parse: parse,
+ parseKmlString: parseKmlString,
+ hideDocument: hideDocument,
+ showDocument: showDocument,
+ processStyles: processStyles,
+ createMarker: createMarker,
+ createOverlay: createOverlay,
+ createPolyline: createPolyline,
+ createPolygon: createPolygon
+ };
+};
+// End of KML Parser
+
+// Helper objects and functions
+geoXML3.getOpacity = function (kmlColor) {
+ // Extract opacity encoded in a KML color value. Returns a number between 0 and 1.
+ if (!!kmlColor &&
+ (kmlColor !== '') &&
+ (kmlColor.length == 8)) {
+ var transparency = parseInt(kmlColor.substr(0, 2), 16);
+ return transparency / 255;
+ } else {
+ return 1;
+ }
+};
+
+// Log a message to the debugging console, if one exists
+geoXML3.log = function(msg) {
+ if (!!window.console) {
+ console.log(msg);
+ } else { alert("log:"+msg); }
+};
+
+/**
+ * Creates a new parserOptions object.
+ * @class GeoXML3 parser options.
+ * @param {Object} overrides Any options you want to declare outside of the defaults should be included here.
+ * @property {google.maps.Map} map The API map on which geo objects should be rendered.
+ * @property {google.maps.MarkerOptions} markerOptions If the parser is adding Markers to the map itself, any options specified here will be applied to them.
+ * @property {google.maps.InfoWindowOptions} infoWindowOptions If the parser is adding Markers to the map itself, any options specified here will be applied to their attached InfoWindows.
+ * @property {ProjectedOverlay.options} overlayOptions If the parser is adding ProjectedOverlays to the map itself, any options specified here will be applied to them.
+ */
+geoXML3.parserOptions = function (overrides) {
+ this.map = null,
+ /** If true, the parser will automatically move the map to a best-fit of the geodata after parsing of a KML document completes.
+ * @type Boolean
+ * @default true
+ */
+ this.zoom = true,
+ /**#@+ @type Boolean
+ * @default false */
+ /** If true, only a single Marker created by the parser will be able to have its InfoWindow open at once (simulating the behavior of GMaps API v2). */
+ this.singleInfoWindow = false,
+ /** If true, suppresses the rendering of info windows. */
+ this.suppressInfoWindows = false,
+ /**
+ * Control whether to process styles now or later.
+ *
+ * <p>By default, the parser only processes KML &lt;Style&gt; elements into their GMaps equivalents
+ * if it will be creating its own Markers (the createMarker option is null). Setting this option
+ * to true will force such processing to happen anyway, useful if you're going to be calling parser.createMarker
+ * yourself later. OTOH, leaving this option false removes runtime dependency on the GMaps API, enabling
+ * the use of geoXML3 as a standalone KML parser.</p>
+ */
+ this.processStyles = false,
+ /**#@-*/
+
+ this.markerOptions = {},
+ this.infoWindowOptions = {},
+ this.overlayOptions = {},
+
+ /**#@+ @event */
+ /** This function will be called when parsing of a KML document is complete.
+ * @param {geoXML3.parser#docs} doc Parsed KML data. */
+ this.afterParse = null,
+ /** This function will be called when parsing of a KML document is complete.
+ * @param {geoXML3.parser#docs} doc Parsed KML data. */
+ this.failedParse = null,
+ /**
+ * If supplied, this function will be called once for each marker <Placemark> in the KML document, instead of the parser adding its own Marker to the map.
+ * @param {geoXML3.parser.render#placemark} placemark Placemark object.
+ * @param {geoXML3.parser#docs} doc Parsed KML data.
+ */
+ this.createMarker = null,
+ /**
+ * If supplied, this function will be called once for each <GroundOverlay> in the KML document, instead of the parser adding its own ProjectedOverlay to the map.
+ * @param {geoXML3.parser.render#groundOverlay} groundOverlay GroundOverlay object.
+ * @param {geoXML3.parser#docs} doc Parsed KML data.
+ */
+ this.createOverlay = null
+ /**#@-*/
+
+ if (overrides) {
+ for (var prop in overrides) {
+ if (overrides.hasOwnProperty(prop)) this[prop] = overrides[prop];
+ }
+ }
+ return this;
+};
+
+/**
+ * Combine two options objects: a set of default values and a set of override values.
+ *
+ * @deprecated This has been replaced with {@link geoXML3.parserOptions#combineOptions}.
+ * @param {geoXML3.parserOptions|Object} overrides Override values.
+ * @param {geoXML3.parserOptions|Object} defaults Default values.
+ * @return {geoXML3.parserOptions} Combined result.
+ */
+geoXML3.combineOptions = function (overrides, defaults) {
+ var result = {};
+ if (!!overrides) {
+ for (var prop in overrides) {
+ if (overrides.hasOwnProperty(prop)) result[prop] = overrides[prop];
+ }
+ }
+ if (!!defaults) {
+ for (prop in defaults) {
+ if (defaults.hasOwnProperty(prop) && result[prop] === undefined) result[prop] = defaults[prop];
+ }
+ }
+ return result;
+};
+
+/**
+ * Combine two options objects: a set of default values and a set of override values.
+ *
+ * @function
+ * @param {geoXML3.parserOptions|Object} overrides Override values.
+ * @param {geoXML3.parserOptions|Object} defaults Default values.
+ * @return {geoXML3.parserOptions} Combined result.
+ */
+geoXML3.parserOptions.prototype.combineOptions = geoXML3.combineOptions;
+
+// Retrieve an XML document from url and pass it to callback as a DOM document
+geoXML3.fetchers = [];
+
+/**
+ * Parses a XML string.
+ *
+ * <p>Parses the given XML string and returns the parsed document in a
+ * DOM data structure. This function will return an empty DOM node if
+ * XML parsing is not supported in this browser.</p>
+ *
+ * @param {String} str XML string.
+ * @return {Element|Document} DOM.
+ */
+geoXML3.xmlParse = function (str) {
+ if (!!window.DOMParser) return (new DOMParser()).parseFromString(str, 'text/xml');
+ else if (!!window.ActiveXObject) {
+ var doc;
+
+ // the many versions of IE's DOM parsers
+ var AXOs = [
+ 'MSXML2.DOMDocument.6.0',
+ 'MSXML2.DOMDocument.5.0',
+ 'MSXML2.DOMDocument.4.0',
+ 'MSXML2.DOMDocument.3.0',
+ 'MSXML2.DOMDocument',
+ 'Microsoft.XMLDOM',
+ 'MSXML.DOMDocument'
+ ];
+ for (var i = 0; i < AXOs.length; i++) {
+ try { doc = new ActiveXObject(AXOs[i]); break; }
+ catch(e) { continue; }
+ }
+ if (!doc) return createElement('div', null);
+
+ if (doc.async) doc.async = false;
+ doc.loadXML(str);
+ return doc;
+ }
+
+ return createElement('div', null);
+}
+
+/**
+ * Fetches a XML document.
+ *
+ * <p>Fetches/parses the given XML URL and passes the parsed document (in a
+ * DOM data structure) to the given callback. Documents are downloaded
+ * and parsed asynchronously.</p>
+ *
+ * @param {String} url URL of XML document. Must be uncompressed XML only.
+ * @param {Function(Document)} callback Function to call when the document is processed.
+ */
+geoXML3.fetchXML = function (url, callback) {
+ function timeoutHandler() { callback(); };
+
+ var xhrFetcher = new Object();
+ if (!!geoXML3.fetchers.length) xhrFetcher = geoXML3.fetchers.pop();
+ else if (!!window.XMLHttpRequest) xhrFetcher.fetcher = new window.XMLHttpRequest(); // Most browsers
+ else if (!!window.ActiveXObject) { // Some IE
+ // the many versions of IE's XML fetchers
+ var AXOs = [
+ 'MSXML2.XMLHTTP.6.0',
+ 'MSXML2.XMLHTTP.5.0',
+ 'MSXML2.XMLHTTP.4.0',
+ 'MSXML2.XMLHTTP.3.0',
+ 'MSXML2.XMLHTTP',
+ 'Microsoft.XMLHTTP',
+ 'MSXML.XMLHTTP'
+ ];
+ for (var i = 0; i < AXOs.length; i++) {
+ try { xhrFetcher.fetcher = new ActiveXObject(AXOs[i]); break; }
+ catch(e) { continue; }
+ }
+ if (!xhrFetcher.fetcher) {
+ geoXML3.log('Unable to create XHR object');
+ callback(null);
+ return null;
+ }
+ }
+
+ if (!!xhrFetcher.fetcher.overrideMimeType) xhrFetcher.fetcher.overrideMimeType('text/xml');
+ xhrFetcher.fetcher.open('GET', url, true);
+ xhrFetcher.fetcher.onreadystatechange = function () {
+ if (xhrFetcher.fetcher.readyState === 4) {
+ // Retrieval complete
+ if (!!xhrFetcher.xhrtimeout) clearTimeout(xhrFetcher.xhrtimeout);
+ if (xhrFetcher.fetcher.status >= 400) {
+ geoXML3.log('HTTP error ' + xhrFetcher.fetcher.status + ' retrieving ' + url);
+ callback();
+ }
+ // Returned successfully
+ else {
+ if (xhrFetcher.fetcher.responseXML) {
+ // Sometimes IE will get the data, but won't bother loading it as an XML doc
+ var xmlDoc = xhrFetcher.fetcher.responseXML;
+ if (xmlDoc && !xmlDoc.documentElement && !xmlDoc.ownerElement) xmlDoc.loadXML(xhrFetcher.fetcher.responseText);
+ callback(xmlDoc);
+ } else // handle valid xml sent with wrong MIME type
+ callback(geoXML3.xmlParse(xhrFetcher.fetcher.responseText));
+ }
+
+ // We're done with this fetcher object
+ geoXML3.fetchers.push(xhrFetcher);
+ }
+ };
+
+ xhrFetcher.xhrtimeout = setTimeout(timeoutHandler, 60000);
+ xhrFetcher.fetcher.send(null);
+ return null;
+};
+
+/**
+ * Fetches a KMZ document.
+ *
+ * <p>Fetches/parses the given ZIP URL, parses each image file, and passes
+ * the parsed KML document to the given callback. Documents are downloaded
+ * and parsed asynchronously, though the KML file is always passed after the
+ * images have been processed, in case the callback requires the image data.</p>
+ *
+ * @requires ZipFile.complete.js
+ * @param {String} url URL of KMZ document. Must be a valid KMZ/ZIP archive.
+ * @param {Function(Document)} callback Function to call when the document is processed.
+ * @param {geoXML3.parser} parser A geoXML3.parser object. This is used to populate the KMZ image data.
+ * @author Brendan Byrd
+ * @see http://code.google.com/apis/kml/documentation/kmzarchives.html
+ */
+geoXML3.fetchZIP = function (url, callback, parser) {
+ // Just need a single 'new' declaration with a really long function...
+ var zipFile = new ZipFile(url, function (zip) {
+ // Retrieval complete
+
+ // Check for ERRORs in zip.status
+ for (var i = 0; i < zip.status.length; i++) {
+ var msg = zip.status[i];
+ if (msg.indexOf("ERROR") == 0) {
+ geoXML3.log('HTTP/ZIP error retrieving ' + url + ': ' + msg);
+ callback();
+ return;
+ }
+ else if (msg.indexOf("WARNING") == 0) { // non-fatal, but still might be useful
+ geoXML3.log('HTTP/ZIP warning retrieving ' + url + ': ' + msg);
+ }
+ }
+
+ // Make sure KMZ structure is according to spec (with a single KML file in the root dir)
+ var KMLCount = 0;
+ var KML;
+ for (var i = 0; i < zip.entries.length; i++) {
+ var name = zip.entries[i].name;
+ if (!/\.kml$/.test(name)) continue;
+
+ KMLCount++;
+ if (KMLCount == 1) KML = i;
+ else {
+ geoXML3.log('KMZ warning retrieving ' + url + ': found extra KML "' + name + '" in KMZ; discarding...');
+ }
+ }
+
+ // Returned successfully, but still needs extracting
+ var baseUrl = cleanURL(defileURL(url), url) + '/';
+ var kmlProcessing = { // this is an object just so it gets passed properly
+ timer: null,
+ extractLeft: 0,
+ timerCalls: 0
+ };
+ var extractCb = function(entry, entryContent) {
+ var mdUrl = cleanURL(baseUrl, entry.name);
+ var ext = entry.name.substring(entry.name.lastIndexOf(".") + 1).toLowerCase();
+ kmlProcessing.extractLeft--;
+
+ if ((typeof entryContent.description == "string") && (entryContent.name == "Error")) {
+ geoXML3.log('KMZ error extracting ' + mdUrl + ': ' + entryContent.description);
+ callback();
+ return;
+ }
+
+ // MIME types that can be used in KML
+ var mime;
+ if (ext === 'jpg') ext = 'jpeg';
+ if (/^(gif|jpeg|png)$/.test(ext)) mime = 'image/' + ext;
+ else if (ext === 'mp3') mime = 'audio/mpeg';
+ else if (ext === 'm4a') mime = 'audio/mp4';
+ else if (ext === 'm4a') mime = 'audio/MP4-LATM';
+ else mime = 'application/octet-stream';
+
+ parser.kmzMetaData[mdUrl] = {};
+ parser.kmzMetaData[mdUrl].entry = entry;
+ // data:image/gif;base64,R0lGODlhEAAOALMA...
+ parser.kmzMetaData[mdUrl].dataUrl = 'data:' + mime + ';base64,' + base64Encode(entryContent);
+
+ // IE cannot handle GET requests beyond 2071 characters, even if it's an inline image
+ if (/msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent) && parser.kmzMetaData[mdUrl].dataUrl.length > 2071)
+ parser.kmzMetaData[mdUrl].dataUrl =
+ // this is a simple IE icon; to hint at the problem...
+ 'data:image/gif;base64,R0lGODlhDwAQAOMPADBPvSpQ1Dpoyz1p6FhwvU2A6ECP63CM04CWxYCk+V6x+UK++Jao3rvC3fj7+v///yH5BAEKAA8ALAAAAAAPABAAAASC8Mk5mwCAUMlWwcLRHEelLA' +
+ 'oGDMgzSsiyGCAhCETDPMh5XQCBwYBrNBIKWmg0MCQHj8MJU5IoroYCY6AAAgrDIbbQDGIK6DR5UPhlNo0JAlSUNAiDgH7eNAxEDWAKCQM2AAFheVxYAA0AIkFOJ1gBcQQaUQKKA5w7LpcEBwkJaKMUEQA7';
+ };
+ var kmlExtractCb = function(entry, entryContent) {
+ if ((typeof entryContent.description == "string") && (entryContent.name == "Error")) {
+ geoXML3.log('KMZ error extracting ' + mdUrl + ': ' + entryContent.description);
+ callback();
+ return;
+ }
+
+ // check to see if the KML is the last file extracted
+ clearTimeout(kmlProcessing.timer);
+ if (kmlProcessing.extractLeft <= 1) {
+ kmlProcessing.extractLeft--;
+ callback(geoXML3.xmlParse(entryContent));
+ return;
+ }
+ else {
+ // KML file isn't last yet; it may need to use those files, so wait a bit (100ms)
+ kmlProcessing.timerCalls++;
+ if (kmlProcessing.timerCalls < 100) {
+ kmlProcessing.timer = setTimeout(function() { kmlExtractCb(entry, entryContent); }, 100);
+ }
+ else {
+ geoXML3.log('KMZ warning extracting ' + url + ': entire ZIP has not been extracted after 10 seconds; running through KML, anyway...');
+ kmlProcessing.extractLeft--;
+ callback(geoXML3.xmlParse(entryContent));
+ }
+ }
+ return;
+ };
+ for (var i = 0; i < zip.entries.length; i++) {
+ var entry = zip.entries[i];
+ var ext = entry.name.substring(entry.name.lastIndexOf(".") + 1).toLowerCase();
+ if (!/^(gif|jpe?g|png|kml)$/.test(ext)) continue; // not going to bother to extract files we don't support
+ if (ext === "kml" && i != KML) continue; // extra KMLs get discarded
+ if (!parser && ext != "kml") continue; // cannot store images without a parser object
+
+ // extract asynchronously
+ kmlProcessing.extractLeft++;
+ if (ext === "kml") entry.extract(kmlExtractCb);
+ else entry.extract(extractCb);
+ }
+ });
+
+};
+
+/**
+ * Extract the text value of a DOM node, with leading and trailing whitespace trimmed.
+ *
+ * @param {Element} node XML node/element.
+ * @param {Any} delVal Default value if the node doesn't exist.
+ * @return {String|Null}
+ */
+geoXML3.nodeValue = function(node, defVal) {
+ var retStr="";
+ if (!node) {
+ return (typeof defVal === 'undefined' || defVal === null) ? null : defVal;
+ }
+ if(node.nodeType==3||node.nodeType==4||node.nodeType==2){
+ retStr+=node.nodeValue;
+ }else if(node.nodeType==1||node.nodeType==9||node.nodeType==11){
+ for(var i=0;i<node.childNodes.length;++i){
+ retStr+=arguments.callee(node.childNodes[i]);
+ }
+ }
+ return retStr;
+};
+
+/**
+ * Loosely translate various values of a DOM node to a boolean.
+ *
+ * @param {Element} node XML node/element.
+ * @param {Boolean} delVal Default value if the node doesn't exist.
+ * @return {Boolean|Null}
+ */
+geoXML3.getBooleanValue = function(node, defVal) {
+ var nodeContents = geoXML3.nodeValue(node);
+ if (nodeContents === null) return defVal || false;
+ nodeContents = parseInt(nodeContents);
+ if (isNaN(nodeContents)) return true;
+ if (nodeContents == 0) return false;
+ else return true;
+}
+
+/**
+ * Browser-normalized version of getElementsByTagNameNS.
+ *
+ * <p>Required because IE8 doesn't define it.</p>
+ *
+ * @param {Element|Document} node DOM object.
+ * @param {String} namespace Full namespace URL to search against.
+ * @param {String} tagname XML local tag name.
+ * @return {Array of Elements}
+ * @author Brendan Byrd
+ */
+geoXML3.getElementsByTagNameNS = function(node, namespace, tagname) {
+ if (node && typeof node.getElementsByTagNameNS != 'undefined') return node.getElementsByTagNameNS(namespace, tagname);
+ if (!node) return [];
+
+ var root = node.documentElement || node.ownerDocument && node.ownerDocument.documentElement;
+ if (!root || !root.attributes) return [];
+
+ // search for namespace prefix
+ for (var i = 0; i < root.attributes.length; i++) {
+ var attr = root.attributes[i];
+ if (attr.prefix === 'xmlns' && attr.nodeValue === namespace) return node.getElementsByTagName(attr.baseName + ':' + tagname);
+ else if (attr.nodeName === 'xmlns' && attr.nodeValue === namespace) {
+ // default namespace
+ if (typeof node.selectNodes != 'undefined') {
+ // Newer IEs have the SelectionNamespace property that can be used with selectNodes
+ if (!root.ownerDocument.getProperty('SelectionNamespaces'))
+ root.ownerDocument.setProperty('SelectionNamespaces', "xmlns:defaultNS='" + namespace + "'");
+ return node.selectNodes('.//defaultNS:' + tagname);
+ }
+ else {
+ // Otherwise, you can still try to tack on the 'xmlns' attribute to root
+ root.setAttribute('xmlns:defaultNS', namespace);
+ return node.getElementsByTagName('defaultNS:' + tagname);
+ }
+ }
+ }
+ return geoXML3.getElementsByTagName(node, tagname); // try the unqualified version
+};
+
+/**
+ * Browser-normalized version of getElementsByTagName.
+ *
+ * <p>Required because MSXML 6.0 will treat this function as a NS-qualified function,
+ * despite the missing NS parameter.</p>
+ *
+ * @param {Element|Document} node DOM object.
+ * @param {String} tagname XML local tag name.
+ * @return {Array of Elements}
+ * @author Brendan Byrd
+ */
+geoXML3.getElementsByTagName = function(node, tagname) {
+ if (node && typeof node.getElementsByTagNameNS != 'undefined') return node.getElementsByTagName(tagname); // if it has both functions, it should be accurate
+// if (node && typeof node.selectNodes != 'undefined') return node.selectNodes(".//*[local-name()='" + tagname + "']");
+ return node.getElementsByTagName(tagname); // hope for the best...
+}
+
+/**
+ * Turn a directory + relative URL into an absolute one.
+ *
+ * @private
+ * @param {String} d Base directory.
+ * @param {String} s Relative URL.
+ * @return {String} Absolute URL.
+ * @author Brendan Byrd
+ */
+var toAbsURL = function (d, s) {
+ var p, f, i;
+ var h = location.protocol + "://" + location.host;
+
+ if (!s.length) return '';
+ if (/^\w+:/.test(s)) return s;
+ if (s.indexOf('/') == 0) return h + s;
+
+ p = d.replace(/\/[^\/]*$/, '');
+ f = s.match(/\.\.\//g);
+ if (f) {
+ s = s.substring(f.length * 3);
+ for (i = f.length; i--;) { p = p.substring(0, p.lastIndexOf('/')); }
+ }
+
+ return h + p + '/' + s;
+}
+
+/**
+ * Remove current host from URL
+ *
+ * @private
+ * @param {String} s Absolute or relative URL.
+ * @return {String} Root-based relative URL.
+ * @author Brendan Byrd
+ */
+var dehostURL = function (s) {
+ var h = location.protocol + "://" + location.host;
+ h = h.replace(/([\.\\\+\*\?\[\^\]\$\(\)])/g, '\\$1'); // quotemeta
+ return s.replace(new RegExp('^' + h, 'i'), '');
+}
+
+/**
+ * Removes all query strings, #IDs, '../' references, and
+ * hosts from a URL.
+ *
+ * @private
+ * @param {String} d Base directory.
+ * @param {String} s Absolute or relative URL.
+ * @return {String} Root-based relative URL.
+ * @author Brendan Byrd
+ */
+var cleanURL = function (d, s) { return dehostURL(toAbsURL(d ? d.split('#')[0].split('?')[0] : defileURL(location.pathname), s ? s.split('#')[0].split('?')[0] : '')); }
+/**
+ * Remove filename from URL
+ *
+ * @private
+ * @param {String} s Relative URL.
+ * @return {String} Base directory.
+ * @author Brendan Byrd
+ */
+var defileURL = function (s) { return s ? s.substr(0, s.lastIndexOf('/') + 1) : '/'; }
+
+
+// Some extra Array subs for ease of use
+// http://stackoverflow.com/questions/143847/best-way-to-find-an-item-in-a-javascript-array
+Array.prototype.hasObject = (
+ !Array.indexOf ? function (obj) {
+ var l = this.length + 1;
+ while (l--) {
+ if (this[l - 1] === obj) return true;
+ }
+ return false;
+ } : function (obj) {
+ return (this.indexOf(obj) !== -1);
+ }
+);
+Array.prototype.hasItemInObj = function (name, item) {
+ var l = this.length + 1;
+ while (l--) {
+ if (this[l - 1][name] === item) return true;
+ }
+ return false;
+};
+if (!Array.prototype.indexOf) {
+ Array.prototype.indexOf = function (obj, fromIndex) {
+ if (fromIndex == null) {
+ fromIndex = 0;
+ } else if (fromIndex < 0) {
+ fromIndex = Math.max(0, this.length + fromIndex);
+ }
+ for (var i = fromIndex, j = this.length; i < j; i++) {
+ if (this[i] === obj) return i;
+ }
+ return -1;
+ };
+}
+Array.prototype.indexOfObjWithItem = function (name, item, fromIndex) {
+ if (fromIndex == null) {
+ fromIndex = 0;
+ } else if (fromIndex < 0) {
+ fromIndex = Math.max(0, this.length + fromIndex);
+ }
+ for (var i = fromIndex, j = this.length; i < j; i++) {
+ if (this[i][name] === item) return i;
+ }
+ return -1;
+};
+
+/**
+ * Borrowed from jquery.base64.js, with some "Array as input" corrections
+ *
+ * @private
+ * @param {Array of charCodes} input An array of byte ASCII codes (0-255).
+ * @return {String} A base64-encoded string.
+ * @author Brendan Byrd
+ */
+var base64Encode = function(input) {
+ var keyString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
+ var output = "";
+ var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
+ var i = 0;
+ while (i < input.length) {
+ chr1 = input[i++];
+ chr2 = input[i++];
+ chr3 = input[i++];
+ enc1 = chr1 >> 2;
+ enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
+ enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
+ enc4 = chr3 & 63;
+
+ if (chr2 == undefined) enc3 = enc4 = 64;
+ else if (chr3 == undefined) enc4 = 64;
+
+ output = output + keyString.charAt(enc1) + keyString.charAt(enc2) + keyString.charAt(enc3) + keyString.charAt(enc4);
+ }
+ return output;
+};
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/README b/www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/README
new file mode 100644
index 00000000..64a2208c
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/README
@@ -0,0 +1,6 @@
+==markerclusterer.js and markerwithlabel.js==
+Is fetched from trunk @ https://github.com/googlemaps/v3-utility-library
+
+On update don't forget to add the following line at the end of the file:
+
+ window.MarkerClusterer = MarkerClusterer;
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/googleearth-compiled.js b/www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/googleearth-compiled.js
new file mode 100644
index 00000000..e1870183
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/googleearth-compiled.js
@@ -0,0 +1,20 @@
+(function(){var j=window,k=document,l=Math;function n(a,b){return a.width=b}function o(a,b){return a.position=b}function p(a,b){return a.height=b}function q(a,b){return a.zIndex=b}var r="appendChild",s="createElement",t="getCoordinates",u="getView",w="pushLatLngAlt",x="setTimeout",y="style",z="addListener",A="getFeatures",B="InfoWindow",C="maps",D="getPosition",E="setStyleSelector",F="earth",H="prototype",I="setGeometry",J="substring",K="parentNode",L="event";
+function M(a){if(!google||!google[F])throw"google.earth not loaded";if(!google[F].isSupported())throw"Google Earth API is not supported on this system";if(!google[F].isInstalled())throw"Google Earth API is not installed on this system";this.l="//maps.google.com/mapfiles/kml/paddle/red-circle.png";this.b=a;this.k=a.getDiv();this.d=!1;this.f="Earth";this.i=[];this.e={};this.j=null;this.g=0;aa(this);ba(this)}j.GoogleEarth=M;M.MAP_TYPE_ID="GoogleEarthAPI";M[H].o=function(){return this.a};
+M[H].getInstance=M[H].o;
+function aa(a){var b=a.b,c={tileSize:new google[C].Size(256,256),maxZoom:19,name:a.f,alt:a.f,getTile:function(a,b,c){return c[s]("DIV")}};b.mapTypes.set("GoogleEarthAPI",c);b.setOptions({mapTypeControlOptions:{mapTypeIds:[google[C].MapTypeId.ROADMAP,google[C].MapTypeId.SATELLITE,"GoogleEarthAPI"]}});google[C][L][z](b,"maptypeid_changed",function(){if(a.b.getMapTypeId()=="GoogleEarthAPI"){var b;a:{b=RegExp("title=['\"]?"+a.f+"[\"']?");for(var c=a.c[K].childNodes,e=0,g;g=c[e];e++)if(b.test(g.innerHTML)){b=g;
+ break a}b=void 0}c=b;e=c[y].zIndex;g=a.c[K].childNodes;for(var h=0,i;i=g[h];h++)i.__gme_ozi=i[y].zIndex,q(i[y],-1);c.__gme_ozi=e;q(a.c[y],q(c[y],0));c=a.h=k[s]("IFRAME");c.src="javascript:false;";c.scrolling="no";c.frameBorder="0";e=c[y];q(e,-1E5);n(e,p(e,"100%"));o(e,"absolute");e.left=e.top=0;b[r](c);a.c[y].display="";a.d=!0;a.a?N(a):ca(a)}else da(a)})}
+function N(a){a.e={};O(a,!0);for(var b=a.a[A]();b.getFirstChild();)b.removeChild(b.getFirstChild());a.g++;for(var b=0,c;c=a.i[b];b++)google[C][L].removeListener(c);b={};c=P;for(var d=0,f;f=c[d];d++)b[f]=Q(a,f);for(c=0;d=b.Marker[c];c++)ea(a,d);for(c=0;d=b.Polygon[c];c++){var e=a,g=d,d=e.a;f=S(e,g);var h=d.createPolygon("");f[I](h);e=T(e,g);f[E](e);e=d.createLinearRing("");h.setOuterBoundary(e);for(var h=e[t](),g=g.getPath().getArray(),e=0,i=void 0;i=g[e];e++)h[w](i.lat(),i.lng(),0);d[A]()[r](f)}for(c=
+ 0;d=b.Polyline[c];c++){h=a;g=d;d=h.a;f=S(h,g);e=d.createLineString("");e.setTessellate(!0);f[I](e);h=T(h,g);f[E](h);h=e[t]();g=g.getPath().getArray();e=0;for(i=void 0;i=g[e];e++)h[w](i.lat(),i.lng(),0);d[A]()[r](f)}for(c=0;d=b.Rectangle[c];c++){var g=a,m=d,d=g.a,h=m.getBounds();f=h.getNorthEast();h=h.getSouthWest();e=S(g,m);e[I](d.createPolygon(""));i=d.createLinearRing("");g=T(g,m);e[E](g);g=i[t]();g[w](f.lat(),f.lng(),0);g[w](f.lat(),h.lng(),0);g[w](h.lat(),h.lng(),0);g[w](h.lat(),f.lng(),0);g[w](f.lat(),
+ f.lng(),0);e.getGeometry().setOuterBoundary(i);e.setName("placemark");d[A]()[r](e)}for(c=0;d=b.Circle[c];c++){i=a;m=d;d=i.a;f=m.getCenter();g=m.getRadius();h=S(i,m);h[I](d.createPolygon(""));e=d.createLinearRing("");i=T(i,m);h[E](i);for(i=0;i<25;i++){var R=f,v=g,m=14.4*i;v/=6378137;m*=l.PI/180;var G=R.lat()*(l.PI/180),R=R.lng()*(l.PI/180),X=l.cos(v),v=l.sin(v),Y=l.sin(G),G=l.cos(G),Z=X*Y+v*G*l.cos(m),m=new google[C].LatLng(l.asin(Z)/(l.PI/180),(R+l.atan2(v*G*l.sin(m),X-Y*Z))/(l.PI/180));e[t]()[w](m.lat(),
+ m.lng(),0)}h.getGeometry().setOuterBoundary(e);h.setName("placemark");d[A]()[r](h)}for(c=0;d=b.KmlLayer[c];c++)fa(a,d.getUrl());for(c=0;d=b.GroundOverlay[c];c++)g=d.getBounds(),f=g.getNorthEast(),g=g.getSouthWest(),h=a.a,e=h.createGroundOverlay(""),e.setLatLonBox(h.createLatLonBox("")),e.getLatLonBox().setBox(f.lat(),g.lat(),f.lng(),g.lng(),0),e.setIcon(h.createIcon("")),e.getIcon().setHref(d.getUrl()),h[A]()[r](e)}
+function O(a,b){var c=a.b.getCenter(),d=a.a.createLookAt("");d.setRange(l.pow(2,27-a.b.getZoom()));d.setLatitude(c.lat());d.setLongitude(c.lng());d.setHeading(0);d.setAltitude(0);var f=a.a;b?(f.getOptions().setFlyToSpeed(5),f[u]().setAbstractView(d),d.setTilt(15),f.getOptions().setFlyToSpeed(0.75),j[x](function(){f[u]().setAbstractView(d)},200),j[x](function(){f.getOptions().setFlyToSpeed(1)},250)):f[u]().setAbstractView(d)}
+function U(a,b){a[0]=="#"&&(a=a[J](1,9));typeof b=="undefined"?b="FF":(b=parseInt(parseFloat(b)*255,10).toString(16),b.length==1&&(b="0"+b));return[b,a[J](4,6),a[J](2,4),a[J](0,2)].join("")}function S(a,b){var c=a.g+"GEV3_"+b.__gme_id;a.e[c]=b;return a.a.createPlacemark(c)}function fa(a,b){var c=a.a;google[F].fetchKml(c,b,function(a){a?c[A]()[r](a):j[x](function(){alert("Bad or null KML.")},0)})}
+function ea(a,b){if(b[D]()){var c=a.a,d=S(a,b);b.getTitle()&&d.setName(b.getTitle());var f=c.createIcon("");b.getIcon()?f.setHref(b.getIcon()):f.setHref(a.l);var e=c.createStyle("");e.getIconStyle().setIcon(f);d[E](e);f=c.createPoint("");f.setLatitude(b[D]().lat());f.setLongitude(b[D]().lng());d[I](f);c[A]()[r](d);a.i.push(google[C][L][z](b,"position_changed",function(){var c=a.g+"GEV3_"+b.__gme_id,d=a.e[c],c=a.a.getElementById(c).getGeometry(),d=d[D]();c.setLatitude(d.lat());c.setLongitude(d.lng())}))}}
+function T(a,b){var c=a.a.createStyle(""),d=c.getPolyStyle(),f=c.getLineStyle();f.setWidth(V(b,"strokeWeight",3));var e=V(b,"strokeOpacity",1),g=V(b,"fillOpacity",0.3),h=V(b,"strokeColor","#000000"),i=V(b,"fillColor","#000000");f.getColor().set(U(h,e));d.getColor().set(U(i,g));return c}function V(a,b,c){a=a.get(b);return typeof a=="undefined"?c:a}
+function ca(a){google[F].createInstance(a.m,function(b){a.a=b;ga(a);N(a)},function(b){ha(a);a.b.setMapTypeId(google[C].MapTypeId.ROADMAP);throw"Google Earth API failed to initialize: "+b;})}
+function ga(a){var b=a.a;b.getWindow().setVisibility(!0);var c=b.getNavigationControl();c.setVisibility(b.VISIBILITY_AUTO);c=c.getScreenXY();c.setYUnits(b.UNITS_INSET_PIXELS);c.setXUnits(b.UNITS_PIXELS);c=b.getLayerRoot();c.enableLayerById(b.LAYER_BORDERS,!0);c.enableLayerById(b.LAYER_ROADS,!0);google[C][L][z](a.b,"GEInfoWindowOpened",function(b){if(a.d){var c=a.a.createHtmlStringBalloon("");c.setFeature(a.j);c.setContentString(b.getContent());a.a.setBalloon(c)}});google[F].addEventListener(b.getGlobe(),
+ "click",function(b){var c=b.getTarget(),e=a.e[c.getId()];if(e){b.preventDefault();for(var b=Q(a,"InfoWindow"),g=0,h;h=b[g];g++)h.close();a.j=c;google[C][L].trigger(e,"click")}})}function ia(a){var b=a.a[u]().copyAsLookAt(a.a.ALTITUDE_RELATIVE_TO_GROUND),c=b.getRange(),c=l.round(27-l.log(c)/l.log(2));!a.b.getZoom()==c?a.b.setZoom(c-1):a.b.setZoom(c);a.b.panTo(new google[C].LatLng(b.getLatitude(),b.getLongitude()))}function da(a){a.d&&(ia(a),j[x](function(){O(a)},50),j[x](function(){ha(a)},2200))}
+function ha(a){for(var b=a.c[K].childNodes,c=0,d;d=b[c];c++)q(d[y],d.__gme_ozi);a.h[K].removeChild(a.h);a.h=null;a.c[y].display="none";a.d=!1}
+function ba(a){var b=a.k,c=a.c=k[s]("DIV");o(c[y],"absolute");n(c[y],0);p(c[y],0);c.index=0;c[y].display="none";var d=a.n=k[s]("DIV");n(d[y],b.clientWidth+"px");p(d[y],b.clientHeight+"px");o(d[y],"absolute");c[r](d);b=a.m=k[s]("DIV");o(b[y],"absolute");n(b[y],"100%");p(b[y],"100%");d[r](b);a.b.controls[google[C].ControlPosition.TOP_LEFT].push(c);google[C][L][z](a.b,"resize",function(){var b=a.n[y],c=a.k;n(b,c.clientWidth+"px");p(b,c.clientHeight+"px")})}
+function Q(a,b){var c=[],d=W[b],f;for(f in d)if(d.hasOwnProperty(f)){var e=d[f];e.get("map")==a.b&&c.push(e)}return c}var W={};function ja(){var a=$,b=google[C][a][H];b.__gme_setMapOriginal=b.setMap;W[a]={};google[C][a][H].setMap=function(b){if(b){if(!this.__gme_id)this.__gme_id=ka++,W[a][this.__gme_id]=this}else delete W[a][this.__gme_id],this.__gme_id=void 0;this.__gme_setMapOriginal(b)}}
+for(var P="Marker,Polyline,Polygon,Rectangle,Circle,KmlLayer,GroundOverlay,InfoWindow".split(","),ka=0,la=P,ma=0,$;$=la[ma];ma++)if(ja(),$=="InfoWindow")google[C][B][H].p=google[C][B][H].open,W.InfoWindow={},google[C][B][H].open=function(a,b){if(a){if(!this.__gme_id)this.__gme_id=ka++,W[B][this.__gme_id]=this}else delete W[B][this.__gme_id],this.__gme_id=void 0;google[C][L].trigger(a,"GEInfoWindowOpened",this);this.p(a,b)};})();
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/markerclusterer.js b/www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/markerclusterer.js
new file mode 100644
index 00000000..e5379c62
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/markerclusterer.js
@@ -0,0 +1,1637 @@
+/*jslint browser: true, confusion: true, sloppy: true, vars: true, nomen: false, plusplus: false, indent: 2 */
+/*global window,google */
+
+/**
+ * @name MarkerClustererPlus for Google Maps V3
+ * @version 2.1.2 [May 28, 2014]
+ * @author Gary Little
+ * @fileoverview
+ * The library creates and manages per-zoom-level clusters for large amounts of markers.
+ * <p>
+ * This is an enhanced V3 implementation of the
+ * <a href="http://gmaps-utility-library-dev.googlecode.com/svn/tags/markerclusterer/"
+ * >V2 MarkerClusterer</a> by Xiaoxi Wu. It is based on the
+ * <a href="http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclusterer/"
+ * >V3 MarkerClusterer</a> port by Luke Mahe. MarkerClustererPlus was created by Gary Little.
+ * <p>
+ * v2.0 release: MarkerClustererPlus v2.0 is backward compatible with MarkerClusterer v1.0. It
+ * adds support for the <code>ignoreHidden</code>, <code>title</code>, <code>batchSizeIE</code>,
+ * and <code>calculator</code> properties as well as support for four more events. It also allows
+ * greater control over the styling of the text that appears on the cluster marker. The
+ * documentation has been significantly improved and the overall code has been simplified and
+ * polished. Very large numbers of markers can now be managed without causing Javascript timeout
+ * errors on Internet Explorer. Note that the name of the <code>clusterclick</code> event has been
+ * deprecated. The new name is <code>click</code>, so please change your application code now.
+ */
+
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/**
+ * @name ClusterIconStyle
+ * @class This class represents the object for values in the <code>styles</code> array passed
+ * to the {@link MarkerClusterer} constructor. The element in this array that is used to
+ * style the cluster icon is determined by calling the <code>calculator</code> function.
+ *
+ * @property {string} url The URL of the cluster icon image file. Required.
+ * @property {number} height The display height (in pixels) of the cluster icon. Required.
+ * @property {number} width The display width (in pixels) of the cluster icon. Required.
+ * @property {Array} [anchorText] The position (in pixels) from the center of the cluster icon to
+ * where the text label is to be centered and drawn. The format is <code>[yoffset, xoffset]</code>
+ * where <code>yoffset</code> increases as you go down from center and <code>xoffset</code>
+ * increases to the right of center. The default is <code>[0, 0]</code>.
+ * @property {Array} [anchorIcon] The anchor position (in pixels) of the cluster icon. This is the
+ * spot on the cluster icon that is to be aligned with the cluster position. The format is
+ * <code>[yoffset, xoffset]</code> where <code>yoffset</code> increases as you go down and
+ * <code>xoffset</code> increases to the right of the top-left corner of the icon. The default
+ * anchor position is the center of the cluster icon.
+ * @property {string} [textColor="black"] The color of the label text shown on the
+ * cluster icon.
+ * @property {number} [textSize=11] The size (in pixels) of the label text shown on the
+ * cluster icon.
+ * @property {string} [textDecoration="none"] The value of the CSS <code>text-decoration</code>
+ * property for the label text shown on the cluster icon.
+ * @property {string} [fontWeight="bold"] The value of the CSS <code>font-weight</code>
+ * property for the label text shown on the cluster icon.
+ * @property {string} [fontStyle="normal"] The value of the CSS <code>font-style</code>
+ * property for the label text shown on the cluster icon.
+ * @property {string} [fontFamily="Arial,sans-serif"] The value of the CSS <code>font-family</code>
+ * property for the label text shown on the cluster icon.
+ * @property {string} [backgroundPosition="0 0"] The position of the cluster icon image
+ * within the image defined by <code>url</code>. The format is <code>"xpos ypos"</code>
+ * (the same format as for the CSS <code>background-position</code> property). You must set
+ * this property appropriately when the image defined by <code>url</code> represents a sprite
+ * containing multiple images. Note that the position <i>must</i> be specified in px units.
+ */
+/**
+ * @name ClusterIconInfo
+ * @class This class is an object containing general information about a cluster icon. This is
+ * the object that a <code>calculator</code> function returns.
+ *
+ * @property {string} text The text of the label to be shown on the cluster icon.
+ * @property {number} index The index plus 1 of the element in the <code>styles</code>
+ * array to be used to style the cluster icon.
+ * @property {string} title The tooltip to display when the mouse moves over the cluster icon.
+ * If this value is <code>undefined</code> or <code>""</code>, <code>title</code> is set to the
+ * value of the <code>title</code> property passed to the MarkerClusterer.
+ */
+/**
+ * A cluster icon.
+ *
+ * @constructor
+ * @extends google.maps.OverlayView
+ * @param {Cluster} cluster The cluster with which the icon is to be associated.
+ * @param {Array} [styles] An array of {@link ClusterIconStyle} defining the cluster icons
+ * to use for various cluster sizes.
+ * @private
+ */
+function ClusterIcon(cluster, styles) {
+ cluster.getMarkerClusterer().extend(ClusterIcon, google.maps.OverlayView);
+
+ this.cluster_ = cluster;
+ this.className_ = cluster.getMarkerClusterer().getClusterClass();
+ this.styles_ = styles;
+ this.center_ = null;
+ this.div_ = null;
+ this.sums_ = null;
+ this.visible_ = false;
+
+ this.setMap(cluster.getMap()); // Note: this causes onAdd to be called
+}
+
+
+/**
+ * Adds the icon to the DOM.
+ */
+ClusterIcon.prototype.onAdd = function () {
+ var cClusterIcon = this;
+ var cMouseDownInCluster;
+ var cDraggingMapByCluster;
+
+ this.div_ = document.createElement("div");
+ this.div_.className = this.className_;
+ if (this.visible_) {
+ this.show();
+ }
+
+ this.getPanes().overlayMouseTarget.appendChild(this.div_);
+
+ // Fix for Issue 157
+ this.boundsChangedListener_ = google.maps.event.addListener(this.getMap(), "bounds_changed", function () {
+ cDraggingMapByCluster = cMouseDownInCluster;
+ });
+
+ google.maps.event.addDomListener(this.div_, "mousedown", function () {
+ cMouseDownInCluster = true;
+ cDraggingMapByCluster = false;
+ });
+
+ google.maps.event.addDomListener(this.div_, "click", function (e) {
+ cMouseDownInCluster = false;
+ if (!cDraggingMapByCluster) {
+ var theBounds;
+ var mz;
+ var mc = cClusterIcon.cluster_.getMarkerClusterer();
+ /**
+ * This event is fired when a cluster marker is clicked.
+ * @name MarkerClusterer#click
+ * @param {Cluster} c The cluster that was clicked.
+ * @event
+ */
+ google.maps.event.trigger(mc, "click", cClusterIcon.cluster_);
+ google.maps.event.trigger(mc, "clusterclick", cClusterIcon.cluster_); // deprecated name
+
+ // The default click handler follows. Disable it by setting
+ // the zoomOnClick property to false.
+ if (mc.getZoomOnClick()) {
+ // Zoom into the cluster.
+ mz = mc.getMaxZoom();
+ theBounds = cClusterIcon.cluster_.getBounds();
+ mc.getMap().fitBounds(theBounds);
+ // There is a fix for Issue 170 here:
+ setTimeout(function () {
+ mc.getMap().fitBounds(theBounds);
+ // Don't zoom beyond the max zoom level
+ if (mz !== null && (mc.getMap().getZoom() > mz)) {
+ mc.getMap().setZoom(mz + 1);
+ }
+ }, 100);
+ }
+
+ // Prevent event propagation to the map:
+ e.cancelBubble = true;
+ if (e.stopPropagation) {
+ e.stopPropagation();
+ }
+ }
+ });
+
+ google.maps.event.addDomListener(this.div_, "mouseover", function () {
+ var mc = cClusterIcon.cluster_.getMarkerClusterer();
+ /**
+ * This event is fired when the mouse moves over a cluster marker.
+ * @name MarkerClusterer#mouseover
+ * @param {Cluster} c The cluster that the mouse moved over.
+ * @event
+ */
+ google.maps.event.trigger(mc, "mouseover", cClusterIcon.cluster_);
+ });
+
+ google.maps.event.addDomListener(this.div_, "mouseout", function () {
+ var mc = cClusterIcon.cluster_.getMarkerClusterer();
+ /**
+ * This event is fired when the mouse moves out of a cluster marker.
+ * @name MarkerClusterer#mouseout
+ * @param {Cluster} c The cluster that the mouse moved out of.
+ * @event
+ */
+ google.maps.event.trigger(mc, "mouseout", cClusterIcon.cluster_);
+ });
+};
+
+
+/**
+ * Removes the icon from the DOM.
+ */
+ClusterIcon.prototype.onRemove = function () {
+ if (this.div_ && this.div_.parentNode) {
+ this.hide();
+ google.maps.event.removeListener(this.boundsChangedListener_);
+ google.maps.event.clearInstanceListeners(this.div_);
+ this.div_.parentNode.removeChild(this.div_);
+ this.div_ = null;
+ }
+};
+
+
+/**
+ * Draws the icon.
+ */
+ClusterIcon.prototype.draw = function () {
+ if (this.visible_) {
+ var pos = this.getPosFromLatLng_(this.center_);
+ this.div_.style.top = pos.y + "px";
+ this.div_.style.left = pos.x + "px";
+ }
+};
+
+
+/**
+ * Hides the icon.
+ */
+ClusterIcon.prototype.hide = function () {
+ if (this.div_) {
+ this.div_.style.display = "none";
+ }
+ this.visible_ = false;
+};
+
+
+/**
+ * Positions and shows the icon.
+ */
+ClusterIcon.prototype.show = function () {
+ if (this.div_) {
+ var img = "";
+ // NOTE: values must be specified in px units
+ var bp = this.backgroundPosition_.split(" ");
+ var spriteH = parseInt(bp[0].replace(/^\s+|\s+$/g, ""), 10);
+ var spriteV = parseInt(bp[1].replace(/^\s+|\s+$/g, ""), 10);
+ var pos = this.getPosFromLatLng_(this.center_);
+ this.div_.style.cssText = this.createCss(pos);
+ img = "<img src='" + this.url_ + "' style='position: absolute; top: " + spriteV + "px; left: " + spriteH + "px; ";
+ if (!this.cluster_.getMarkerClusterer().enableRetinaIcons_) {
+ img += "clip: rect(" + (-1 * spriteV) + "px, " + ((-1 * spriteH) + this.width_) + "px, " +
+ ((-1 * spriteV) + this.height_) + "px, " + (-1 * spriteH) + "px);";
+ }
+ img += "'>";
+ this.div_.innerHTML = img + "<div style='" +
+ "position: absolute;" +
+ "top: " + this.anchorText_[0] + "px;" +
+ "left: " + this.anchorText_[1] + "px;" +
+ "color: " + this.textColor_ + ";" +
+ "font-size: " + this.textSize_ + "px;" +
+ "font-family: " + this.fontFamily_ + ";" +
+ "font-weight: " + this.fontWeight_ + ";" +
+ "font-style: " + this.fontStyle_ + ";" +
+ "text-decoration: " + this.textDecoration_ + ";" +
+ "text-align: center;" +
+ "width: " + this.width_ + "px;" +
+ "line-height:" + this.height_ + "px;" +
+ "'>" + this.sums_.text + "</div>";
+ if (typeof this.sums_.title === "undefined" || this.sums_.title === "") {
+ this.div_.title = this.cluster_.getMarkerClusterer().getTitle();
+ } else {
+ this.div_.title = this.sums_.title;
+ }
+ this.div_.style.display = "";
+ }
+ this.visible_ = true;
+};
+
+
+/**
+ * Sets the icon styles to the appropriate element in the styles array.
+ *
+ * @param {ClusterIconInfo} sums The icon label text and styles index.
+ */
+ClusterIcon.prototype.useStyle = function (sums) {
+ this.sums_ = sums;
+ var index = Math.max(0, sums.index - 1);
+ index = Math.min(this.styles_.length - 1, index);
+ var style = this.styles_[index];
+ this.url_ = style.url;
+ this.height_ = style.height;
+ this.width_ = style.width;
+ this.anchorText_ = style.anchorText || [0, 0];
+ this.anchorIcon_ = style.anchorIcon || [parseInt(this.height_ / 2, 10), parseInt(this.width_ / 2, 10)];
+ this.textColor_ = style.textColor || "black";
+ this.textSize_ = style.textSize || 11;
+ this.textDecoration_ = style.textDecoration || "none";
+ this.fontWeight_ = style.fontWeight || "bold";
+ this.fontStyle_ = style.fontStyle || "normal";
+ this.fontFamily_ = style.fontFamily || "Arial,sans-serif";
+ this.backgroundPosition_ = style.backgroundPosition || "0 0";
+};
+
+
+/**
+ * Sets the position at which to center the icon.
+ *
+ * @param {google.maps.LatLng} center The latlng to set as the center.
+ */
+ClusterIcon.prototype.setCenter = function (center) {
+ this.center_ = center;
+};
+
+
+/**
+ * Creates the cssText style parameter based on the position of the icon.
+ *
+ * @param {google.maps.Point} pos The position of the icon.
+ * @return {string} The CSS style text.
+ */
+ClusterIcon.prototype.createCss = function (pos) {
+ var style = [];
+ style.push("cursor: pointer;");
+ style.push("position: absolute; top: " + pos.y + "px; left: " + pos.x + "px;");
+ style.push("width: " + this.width_ + "px; height: " + this.height_ + "px;");
+ return style.join("");
+};
+
+
+/**
+ * Returns the position at which to place the DIV depending on the latlng.
+ *
+ * @param {google.maps.LatLng} latlng The position in latlng.
+ * @return {google.maps.Point} The position in pixels.
+ */
+ClusterIcon.prototype.getPosFromLatLng_ = function (latlng) {
+ var pos = this.getProjection().fromLatLngToDivPixel(latlng);
+ pos.x -= this.anchorIcon_[1];
+ pos.y -= this.anchorIcon_[0];
+ pos.x = parseInt(pos.x, 10);
+ pos.y = parseInt(pos.y, 10);
+ return pos;
+};
+
+
+/**
+ * Creates a single cluster that manages a group of proximate markers.
+ * Used internally, do not call this constructor directly.
+ * @constructor
+ * @param {MarkerClusterer} mc The <code>MarkerClusterer</code> object with which this
+ * cluster is associated.
+ */
+function Cluster(mc) {
+ this.markerClusterer_ = mc;
+ this.map_ = mc.getMap();
+ this.gridSize_ = mc.getGridSize();
+ this.minClusterSize_ = mc.getMinimumClusterSize();
+ this.averageCenter_ = mc.getAverageCenter();
+ this.markers_ = [];
+ this.center_ = null;
+ this.bounds_ = null;
+ this.clusterIcon_ = new ClusterIcon(this, mc.getStyles());
+}
+
+
+/**
+ * Returns the number of markers managed by the cluster. You can call this from
+ * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
+ * for the <code>MarkerClusterer</code> object.
+ *
+ * @return {number} The number of markers in the cluster.
+ */
+Cluster.prototype.getSize = function () {
+ return this.markers_.length;
+};
+
+
+/**
+ * Returns the array of markers managed by the cluster. You can call this from
+ * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
+ * for the <code>MarkerClusterer</code> object.
+ *
+ * @return {Array} The array of markers in the cluster.
+ */
+Cluster.prototype.getMarkers = function () {
+ return this.markers_;
+};
+
+
+/**
+ * Returns the center of the cluster. You can call this from
+ * a <code>click</code>, <code>mouseover</code>, or <code>mouseout</code> event handler
+ * for the <code>MarkerClusterer</code> object.
+ *
+ * @return {google.maps.LatLng} The center of the cluster.
+ */
+Cluster.prototype.getCenter = function () {
+ return this.center_;
+};
+
+
+/**
+ * Returns the map with which the cluster is associated.
+ *
+ * @return {google.maps.Map} The map.
+ * @ignore
+ */
+Cluster.prototype.getMap = function () {
+ return this.map_;
+};
+
+
+/**
+ * Returns the <code>MarkerClusterer</code> object with which the cluster is associated.
+ *
+ * @return {MarkerClusterer} The associated marker clusterer.
+ * @ignore
+ */
+Cluster.prototype.getMarkerClusterer = function () {
+ return this.markerClusterer_;
+};
+
+
+/**
+ * Returns the bounds of the cluster.
+ *
+ * @return {google.maps.LatLngBounds} the cluster bounds.
+ * @ignore
+ */
+Cluster.prototype.getBounds = function () {
+ var i;
+ var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
+ var markers = this.getMarkers();
+ for (i = 0; i < markers.length; i++) {
+ bounds.extend(markers[i].getPosition());
+ }
+ return bounds;
+};
+
+
+/**
+ * Removes the cluster from the map.
+ *
+ * @ignore
+ */
+Cluster.prototype.remove = function () {
+ this.clusterIcon_.setMap(null);
+ this.markers_ = [];
+ delete this.markers_;
+};
+
+
+/**
+ * Adds a marker to the cluster.
+ *
+ * @param {google.maps.Marker} marker The marker to be added.
+ * @return {boolean} True if the marker was added.
+ * @ignore
+ */
+Cluster.prototype.addMarker = function (marker) {
+ var i;
+ var mCount;
+ var mz;
+
+ if (this.isMarkerAlreadyAdded_(marker)) {
+ return false;
+ }
+
+ if (!this.center_) {
+ this.center_ = marker.getPosition();
+ this.calculateBounds_();
+ } else {
+ if (this.averageCenter_) {
+ var l = this.markers_.length + 1;
+ var lat = (this.center_.lat() * (l - 1) + marker.getPosition().lat()) / l;
+ var lng = (this.center_.lng() * (l - 1) + marker.getPosition().lng()) / l;
+ this.center_ = new google.maps.LatLng(lat, lng);
+ this.calculateBounds_();
+ }
+ }
+
+ marker.isAdded = true;
+ this.markers_.push(marker);
+
+ mCount = this.markers_.length;
+ mz = this.markerClusterer_.getMaxZoom();
+ if (mz !== null && this.map_.getZoom() > mz) {
+ // Zoomed in past max zoom, so show the marker.
+ if (marker.getMap() !== this.map_) {
+ marker.setMap(this.map_);
+ }
+ } else if (mCount < this.minClusterSize_) {
+ // Min cluster size not reached so show the marker.
+ if (marker.getMap() !== this.map_) {
+ marker.setMap(this.map_);
+ }
+ } else if (mCount === this.minClusterSize_) {
+ // Hide the markers that were showing.
+ for (i = 0; i < mCount; i++) {
+ this.markers_[i].setMap(null);
+ }
+ } else {
+ marker.setMap(null);
+ }
+
+ this.updateIcon_();
+ return true;
+};
+
+
+/**
+ * Determines if a marker lies within the cluster's bounds.
+ *
+ * @param {google.maps.Marker} marker The marker to check.
+ * @return {boolean} True if the marker lies in the bounds.
+ * @ignore
+ */
+Cluster.prototype.isMarkerInClusterBounds = function (marker) {
+ return this.bounds_.contains(marker.getPosition());
+};
+
+
+/**
+ * Calculates the extended bounds of the cluster with the grid.
+ */
+Cluster.prototype.calculateBounds_ = function () {
+ var bounds = new google.maps.LatLngBounds(this.center_, this.center_);
+ this.bounds_ = this.markerClusterer_.getExtendedBounds(bounds);
+};
+
+
+/**
+ * Updates the cluster icon.
+ */
+Cluster.prototype.updateIcon_ = function () {
+ var mCount = this.markers_.length;
+ var mz = this.markerClusterer_.getMaxZoom();
+
+ if (mz !== null && this.map_.getZoom() > mz) {
+ this.clusterIcon_.hide();
+ return;
+ }
+
+ if (mCount < this.minClusterSize_) {
+ // Min cluster size not yet reached.
+ this.clusterIcon_.hide();
+ return;
+ }
+
+ var numStyles = this.markerClusterer_.getStyles().length;
+ var sums = this.markerClusterer_.getCalculator()(this.markers_, numStyles);
+ this.clusterIcon_.setCenter(this.center_);
+ this.clusterIcon_.useStyle(sums);
+ this.clusterIcon_.show();
+};
+
+
+/**
+ * Determines if a marker has already been added to the cluster.
+ *
+ * @param {google.maps.Marker} marker The marker to check.
+ * @return {boolean} True if the marker has already been added.
+ */
+Cluster.prototype.isMarkerAlreadyAdded_ = function (marker) {
+ var i;
+ if (this.markers_.indexOf) {
+ return this.markers_.indexOf(marker) !== -1;
+ } else {
+ for (i = 0; i < this.markers_.length; i++) {
+ if (marker === this.markers_[i]) {
+ return true;
+ }
+ }
+ }
+ return false;
+};
+
+
+/**
+ * @name MarkerClustererOptions
+ * @class This class represents the optional parameter passed to
+ * the {@link MarkerClusterer} constructor.
+ * @property {number} [gridSize=60] The grid size of a cluster in pixels. The grid is a square.
+ * @property {number} [maxZoom=null] The maximum zoom level at which clustering is enabled or
+ * <code>null</code> if clustering is to be enabled at all zoom levels.
+ * @property {boolean} [zoomOnClick=true] Whether to zoom the map when a cluster marker is
+ * clicked. You may want to set this to <code>false</code> if you have installed a handler
+ * for the <code>click</code> event and it deals with zooming on its own.
+ * @property {boolean} [averageCenter=false] Whether the position of a cluster marker should be
+ * the average position of all markers in the cluster. If set to <code>false</code>, the
+ * cluster marker is positioned at the location of the first marker added to the cluster.
+ * @property {number} [minimumClusterSize=2] The minimum number of markers needed in a cluster
+ * before the markers are hidden and a cluster marker appears.
+ * @property {boolean} [ignoreHidden=false] Whether to ignore hidden markers in clusters. You
+ * may want to set this to <code>true</code> to ensure that hidden markers are not included
+ * in the marker count that appears on a cluster marker (this count is the value of the
+ * <code>text</code> property of the result returned by the default <code>calculator</code>).
+ * If set to <code>true</code> and you change the visibility of a marker being clustered, be
+ * sure to also call <code>MarkerClusterer.repaint()</code>.
+ * @property {string} [title=""] The tooltip to display when the mouse moves over a cluster
+ * marker. (Alternatively, you can use a custom <code>calculator</code> function to specify a
+ * different tooltip for each cluster marker.)
+ * @property {function} [calculator=MarkerClusterer.CALCULATOR] The function used to determine
+ * the text to be displayed on a cluster marker and the index indicating which style to use
+ * for the cluster marker. The input parameters for the function are (1) the array of markers
+ * represented by a cluster marker and (2) the number of cluster icon styles. It returns a
+ * {@link ClusterIconInfo} object. The default <code>calculator</code> returns a
+ * <code>text</code> property which is the number of markers in the cluster and an
+ * <code>index</code> property which is one higher than the lowest integer such that
+ * <code>10^i</code> exceeds the number of markers in the cluster, or the size of the styles
+ * array, whichever is less. The <code>styles</code> array element used has an index of
+ * <code>index</code> minus 1. For example, the default <code>calculator</code> returns a
+ * <code>text</code> value of <code>"125"</code> and an <code>index</code> of <code>3</code>
+ * for a cluster icon representing 125 markers so the element used in the <code>styles</code>
+ * array is <code>2</code>. A <code>calculator</code> may also return a <code>title</code>
+ * property that contains the text of the tooltip to be used for the cluster marker. If
+ * <code>title</code> is not defined, the tooltip is set to the value of the <code>title</code>
+ * property for the MarkerClusterer.
+ * @property {string} [clusterClass="cluster"] The name of the CSS class defining general styles
+ * for the cluster markers. Use this class to define CSS styles that are not set up by the code
+ * that processes the <code>styles</code> array.
+ * @property {Array} [styles] An array of {@link ClusterIconStyle} elements defining the styles
+ * of the cluster markers to be used. The element to be used to style a given cluster marker
+ * is determined by the function defined by the <code>calculator</code> property.
+ * The default is an array of {@link ClusterIconStyle} elements whose properties are derived
+ * from the values for <code>imagePath</code>, <code>imageExtension</code>, and
+ * <code>imageSizes</code>.
+ * @property {boolean} [enableRetinaIcons=false] Whether to allow the use of cluster icons that
+ * have sizes that are some multiple (typically double) of their actual display size. Icons such
+ * as these look better when viewed on high-resolution monitors such as Apple's Retina displays.
+ * Note: if this property is <code>true</code>, sprites cannot be used as cluster icons.
+ * @property {number} [batchSize=MarkerClusterer.BATCH_SIZE] Set this property to the
+ * number of markers to be processed in a single batch when using a browser other than
+ * Internet Explorer (for Internet Explorer, use the batchSizeIE property instead).
+ * @property {number} [batchSizeIE=MarkerClusterer.BATCH_SIZE_IE] When Internet Explorer is
+ * being used, markers are processed in several batches with a small delay inserted between
+ * each batch in an attempt to avoid Javascript timeout errors. Set this property to the
+ * number of markers to be processed in a single batch; select as high a number as you can
+ * without causing a timeout error in the browser. This number might need to be as low as 100
+ * if 15,000 markers are being managed, for example.
+ * @property {string} [imagePath=MarkerClusterer.IMAGE_PATH]
+ * The full URL of the root name of the group of image files to use for cluster icons.
+ * The complete file name is of the form <code>imagePath</code>n.<code>imageExtension</code>
+ * where n is the image file number (1, 2, etc.).
+ * @property {string} [imageExtension=MarkerClusterer.IMAGE_EXTENSION]
+ * The extension name for the cluster icon image files (e.g., <code>"png"</code> or
+ * <code>"jpg"</code>).
+ * @property {Array} [imageSizes=MarkerClusterer.IMAGE_SIZES]
+ * An array of numbers containing the widths of the group of
+ * <code>imagePath</code>n.<code>imageExtension</code> image files.
+ * (The images are assumed to be square.)
+ */
+/**
+ * Creates a MarkerClusterer object with the options specified in {@link MarkerClustererOptions}.
+ * @constructor
+ * @extends google.maps.OverlayView
+ * @param {google.maps.Map} map The Google map to attach to.
+ * @param {Array.<google.maps.Marker>} [opt_markers] The markers to be added to the cluster.
+ * @param {MarkerClustererOptions} [opt_options] The optional parameters.
+ */
+function MarkerClusterer(map, opt_markers, opt_options) {
+ // MarkerClusterer implements google.maps.OverlayView interface. We use the
+ // extend function to extend MarkerClusterer with google.maps.OverlayView
+ // because it might not always be available when the code is defined so we
+ // look for it at the last possible moment. If it doesn't exist now then
+ // there is no point going ahead :)
+ this.extend(MarkerClusterer, google.maps.OverlayView);
+
+ opt_markers = opt_markers || [];
+ opt_options = opt_options || {};
+
+ this.markers_ = [];
+ this.clusters_ = [];
+ this.listeners_ = [];
+ this.activeMap_ = null;
+ this.ready_ = false;
+
+ this.gridSize_ = opt_options.gridSize || 60;
+ this.minClusterSize_ = opt_options.minimumClusterSize || 2;
+ this.maxZoom_ = opt_options.maxZoom || null;
+ this.styles_ = opt_options.styles || [];
+ this.title_ = opt_options.title || "";
+ this.zoomOnClick_ = true;
+ if (opt_options.zoomOnClick !== undefined) {
+ this.zoomOnClick_ = opt_options.zoomOnClick;
+ }
+ this.averageCenter_ = false;
+ if (opt_options.averageCenter !== undefined) {
+ this.averageCenter_ = opt_options.averageCenter;
+ }
+ this.ignoreHidden_ = false;
+ if (opt_options.ignoreHidden !== undefined) {
+ this.ignoreHidden_ = opt_options.ignoreHidden;
+ }
+ this.enableRetinaIcons_ = false;
+ if (opt_options.enableRetinaIcons !== undefined) {
+ this.enableRetinaIcons_ = opt_options.enableRetinaIcons;
+ }
+ this.imagePath_ = opt_options.imagePath || MarkerClusterer.IMAGE_PATH;
+ this.imageExtension_ = opt_options.imageExtension || MarkerClusterer.IMAGE_EXTENSION;
+ this.imageSizes_ = opt_options.imageSizes || MarkerClusterer.IMAGE_SIZES;
+ this.calculator_ = opt_options.calculator || MarkerClusterer.CALCULATOR;
+ this.batchSize_ = opt_options.batchSize || MarkerClusterer.BATCH_SIZE;
+ this.batchSizeIE_ = opt_options.batchSizeIE || MarkerClusterer.BATCH_SIZE_IE;
+ this.clusterClass_ = opt_options.clusterClass || "cluster";
+
+ if (navigator.userAgent.toLowerCase().indexOf("msie") !== -1) {
+ // Try to avoid IE timeout when processing a huge number of markers:
+ this.batchSize_ = this.batchSizeIE_;
+ }
+
+ this.setupStyles_();
+
+ this.addMarkers(opt_markers, true);
+ this.setMap(map); // Note: this causes onAdd to be called
+}
+
+
+/**
+ * Implementation of the onAdd interface method.
+ * @ignore
+ */
+MarkerClusterer.prototype.onAdd = function () {
+ var cMarkerClusterer = this;
+
+ this.activeMap_ = this.getMap();
+ this.ready_ = true;
+
+ this.repaint();
+
+ // Add the map event listeners
+ this.listeners_ = [
+ google.maps.event.addListener(this.getMap(), "zoom_changed", function () {
+ cMarkerClusterer.resetViewport_(false);
+ // Workaround for this Google bug: when map is at level 0 and "-" of
+ // zoom slider is clicked, a "zoom_changed" event is fired even though
+ // the map doesn't zoom out any further. In this situation, no "idle"
+ // event is triggered so the cluster markers that have been removed
+ // do not get redrawn. Same goes for a zoom in at maxZoom.
+ if (this.getZoom() === (this.get("minZoom") || 0) || this.getZoom() === this.get("maxZoom")) {
+ google.maps.event.trigger(this, "idle");
+ }
+ }),
+ google.maps.event.addListener(this.getMap(), "idle", function () {
+ cMarkerClusterer.redraw_();
+ })
+ ];
+};
+
+
+/**
+ * Implementation of the onRemove interface method.
+ * Removes map event listeners and all cluster icons from the DOM.
+ * All managed markers are also put back on the map.
+ * @ignore
+ */
+MarkerClusterer.prototype.onRemove = function () {
+ var i;
+
+ // Put all the managed markers back on the map:
+ for (i = 0; i < this.markers_.length; i++) {
+ if (this.markers_[i].getMap() !== this.activeMap_) {
+ this.markers_[i].setMap(this.activeMap_);
+ }
+ }
+
+ // Remove all clusters:
+ for (i = 0; i < this.clusters_.length; i++) {
+ this.clusters_[i].remove();
+ }
+ this.clusters_ = [];
+
+ // Remove map event listeners:
+ for (i = 0; i < this.listeners_.length; i++) {
+ google.maps.event.removeListener(this.listeners_[i]);
+ }
+ this.listeners_ = [];
+
+ this.activeMap_ = null;
+ this.ready_ = false;
+};
+
+
+/**
+ * Implementation of the draw interface method.
+ * @ignore
+ */
+MarkerClusterer.prototype.draw = function () {};
+
+
+/**
+ * Sets up the styles object.
+ */
+MarkerClusterer.prototype.setupStyles_ = function () {
+ var i, size;
+ if (this.styles_.length > 0) {
+ return;
+ }
+
+ for (i = 0; i < this.imageSizes_.length; i++) {
+ size = this.imageSizes_[i];
+ this.styles_.push({
+ url: this.imagePath_ + (i + 1) + "." + this.imageExtension_,
+ height: size,
+ width: size
+ });
+ }
+};
+
+
+/**
+ * Fits the map to the bounds of the markers managed by the clusterer.
+ */
+MarkerClusterer.prototype.fitMapToMarkers = function () {
+ var i;
+ var markers = this.getMarkers();
+ var bounds = new google.maps.LatLngBounds();
+ for (i = 0; i < markers.length; i++) {
+ bounds.extend(markers[i].getPosition());
+ }
+
+ this.getMap().fitBounds(bounds);
+};
+
+
+/**
+ * Returns the value of the <code>gridSize</code> property.
+ *
+ * @return {number} The grid size.
+ */
+MarkerClusterer.prototype.getGridSize = function () {
+ return this.gridSize_;
+};
+
+
+/**
+ * Sets the value of the <code>gridSize</code> property.
+ *
+ * @param {number} gridSize The grid size.
+ */
+MarkerClusterer.prototype.setGridSize = function (gridSize) {
+ this.gridSize_ = gridSize;
+};
+
+
+/**
+ * Returns the value of the <code>minimumClusterSize</code> property.
+ *
+ * @return {number} The minimum cluster size.
+ */
+MarkerClusterer.prototype.getMinimumClusterSize = function () {
+ return this.minClusterSize_;
+};
+
+/**
+ * Sets the value of the <code>minimumClusterSize</code> property.
+ *
+ * @param {number} minimumClusterSize The minimum cluster size.
+ */
+MarkerClusterer.prototype.setMinimumClusterSize = function (minimumClusterSize) {
+ this.minClusterSize_ = minimumClusterSize;
+};
+
+
+/**
+ * Returns the value of the <code>maxZoom</code> property.
+ *
+ * @return {number} The maximum zoom level.
+ */
+MarkerClusterer.prototype.getMaxZoom = function () {
+ return this.maxZoom_;
+};
+
+
+/**
+ * Sets the value of the <code>maxZoom</code> property.
+ *
+ * @param {number} maxZoom The maximum zoom level.
+ */
+MarkerClusterer.prototype.setMaxZoom = function (maxZoom) {
+ this.maxZoom_ = maxZoom;
+};
+
+
+/**
+ * Returns the value of the <code>styles</code> property.
+ *
+ * @return {Array} The array of styles defining the cluster markers to be used.
+ */
+MarkerClusterer.prototype.getStyles = function () {
+ return this.styles_;
+};
+
+
+/**
+ * Sets the value of the <code>styles</code> property.
+ *
+ * @param {Array.<ClusterIconStyle>} styles The array of styles to use.
+ */
+MarkerClusterer.prototype.setStyles = function (styles) {
+ this.styles_ = styles;
+};
+
+
+/**
+ * Returns the value of the <code>title</code> property.
+ *
+ * @return {string} The content of the title text.
+ */
+MarkerClusterer.prototype.getTitle = function () {
+ return this.title_;
+};
+
+
+/**
+ * Sets the value of the <code>title</code> property.
+ *
+ * @param {string} title The value of the title property.
+ */
+MarkerClusterer.prototype.setTitle = function (title) {
+ this.title_ = title;
+};
+
+
+/**
+ * Returns the value of the <code>zoomOnClick</code> property.
+ *
+ * @return {boolean} True if zoomOnClick property is set.
+ */
+MarkerClusterer.prototype.getZoomOnClick = function () {
+ return this.zoomOnClick_;
+};
+
+
+/**
+ * Sets the value of the <code>zoomOnClick</code> property.
+ *
+ * @param {boolean} zoomOnClick The value of the zoomOnClick property.
+ */
+MarkerClusterer.prototype.setZoomOnClick = function (zoomOnClick) {
+ this.zoomOnClick_ = zoomOnClick;
+};
+
+
+/**
+ * Returns the value of the <code>averageCenter</code> property.
+ *
+ * @return {boolean} True if averageCenter property is set.
+ */
+MarkerClusterer.prototype.getAverageCenter = function () {
+ return this.averageCenter_;
+};
+
+
+/**
+ * Sets the value of the <code>averageCenter</code> property.
+ *
+ * @param {boolean} averageCenter The value of the averageCenter property.
+ */
+MarkerClusterer.prototype.setAverageCenter = function (averageCenter) {
+ this.averageCenter_ = averageCenter;
+};
+
+
+/**
+ * Returns the value of the <code>ignoreHidden</code> property.
+ *
+ * @return {boolean} True if ignoreHidden property is set.
+ */
+MarkerClusterer.prototype.getIgnoreHidden = function () {
+ return this.ignoreHidden_;
+};
+
+
+/**
+ * Sets the value of the <code>ignoreHidden</code> property.
+ *
+ * @param {boolean} ignoreHidden The value of the ignoreHidden property.
+ */
+MarkerClusterer.prototype.setIgnoreHidden = function (ignoreHidden) {
+ this.ignoreHidden_ = ignoreHidden;
+};
+
+
+/**
+ * Returns the value of the <code>enableRetinaIcons</code> property.
+ *
+ * @return {boolean} True if enableRetinaIcons property is set.
+ */
+MarkerClusterer.prototype.getEnableRetinaIcons = function () {
+ return this.enableRetinaIcons_;
+};
+
+
+/**
+ * Sets the value of the <code>enableRetinaIcons</code> property.
+ *
+ * @param {boolean} enableRetinaIcons The value of the enableRetinaIcons property.
+ */
+MarkerClusterer.prototype.setEnableRetinaIcons = function (enableRetinaIcons) {
+ this.enableRetinaIcons_ = enableRetinaIcons;
+};
+
+
+/**
+ * Returns the value of the <code>imageExtension</code> property.
+ *
+ * @return {string} The value of the imageExtension property.
+ */
+MarkerClusterer.prototype.getImageExtension = function () {
+ return this.imageExtension_;
+};
+
+
+/**
+ * Sets the value of the <code>imageExtension</code> property.
+ *
+ * @param {string} imageExtension The value of the imageExtension property.
+ */
+MarkerClusterer.prototype.setImageExtension = function (imageExtension) {
+ this.imageExtension_ = imageExtension;
+};
+
+
+/**
+ * Returns the value of the <code>imagePath</code> property.
+ *
+ * @return {string} The value of the imagePath property.
+ */
+MarkerClusterer.prototype.getImagePath = function () {
+ return this.imagePath_;
+};
+
+
+/**
+ * Sets the value of the <code>imagePath</code> property.
+ *
+ * @param {string} imagePath The value of the imagePath property.
+ */
+MarkerClusterer.prototype.setImagePath = function (imagePath) {
+ this.imagePath_ = imagePath;
+};
+
+
+/**
+ * Returns the value of the <code>imageSizes</code> property.
+ *
+ * @return {Array} The value of the imageSizes property.
+ */
+MarkerClusterer.prototype.getImageSizes = function () {
+ return this.imageSizes_;
+};
+
+
+/**
+ * Sets the value of the <code>imageSizes</code> property.
+ *
+ * @param {Array} imageSizes The value of the imageSizes property.
+ */
+MarkerClusterer.prototype.setImageSizes = function (imageSizes) {
+ this.imageSizes_ = imageSizes;
+};
+
+
+/**
+ * Returns the value of the <code>calculator</code> property.
+ *
+ * @return {function} the value of the calculator property.
+ */
+MarkerClusterer.prototype.getCalculator = function () {
+ return this.calculator_;
+};
+
+
+/**
+ * Sets the value of the <code>calculator</code> property.
+ *
+ * @param {function(Array.<google.maps.Marker>, number)} calculator The value
+ * of the calculator property.
+ */
+MarkerClusterer.prototype.setCalculator = function (calculator) {
+ this.calculator_ = calculator;
+};
+
+
+/**
+ * Returns the value of the <code>batchSizeIE</code> property.
+ *
+ * @return {number} the value of the batchSizeIE property.
+ */
+MarkerClusterer.prototype.getBatchSizeIE = function () {
+ return this.batchSizeIE_;
+};
+
+
+/**
+ * Sets the value of the <code>batchSizeIE</code> property.
+ *
+ * @param {number} batchSizeIE The value of the batchSizeIE property.
+ */
+MarkerClusterer.prototype.setBatchSizeIE = function (batchSizeIE) {
+ this.batchSizeIE_ = batchSizeIE;
+};
+
+
+/**
+ * Returns the value of the <code>clusterClass</code> property.
+ *
+ * @return {string} the value of the clusterClass property.
+ */
+MarkerClusterer.prototype.getClusterClass = function () {
+ return this.clusterClass_;
+};
+
+
+/**
+ * Sets the value of the <code>clusterClass</code> property.
+ *
+ * @param {string} clusterClass The value of the clusterClass property.
+ */
+MarkerClusterer.prototype.setClusterClass = function (clusterClass) {
+ this.clusterClass_ = clusterClass;
+};
+
+
+/**
+ * Returns the array of markers managed by the clusterer.
+ *
+ * @return {Array} The array of markers managed by the clusterer.
+ */
+MarkerClusterer.prototype.getMarkers = function () {
+ return this.markers_;
+};
+
+
+/**
+ * Returns the number of markers managed by the clusterer.
+ *
+ * @return {number} The number of markers.
+ */
+MarkerClusterer.prototype.getTotalMarkers = function () {
+ return this.markers_.length;
+};
+
+
+/**
+ * Returns the current array of clusters formed by the clusterer.
+ *
+ * @return {Array} The array of clusters formed by the clusterer.
+ */
+MarkerClusterer.prototype.getClusters = function () {
+ return this.clusters_;
+};
+
+
+/**
+ * Returns the number of clusters formed by the clusterer.
+ *
+ * @return {number} The number of clusters formed by the clusterer.
+ */
+MarkerClusterer.prototype.getTotalClusters = function () {
+ return this.clusters_.length;
+};
+
+
+/**
+ * Adds a marker to the clusterer. The clusters are redrawn unless
+ * <code>opt_nodraw</code> is set to <code>true</code>.
+ *
+ * @param {google.maps.Marker} marker The marker to add.
+ * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
+ */
+MarkerClusterer.prototype.addMarker = function (marker, opt_nodraw) {
+ this.pushMarkerTo_(marker);
+ if (!opt_nodraw) {
+ this.redraw_();
+ }
+};
+
+
+/**
+ * Adds an array of markers to the clusterer. The clusters are redrawn unless
+ * <code>opt_nodraw</code> is set to <code>true</code>.
+ *
+ * @param {Array.<google.maps.Marker>} markers The markers to add.
+ * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
+ */
+MarkerClusterer.prototype.addMarkers = function (markers, opt_nodraw) {
+ var key;
+ for (key in markers) {
+ if (markers.hasOwnProperty(key)) {
+ this.pushMarkerTo_(markers[key]);
+ }
+ }
+ if (!opt_nodraw) {
+ this.redraw_();
+ }
+};
+
+
+/**
+ * Pushes a marker to the clusterer.
+ *
+ * @param {google.maps.Marker} marker The marker to add.
+ */
+MarkerClusterer.prototype.pushMarkerTo_ = function (marker) {
+ // If the marker is draggable add a listener so we can update the clusters on the dragend:
+ if (marker.getDraggable()) {
+ var cMarkerClusterer = this;
+ google.maps.event.addListener(marker, "dragend", function () {
+ if (cMarkerClusterer.ready_) {
+ this.isAdded = false;
+ cMarkerClusterer.repaint();
+ }
+ });
+ }
+ marker.isAdded = false;
+ this.markers_.push(marker);
+};
+
+
+/**
+ * Removes a marker from the cluster. The clusters are redrawn unless
+ * <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if the
+ * marker was removed from the clusterer.
+ *
+ * @param {google.maps.Marker} marker The marker to remove.
+ * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
+ * @return {boolean} True if the marker was removed from the clusterer.
+ */
+MarkerClusterer.prototype.removeMarker = function (marker, opt_nodraw) {
+ var removed = this.removeMarker_(marker);
+
+ if (!opt_nodraw && removed) {
+ this.repaint();
+ }
+
+ return removed;
+};
+
+
+/**
+ * Removes an array of markers from the cluster. The clusters are redrawn unless
+ * <code>opt_nodraw</code> is set to <code>true</code>. Returns <code>true</code> if markers
+ * were removed from the clusterer.
+ *
+ * @param {Array.<google.maps.Marker>} markers The markers to remove.
+ * @param {boolean} [opt_nodraw] Set to <code>true</code> to prevent redrawing.
+ * @return {boolean} True if markers were removed from the clusterer.
+ */
+MarkerClusterer.prototype.removeMarkers = function (markers, opt_nodraw) {
+ var i, r;
+ var removed = false;
+
+ for (i = 0; i < markers.length; i++) {
+ r = this.removeMarker_(markers[i]);
+ removed = removed || r;
+ }
+
+ if (!opt_nodraw && removed) {
+ this.repaint();
+ }
+
+ return removed;
+};
+
+
+/**
+ * Removes a marker and returns true if removed, false if not.
+ *
+ * @param {google.maps.Marker} marker The marker to remove
+ * @return {boolean} Whether the marker was removed or not
+ */
+MarkerClusterer.prototype.removeMarker_ = function (marker) {
+ var i;
+ var index = -1;
+ if (this.markers_.indexOf) {
+ index = this.markers_.indexOf(marker);
+ } else {
+ for (i = 0; i < this.markers_.length; i++) {
+ if (marker === this.markers_[i]) {
+ index = i;
+ break;
+ }
+ }
+ }
+
+ if (index === -1) {
+ // Marker is not in our list of markers, so do nothing:
+ return false;
+ }
+
+ marker.setMap(null);
+ this.markers_.splice(index, 1); // Remove the marker from the list of managed markers
+ return true;
+};
+
+
+/**
+ * Removes all clusters and markers from the map and also removes all markers
+ * managed by the clusterer.
+ */
+MarkerClusterer.prototype.clearMarkers = function () {
+ this.resetViewport_(true);
+ this.markers_ = [];
+};
+
+
+/**
+ * Recalculates and redraws all the marker clusters from scratch.
+ * Call this after changing any properties.
+ */
+MarkerClusterer.prototype.repaint = function () {
+ var oldClusters = this.clusters_.slice();
+ this.clusters_ = [];
+ this.resetViewport_(false);
+ this.redraw_();
+
+ // Remove the old clusters.
+ // Do it in a timeout to prevent blinking effect.
+ setTimeout(function () {
+ var i;
+ for (i = 0; i < oldClusters.length; i++) {
+ oldClusters[i].remove();
+ }
+ }, 0);
+};
+
+
+/**
+ * Returns the current bounds extended by the grid size.
+ *
+ * @param {google.maps.LatLngBounds} bounds The bounds to extend.
+ * @return {google.maps.LatLngBounds} The extended bounds.
+ * @ignore
+ */
+MarkerClusterer.prototype.getExtendedBounds = function (bounds) {
+ var projection = this.getProjection();
+
+ // Turn the bounds into latlng.
+ var tr = new google.maps.LatLng(bounds.getNorthEast().lat(),
+ bounds.getNorthEast().lng());
+ var bl = new google.maps.LatLng(bounds.getSouthWest().lat(),
+ bounds.getSouthWest().lng());
+
+ // Convert the points to pixels and the extend out by the grid size.
+ var trPix = projection.fromLatLngToDivPixel(tr);
+ trPix.x += this.gridSize_;
+ trPix.y -= this.gridSize_;
+
+ var blPix = projection.fromLatLngToDivPixel(bl);
+ blPix.x -= this.gridSize_;
+ blPix.y += this.gridSize_;
+
+ // Convert the pixel points back to LatLng
+ var ne = projection.fromDivPixelToLatLng(trPix);
+ var sw = projection.fromDivPixelToLatLng(blPix);
+
+ // Extend the bounds to contain the new bounds.
+ bounds.extend(ne);
+ bounds.extend(sw);
+
+ return bounds;
+};
+
+
+/**
+ * Redraws all the clusters.
+ */
+MarkerClusterer.prototype.redraw_ = function () {
+ this.createClusters_(0);
+};
+
+
+/**
+ * Removes all clusters from the map. The markers are also removed from the map
+ * if <code>opt_hide</code> is set to <code>true</code>.
+ *
+ * @param {boolean} [opt_hide] Set to <code>true</code> to also remove the markers
+ * from the map.
+ */
+MarkerClusterer.prototype.resetViewport_ = function (opt_hide) {
+ var i, marker;
+ // Remove all the clusters
+ for (i = 0; i < this.clusters_.length; i++) {
+ this.clusters_[i].remove();
+ }
+ this.clusters_ = [];
+
+ // Reset the markers to not be added and to be removed from the map.
+ for (i = 0; i < this.markers_.length; i++) {
+ marker = this.markers_[i];
+ marker.isAdded = false;
+ if (opt_hide) {
+ marker.setMap(null);
+ }
+ }
+};
+
+
+/**
+ * Calculates the distance between two latlng locations in km.
+ *
+ * @param {google.maps.LatLng} p1 The first lat lng point.
+ * @param {google.maps.LatLng} p2 The second lat lng point.
+ * @return {number} The distance between the two points in km.
+ * @see http://www.movable-type.co.uk/scripts/latlong.html
+ */
+MarkerClusterer.prototype.distanceBetweenPoints_ = function (p1, p2) {
+ var R = 6371; // Radius of the Earth in km
+ var dLat = (p2.lat() - p1.lat()) * Math.PI / 180;
+ var dLon = (p2.lng() - p1.lng()) * Math.PI / 180;
+ var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+ Math.cos(p1.lat() * Math.PI / 180) * Math.cos(p2.lat() * Math.PI / 180) *
+ Math.sin(dLon / 2) * Math.sin(dLon / 2);
+ var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+ var d = R * c;
+ return d;
+};
+
+
+/**
+ * Determines if a marker is contained in a bounds.
+ *
+ * @param {google.maps.Marker} marker The marker to check.
+ * @param {google.maps.LatLngBounds} bounds The bounds to check against.
+ * @return {boolean} True if the marker is in the bounds.
+ */
+MarkerClusterer.prototype.isMarkerInBounds_ = function (marker, bounds) {
+ return bounds.contains(marker.getPosition());
+};
+
+
+/**
+ * Adds a marker to a cluster, or creates a new cluster.
+ *
+ * @param {google.maps.Marker} marker The marker to add.
+ */
+MarkerClusterer.prototype.addToClosestCluster_ = function (marker) {
+ var i, d, cluster, center;
+ var distance = 40000; // Some large number
+ var clusterToAddTo = null;
+ for (i = 0; i < this.clusters_.length; i++) {
+ cluster = this.clusters_[i];
+ center = cluster.getCenter();
+ if (center) {
+ d = this.distanceBetweenPoints_(center, marker.getPosition());
+ if (d < distance) {
+ distance = d;
+ clusterToAddTo = cluster;
+ }
+ }
+ }
+
+ if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)) {
+ clusterToAddTo.addMarker(marker);
+ } else {
+ cluster = new Cluster(this);
+ cluster.addMarker(marker);
+ this.clusters_.push(cluster);
+ }
+};
+
+
+/**
+ * Creates the clusters. This is done in batches to avoid timeout errors
+ * in some browsers when there is a huge number of markers.
+ *
+ * @param {number} iFirst The index of the first marker in the batch of
+ * markers to be added to clusters.
+ */
+MarkerClusterer.prototype.createClusters_ = function (iFirst) {
+ var i, marker;
+ var mapBounds;
+ var cMarkerClusterer = this;
+ if (!this.ready_) {
+ return;
+ }
+
+ // Cancel previous batch processing if we're working on the first batch:
+ if (iFirst === 0) {
+ /**
+ * This event is fired when the <code>MarkerClusterer</code> begins
+ * clustering markers.
+ * @name MarkerClusterer#clusteringbegin
+ * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered.
+ * @event
+ */
+ google.maps.event.trigger(this, "clusteringbegin", this);
+
+ if (typeof this.timerRefStatic !== "undefined") {
+ clearTimeout(this.timerRefStatic);
+ delete this.timerRefStatic;
+ }
+ }
+
+ // Get our current map view bounds.
+ // Create a new bounds object so we don't affect the map.
+ //
+ // See Comments 9 & 11 on Issue 3651 relating to this workaround for a Google Maps bug:
+ if (this.getMap().getZoom() > 3) {
+ mapBounds = new google.maps.LatLngBounds(this.getMap().getBounds().getSouthWest(),
+ this.getMap().getBounds().getNorthEast());
+ } else {
+ mapBounds = new google.maps.LatLngBounds(new google.maps.LatLng(85.02070771743472, -178.48388434375), new google.maps.LatLng(-85.08136444384544, 178.00048865625));
+ }
+ var bounds = this.getExtendedBounds(mapBounds);
+
+ var iLast = Math.min(iFirst + this.batchSize_, this.markers_.length);
+
+ for (i = iFirst; i < iLast; i++) {
+ marker = this.markers_[i];
+ if (!marker.isAdded && this.isMarkerInBounds_(marker, bounds)) {
+ if (!this.ignoreHidden_ || (this.ignoreHidden_ && marker.getVisible())) {
+ this.addToClosestCluster_(marker);
+ }
+ }
+ }
+
+ if (iLast < this.markers_.length) {
+ this.timerRefStatic = setTimeout(function () {
+ cMarkerClusterer.createClusters_(iLast);
+ }, 0);
+ } else {
+ delete this.timerRefStatic;
+
+ /**
+ * This event is fired when the <code>MarkerClusterer</code> stops
+ * clustering markers.
+ * @name MarkerClusterer#clusteringend
+ * @param {MarkerClusterer} mc The MarkerClusterer whose markers are being clustered.
+ * @event
+ */
+ google.maps.event.trigger(this, "clusteringend", this);
+ }
+};
+
+
+/**
+ * Extends an object's prototype by another's.
+ *
+ * @param {Object} obj1 The object to be extended.
+ * @param {Object} obj2 The object to extend with.
+ * @return {Object} The new extended object.
+ * @ignore
+ */
+MarkerClusterer.prototype.extend = function (obj1, obj2) {
+ return (function (object) {
+ var property;
+ for (property in object.prototype) {
+ this.prototype[property] = object.prototype[property];
+ }
+ return this;
+ }).apply(obj1, [obj2]);
+};
+
+
+/**
+ * The default function for determining the label text and style
+ * for a cluster icon.
+ *
+ * @param {Array.<google.maps.Marker>} markers The array of markers represented by the cluster.
+ * @param {number} numStyles The number of marker styles available.
+ * @return {ClusterIconInfo} The information resource for the cluster.
+ * @constant
+ * @ignore
+ */
+MarkerClusterer.CALCULATOR = function (markers, numStyles) {
+ var index = 0;
+ var title = "";
+ var count = markers.length.toString();
+
+ var dv = count;
+ while (dv !== 0) {
+ dv = parseInt(dv / 10, 10);
+ index++;
+ }
+
+ index = Math.min(index, numStyles);
+ return {
+ text: count,
+ index: index,
+ title: title
+ };
+};
+
+
+/**
+ * The number of markers to process in one batch.
+ *
+ * @type {number}
+ * @constant
+ */
+MarkerClusterer.BATCH_SIZE = 2000;
+
+
+/**
+ * The number of markers to process in one batch (IE only).
+ *
+ * @type {number}
+ * @constant
+ */
+MarkerClusterer.BATCH_SIZE_IE = 500;
+
+
+/**
+ * The default root name for the marker cluster images.
+ *
+ * @type {string}
+ * @constant
+ */
+MarkerClusterer.IMAGE_PATH = "../images/m";
+
+
+/**
+ * The default extension name for the marker cluster images.
+ *
+ * @type {string}
+ * @constant
+ */
+MarkerClusterer.IMAGE_EXTENSION = "png";
+
+
+/**
+ * The default array of sizes for the marker cluster images.
+ *
+ * @type {Array.<number>}
+ * @constant
+ */
+MarkerClusterer.IMAGE_SIZES = [53, 56, 66, 78, 90];
+
+window.MarkerClusterer = MarkerClusterer;
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/markerwithlabel.css b/www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/markerwithlabel.css
new file mode 100644
index 00000000..5ba559de
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/markerwithlabel.css
@@ -0,0 +1,13 @@
+.markerwithlabel {
+ color: black;
+ background-color: white;
+ font-family: "Lucida Grande", "Arial", sans-serif;
+ font-size: 12px;
+ text-align: center;
+ white-space: nowrap;
+ padding: 3px;
+ border-radius: 4px;
+ -moz-border-radius: 4px;
+ -webkit-border-radius: 4px;
+ border: 1px solid darkred;
+} \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/markerwithlabel.js b/www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/markerwithlabel.js
new file mode 100644
index 00000000..90efe70e
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/gm3-util-library/markerwithlabel.js
@@ -0,0 +1,568 @@
+/**
+ * @name MarkerWithLabel for V3
+ * @version 1.1.5 [July 11, 2011]
+ * @author Gary Little (inspired by code from Marc Ridey of Google).
+ * @copyright Copyright 2010 Gary Little [gary at luxcentral.com]
+ * @fileoverview MarkerWithLabel extends the Google Maps JavaScript API V3
+ * <code>google.maps.Marker</code> class.
+ * <p>
+ * MarkerWithLabel allows you to define markers with associated labels. As you would expect,
+ * if the marker is draggable, so too will be the label. In addition, a marker with a label
+ * responds to all mouse events in the same manner as a regular marker. It also fires mouse
+ * events and "property changed" events just as a regular marker would. Version 1.1 adds
+ * support for the raiseOnDrag feature introduced in API V3.3.
+ * <p>
+ * If you drag a marker by its label, you can cancel the drag and return the marker to its
+ * original position by pressing the <code>Esc</code> key. This doesn't work if you drag the marker
+ * itself because this feature is not (yet) supported in the <code>google.maps.Marker</code> class.
+ */
+
+/*!
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*jslint browser:true */
+/*global document,google */
+
+/**
+ * This constructor creates a label and associates it with a marker.
+ * It is for the private use of the MarkerWithLabel class.
+ * @constructor
+ * @param {Marker} marker The marker with which the label is to be associated.
+ * @param {string} crossURL The URL of the cross image =.
+ * @param {string} handCursor The URL of the hand cursor.
+ * @private
+ */
+function MarkerLabel_(marker, crossURL, handCursorURL) {
+ this.marker_ = marker;
+ this.handCursorURL_ = marker.handCursorURL;
+
+ this.labelDiv_ = document.createElement("div");
+ this.labelDiv_.style.cssText = "position: absolute; overflow: hidden;";
+
+ // Set up the DIV for handling mouse events in the label. This DIV forms a transparent veil
+ // in the "overlayMouseTarget" pane, a veil that covers just the label. This is done so that
+ // events can be captured even if the label is in the shadow of a google.maps.InfoWindow.
+ // Code is included here to ensure the veil is always exactly the same size as the label.
+ this.eventDiv_ = document.createElement("div");
+ this.eventDiv_.style.cssText = this.labelDiv_.style.cssText;
+
+ // This is needed for proper behavior on MSIE:
+ this.eventDiv_.setAttribute("onselectstart", "return false;");
+ this.eventDiv_.setAttribute("ondragstart", "return false;");
+
+ // Get the DIV for the "X" to be displayed when the marker is raised.
+ this.crossDiv_ = MarkerLabel_.getSharedCross(crossURL);
+}
+
+// MarkerLabel_ inherits from OverlayView:
+MarkerLabel_.prototype = new google.maps.OverlayView();
+
+/**
+ * Returns the DIV for the cross used when dragging a marker when the
+ * raiseOnDrag parameter set to true. One cross is shared with all markers.
+ * @param {string} crossURL The URL of the cross image =.
+ * @private
+ */
+MarkerLabel_.getSharedCross = function (crossURL) {
+ var div;
+ if (typeof MarkerLabel_.getSharedCross.crossDiv === "undefined") {
+ div = document.createElement("img");
+ div.style.cssText = "position: absolute; z-index: 1000002; display: none;";
+ // Hopefully Google never changes the standard "X" attributes:
+ div.style.marginLeft = "-8px";
+ div.style.marginTop = "-9px";
+ div.src = crossURL;
+ MarkerLabel_.getSharedCross.crossDiv = div;
+ }
+ return MarkerLabel_.getSharedCross.crossDiv;
+};
+
+/**
+ * Adds the DIV representing the label to the DOM. This method is called
+ * automatically when the marker's <code>setMap</code> method is called.
+ * @private
+ */
+MarkerLabel_.prototype.onAdd = function () {
+ var me = this;
+ var cMouseIsDown = false;
+ var cDraggingLabel = false;
+ var cSavedZIndex;
+ var cLatOffset, cLngOffset;
+ var cIgnoreClick;
+ var cRaiseEnabled;
+ var cStartPosition;
+ var cStartCenter;
+ // Constants:
+ var cRaiseOffset = 20;
+ var cDraggingCursor = "url(" + this.handCursorURL_ + ")";
+
+ // Stops all processing of an event.
+ //
+ var cAbortEvent = function (e) {
+ if (e.preventDefault) {
+ e.preventDefault();
+ }
+ e.cancelBubble = true;
+ if (e.stopPropagation) {
+ e.stopPropagation();
+ }
+ };
+
+ var cStopBounce = function () {
+ me.marker_.setAnimation(null);
+ };
+
+ this.getPanes().overlayImage.appendChild(this.labelDiv_);
+ this.getPanes().overlayMouseTarget.appendChild(this.eventDiv_);
+ // One cross is shared with all markers, so only add it once:
+ if (typeof MarkerLabel_.getSharedCross.processed === "undefined") {
+ this.getPanes().overlayImage.appendChild(this.crossDiv_);
+ MarkerLabel_.getSharedCross.processed = true;
+ }
+
+ this.listeners_ = [
+ google.maps.event.addDomListener(this.eventDiv_, "mouseover", function (e) {
+ if (me.marker_.getDraggable() || me.marker_.getClickable()) {
+ this.style.cursor = "pointer";
+ google.maps.event.trigger(me.marker_, "mouseover", e);
+ }
+ }),
+ google.maps.event.addDomListener(this.eventDiv_, "mouseout", function (e) {
+ if ((me.marker_.getDraggable() || me.marker_.getClickable()) && !cDraggingLabel) {
+ this.style.cursor = me.marker_.getCursor();
+ google.maps.event.trigger(me.marker_, "mouseout", e);
+ }
+ }),
+ google.maps.event.addDomListener(this.eventDiv_, "mousedown", function (e) {
+ cDraggingLabel = false;
+ if (me.marker_.getDraggable()) {
+ cMouseIsDown = true;
+ this.style.cursor = cDraggingCursor;
+ }
+ if (me.marker_.getDraggable() || me.marker_.getClickable()) {
+ google.maps.event.trigger(me.marker_, "mousedown", e);
+ cAbortEvent(e); // Prevent map pan when starting a drag on a label
+ }
+ }),
+ google.maps.event.addDomListener(document, "mouseup", function (mEvent) {
+ var position;
+ if (cMouseIsDown) {
+ cMouseIsDown = false;
+ me.eventDiv_.style.cursor = "pointer";
+ google.maps.event.trigger(me.marker_, "mouseup", mEvent);
+ }
+ if (cDraggingLabel) {
+ if (cRaiseEnabled) { // Lower the marker & label
+ position = me.getProjection().fromLatLngToDivPixel(me.marker_.getPosition());
+ position.y += cRaiseOffset;
+ me.marker_.setPosition(me.getProjection().fromDivPixelToLatLng(position));
+ // This is not the same bouncing style as when the marker portion is dragged,
+ // but it will have to do:
+ try { // Will fail if running Google Maps API earlier than V3.3
+ me.marker_.setAnimation(google.maps.Animation.BOUNCE);
+ setTimeout(cStopBounce, 1406);
+ } catch (e) {}
+ }
+ me.crossDiv_.style.display = "none";
+ me.marker_.setZIndex(cSavedZIndex);
+ cIgnoreClick = true; // Set flag to ignore the click event reported after a label drag
+ cDraggingLabel = false;
+ mEvent.latLng = me.marker_.getPosition();
+ google.maps.event.trigger(me.marker_, "dragend", mEvent);
+ }
+ }),
+ google.maps.event.addListener(me.marker_.getMap(), "mousemove", function (mEvent) {
+ var position;
+ if (cMouseIsDown) {
+ if (cDraggingLabel) {
+ // Change the reported location from the mouse position to the marker position:
+ mEvent.latLng = new google.maps.LatLng(mEvent.latLng.lat() - cLatOffset, mEvent.latLng.lng() - cLngOffset);
+ position = me.getProjection().fromLatLngToDivPixel(mEvent.latLng);
+ if (cRaiseEnabled) {
+ me.crossDiv_.style.left = position.x + "px";
+ me.crossDiv_.style.top = position.y + "px";
+ me.crossDiv_.style.display = "";
+ position.y -= cRaiseOffset;
+ }
+ me.marker_.setPosition(me.getProjection().fromDivPixelToLatLng(position));
+ if (cRaiseEnabled) { // Don't raise the veil; this hack needed to make MSIE act properly
+ me.eventDiv_.style.top = (position.y + cRaiseOffset) + "px";
+ }
+ google.maps.event.trigger(me.marker_, "drag", mEvent);
+ } else {
+ // Calculate offsets from the click point to the marker position:
+ cLatOffset = mEvent.latLng.lat() - me.marker_.getPosition().lat();
+ cLngOffset = mEvent.latLng.lng() - me.marker_.getPosition().lng();
+ cSavedZIndex = me.marker_.getZIndex();
+ cStartPosition = me.marker_.getPosition();
+ cStartCenter = me.marker_.getMap().getCenter();
+ cRaiseEnabled = me.marker_.get("raiseOnDrag");
+ cDraggingLabel = true;
+ me.marker_.setZIndex(1000000); // Moves the marker & label to the foreground during a drag
+ mEvent.latLng = me.marker_.getPosition();
+ google.maps.event.trigger(me.marker_, "dragstart", mEvent);
+ }
+ }
+ }),
+ google.maps.event.addDomListener(document, "keydown", function (e) {
+ if (cDraggingLabel) {
+ if (e.keyCode === 27) { // Esc key
+ cRaiseEnabled = false;
+ me.marker_.setPosition(cStartPosition);
+ me.marker_.getMap().setCenter(cStartCenter);
+ google.maps.event.trigger(document, "mouseup", e);
+ }
+ }
+ }),
+ google.maps.event.addDomListener(this.eventDiv_, "click", function (e) {
+ if (me.marker_.getDraggable() || me.marker_.getClickable()) {
+ if (cIgnoreClick) { // Ignore the click reported when a label drag ends
+ cIgnoreClick = false;
+ } else {
+ google.maps.event.trigger(me.marker_, "click", e);
+ cAbortEvent(e); // Prevent click from being passed on to map
+ }
+ }
+ }),
+ google.maps.event.addDomListener(this.eventDiv_, "dblclick", function (e) {
+ if (me.marker_.getDraggable() || me.marker_.getClickable()) {
+ google.maps.event.trigger(me.marker_, "dblclick", e);
+ cAbortEvent(e); // Prevent map zoom when double-clicking on a label
+ }
+ }),
+ google.maps.event.addListener(this.marker_, "dragstart", function (mEvent) {
+ if (!cDraggingLabel) {
+ cRaiseEnabled = this.get("raiseOnDrag");
+ }
+ }),
+ google.maps.event.addListener(this.marker_, "drag", function (mEvent) {
+ if (!cDraggingLabel) {
+ if (cRaiseEnabled) {
+ me.setPosition(cRaiseOffset);
+ // During a drag, the marker's z-index is temporarily set to 1000000 to
+ // ensure it appears above all other markers. Also set the label's z-index
+ // to 1000000 (plus or minus 1 depending on whether the label is supposed
+ // to be above or below the marker).
+ me.labelDiv_.style.zIndex = 1000000 + (this.get("labelInBackground") ? -1 : +1);
+ }
+ }
+ }),
+ google.maps.event.addListener(this.marker_, "dragend", function (mEvent) {
+ if (!cDraggingLabel) {
+ if (cRaiseEnabled) {
+ me.setPosition(0); // Also restores z-index of label
+ }
+ }
+ }),
+ google.maps.event.addListener(this.marker_, "position_changed", function () {
+ me.setPosition();
+ }),
+ google.maps.event.addListener(this.marker_, "zindex_changed", function () {
+ me.setZIndex();
+ }),
+ google.maps.event.addListener(this.marker_, "visible_changed", function () {
+ me.setVisible();
+ }),
+ google.maps.event.addListener(this.marker_, "labelvisible_changed", function () {
+ me.setVisible();
+ }),
+ google.maps.event.addListener(this.marker_, "title_changed", function () {
+ me.setTitle();
+ }),
+ google.maps.event.addListener(this.marker_, "labelcontent_changed", function () {
+ me.setContent();
+ }),
+ google.maps.event.addListener(this.marker_, "labelanchor_changed", function () {
+ me.setAnchor();
+ }),
+ google.maps.event.addListener(this.marker_, "labelclass_changed", function () {
+ me.setStyles();
+ }),
+ google.maps.event.addListener(this.marker_, "labelstyle_changed", function () {
+ me.setStyles();
+ })
+ ];
+};
+
+/**
+ * Removes the DIV for the label from the DOM. It also removes all event handlers.
+ * This method is called automatically when the marker's <code>setMap(null)</code>
+ * method is called.
+ * @private
+ */
+MarkerLabel_.prototype.onRemove = function () {
+ var i;
+ this.labelDiv_.parentNode.removeChild(this.labelDiv_);
+ this.eventDiv_.parentNode.removeChild(this.eventDiv_);
+
+ // Remove event listeners:
+ for (i = 0; i < this.listeners_.length; i++) {
+ google.maps.event.removeListener(this.listeners_[i]);
+ }
+};
+
+/**
+ * Draws the label on the map.
+ * @private
+ */
+MarkerLabel_.prototype.draw = function () {
+ this.setContent();
+ this.setTitle();
+ this.setStyles();
+};
+
+/**
+ * Sets the content of the label.
+ * The content can be plain text or an HTML DOM node.
+ * @private
+ */
+MarkerLabel_.prototype.setContent = function () {
+ var content = this.marker_.get("labelContent");
+ if (typeof content.nodeType === "undefined") {
+ this.labelDiv_.innerHTML = content;
+ this.eventDiv_.innerHTML = this.labelDiv_.innerHTML;
+ } else {
+ this.labelDiv_.innerHTML = ""; // Remove current content
+ this.labelDiv_.appendChild(content);
+ content = content.cloneNode(true);
+ this.eventDiv_.appendChild(content);
+ }
+};
+
+/**
+ * Sets the content of the tool tip for the label. It is
+ * always set to be the same as for the marker itself.
+ * @private
+ */
+MarkerLabel_.prototype.setTitle = function () {
+ this.eventDiv_.title = this.marker_.getTitle() || "";
+};
+
+/**
+ * Sets the style of the label by setting the style sheet and applying
+ * other specific styles requested.
+ * @private
+ */
+MarkerLabel_.prototype.setStyles = function () {
+ var i, labelStyle;
+
+ // Apply style values from the style sheet defined in the labelClass parameter:
+ this.labelDiv_.className = this.marker_.get("labelClass");
+ this.eventDiv_.className = this.labelDiv_.className;
+
+ // Clear existing inline style values:
+ this.labelDiv_.style.cssText = "";
+ this.eventDiv_.style.cssText = "";
+ // Apply style values defined in the labelStyle parameter:
+ labelStyle = this.marker_.get("labelStyle");
+ for (i in labelStyle) {
+ if (labelStyle.hasOwnProperty(i)) {
+ this.labelDiv_.style[i] = labelStyle[i];
+ this.eventDiv_.style[i] = labelStyle[i];
+ }
+ }
+ this.setMandatoryStyles();
+};
+
+/**
+ * Sets the mandatory styles to the DIV representing the label as well as to the
+ * associated event DIV. This includes setting the DIV position, z-index, and visibility.
+ * @private
+ */
+MarkerLabel_.prototype.setMandatoryStyles = function () {
+ this.labelDiv_.style.position = "absolute";
+ this.labelDiv_.style.overflow = "hidden";
+ // Make sure the opacity setting causes the desired effect on MSIE:
+ if (typeof this.labelDiv_.style.opacity !== "undefined" && this.labelDiv_.style.opacity !== "") {
+ this.labelDiv_.style.filter = "alpha(opacity=" + (this.labelDiv_.style.opacity * 100) + ")";
+ }
+
+ this.eventDiv_.style.position = this.labelDiv_.style.position;
+ this.eventDiv_.style.overflow = this.labelDiv_.style.overflow;
+ this.eventDiv_.style.opacity = 0.01; // Don't use 0; DIV won't be clickable on MSIE
+ this.eventDiv_.style.filter = "alpha(opacity=1)"; // For MSIE
+
+ this.setAnchor();
+ this.setPosition(); // This also updates z-index, if necessary.
+ this.setVisible();
+};
+
+/**
+ * Sets the anchor point of the label.
+ * @private
+ */
+MarkerLabel_.prototype.setAnchor = function () {
+ var anchor = this.marker_.get("labelAnchor");
+ this.labelDiv_.style.marginLeft = -anchor.x + "px";
+ this.labelDiv_.style.marginTop = -anchor.y + "px";
+ this.eventDiv_.style.marginLeft = -anchor.x + "px";
+ this.eventDiv_.style.marginTop = -anchor.y + "px";
+};
+
+/**
+ * Sets the position of the label. The z-index is also updated, if necessary.
+ * @private
+ */
+MarkerLabel_.prototype.setPosition = function (yOffset) {
+ var position = this.getProjection().fromLatLngToDivPixel(this.marker_.getPosition());
+ if (typeof yOffset === "undefined") {
+ yOffset = 0;
+ }
+ this.labelDiv_.style.left = Math.round(position.x) + "px";
+ this.labelDiv_.style.top = Math.round(position.y - yOffset) + "px";
+ this.eventDiv_.style.left = this.labelDiv_.style.left;
+ this.eventDiv_.style.top = this.labelDiv_.style.top;
+
+ this.setZIndex();
+};
+
+/**
+ * Sets the z-index of the label. If the marker's z-index property has not been defined, the z-index
+ * of the label is set to the vertical coordinate of the label. This is in keeping with the default
+ * stacking order for Google Maps: markers to the south are in front of markers to the north.
+ * @private
+ */
+MarkerLabel_.prototype.setZIndex = function () {
+ var zAdjust = (this.marker_.get("labelInBackground") ? -1 : +1);
+ if (typeof this.marker_.getZIndex() === "undefined") {
+ this.labelDiv_.style.zIndex = parseInt(this.labelDiv_.style.top, 10) + zAdjust;
+ this.eventDiv_.style.zIndex = this.labelDiv_.style.zIndex;
+ } else {
+ this.labelDiv_.style.zIndex = this.marker_.getZIndex() + zAdjust;
+ this.eventDiv_.style.zIndex = this.labelDiv_.style.zIndex;
+ }
+};
+
+/**
+ * Sets the visibility of the label. The label is visible only if the marker itself is
+ * visible (i.e., its visible property is true) and the labelVisible property is true.
+ * @private
+ */
+MarkerLabel_.prototype.setVisible = function () {
+ if (this.marker_.get("labelVisible")) {
+ this.labelDiv_.style.display = this.marker_.getVisible() ? "block" : "none";
+ } else {
+ this.labelDiv_.style.display = "none";
+ }
+ this.eventDiv_.style.display = this.labelDiv_.style.display;
+};
+
+/**
+ * @name MarkerWithLabelOptions
+ * @class This class represents the optional parameter passed to the {@link MarkerWithLabel} constructor.
+ * The properties available are the same as for <code>google.maps.Marker</code> with the addition
+ * of the properties listed below. To change any of these additional properties after the labeled
+ * marker has been created, call <code>google.maps.Marker.set(propertyName, propertyValue)</code>.
+ * <p>
+ * When any of these properties changes, a property changed event is fired. The names of these
+ * events are derived from the name of the property and are of the form <code>propertyname_changed</code>.
+ * For example, if the content of the label changes, a <code>labelcontent_changed</code> event
+ * is fired.
+ * <p>
+ * @property {string|Node} [labelContent] The content of the label (plain text or an HTML DOM node).
+ * @property {Point} [labelAnchor] By default, a label is drawn with its anchor point at (0,0) so
+ * that its top left corner is positioned at the anchor point of the associated marker. Use this
+ * property to change the anchor point of the label. For example, to center a 50px-wide label
+ * beneath a marker, specify a <code>labelAnchor</code> of <code>google.maps.Point(25, 0)</code>.
+ * (Note: x-values increase to the right and y-values increase to the top.)
+ * @property {string} [labelClass] The name of the CSS class defining the styles for the label.
+ * Note that style values for <code>position</code>, <code>overflow</code>, <code>top</code>,
+ * <code>left</code>, <code>zIndex</code>, <code>display</code>, <code>marginLeft</code>, and
+ * <code>marginTop</code> are ignored; these styles are for internal use only.
+ * @property {Object} [labelStyle] An object literal whose properties define specific CSS
+ * style values to be applied to the label. Style values defined here override those that may
+ * be defined in the <code>labelClass</code> style sheet. If this property is changed after the
+ * label has been created, all previously set styles (except those defined in the style sheet)
+ * are removed from the label before the new style values are applied.
+ * Note that style values for <code>position</code>, <code>overflow</code>, <code>top</code>,
+ * <code>left</code>, <code>zIndex</code>, <code>display</code>, <code>marginLeft</code>, and
+ * <code>marginTop</code> are ignored; these styles are for internal use only.
+ * @property {boolean} [labelInBackground] A flag indicating whether a label that overlaps its
+ * associated marker should appear in the background (i.e., in a plane below the marker).
+ * The default is <code>false</code>, which causes the label to appear in the foreground.
+ * @property {boolean} [labelVisible] A flag indicating whether the label is to be visible.
+ * The default is <code>true</code>. Note that even if <code>labelVisible</code> is
+ * <code>true</code>, the label will <i>not</i> be visible unless the associated marker is also
+ * visible (i.e., unless the marker's <code>visible</code> property is <code>true</code>).
+ * @property {boolean} [raiseOnDrag] A flag indicating whether the label and marker are to be
+ * raised when the marker is dragged. The default is <code>true</code>. If a draggable marker is
+ * being created and a version of Google Maps API earlier than V3.3 is being used, this property
+ * must be set to <code>false</code>.
+ * @property {boolean} [optimized] A flag indicating whether rendering is to be optimized for the
+ * marker. <b>Important: The optimized rendering technique is not supported by MarkerWithLabel,
+ * so the value of this parameter is always forced to <code>false</code>.
+ * @property {string} [crossImage="http://maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png"]
+ * The URL of the cross image to be displayed while dragging a marker.
+ * @property {string} [handCursor="http://maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur"]
+ * The URL of the cursor to be displayed while dragging a marker.
+ */
+/**
+ * Creates a MarkerWithLabel with the options specified in {@link MarkerWithLabelOptions}.
+ * @constructor
+ * @param {MarkerWithLabelOptions} [opt_options] The optional parameters.
+ */
+function MarkerWithLabel(opt_options) {
+ opt_options = opt_options || {};
+ opt_options.labelContent = opt_options.labelContent || "";
+ opt_options.labelAnchor = opt_options.labelAnchor || new google.maps.Point(0, 0);
+ opt_options.labelClass = opt_options.labelClass || "markerLabels";
+ opt_options.labelStyle = opt_options.labelStyle || {};
+ opt_options.labelInBackground = opt_options.labelInBackground || false;
+ if (typeof opt_options.labelVisible === "undefined") {
+ opt_options.labelVisible = true;
+ }
+ if (typeof opt_options.raiseOnDrag === "undefined") {
+ opt_options.raiseOnDrag = true;
+ }
+ if (typeof opt_options.clickable === "undefined") {
+ opt_options.clickable = true;
+ }
+ if (typeof opt_options.draggable === "undefined") {
+ opt_options.draggable = false;
+ }
+ if (typeof opt_options.optimized === "undefined") {
+ opt_options.optimized = false;
+ }
+ opt_options.crossImage = opt_options.crossImage || "//maps.gstatic.com/intl/en_us/mapfiles/drag_cross_67_16.png";
+ opt_options.handCursor = opt_options.handCursor || "//maps.gstatic.com/intl/en_us/mapfiles/closedhand_8_8.cur";
+ opt_options.optimized = false; // Optimized rendering is not supported
+
+ this.label = new MarkerLabel_(this, opt_options.crossImage, opt_options.handCursor); // Bind the label to the marker
+
+ // Call the parent constructor. It calls Marker.setValues to initialize, so all
+ // the new parameters are conveniently saved and can be accessed with get/set.
+ // Marker.set triggers a property changed event (called "propertyname_changed")
+ // that the marker label listens for in order to react to state changes.
+ google.maps.Marker.apply(this, arguments);
+}
+
+// MarkerWithLabel inherits from <code>Marker</code>:
+MarkerWithLabel.prototype = new google.maps.Marker();
+
+/**
+ * Overrides the standard Marker setMap function.
+ * @param {Map} marker The map to which the marker is to be added.
+ * @private
+ */
+MarkerWithLabel.prototype.setMap = function (theMap) {
+
+ // Call the inherited function...
+ google.maps.Marker.prototype.setMap.apply(this, arguments);
+
+ // ... then deal with the label:
+ this.label.setMap(theMap);
+};
+
+window.MarkerWithLabel = MarkerWithLabel;
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/img/blue-dot.png b/www/wiki/extensions/Maps/resources/GoogleMaps/img/blue-dot.png
new file mode 100644
index 00000000..845055e7
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/img/blue-dot.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/GoogleMaps/jquery.googlemap.js b/www/wiki/extensions/Maps/resources/GoogleMaps/jquery.googlemap.js
new file mode 100644
index 00000000..d0317d16
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/GoogleMaps/jquery.googlemap.js
@@ -0,0 +1,900 @@
+/**
+ * JavaScript for Google Maps v3 maps in the Maps extension.
+ * @see http://www.mediawiki.org/wiki/Extension:Maps
+ *
+ * @author Jeroen De Dauw <jeroendedauw at gmail dot com>
+ * @author Peter Grassberger < petertheone@gmail.com >
+ */
+
+(function ($, mw) {
+ $.fn.googlemaps = function (options) {
+
+ var _this = this;
+ this.map = null;
+ this.markercluster = null;
+ this.options = options;
+
+ /**
+ * All markers that are currently on the map.
+ * @type {Array}
+ * @private
+ */
+ this.markers = [];
+
+ /**
+ * All Polylines currently on the map,
+ * @type {Array}
+ * @private
+ */
+ this.lines = [];
+
+ /**
+ * All polygons currently on the map,
+ */
+ this.polygons = [];
+
+
+ /**
+ * All circles on the map
+ */
+ this.circles = [];
+
+
+ /**
+ * All rectangles on the map
+ */
+ this.rectangles = [];
+
+
+ /**
+ * All image overlays on the map
+ */
+ this.imageoverlays = [];
+
+
+ var getBounds = function() {
+ if (( options.centre === false || options.zoom === false ) && options.locations.length > 1) {
+ var bounds = new google.maps.LatLngBounds();
+
+ for (var i = _this.markers.length - 1; i >= 0; i--) {
+ bounds.extend(_this.markers[i].getPosition());
+ }
+ return bounds;
+ }
+ return null;
+ };
+
+ var setZoom = function(bounds) {
+ if (options.zoom === false) {
+ _this.map.fitBounds(bounds);
+ }
+ else {
+ _this.map.setZoom(options.zoom);
+ }
+ };
+
+
+ /**
+ * Creates a new marker with the provided data,
+ * adds it to the map, and returns it.
+ * @param {Object} markerData Contains the fields lat, lon, title, text and icon
+ * @return {google.maps.Marker}
+ */
+ this.addMarker = function (markerData) {
+ var markerOptions = {
+ position: new google.maps.LatLng(markerData.lat, markerData.lon),
+ title: markerData.title,
+ text: markerData.text,
+ labelContent: markerData.inlineLabel,
+ labelAnchor: new google.maps.Point(-15, 34),
+ labelClass: 'markerwithlabel'
+ };
+
+ if ( markerOptions.text !== '' ) {
+ markerOptions.text = $('<div>' + markerOptions.text + '</div>').text();
+ }
+
+ if (!markerData.hasOwnProperty('icon') || markerData.icon !== '') {
+ markerOptions.icon = markerData.icon;
+ }
+
+ if (markerData.visitedicon !== '') {
+ if(markerData.visitedicon === 'on'){
+ //when keyword 'on' is set, set visitedicon to a default official marker
+ markerOptions.visitedicon = mw.config.get('wgScriptPath')+'/extensions/Maps/resources/GoogleMaps/img/blue-dot.png';
+ }else{
+ markerOptions.visitedicon = markerData.visitedicon;
+ }
+ }
+
+ var addToMapAndHandlers = function( marker ) {
+ //Add onclick listener
+ google.maps.event.addListener(marker, 'click', function (e) {
+ if (e.target !== undefined && (e.target instanceof HTMLAnchorElement || e.target.tagName == 'A')) {
+ //click link defined in inlinelabel
+ window.location.href = e.target.href;
+ } else {
+ openBubbleOrLink.call(this, markerData, e, marker);
+ }
+
+ if (markerOptions.visitedicon) {
+ marker.setIcon(markerOptions.visitedicon);
+ markerOptions.visitedicon = undefined;
+ }
+ });
+
+ marker.setMap( _this.map );
+ _this.markers.push( marker );
+
+ return marker;
+ };
+
+ var marker;
+ if (markerData.inlineLabel === undefined || markerData.inlineLabel === null || markerData.inlineLabel.length == 0) {
+ marker = new google.maps.Marker( markerOptions );
+ return addToMapAndHandlers( marker );
+ } else {
+ mw.loader.using(
+ 'ext.maps.gm3.markerwithlabel',
+ function() {
+ marker = new MarkerWithLabel( markerOptions );
+ addToMapAndHandlers( marker );
+ setZoom(getBounds());
+ }
+ );
+ }
+ };
+
+ /**
+ * Removes a single marker from the map.
+ * @param {google.maps.Marker} marker The marker to remove.
+ */
+ this.removeMarker = function (marker) {
+ marker.setMap(null);
+
+ for (var i = this.markers.length - 1; i >= 0; i--) {
+ if (this.markers[i] === marker) {
+ delete this.markers[i];
+ break;
+ }
+ }
+
+ delete marker;
+ };
+
+ /**
+ * Removes all markers from the map.
+ */
+ this.removeMarkers = function () {
+ if (this.markercluster) {
+ this.markercluster.setMap(null);
+ this.markercluster = null;
+ }
+ for (var i = this.markers.length - 1; i >= 0; i--) {
+ this.markers[i].setMap(null);
+ }
+ this.markers = [];
+ };
+
+ /**
+ * Remove the "earth" type from options.types if it's present.
+ *
+ * @since 1.0.1
+ */
+ this.removeEarthType = function () {
+ if (Array.prototype.filter) {
+ options.types = options.types.filter(function (element, index, array) {
+ return element !== 'earth';
+ });
+ }
+ else {
+ // Seems someone is using the o-so-awesome browser that is IE.
+ var types = [];
+
+ for (i in options.types) {
+ if (typeof( options.types[i] ) !== 'function' && options.types[i] !== 'earth') {
+ types.push(options.types[i]);
+ }
+ }
+
+ options.types = types;
+ }
+ };
+
+ this.addOverlays = function () {
+ // Add the Google KML/KMZ layers.
+ for (i = options.gkml.length - 1; i >= 0; i--) {
+ var kmlLayer = new google.maps.KmlLayer(
+ options.gkml[i],
+ {
+ map:this.map,
+ preserveViewport:!options.kmlrezoom
+ }
+ );
+ }
+
+ // If there are any non-Google KML/KMZ layers, load the geoxml library and use it to add these layers.
+ if (options.kml.length != 0) {
+ mw.loader.using('ext.maps.gm3.geoxml', function () {
+
+ function addToCopyCoords(doc){
+ if(options.copycoords){
+ for(var i = 0; i < doc.length; i++){
+ addCopyCoordsOnRightClick([
+ doc[i].gpolygons,
+ doc[i].gpolylines,
+ doc[i].ggroundoverlays
+ ]);
+ }
+ }
+ }
+
+
+ var geoXml = new geoXML3.parser({
+ map:_this.map,
+ zoom:options.kmlrezoom,
+ failedParse:function(){
+ alert(mw.msg('maps-kml-parsing-failed'));
+ }
+ });
+ geoXml.options.afterParse = function(docs){
+ //add toggle functionality
+ var toggleDiv = document.createElement('div');
+ toggleDiv.style.backgroundColor = 'white';
+ toggleDiv.style.marginTop = '5px';
+ toggleDiv.style.padding = '5px';
+ toggleDiv.style.border = '1px solid grey';
+ for(var i = docs.length-1; i >= 0; i--){
+ (function(doc){
+ var label = document.createElement('label');
+ label.style.display = 'block';
+ var text = document.createTextNode(doc.baseUrl.substring(doc.baseDir.length));
+ var checkbox = document.createElement('input');
+ checkbox.setAttribute('type','checkbox');
+ checkbox.style.verticalAlign = '-0.2em';
+ checkbox.checked = true;
+ checkbox.onclick = function(){
+ if(this.checked){
+ geoXml.showDocument(doc);
+ }else{
+ geoXml.hideDocument(doc);
+ }
+ };
+ label.appendChild(checkbox);
+ label.appendChild(text);
+
+ toggleDiv.appendChild(label);
+ })(docs[i]);
+ }
+ _this.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(toggleDiv);
+ };
+
+ geoXml.parse(options.kml);
+ });
+ }
+ };
+
+ this.addLine = function (properties) {
+ var paths = new google.maps.MVCArray();
+ for (var x = 0; x < properties.pos.length; x++) {
+ paths.push(new google.maps.LatLng(properties.pos[x].lat, properties.pos[x].lon));
+ }
+
+ var line = new google.maps.Polyline({
+ map:this.map,
+ path:paths,
+ strokeColor:properties.strokeColor,
+ strokeOpacity:properties.strokeOpacity,
+ strokeWeight:properties.strokeWeight
+ });
+ this.lines.push(line);
+
+ google.maps.event.addListener(line, "click", function (event) {
+ openBubbleOrLink.call(this, properties, event, line);
+ });
+ };
+
+ this.removeLine = function (line) {
+ line.setMap(null);
+
+ for (var i = this.line.length - 1; i >= 0; i--) {
+ if (this.line[i] === line) {
+ delete this.line[i];
+ break;
+ }
+ }
+
+ delete line;
+ };
+
+ this.removeLines = function () {
+ for (var i = this.lines.length - 1; i >= 0; i--) {
+ this.lines[i].setMap(null);
+ }
+ this.lines = [];
+ };
+
+ this.addPolygon = function (properties) {
+ var paths = new google.maps.MVCArray();
+ for (var x = 0; x < properties.pos.length; x++) {
+ paths.push(new google.maps.LatLng(properties.pos[x].lat, properties.pos[x].lon));
+ }
+
+ var polygon = new google.maps.Polygon({
+ map:this.map,
+ path:paths,
+ strokeColor:properties.strokeColor,
+ strokeOpacity:properties.strokeOpacity,
+ strokeWeight:properties.strokeWeight,
+ fillColor:properties.fillColor,
+ fillOpacity:properties.fillOpacity
+ });
+ this.polygons.push(polygon);
+
+ //add hover event/effect
+ if (properties.onlyVisibleOnHover === true) {
+
+ function hidePolygon(polygon) {
+ polygon.setOptions({
+ fillOpacity:0,
+ strokeOpacity:0
+ });
+ }
+
+ hidePolygon(polygon);
+
+ google.maps.event.addListener(polygon, "mouseover", function () {
+ this.setOptions({
+ fillOpacity:properties.fillOpacity,
+ strokeOpacity:properties.strokeOpacity
+ });
+ });
+
+ google.maps.event.addListener(polygon, "mouseout", function () {
+ hidePolygon(this);
+ });
+
+ }
+
+ //add click event
+ google.maps.event.addListener(polygon, "click", function (event) {
+ openBubbleOrLink.call(this, properties, event, polygon);
+ });
+ };
+
+ this.addCircle = function (properties) {
+ var circle = new google.maps.Circle({
+ map:this.map,
+ center:new google.maps.LatLng(properties.centre.lat, properties.centre.lon),
+ radius:properties.radius,
+ fillColor:properties.fillColor,
+ fillOpacity:properties.fillOpacity,
+ strokeColor:properties.strokeColor,
+ strokeOpacity:properties.strokeOpacity,
+ strokeWeight:properties.strokeWeight
+ });
+ this.circles.push(circle);
+
+ //add click event
+ google.maps.event.addListener(circle, "click", function (event) {
+ openBubbleOrLink.call(this, properties, event, circle);
+ });
+ };
+
+
+ this.addRectangle = function (properties) {
+ var rectangle = new google.maps.Rectangle({
+ map:this.map,
+ bounds:new google.maps.LatLngBounds(
+ new google.maps.LatLng(properties.sw.lat, properties.sw.lon), //sw
+ new google.maps.LatLng(properties.ne.lat, properties.ne.lon) //ne
+ ),
+ fillColor:properties.fillColor,
+ fillOpacity:properties.fillOpacity,
+ strokeColor:properties.strokeColor,
+ strokeOpacity:properties.strokeOpacity,
+ strokeWeight:properties.strokeWeight
+ });
+ this.rectangles.push(rectangle);
+
+ //add click event
+ google.maps.event.addListener(rectangle, "click", function (event) {
+ openBubbleOrLink.call(this, properties, event, rectangle);
+ });
+ };
+
+ this.addImageOverlay = function(properties){
+ var imageBounds = new google.maps.LatLngBounds(
+ new google.maps.LatLng(properties.sw.lat,properties.sw.lon),
+ new google.maps.LatLng(properties.ne.lat,properties.ne.lon)
+ );
+
+ var image = new google.maps.GroundOverlay(properties.image,imageBounds);
+ image.setMap(this.map);
+
+ this.imageoverlays.push(image);
+
+ //add click event
+ google.maps.event.addListener(image, "click", function (event) {
+ openBubbleOrLink.call(this, properties, event, image);
+ });
+ };
+
+
+ this.removePolygon = function (polygon) {
+ polygon.setMap(null);
+
+ for (var i = this.polygon.length - 1; i >= 0; i--) {
+ if (this.polygon[i] === polygon) {
+ delete this.polygon[i];
+ break;
+ }
+ }
+
+ delete polygon;
+ };
+
+ this.removePolygons = function () {
+ for (var i = this.polygon.length - 1; i >= 0; i--) {
+ this.polygon[i].setMap(null);
+ }
+ this.polygon = [];
+ };
+
+ //Rezoom's the map to show all visible markers.
+ this.reZoom = function(){
+ var bounds = new google.maps.LatLngBounds();
+ for(var x = 0; x < this.markers.length; x++){
+ var marker = this.markers[x];
+ if (marker.getVisible() === true) {
+ bounds.extend(marker.getPosition());
+ }
+ }
+ this.map.fitBounds(bounds);
+ };
+
+ this.createMarkerCluster = function() {
+ if ( !options.markercluster ) {
+ return;
+ }
+ if (this.markercluster) {
+ this.markercluster.setMap(null);
+ this.markercluster = null;
+ }
+ this.markercluster = new MarkerClusterer( this.map, this.markers, {
+ imagePath: mw.config.get( 'wgScriptPath' ) +
+ '/extensions/Maps/resources/leaflet/cluster/m',
+ gridSize: this.options.clustergridsize,
+ maxZoom: this.options.clustermaxzoom,
+ zoomOnClick: this.options.clusterzoomonclick,
+ averageCenter: this.options.clusteraveragecenter,
+ minimumClusterSize: this.options.clusterminsize
+ } );
+ };
+
+ this.initializeMap = function () {
+ var mapOptions = {
+ disableDefaultUI:true,
+ mapTypeId:options.type == 'earth' ? google.maps.MapTypeId.SATELLITE : google.maps.MapTypeId[ options.type ]
+ };
+
+ // Map controls
+ mapOptions.panControl = $.inArray('pan', options.controls) != -1;
+ mapOptions.zoomControl = $.inArray('zoom', options.controls) != -1;
+ mapOptions.mapTypeControl = $.inArray('type', options.controls) != -1;
+ mapOptions.scaleControl = $.inArray('scale', options.controls) != -1;
+ mapOptions.streetViewControl = $.inArray('streetview', options.controls) != -1;
+ mapOptions.rotateControl = $.inArray('rotate', options.controls) != -1;
+
+ for (i in options.types) {
+ if (typeof( options.types[i] ) !== 'function') {
+ options.types[i] = google.maps.MapTypeId[ options.types[i].toUpperCase() ];
+ }
+ }
+
+ // Map control styles
+ mapOptions.zoomControlOptions = { style:google.maps.ZoomControlStyle[ options.zoomstyle ] };
+ mapOptions.mapTypeControlOptions = {
+ style:google.maps.MapTypeControlStyle[ options.typestyle ],
+ mapTypeIds:options.types
+ };
+
+
+ //max/min -zoom
+ mapOptions.maxZoom = options.maxzoom === false ? null : options.maxzoom;
+ mapOptions.minZoom = options.minzoom === false ? null : options.minzoom;
+
+ //static mode
+ if (options['static']) {
+ mapOptions.draggable = false;
+ mapOptions.disableDoubleClickZoom = true;
+ mapOptions.panControl = false;
+ mapOptions.rotateControl = false;
+ mapOptions.zoomControl = false;
+ mapOptions.scrollwheel = false;
+ mapOptions.streetViewControl = false;
+ mapOptions.overviewMapControl = false;
+ mapOptions.mapTypeControl = false;
+ }
+
+ if (options.scrollwheelzoom === true) {
+ mapOptions.gestureHandling = 'greedy';
+ }
+
+ var map = new google.maps.Map(this.get(0), mapOptions);
+
+ google.maps.event.addListenerOnce(map, 'tilesloaded', function () {
+ _this.addOverlays();
+ });
+
+
+ this.map = map;
+
+ if (options.poi === false) {
+ map.setOptions({ styles:[
+ {
+ featureType:"poi",
+ stylers:[
+ { visibility:"off" }
+ ]
+ }
+ ] });
+ }
+
+ if (!options.locations) {
+ options.locations = [];
+ }
+
+ // Add the markers.
+ for (var i = options.locations.length - 1; i >= 0; i--) {
+ this.addMarker(options.locations[i]);
+ }
+
+ for (i = options.layers.length - 1; i >= 0; i--) {
+ if ( options.layers[i] === 'traffic' ) {
+ ( new google.maps.TrafficLayer() ).setMap(map);
+ }
+ else if ( options.layers[i] === 'bicycling' ) {
+ ( new google.maps.BicyclingLayer() ).setMap(map);
+ }
+ else if ( options.layers[i] === 'transit' ) {
+ ( new google.maps.TransitLayer() ).setMap(map);
+ }
+ }
+
+ var bounds = getBounds();
+
+ setZoom(bounds);
+
+ var centre;
+
+ if (options.centre === false) {
+ if (options.locations.length > 1) {
+ centre = bounds.getCenter();
+ }
+ else if (options.locations.length == 1) {
+ centre = new google.maps.LatLng(options.locations[0].lat, options.locations[0].lon);
+ }
+ else {
+ centre = new google.maps.LatLng(0, 0);
+ }
+ }
+ else {
+ centre = new google.maps.LatLng(options.centre.lat, options.centre.lon);
+ }
+
+ map.setCenter(centre);
+
+ setTimeout(
+ function() {
+ if ( options.autoinfowindows ) {
+ for ( var i = _this.markers.length - 1; i >= 0; i-- ) {
+ google.maps.event.trigger( _this.markers[i], 'click', {} );
+ }
+ }
+ },
+ 500 // If we wait a bit, the map will re-position to accommodate for the info windows.
+ );
+
+ if (options.resizable) {
+ mw.loader.using('ext.maps.resizable', function () {
+ _this.resizable();
+ });
+ }
+
+ /**
+ * used in display_line functionality
+ * draws paths between markers
+ */
+ if (options.lines) {
+ for (var i = 0; i < options.lines.length; i++) {
+ this.addLine(options.lines[i]);
+ }
+ }
+
+ /**
+ * used in display_line to draw polygons
+ */
+ if (options.polygons) {
+ for (var i = 0; i < options.polygons.length; i++) {
+ this.addPolygon(options.polygons[i]);
+ }
+ }
+
+ /**
+ * used in display_line to draw circles
+ */
+ if (options.circles) {
+ for (var i = 0; i < options.circles.length; i++) {
+ this.addCircle(options.circles[i]);
+ }
+ }
+
+ /**
+ * used in display_line to draw rectangles
+ */
+ if (options.rectangles) {
+ for (var i = 0; i < options.rectangles.length; i++) {
+ this.addRectangle(options.rectangles[i]);
+ }
+ }
+
+ /**
+ * Allows grouping of markers.
+ */
+ this.createMarkerCluster();
+
+
+ if (options.searchmarkers) {
+ var searchBoxValue = mw.msg('maps-searchmarkers-text');
+ var searchBox = $('<input type="text" value="' + searchBoxValue + '" />');
+ var searchContainer = document.createElement('div');
+ searchContainer.style.padding = '5px';
+ searchContainer.index = 1;
+ searchBox.appendTo(searchContainer);
+ map.controls[google.maps.ControlPosition.TOP_RIGHT].push(searchContainer);
+
+ //prevents markers and other map objects to be placed beneath searchfield
+ google.maps.event.addListenerOnce(map, 'bounds_changed', function () {
+ map.panBy(0,-30);
+ });
+
+
+ searchBox.on('keyup',function (e) {
+ for (var i = 0; i < _this.markers.length; i++) {
+ var haystack = '';
+ var marker = _this.markers[i];
+ if (options.searchmarkers == 'title') {
+ haystack = marker.title;
+ } else {
+ haystack = marker.title + marker.text;
+ }
+
+ var visible = haystack.toLowerCase().indexOf(e.target.value.toLowerCase()) != -1;
+ marker.setVisible(visible);
+ }
+ _this.reZoom();
+ }).on('focusin',function () {
+ if ($(this).val() === searchBoxValue) {
+ $(this).val('');
+ }
+ }).on('focusout', function () {
+ if ($(this).val() === '') {
+ $(this).val(searchBoxValue);
+ }
+ });
+ }
+
+ if(options.imageoverlays){
+ for (var i = 0; i < options.imageoverlays.length; i++) {
+ this.addImageOverlay(options.imageoverlays[i]);
+ }
+ }
+
+ if (options.copycoords) {
+
+ addCopyCoordsOnRightClick([
+ this.lines,
+ this.circles,
+ this.polygons,
+ this.markers,
+ this.rectangles,
+ this.imageoverlays,
+ this.map
+ ]);
+ }
+
+ if (options.wmsoverlay) {
+ var wmsOptions = {
+ alt: "OpenLayers",
+ getTileUrl:function (tile, zoom) {
+ var projection = _this.map.getProjection();
+ var zpow = Math.pow(2, zoom);
+ var ul = new google.maps.Point(tile.x * 256.0 / zpow, (tile.y + 1) * 256.0 / zpow);
+ var lr = new google.maps.Point((tile.x + 1) * 256.0 / zpow, (tile.y) * 256.0 / zpow);
+ var ulw = projection.fromPointToLatLng(ul);
+ var lrw = projection.fromPointToLatLng(lr);
+ //The user will enter the address to the public WMS layer here. The data must be in WGS84
+ var baseURL = options.wmsoverlay.wmsServerUrl;
+ //The layer ID. Can be found when using the layers properties tool in ArcMap or from the WMS settings
+ var layers = options.wmsoverlay.wmsLayerName;
+
+ var style = options.wmsoverlay.wmsStyleName;
+ //With the 1.3.0 version the coordinates are read in LatLon, as opposed to LonLat in previous versions
+ var bbox = ulw.lat() + "," + ulw.lng() + "," + lrw.lat() + "," + lrw.lng();
+ //Establish the baseURL. Several elements, including &EXCEPTIONS=INIMAGE and &Service are unique to openLayers addresses.
+ var url = baseURL + "version=1.3.0&EXCEPTIONS=INIMAGE&Service=WMS" +
+ "&request=GetMap&Styles=" + encodeURI(style) + "&format=image%2Fjpeg&CRS=EPSG:4326" +
+ "&width=256&height=256" + "&Layers=" + layers + "&BBOX=" + bbox;
+ return url;
+ },
+ isPng: false,
+ maxZoom: 17,
+ minZoom: 1,
+ name: "OpenLayers",
+ tileSize: new google.maps.Size(256, 256)
+
+ };
+
+
+
+ //Creating the object to create the ImageMapType that will call the WMS Layer Options.
+
+ openlayersWMS = new google.maps.ImageMapType(wmsOptions);
+
+
+ //Layers to appear on Map A. The first string will give the map the map a name in the dropdown and the second object calls the map type
+
+ map.mapTypes.set('OpenLayers', openlayersWMS);
+ map.setMapTypeId('OpenLayers');
+ }
+
+ //Add custom controls
+ // - Fullscreen
+ if(options.enablefullscreen){
+ this.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(new FullscreenControl(this.map));
+ }
+ };
+
+ this.setup = function () {
+
+ var showEarth = $.inArray('earth', options.types) !== -1 || options.type == 'earth';
+
+ // If there are any non-Google KML/KMZ layers, load the geoxml library and use it to add these layers.
+ if (showEarth) {
+ this.removeEarthType();
+ $.getScript(
+ 'https://www.google.com/jsapi',
+ function (data, textStatus) {
+ google.load('earth', '1', { callback:function () {
+ mw.loader.using('ext.maps.gm3.earth', function () {
+ _this.initializeMap();
+ if (google.earth.isSupported()) {
+ _this.ge = new GoogleEarth(_this.map);
+ }
+ });
+ } });
+ }
+ );
+ }else{
+ this.initializeMap();
+ }
+
+
+ };
+
+ function FullscreenControl(map) {
+
+ var controlDiv = document.createElement('div');
+ controlDiv.style.padding = '5px';
+ controlDiv.index = 1;
+
+ var controlUI = document.createElement('div');
+ controlUI.style.backgroundColor = 'white';
+ controlUI.style.borderStyle = 'solid';
+ controlUI.style.borderColor = 'rgba(0, 0, 0, 0.14902)';
+ controlUI.style.borderWidth = '1px';
+ controlUI.style.borderRadius = '2px';
+ controlUI.style.cursor = 'pointer';
+ controlUI.style.textAlign = 'center';
+ controlUI.style.boxShadow = 'rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px';
+ controlUI.style.backgroundClip = 'padding-box';
+ controlUI.title = mw.msg('maps-fullscreen-button-tooltip');
+ controlDiv.appendChild(controlUI);
+
+ var controlText = document.createElement('div');
+ controlText.style.fontFamily = 'Roboto, Arial, sans-serif';
+ controlText.style.fontSize = '11px';
+ controlText.style.fontWeight = '400';
+ controlText.style.color = 'rgb(86, 86, 86)';
+ controlText.style.padding = '1px 6px';
+ controlText.innerHTML = mw.msg('maps-fullscreen-button');
+ controlUI.appendChild(controlText);
+
+ google.maps.event.addDomListener(controlUI, 'click', function() {
+ var mapDiv = $(map.getDiv());
+ if(mapDiv.data('preFullscreenCss') != null){
+ mapDiv.css(mapDiv.data('preFullscreenCss'));
+ mapDiv.removeData('preFullscreenCss');
+ }else{
+ var fullscreenCss = {
+ position:'fixed',
+ top: 0,
+ left:0,
+ width:'100%',
+ height:'100%',
+ zIndex:10000
+ };
+ var oldState = {};
+ for(var cssProp in fullscreenCss){
+ oldState[cssProp] = mapDiv.css(cssProp);
+ }
+ mapDiv.data('preFullscreenCss',oldState);
+ mapDiv.css(fullscreenCss);
+ }
+
+ google.maps.event.trigger(map, "resize");
+ });
+
+ return controlDiv;
+ }
+
+
+ function openBubbleOrLink(markerData, event, obj) {
+ if (markerData.link) {
+ window.location.href = markerData.link;
+ } else if (markerData.text.trim() !== '') {
+ openBubble.call(this, markerData, event, obj);
+ }
+ }
+
+ function openBubble( markerData, event, obj ) {
+ if ( this.openWindow !== undefined ) {
+ this.openWindow.close();
+ }
+
+ this.openWindow = new google.maps.InfoWindow();
+ this.openWindow.setContent( markerData.text );
+
+ if ( event.latLng !== undefined ) {
+ this.openWindow.setPosition( event.latLng );
+ }
+
+ this.openWindow.closeclick = function () {
+ obj.openWindow = undefined;
+ };
+
+ if ( event.latLng === undefined ) {
+ this.openWindow.open( _this.map, this );
+ }
+ else {
+ this.openWindow.open( _this.map );
+ }
+ }
+
+ function addCopyCoordsOnRightClick(object) {
+ if(object instanceof Array){
+ for (var x = 0; x < object.length; x++) {
+ addCopyCoordsOnRightClick(object[x]);
+ }
+ }else{
+ google.maps.event.addListener(object, 'rightclick', function (event) {
+ prompt(mw.msg('maps-copycoords-prompt'), event.latLng.lat() + ',' + event.latLng.lng());
+ });
+ }
+ }
+
+ //Complete path to OpenLayers WMS layer
+
+ if (!options.markercluster) {
+ this.setup();
+ } else {
+ mw.loader.using( 'ext.maps.gm3.markercluster', function() {
+ _this.setup();
+ } );
+ }
+
+ return this;
+
+ };
+})(jQuery, window.mediaWiki);
diff --git a/www/wiki/extensions/Maps/resources/editor/css/jquery.miniColors.css b/www/wiki/extensions/Maps/resources/editor/css/jquery.miniColors.css
new file mode 100644
index 00000000..664c2fd4
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/editor/css/jquery.miniColors.css
@@ -0,0 +1,70 @@
+.miniColors-trigger {
+ height: 22px;
+ width: 22px;
+ /* @embed */
+ background: url(../images/trigger.png) center no-repeat;
+ vertical-align: middle;
+ margin: 0 .25em;
+ display: inline-block;
+ outline: none;
+}
+
+.miniColors-selector {
+ position: absolute;
+ width: 175px;
+ height: 150px;
+ background: #FFF;
+ border: solid 1px #BBB;
+ -moz-box-shadow: 0 0 6px rgba(0, 0, 0, .25);
+ -webkit-box-shadow: 0 0 6px rgba(0, 0, 0, .25);
+ box-shadow: 0 0 6px rgba(0, 0, 0, .25);
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ border-radius: 5px;
+ padding: 5px;
+ z-index: 999999;
+}
+
+.miniColors-selector.black {
+ background: #000;
+ border-color: #000;
+}
+
+.miniColors-colors {
+ position: absolute;
+ top: 5px;
+ left: 5px;
+ width: 150px;
+ height: 150px;
+ /* @embed */
+ background: url(../images/gradient.png) center no-repeat;
+ cursor: crosshair;
+}
+
+.miniColors-hues {
+ position: absolute;
+ top: 5px;
+ left: 160px;
+ width: 20px;
+ height: 150px;
+ /* @embed */
+ background: url(../images/rainbow.png) center no-repeat;
+ cursor: crosshair;
+}
+
+.miniColors-colorPicker {
+ position: absolute;
+ width: 11px;
+ height: 11px;
+ /* @embed */
+ background: url(../images/circle.gif) center no-repeat;
+}
+
+.miniColors-huePicker {
+ position: absolute;
+ left: -3px;
+ width: 26px;
+ height: 3px;
+ /* @embed */
+ background: url(../images/line.gif) center no-repeat;
+} \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/editor/css/mapeditor.css b/www/wiki/extensions/Maps/resources/editor/css/mapeditor.css
new file mode 100644
index 00000000..9c3fe7c4
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/editor/css/mapeditor.css
@@ -0,0 +1,79 @@
+#code-output-container, #code-input-container,#code-input {
+ height: 100% !important;
+ width: 100%;
+}
+
+#code-output-container {
+ overflow: visible;
+}
+
+textarea#code-input, textarea#code-output {
+ resize: none;
+}
+
+#code-output {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ border: none;
+ padding: 10px;
+}
+
+#map-canvas {
+ width: 100%;
+ height: 500px;
+}
+
+#map-canvas,#code-input {
+ display: block;
+}
+
+.mapeditor-dialog-form fieldset label{
+ display: block;
+}
+
+.mapeditor-dialog-form fieldset input{
+ width: 200px;
+}
+
+.mapeditor-dialog-form fieldset input[type="checkbox"]{
+ width: auto;
+}
+
+.mapeditor-dialog-form fieldset input[name="strokeColor"],.mapeditor-dialog-form fieldset input[name="fillColor"]{
+ width: 100px;
+}
+
+.mapeditor-dialog-form .ui-slider{
+ margin: 5px 2px;
+ width: 200px;
+}
+
+.link-title-switcher {
+ margin: 5px;
+}
+
+#map-parameter-form{
+ text-align: center;
+}
+
+.mapeditor-controls{
+ padding: 5px;
+}
+
+.mapeditor-control-element{
+ background-color:white;
+ border: 1px solid #717B87;
+ cursor: pointer;
+ text-align: center;
+ float: left;
+ padding: 2px;
+}
+
+.mapeditor-control-text{
+ font-family:"Arial","sans-serif";
+ font-size: 12px;
+ padding-left: 4px;
+ padding-right: 4px;
+ font-weight: bold;
+} \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/editor/images/circle.gif b/www/wiki/extensions/Maps/resources/editor/images/circle.gif
new file mode 100644
index 00000000..599f7f13
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/editor/images/circle.gif
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/editor/images/gradient.png b/www/wiki/extensions/Maps/resources/editor/images/gradient.png
new file mode 100644
index 00000000..486a9f6d
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/editor/images/gradient.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/editor/images/line.gif b/www/wiki/extensions/Maps/resources/editor/images/line.gif
new file mode 100644
index 00000000..9eb19837
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/editor/images/line.gif
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/editor/images/rainbow.png b/www/wiki/extensions/Maps/resources/editor/images/rainbow.png
new file mode 100644
index 00000000..d16fcd86
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/editor/images/rainbow.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/editor/images/trigger.png b/www/wiki/extensions/Maps/resources/editor/images/trigger.png
new file mode 100644
index 00000000..20ec282b
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/editor/images/trigger.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/editor/js/README b/www/wiki/extensions/Maps/resources/editor/js/README
new file mode 100644
index 00000000..53598d48
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/editor/js/README
@@ -0,0 +1,3 @@
+== jquery.miniColors.js ==
+Is dual licensed under the MIT / GPLv2 licenses
+https://github.com/claviska/jquery-miniColors \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/editor/js/jquery.miniColors.js b/www/wiki/extensions/Maps/resources/editor/js/jquery.miniColors.js
new file mode 100644
index 00000000..dfbce542
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/editor/js/jquery.miniColors.js
@@ -0,0 +1,567 @@
+/*
+ * jQuery miniColors: A small color selector
+ *
+ * Copyright 2011 Cory LaViska for A Beautiful Site, LLC. (http://abeautifulsite.net/)
+ *
+ * Dual licensed under the MIT or GPL Version 2 licenses
+ *
+*/
+if(jQuery) (function($) {
+
+ $.extend($.fn, {
+
+ miniColors: function(o, data) {
+
+ var create = function(input, o, data) {
+ //
+ // Creates a new instance of the miniColors selector
+ //
+
+ // Determine initial color (defaults to white)
+ var color = expandHex(input.val());
+ if( !color ) color = 'ffffff';
+ var hsb = hex2hsb(color);
+
+ // Create trigger
+ var trigger = $('<a class="miniColors-trigger" style="background-color: #' + color + '" href="#"></a>');
+ trigger.insertAfter(input);
+
+ // Set input data and update attributes
+ input
+ .addClass('miniColors')
+ .data('original-maxlength', input.attr('maxlength') || null)
+ .data('original-autocomplete', input.attr('autocomplete') || null)
+ .data('letterCase', 'uppercase')
+ .data('trigger', trigger)
+ .data('hsb', hsb)
+ .data('change', o.change ? o.change : null)
+ .attr('maxlength', 7)
+ .attr('autocomplete', 'off')
+ .val('#' + convertCase(color, o.letterCase));
+
+ // Handle options
+ if( o.readonly ) input.prop('readonly', true);
+ if( o.disabled ) disable(input);
+
+ // Show selector when trigger is clicked
+ trigger.bind('click.miniColors', function(event) {
+ event.preventDefault();
+ if( input.val() === '' ) input.val('#');
+ show(input);
+
+ });
+
+ // Show selector when input receives focus
+ input.bind('focus.miniColors', function(event) {
+ if( input.val() === '' ) input.val('#');
+ show(input);
+ });
+
+ // Hide on blur
+ input.bind('blur.miniColors', function(event) {
+ var hex = expandHex(input.val());
+ input.val( hex ? '#' + convertCase(hex, input.data('letterCase')) : '' );
+ });
+
+ // Hide when tabbing out of the input
+ input.bind('keydown.miniColors', function(event) {
+ if( event.keyCode === 9 ) hide(input);
+ });
+
+ // Update when color is typed in
+ input.bind('keyup.miniColors', function(event) {
+ setColorFromInput(input);
+ });
+
+ // Handle pasting
+ input.bind('paste.miniColors', function(event) {
+ // Short pause to wait for paste to complete
+ setTimeout( function() {
+ setColorFromInput(input);
+ }, 5);
+ });
+
+ };
+
+ var destroy = function(input) {
+ //
+ // Destroys an active instance of the miniColors selector
+ //
+
+ hide();
+ input = $(input);
+
+ // Restore to original state
+ input.data('trigger').remove();
+ input
+ .attr('autocomplete', input.data('original-autocomplete'))
+ .attr('maxlength', input.data('original-maxlength'))
+ .removeData()
+ .removeClass('miniColors')
+ .unbind('.miniColors');
+ $(document).unbind('.miniColors');
+ };
+
+ var enable = function(input) {
+ //
+ // Enables the input control and the selector
+ //
+ input
+ .prop('disabled', false)
+ .data('trigger')
+ .css('opacity', 1);
+ };
+
+ var disable = function(input) {
+ //
+ // Disables the input control and the selector
+ //
+ hide(input);
+ input
+ .prop('disabled', true)
+ .data('trigger')
+ .css('opacity', 0.5);
+ };
+
+ var show = function(input) {
+ //
+ // Shows the miniColors selector
+ //
+ if( input.prop('disabled') ) return false;
+
+ // Hide all other instances
+ hide();
+
+ // Generate the selector
+ var selector = $('<div class="miniColors-selector"></div>');
+ selector
+ .append('<div class="miniColors-colors" style="background-color: #FFF;"><div class="miniColors-colorPicker"></div></div>')
+ .append('<div class="miniColors-hues"><div class="miniColors-huePicker"></div></div>')
+ .css({
+ top: input.is(':visible') ? input.offset().top + input.outerHeight() : input.data('trigger').offset().top + input.data('trigger').outerHeight(),
+ left: input.is(':visible') ? input.offset().left : input.data('trigger').offset().left,
+ display: 'none'
+ })
+ .addClass( input.attr('class') );
+
+ // Set background for colors
+ var hsb = input.data('hsb');
+ selector
+ .find('.miniColors-colors')
+ .css('backgroundColor', '#' + hsb2hex({ h: hsb.h, s: 100, b: 100 }));
+
+ // Set colorPicker position
+ var colorPosition = input.data('colorPosition');
+ if( !colorPosition ) colorPosition = getColorPositionFromHSB(hsb);
+ selector.find('.miniColors-colorPicker')
+ .css('top', colorPosition.y + 'px')
+ .css('left', colorPosition.x + 'px');
+
+ // Set huePicker position
+ var huePosition = input.data('huePosition');
+ if( !huePosition ) huePosition = getHuePositionFromHSB(hsb);
+ selector.find('.miniColors-huePicker').css('top', huePosition.y + 'px');
+
+ // Set input data
+ input
+ .data('selector', selector)
+ .data('huePicker', selector.find('.miniColors-huePicker'))
+ .data('colorPicker', selector.find('.miniColors-colorPicker'))
+ .data('mousebutton', 0);
+
+ $('BODY').append(selector);
+ selector.fadeIn(100);
+
+ // Prevent text selection in IE
+ selector.bind('selectstart', function() { return false; });
+
+ $(document).bind('mousedown.miniColors touchstart.miniColors', function(event) {
+
+ input.data('mousebutton', 1);
+
+ if( $(event.target).parents().andSelf().hasClass('miniColors-colors') ) {
+ event.preventDefault();
+ input.data('moving', 'colors');
+ moveColor(input, event);
+ }
+
+ if( $(event.target).parents().andSelf().hasClass('miniColors-hues') ) {
+ event.preventDefault();
+ input.data('moving', 'hues');
+ moveHue(input, event);
+ }
+
+ if( $(event.target).parents().andSelf().hasClass('miniColors-selector') ) {
+ event.preventDefault();
+ return;
+ }
+
+ if( $(event.target).parents().andSelf().hasClass('miniColors') ) return;
+
+ hide(input);
+ });
+
+ $(document)
+ .bind('mouseup.miniColors touchend.miniColors', function(event) {
+ event.preventDefault();
+ input.data('mousebutton', 0).removeData('moving');
+ })
+ .bind('mousemove.miniColors touchmove.miniColors', function(event) {
+ event.preventDefault();
+ if( input.data('mousebutton') === 1 ) {
+ if( input.data('moving') === 'colors' ) moveColor(input, event);
+ if( input.data('moving') === 'hues' ) moveHue(input, event);
+ }
+ });
+
+ };
+
+ var hide = function(input) {
+
+ //
+ // Hides one or more miniColors selectors
+ //
+
+ // Hide all other instances if input isn't specified
+ if( !input ) input = '.miniColors';
+
+ $(input).each( function() {
+ var selector = $(this).data('selector');
+ $(this).removeData('selector');
+ $(selector).fadeOut(100, function() {
+ $(this).remove();
+ });
+ });
+
+ $(document).unbind('.miniColors');
+
+ };
+
+ var moveColor = function(input, event) {
+
+ var colorPicker = input.data('colorPicker');
+
+ colorPicker.hide();
+
+ var position = {
+ x: event.pageX,
+ y: event.pageY
+ };
+
+ // Touch support
+ if( event.originalEvent.changedTouches ) {
+ position.x = event.originalEvent.changedTouches[0].pageX;
+ position.y = event.originalEvent.changedTouches[0].pageY;
+ }
+ position.x = position.x - input.data('selector').find('.miniColors-colors').offset().left - 5;
+ position.y = position.y - input.data('selector').find('.miniColors-colors').offset().top - 5;
+ if( position.x <= -5 ) position.x = -5;
+ if( position.x >= 144 ) position.x = 144;
+ if( position.y <= -5 ) position.y = -5;
+ if( position.y >= 144 ) position.y = 144;
+
+ input.data('colorPosition', position);
+ colorPicker.css('left', position.x).css('top', position.y).show();
+
+ // Calculate saturation
+ var s = Math.round((position.x + 5) * 0.67);
+ if( s < 0 ) s = 0;
+ if( s > 100 ) s = 100;
+
+ // Calculate brightness
+ var b = 100 - Math.round((position.y + 5) * 0.67);
+ if( b < 0 ) b = 0;
+ if( b > 100 ) b = 100;
+
+ // Update HSB values
+ var hsb = input.data('hsb');
+ hsb.s = s;
+ hsb.b = b;
+
+ // Set color
+ setColor(input, hsb, true);
+ };
+
+ var moveHue = function(input, event) {
+
+ var huePicker = input.data('huePicker');
+
+ huePicker.hide();
+
+ var position = {
+ y: event.pageY
+ };
+
+ // Touch support
+ if( event.originalEvent.changedTouches ) {
+ position.y = event.originalEvent.changedTouches[0].pageY;
+ }
+
+ position.y = position.y - input.data('selector').find('.miniColors-colors').offset().top - 1;
+ if( position.y <= -1 ) position.y = -1;
+ if( position.y >= 149 ) position.y = 149;
+ input.data('huePosition', position);
+ huePicker.css('top', position.y).show();
+
+ // Calculate hue
+ var h = Math.round((150 - position.y - 1) * 2.4);
+ if( h < 0 ) h = 0;
+ if( h > 360 ) h = 360;
+
+ // Update HSB values
+ var hsb = input.data('hsb');
+ hsb.h = h;
+
+ // Set color
+ setColor(input, hsb, true);
+
+ };
+
+ var setColor = function(input, hsb, updateInput) {
+ input.data('hsb', hsb);
+ var hex = hsb2hex(hsb);
+ if( updateInput ) input.val( '#' + convertCase(hex, input.data('letterCase')) );
+ input.data('trigger').css('backgroundColor', '#' + hex);
+ if( input.data('selector') ) input.data('selector').find('.miniColors-colors').css('backgroundColor', '#' + hsb2hex({ h: hsb.h, s: 100, b: 100 }));
+
+ // Fire change callback
+ if( input.data('change') ) {
+ if( hex === input.data('lastChange') ) return;
+ input.data('change').call(input.get(0), '#' + hex, hsb2rgb(hsb));
+ input.data('lastChange', hex);
+ }
+
+ };
+
+ var setColorFromInput = function(input) {
+
+ input.val('#' + cleanHex(input.val()));
+ var hex = expandHex(input.val());
+ if( !hex ) return false;
+
+ // Get HSB equivalent
+ var hsb = hex2hsb(hex);
+
+ // If color is the same, no change required
+ var currentHSB = input.data('hsb');
+ if( hsb.h === currentHSB.h && hsb.s === currentHSB.s && hsb.b === currentHSB.b ) return true;
+
+ // Set colorPicker position
+ var colorPosition = getColorPositionFromHSB(hsb);
+ var colorPicker = $(input.data('colorPicker'));
+ colorPicker.css('top', colorPosition.y + 'px').css('left', colorPosition.x + 'px');
+ input.data('colorPosition', colorPosition);
+
+ // Set huePosition position
+ var huePosition = getHuePositionFromHSB(hsb);
+ var huePicker = $(input.data('huePicker'));
+ huePicker.css('top', huePosition.y + 'px');
+ input.data('huePosition', huePosition);
+
+ setColor(input, hsb);
+
+ return true;
+
+ };
+
+ var convertCase = function(string, letterCase) {
+ if( letterCase === 'lowercase' ) return string.toLowerCase();
+ if( letterCase === 'uppercase' ) return string.toUpperCase();
+ return string;
+ };
+
+ var getColorPositionFromHSB = function(hsb) {
+ var x = Math.ceil(hsb.s / 0.67);
+ if( x < 0 ) x = 0;
+ if( x > 150 ) x = 150;
+ var y = 150 - Math.ceil(hsb.b / 0.67);
+ if( y < 0 ) y = 0;
+ if( y > 150 ) y = 150;
+ return { x: x - 5, y: y - 5 };
+ };
+
+ var getHuePositionFromHSB = function(hsb) {
+ var y = 150 - (hsb.h / 2.4);
+ if( y < 0 ) h = 0;
+ if( y > 150 ) h = 150;
+ return { y: y - 1 };
+ };
+
+ var cleanHex = function(hex) {
+ return hex.replace(/[^A-F0-9]/ig, '');
+ };
+
+ var expandHex = function(hex) {
+ hex = cleanHex(hex);
+ if( !hex ) return null;
+ if( hex.length === 3 ) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
+ return hex.length === 6 ? hex : null;
+ };
+
+ var hsb2rgb = function(hsb) {
+ var rgb = {};
+ var h = Math.round(hsb.h);
+ var s = Math.round(hsb.s*255/100);
+ var v = Math.round(hsb.b*255/100);
+ if(s === 0) {
+ rgb.r = rgb.g = rgb.b = v;
+ } else {
+ var t1 = v;
+ var t2 = (255 - s) * v / 255;
+ var t3 = (t1 - t2) * (h % 60) / 60;
+ if( h === 360 ) h = 0;
+ if( h < 60 ) { rgb.r = t1; rgb.b = t2; rgb.g = t2 + t3; }
+ else if( h < 120 ) {rgb.g = t1; rgb.b = t2; rgb.r = t1 - t3; }
+ else if( h < 180 ) {rgb.g = t1; rgb.r = t2; rgb.b = t2 + t3; }
+ else if( h < 240 ) {rgb.b = t1; rgb.r = t2; rgb.g = t1 - t3; }
+ else if( h < 300 ) {rgb.b = t1; rgb.g = t2; rgb.r = t2 + t3; }
+ else if( h < 360 ) {rgb.r = t1; rgb.g = t2; rgb.b = t1 - t3; }
+ else { rgb.r = 0; rgb.g = 0; rgb.b = 0; }
+ }
+ return {
+ r: Math.round(rgb.r),
+ g: Math.round(rgb.g),
+ b: Math.round(rgb.b)
+ };
+ };
+
+ var rgb2hex = function(rgb) {
+ var hex = [
+ rgb.r.toString(16),
+ rgb.g.toString(16),
+ rgb.b.toString(16)
+ ];
+ $.each(hex, function(nr, val) {
+ if (val.length === 1) hex[nr] = '0' + val;
+ });
+ return hex.join('');
+ };
+
+ var hex2rgb = function(hex) {
+ hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
+
+ return {
+ r: hex >> 16,
+ g: (hex & 0x00FF00) >> 8,
+ b: (hex & 0x0000FF)
+ };
+ };
+
+ var rgb2hsb = function(rgb) {
+ var hsb = { h: 0, s: 0, b: 0 };
+ var min = Math.min(rgb.r, rgb.g, rgb.b);
+ var max = Math.max(rgb.r, rgb.g, rgb.b);
+ var delta = max - min;
+ hsb.b = max;
+ hsb.s = max !== 0 ? 255 * delta / max : 0;
+ if( hsb.s !== 0 ) {
+ if( rgb.r === max ) {
+ hsb.h = (rgb.g - rgb.b) / delta;
+ } else if( rgb.g === max ) {
+ hsb.h = 2 + (rgb.b - rgb.r) / delta;
+ } else {
+ hsb.h = 4 + (rgb.r - rgb.g) / delta;
+ }
+ } else {
+ hsb.h = -1;
+ }
+ hsb.h *= 60;
+ if( hsb.h < 0 ) {
+ hsb.h += 360;
+ }
+ hsb.s *= 100/255;
+ hsb.b *= 100/255;
+ return hsb;
+ };
+
+ var hex2hsb = function(hex) {
+ var hsb = rgb2hsb(hex2rgb(hex));
+ // Zero out hue marker for black, white, and grays (saturation === 0)
+ if( hsb.s === 0 ) hsb.h = 360;
+ return hsb;
+ };
+
+ var hsb2hex = function(hsb) {
+ return rgb2hex(hsb2rgb(hsb));
+ };
+
+
+ // Handle calls to $([selector]).miniColors()
+ switch(o) {
+
+ case 'readonly':
+
+ $(this).each( function() {
+ if( !$(this).hasClass('miniColors') ) return;
+ $(this).prop('readonly', data);
+ });
+
+ return $(this);
+
+ case 'disabled':
+
+ $(this).each( function() {
+ if( !$(this).hasClass('miniColors') ) return;
+ if( data ) {
+ disable($(this));
+ } else {
+ enable($(this));
+ }
+ });
+
+ return $(this);
+
+ case 'value':
+
+ // Getter
+ if( data === undefined ) {
+ if( !$(this).hasClass('miniColors') ) return;
+ var input = $(this),
+ hex = expandHex(input.val());
+ return hex ? '#' + convertCase(hex, input.data('letterCase')) : null;
+ }
+
+ // Setter
+ $(this).each( function() {
+ if( !$(this).hasClass('miniColors') ) return;
+ $(this).val(data);
+ setColorFromInput($(this));
+ });
+
+ return $(this);
+
+ case 'destroy':
+
+ $(this).each( function() {
+ if( !$(this).hasClass('miniColors') ) return;
+ destroy($(this));
+ });
+
+ return $(this);
+
+ default:
+
+ if( !o ) o = {};
+
+ $(this).each( function() {
+
+ // Must be called on an input element
+ if( $(this)[0].tagName.toLowerCase() !== 'input' ) return;
+
+ // If a trigger is present, the control was already created
+ if( $(this).data('trigger') ) return;
+
+ // Create the control
+ create($(this), o, data);
+
+ });
+
+ return $(this);
+
+ }
+
+ }
+
+ });
+
+})(jQuery); \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/editor/js/mapeditor.iefixes.js b/www/wiki/extensions/Maps/resources/editor/js/mapeditor.iefixes.js
new file mode 100644
index 00000000..a49e42fb
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/editor/js/mapeditor.iefixes.js
@@ -0,0 +1,28 @@
+if (!Array.prototype.indexOf)
+{
+ Array.prototype.indexOf = function(elt /*, from*/)
+ {
+ var len = this.length >>> 0;
+
+ var from = Number(arguments[1]) || 0;
+ from = (from < 0)
+ ? Math.ceil(from)
+ : Math.floor(from);
+ if (from < 0)
+ from += len;
+
+ for (; from < len; from++)
+ {
+ if (from in this &&
+ this[from] === elt)
+ return from;
+ }
+ return -1;
+ };
+}
+
+if(typeof String.prototype.trim !== 'function') {
+ String.prototype.trim = function() {
+ return this.replace(/^\s+|\s+$/g, '');
+ }
+} \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/editor/js/mapeditor.js b/www/wiki/extensions/Maps/resources/editor/js/mapeditor.js
new file mode 100644
index 00000000..795fcc53
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/editor/js/mapeditor.js
@@ -0,0 +1,919 @@
+var mapEditor = {
+ __map:null,
+ __drawingManager:null,
+ __mapObjects:[],
+ __controlsDiv:null,
+ __options:{
+ canvas:'map-canvas',
+ onRightClick:function(){}
+ },
+ __mapObjectOptions: {
+ marker:{
+ draggable:true
+ },
+ polyline:{
+ strokeWeight:2,
+ strokeOpacity:1,
+ strokeColor:'#FF0000',
+ editable:true
+ },
+ circle:{
+ strokeWeight:2,
+ strokeOpacity:1,
+ strokeColor:'#FF0000',
+ fillColor:'#FF0000',
+ fillOpacity:0.5,
+ editable:true
+ },
+ rectangle:{
+ strokeWeight:2,
+ strokeOpacity:1,
+ strokeColor:'#FF0000',
+ fillColor:'#FF0000',
+ fillOpacity:0.5,
+ editable:true
+ },
+ polygon:{
+ strokeWeight:2,
+ strokeOpacity:1,
+ strokeColor:'#FF0000',
+ fillColor:'#FF0000',
+ fillOpacity:0.5,
+ editable:true
+ },
+ imageoverlay:{
+ strokeWeight:1,
+ strokeOpacity:0.5,
+ strokeColor:'#000',
+ fillOpacity:0.0,
+ editable:true
+ }
+ },
+ __mapParameters:{
+ mappingservice: {
+ values:mw.config.get( 'egMapsAvailableServices' )
+ },
+ copycoords: {
+ values:['on','off']
+ },
+ markercluster: {
+ values:['on','off']
+ },
+ searchmarkers:{
+ values:['title','all']
+ },
+ 'static':{
+ values:['on','off']
+ },
+ maxzoom: {
+ values:[]
+ },
+ minzoom: {
+ values:[]
+ },
+ zoom:{
+ values:[]
+ },
+ centre:{
+ values:[]
+ },
+ title:{
+ values:[]
+ },
+ label:{
+ values:[]
+ },
+ icon:{
+ values:[]
+ },
+ type:{
+ values:[]
+ },
+ types:{
+ values:[]
+ },
+ layers:{
+ values:[]
+ },
+ controls:{
+ values:[]
+ },
+ zoomstyle:{
+ values:['default','small','large']
+ },
+ typestyle:{
+ values:[]
+ },
+ autoinfowindows:{
+ values:['on','off']
+ },
+ kml:{
+ values:[]
+ },
+ gkml:{
+ values:[]
+ },
+ resizable:{
+ values:[]
+ },
+ tilt:{
+ values:[]
+ },
+ kmlrezoom:{
+ values:['on','off']
+ },
+ poi:{
+ values:['on','off']
+ },
+ visitedicon:{
+ values:[]
+ }
+ },
+ __addMapObject:function(o){
+ var idx = this.__mapObjects.indexOf(o);
+ if(idx === -1){
+ this.__mapObjects.push(o);
+ o.overlay.setMap(this.__map);
+ }
+ },
+ __removeMapObject:function(o){
+ var idx = this.__mapObjects.indexOf(o);
+ if(idx !== -1){
+ this.__mapObjects[idx].overlay.setMap(null);
+ this.__mapObjects.splice(idx,1);
+ }
+ },
+ __createOrUpdateImageOverlay:function(e,imageUrl){
+ //remove old image overlay if exists
+ this.__removeMapObject(e.imageoverlay);
+
+ //set to new type so it doesn't collide with rectangle
+ e.type = 'imageoverlaybounds';
+
+ //add map objects
+ this.__addMapObject(e);
+ var image = new google.maps.GroundOverlay(imageUrl, e.overlay.getBounds());
+ var imageOverlay = {
+ overlay:image,
+ type:'imageoverlay'
+ };
+ this.__addMapObject(imageOverlay);
+ e.imageoverlay = imageOverlay;
+ imageOverlay.metadata = e.metadata;
+
+ //register right click listener if not already done
+ if(e.rightClickListener !== true){
+ google.maps.event.addListener(e.overlay, 'rightclick', function () {
+ mapEditor.__options.onRightClick(e);
+ });
+ e.rightClickListener = true;
+ }
+
+ //register bounds change listener if not already done
+ if(e.boundsChangedListener !== true){
+ google.maps.event.addListener(e.overlay, 'bounds_changed', function () {
+ //destroy and recreate imageoverlay (due to no e.imageoverlay.setBounds() method)
+ mapEditor.__createOrUpdateImageOverlay(e, e.imageoverlay.overlay.getUrl());
+ });
+ e.boundsChangedListener = true;
+ }
+
+ return imageOverlay;
+ },
+ __initControls:function(){
+ //Create root controls element
+ var controlDiv = this.__controlsDiv = document.createElement('div');
+ controlDiv.setAttribute('class','mapeditor-controls');
+ controlDiv.index = 1;
+
+ this.__map.controls[google.maps.ControlPosition.BOTTOM_CENTER].push(controlDiv);
+ },
+ __initMap:function(){
+ var myOptions = {
+ center:new google.maps.LatLng(0, 0),
+ zoom:1,
+ mapTypeId:google.maps.MapTypeId.ROADMAP
+ };
+
+ this.__map = new google.maps.Map(document.getElementById(this.__options.canvas),myOptions);
+
+ //noinspection JSUnresolvedVariable
+ var drawingManager = this.__drawingManager = new google.maps.drawing.DrawingManager({
+ drawingMode:null,
+ drawingControl:true,
+ drawingControlOptions:{
+ position:google.maps.ControlPosition.TOP_CENTER
+ },
+ markerOptions:this.__mapObjectOptions.marker,
+ circleOptions:this.__mapObjectOptions.circle,
+ rectangleOptions:this.__mapObjectOptions.rectangle,
+ polygonOptions:this.__mapObjectOptions.polygon,
+ polylineOptions:this.__mapObjectOptions.polyline
+ });
+ drawingManager.setMap(this.__map);
+
+ google.maps.event.addListener(drawingManager, 'overlaycomplete', function (e) {
+ mapEditor.__addMapObject(e);
+ mapEditor.__registerRightClickListener(e);
+ google.maps.event.trigger(e.overlay, 'rightclick');
+ });
+ },
+ __readMapObjectOptionsFromMetadata:function(e){
+ var options = $.extend({},this.__mapObjectOptions[e.type]);
+ for(var key in e.metadata){
+ var data = e.metadata[key];
+ if(data.value.trim() !== ''){
+ options[data.name] = data.value;
+ }
+ }
+ try{
+ e.overlay.setOptions(options);
+ return true;
+ }catch(e){
+ return false;
+ }
+ },
+ __writeMetaDataToMapObject:function(e,metadata){
+ e.metadata = this.__arrayToObject(metadata);
+ },
+ __convertPositionalParametersToMetaData:function(positionalArray){
+ var positionalNames = [
+ 'title',
+ 'text',
+ 'strokeColor',
+ 'strokeOpacity',
+ 'strokeWeight',
+ 'fillColor',
+ 'fillOpacity',
+ 'showOnHover'
+ ];
+
+ if(positionalArray !== undefined && positionalArray.length > 0){
+ if(positionalArray[0].trim().indexOf('link:') === 0){
+ positionalNames.splice(0,1);
+ positionalNames[0] = 'link';
+ }
+ }
+
+ for(var x = 0; x < positionalArray.length; x++){
+ positionalArray[x] = {
+ name:positionalNames[x],
+ value:positionalArray[x].trim()
+ };
+ }
+ return this.__arrayToObject(positionalArray);
+ },
+ __arrayToObject:function(arr){
+ var o = {};
+ for (var i = 0; i < arr.length; ++i){
+ o[i] = arr[i];
+ }
+ return o;
+ },
+ __registerRightClickListener:function(e){
+ google.maps.event.addListener(e.overlay, 'rightclick', function () {
+ mapEditor.__options.onRightClick(e);
+ });
+ },
+ __generateWikiCode:function( separators ){
+ var code = separators.codeStart;
+
+ var markers = '';
+ var circles = '';
+ var polygons = '';
+ var lines = '';
+ var rectangles = '';
+ var imageoverlays = '';
+
+ for(var x = 0; x < this.__mapObjects.length; x++){
+ var mapObject = this.__mapObjects[x].overlay;
+ var mapObjectType = this.__mapObjects[x].type;
+ var mapObjectMeta = this.__mapObjects[x].metadata;
+
+ var metadata = '';
+ if(mapObjectMeta !== undefined){
+ var delimiterPosition = '';
+ for(var key in mapObjectMeta){
+ var data = mapObjectMeta[key];
+ delimiterPosition += delimiterPosition.length > 0 ? ' ~' : '~';
+ if(data.value !== ''){
+ if(data.name === 'link' && data.value.indexOf('link:') === -1){
+ data.value = 'link:'+data.value;
+ }
+ if(!(mapObjectType === 'imageoverlay' && data.name === 'image')){
+ metadata += delimiterPosition+data.value;
+ delimiterPosition = '';
+ }
+ }
+ }
+ }
+
+ var serializedData = mapObject+metadata;
+ if (mapObjectType === 'marker') {
+ markers += markers === '' ? serializedData : '; '+serializedData;
+ } else if (mapObjectType === 'circle') {
+ circles += circles === '' ? serializedData : '; '+serializedData;
+ } else if (mapObjectType === 'polygon') {
+ polygons += polygons === '' ? serializedData : '; '+serializedData;
+ } else if (mapObjectType === 'polyline') {
+ lines += lines === '' ? serializedData : '; '+serializedData;
+ } else if (mapObjectType === 'rectangle') {
+ rectangles += rectangles === '' ? serializedData : '; '+serializedData;
+ }else if(mapObjectType === 'imageoverlay'){
+ imageoverlays += imageoverlays === '' ? serializedData : '; '+serializedData;
+ }
+ }
+
+
+ code += markers !== '' ? markers : '';
+ code += circles !== '' ? separators.separator+'circles='+circles : '';
+ code += polygons !== '' ? separators.separator+'polygons='+polygons : '';
+ code += lines !== '' ? separators.separator+'lines='+lines : '';
+ code += rectangles !== '' ? separators.separator+'rectangles='+rectangles : '';
+ code += imageoverlays !== '' ? separators.separator+'imageoverlays='+imageoverlays : '';
+
+ //add map parameters
+ for(var param in this.__mapParameters){
+ var value = this.__mapParameters[param].value;
+ if(value === undefined || value === ''){
+ continue;
+ }
+ code += '\n|'+param+'='+value;
+ }
+
+ code += separators.codeEnd;
+ return code;
+ },
+ __importWikiCode:function(rawData){
+ var syntaxPattern = /^\{\{#display_map:[\s\S]*\}\}[\s\n]*$/i;
+ if(rawData.match(syntaxPattern) === null){ //NO MATCH
+ return false;
+ }else{
+ try{
+ var patterns = {
+ marker: /^\{\{#display_map:\s*(.*)/i,
+ polyline: /\|\s*lines=(.*)/i,
+ circle:/\|\s*circles=(.*)/i,
+ polygon:/\|\s*polygons=(.*)/i,
+ rectangle:/\|\s*rectangles=(.*)/i,
+ imageoverlay:/\|\s*imageoverlays=(.*)/i,
+ parameter:/\|\s*(.*)=(.*)/i
+ };
+ var mapObjects = [];
+ rawData = rawData.split('\n');
+ for(var j = 0; j < rawData.length; j++){
+ for (var key in patterns){
+ var match = rawData[j].match(patterns[key]);
+ if(match !== null && match[1].trim().length !== 0){
+ var isMapObject = false;
+ if(key !== 'parameter'){
+ var data = match[1].split(';');
+ for(var i = 0; i < data.length; i++){
+
+ var metadata = data[i].split('~');
+ metadata.splice(0,1);
+ if(metadata.length > 0){
+ data[i] = data[i].substring(0,data[i].indexOf('~'));
+ }
+ metadata = this.__convertPositionalParametersToMetaData(metadata);
+
+ var options = this.__mapObjectOptions[key];
+ var mapObject = null;
+ if (key === 'marker') {
+ var position = data[i].split(',');
+ //noinspection JSValidateTypes
+ options = $.extend({
+ position: new google.maps.LatLng(position[0],position[1])
+ },options);
+ mapObject = new google.maps.Marker(options);
+ } else if (key === 'circle') {
+ var parts = data[i].split(':');
+ var radius = parts[1];
+ var position = parts[0].split(',');
+ //noinspection JSValidateTypes
+ options = $.extend({
+ center: new google.maps.LatLng(position[0],position[1]),
+ radius: parseFloat(radius)
+ },options);
+ mapObject = new google.maps.Circle(options);
+ } else if (key === 'polygon') {
+ var paths = data[i].split(':');
+ for(var x = 0; x < paths.length; x++){
+ var position = paths[x].split(',');
+ paths[x] = new google.maps.LatLng(position[0],position[1]);
+ }
+ paths = new google.maps.MVCArray(paths);
+ //noinspection JSValidateTypes
+ options = $.extend({
+ paths: paths
+ },options);
+ mapObject = new google.maps.Polygon(options);
+ } else if (key === 'polyline') {
+ var paths = data[i].split(':');
+ for(var x = 0; x < paths.length; x++){
+ var position = paths[x].split(',');
+ paths[x] = new google.maps.LatLng(position[0],position[1]);
+ }
+ paths = new google.maps.MVCArray(paths);
+ //noinspection JSValidateTypes
+ options = $.extend({
+ path: paths
+ },options);
+ mapObject = new google.maps.Polyline(options);
+ } else if (key === 'rectangle') {
+ var parts = data[i].split(':');
+ var ne = parts[0].split(',');
+ var sw = parts[1].split(',');
+ sw = new google.maps.LatLng(sw[0],sw[1]);
+ ne = new google.maps.LatLng(ne[0],ne[1]);
+ //noinspection JSValidateTypes
+ options = $.extend({
+ bounds: new google.maps.LatLngBounds(sw,ne)
+ },options);
+ mapObject = new google.maps.Rectangle(options);
+ }else if (key === 'imageoverlay'){
+ var parts = data[i].split(':');
+ var ne = parts[0].split(',');
+ var sw = parts[1].split(',');
+ var imageUrl = parts[2];
+ sw = new google.maps.LatLng(sw[0],sw[1]);
+ ne = new google.maps.LatLng(ne[0],ne[1]);
+
+ options = $.extend({
+ bounds: new google.maps.LatLngBounds(sw,ne)
+ },options);
+ var rectangle = new google.maps.Rectangle(options);
+
+ //add image url as metadata entry
+ metadata.image = {
+ name:'image',value:imageUrl
+ };
+
+ mapObject = {
+ type:'imageoverlaybounds',
+ overlay:rectangle,
+ metadata:metadata
+ };
+
+ this.__createOrUpdateImageOverlay(mapObject,imageUrl);
+ this.__readMapObjectOptionsFromMetadata(mapObject);
+
+ }
+ if(mapObject !== null){
+ //imageoverlay needs special handling
+ if(key !== 'imageoverlay' ){
+ mapObject = {
+ type:key,
+ overlay:mapObject,
+ metadata:metadata
+ };
+
+ this.__registerRightClickListener(mapObject);
+ this.__addMapObject(mapObject);
+ this.__readMapObjectOptionsFromMetadata(mapObject);
+ }
+
+ isMapObject = true;
+
+ }
+ }
+ }else if(!isMapObject){
+ //handle global map parameters
+ if(this.__mapParameters[match[1]] === undefined){
+ this.__mapParameters[match[1]] = {};
+ }
+ this.__mapParameters[match[1]].value = match[2];
+ }
+ }
+ }
+ }
+ }catch(e){
+ console.log('An error occurred when parsing data');
+ return false;
+ }
+ return true;
+ }
+ },
+ addControlButton:function (text, onclick){
+ // Set CSS for the control border
+ var controlUI = $('<div class="mapeditor-control-element"></div>');
+ $(controlUI).click(function(){
+ onclick.call(this);
+ }).appendTo(this.__controlsDiv);
+
+ // Set CSS for the control interior
+ var controlText = $('<span class="mapeditor-control-text"></span>')
+ .text(text).appendTo(controlUI);
+ },
+ setup:function(o){
+ //extend options
+ $.extend(this.__options,o);
+
+ //Override tostring methods for wiki code generation
+ google.maps.LatLng.prototype.toString = function(){
+ return this.lat()+','+this.lng();
+ };
+
+ google.maps.Rectangle.prototype.toString = function(){
+ var bounds = this.getBounds();
+ var ne = bounds.getNorthEast();
+ var sw = bounds.getSouthWest();
+ return ne+':'+sw;
+ };
+
+ google.maps.Marker.prototype.toString = function(){
+ var position = this.getPosition();
+ return position.lat()+','+position.lng();
+ };
+
+ google.maps.Circle.prototype.toString = function(){
+ var center = this.getCenter();
+ var radius = this.getRadius();
+ return center.lat()+','+center.lng()+':'+radius;
+ };
+
+ google.maps.Polygon.prototype.toString = function(){
+ var polygons = '';
+ this.getPath().forEach(function(e){
+ polygons += ':'+e;
+ });
+ return polygons.substr(1);
+ };
+
+ google.maps.Polyline.prototype.toString = function(){
+ var lines = '';
+ this.getPath().forEach(function(e){
+ lines += ':'+e;
+ });
+ return lines.substr(1);
+ };
+
+ google.maps.GroundOverlay.prototype.toString = function(){
+ var bounds = this.getBounds();
+ var sw = bounds.getSouthWest();
+ var ne = bounds.getNorthEast();
+ return [ne,sw,this.getUrl()].join(':');
+ };
+
+ //initialize rest
+ this.__initMap();
+ this.__initControls();
+ }
+};
+
+$(document).ready(function(){
+
+
+ function openDialog(e){
+ if(e.metadata !== undefined){
+ for(var key in e.metadata){
+ var data = e.metadata[key];
+ $(this).find('form input[name="'+data.name+'"]').val(data.value);
+ }
+ }
+ var i18nButtons = {};
+ i18nButtons[mw.msg('mapeditor-done-button')] = function () {
+ var form = $(this).find('form');
+ form.find('.miniColors').each(function(){
+ if($(this).val() === '#'){
+ $(this).val('');
+ }
+ });
+ mapEditor.__writeMetaDataToMapObject(e,form.serializeArray());
+ $(this).dialog("close");
+ if(!mapEditor.__readMapObjectOptionsFromMetadata(e)){
+ alert(mw.msg('mapeditor-parser-error'));
+ }
+ };
+ i18nButtons[mw.msg('mapeditor-remove-button')] = function () {
+ mapEditor.__removeMapObject(e);
+ $(this).dialog("close");
+ };
+
+ this.dialog({
+ modal:true,
+ buttons:i18nButtons,
+ open:function(){
+ if(e.metadata !== undefined){
+ var isText = true;
+ for(var key in e.metadata){
+ var data = e.metadata[key];
+ if(data.name === 'link' && data.value.length > 0){
+ isText = false;
+ break;
+ }
+ }
+ //depending on existing metadata,
+ //show either form with title/text fields or just link field
+ if(isText){
+ $(this).find('input[value="text"]').trigger('click');
+ }else{
+ $(this).find('input[value="link"]').trigger('click');
+ }
+ }else{
+ //default trigger click on text radio button
+ $(this).find('input[value="text"]').trigger('click');
+ }
+
+
+ },
+ beforeClose:function(){
+ //reset the form
+ var form = $(this).find('form');
+ form[0].reset();
+ form.find('.opacity-data-holder').text(mw.msg('mapeditor-none-text'));
+ form.find('.ui-slider').slider('value',-1);
+ form.find('.miniColors').miniColors('value','#fff').val('');
+ }
+ });
+ }
+
+ function openImageOverlayDialog(e,callback){
+
+ var form = $('#imageoverlay-form');
+
+ var i18nButtons = {};
+ i18nButtons[mw.msg('mapeditor-done-button')] = function(){
+ var imageUrl = $(this).find('input[name="image"]').val();
+ mapEditor.__createOrUpdateImageOverlay(e,imageUrl);
+
+ var metadata = form.find('form').serializeArray();
+ mapEditor.__writeMetaDataToMapObject(e,metadata);
+ mapEditor.__writeMetaDataToMapObject(e.imageoverlay,metadata);
+
+ form.dialog("close");
+ };
+
+ i18nButtons[mw.msg('mapeditor-remove-button')] = function () {
+ mapEditor.__removeMapObject(e.imageoverlay);
+ e.imageoverlay = undefined;
+ form.dialog("close");
+ };
+
+ form.dialog({
+ modal:true,
+ buttons:i18nButtons,
+ open:function(){
+ //restore data from previous edits
+ if(e.metadata !== undefined){
+ var isText = true;
+ for(var key in e.metadata){
+ var data = e.metadata[key];
+ if(data.name === 'link' && data.value.length > 0){
+ isText = false;
+ }
+ form.find('form input[name="'+data.name+'"]').val(data.value);
+ }
+ //depending on existing metadata,
+ //show either form with title/text fields or just link field
+ if(isText){
+ form.find('input[value="text"]').trigger('click');
+ }else{
+ form.find('input[value="link"]').trigger('click');
+ }
+ }else{
+ //default trigger click on text radio button
+ form.find('input[value="text"]').trigger('click');
+ }
+ },
+ beforeClose:function(){
+ mapEditor.__drawingManager.setMap(mapEditor.__map); //re-enable standard drawing manager
+ if(e.imageoverlay === undefined){
+ mapEditor.__removeMapObject(e);
+ }
+
+ //reset the form
+ var formElement = form.find('form');
+ formElement[0].reset();
+
+ if(callback !== undefined && typeof(callback) === 'function'){
+ callback();
+ }
+
+ }
+ });
+ }
+
+ mapEditor.setup({
+ onRightClick:function(e){
+ if (e.type === 'marker') {
+ openDialog.call($('#marker-form'),e);
+ } else if (e.type === 'circle') {
+ openDialog.call($('#fillable-form'),e);
+ } else if (e.type === 'polygon') {
+ openDialog.call($('#polygon-form'),e);
+ } else if (e.type === 'polyline') {
+ openDialog.call($('#strokable-form'),e);
+ } else if (e.type === 'rectangle') {
+ openDialog.call($('#fillable-form'),e);
+ } else if (e.type === 'imageoverlaybounds') {
+ openImageOverlayDialog(e);
+ }
+ }
+ });
+
+ //add custom controls
+ if( $('#map-canvas').attr('context') != 'forminput' ) { //for Special:MapEditor
+ var editorMarkers = {
+ 'codeStart' : '{{#display_map: ',
+ 'separator' : '\n|',
+ 'codeEnd' : '\n}}\n'
+ };
+ mapEditor.addControlButton(mw.msg('mapeditor-export-button'),function(){
+ var code = mapEditor.__generateWikiCode( editorMarkers );
+ if(navigator.appName == 'Microsoft Internet Explorer'){
+ //if IE replace /n with /r/n so it is displayed properly
+ code = code.split('\n').join('\r\n');
+ }
+ $('#code-output').text(code);
+ $('#code-output-container').dialog({
+ modal:true,
+ width:'80%',
+ open:function(){
+ $('#code-output').focus();
+ }
+ });
+ });
+
+ mapEditor.addControlButton(mw.msg('mapeditor-import-button'), function(){
+ var i18nButtons = {};
+ i18nButtons[mw.msg('mapeditor-import-button2')] = function () {
+ var data = $('#code-input').val();
+ if(mapEditor.__importWikiCode(data)){
+ $(this).dialog('close');
+ }else{
+ alert('Could not parse input! make sure the input has the same structure as exported wiki code');
+ }
+ };
+ $('#code-input-container').dialog({
+ modal:true,
+ width:'80%',
+ buttons: i18nButtons
+ });
+ });
+ } else { //for form input
+ var forminputMarkers = {
+ 'codeStart' : '',
+ 'separator' : '',
+ 'codeEnd' : ''
+ };
+ mapEditor.addControlButton(mw.msg('mapeditor-export-button'),function(){
+ var code = mapEditor.__generateWikiCode( forminputMarkers );
+ if(navigator.appName == 'Microsoft Internet Explorer'){
+ //if IE replace /n with /r/n so it is displayed properly
+ code = code.split('\n').join('\r\n');
+ }
+ $('#map-polygon').text(code);
+ });
+ }
+
+ mapEditor.addControlButton(mw.msg('mapeditor-mapparam-button'), function(){
+ var i18nButtons = {};
+ i18nButtons[mw.msg('mapeditor-done-button')] = function(){
+ var data = $(this).find('form input').not('select').serializeArray();
+ for(var x = 0; x < data.length; x++){
+ mapEditor.__mapParameters[data[x].name].value = data[x].value;
+ }
+ $(this).dialog('close');
+ };
+ $('#map-parameter-form').dialog({
+ modal:true,
+ width: 500,
+ buttons:i18nButtons
+ });
+ });
+
+ mapEditor.addControlButton(mw.msg('mapeditor-clear-button'), function(){
+ while(mapEditor.__mapObjects.length > 0){
+ var mapObj = mapEditor.__mapObjects.pop();
+ mapObj.overlay.setMap(null);
+ }
+ for(var param in mapEditor.__mapParameters){
+ mapEditor.__mapParameters[param].value = undefined;
+ }
+ });
+
+ mapEditor.addControlButton(mw.msg('mapeditor-imageoverlay-button'), function(){
+ var button = $(this);
+ if(button.data('clicked') === true){
+ return; //already clicked, disregard this click
+ }else{
+ button.data('clicked',true);
+ }
+
+ mapEditor.__drawingManager.setMap(null); //disable current drawing manager
+
+ var drawingManager = new google.maps.drawing.DrawingManager({
+ drawingMode:google.maps.drawing.OverlayType.RECTANGLE,
+ drawingControl:false,
+ rectangleOptions:mapEditor.__mapObjectOptions.imageoverlay
+ });
+ drawingManager.setMap(mapEditor.__map);
+
+ google.maps.event.addListener(drawingManager, 'overlaycomplete', function (e) {
+ mapEditor.__addMapObject(e); //add if it doesn't already exist.
+
+ drawingManager.setMap(null);
+
+ openImageOverlayDialog(e,function(){
+ //re-enable button
+ button.data('clicked',false);
+ });
+
+ });
+
+ });
+
+
+
+ //init map parameters
+ var formselect = $('#map-parameter-form select[name="key"]');
+ formselect.on('change',function(){
+ var option = $(this);
+ var key = option.val();
+ var value = mapEditor.__mapParameters[key].value;
+ if(value === undefined){
+ value = '';
+ }
+
+ var parent = option.parent();
+ var input = $('<input type="text" name="'+key+'" value="'+value+'"></input>');
+ var removeButton = $('<a href="#">[x]</a>');
+ var option2 = option.clone(true);
+ var container = $('<div></div>');
+
+ option2.find('option[value="'+key+'"]').remove();
+ option.attr('disabled',true);
+ removeButton.on('click',function(){
+ var removedKey = $(this).prevAll('select').val();
+ var activeSelect = $(this).parent().parent().find('select').not('select[disabled="disabled"]');
+ var option = $('<option value="'+removedKey+'">'+removedKey+'</option>');
+ activeSelect.children().first().after(option);
+ $(this).parent().remove();
+ mapEditor.__mapParameters[key].value = undefined;
+ return false;
+ });
+
+ parent.append(input);
+ parent.append(removeButton);
+ parent.parent().append(container);
+ container.append(option2);
+
+ input.autocomplete({
+ source: mapEditor.__mapParameters[key].values,
+ minLength: 0,
+ autoFocus: true
+ });
+ input.autocomplete('search','');
+ });
+
+ for(var parameter in mapEditor.__mapParameters){
+ var option = $('<option value="'+parameter+'">'+parameter+'</option>');
+ formselect.append(option);
+ }
+
+ //hide link input initially
+ $('input[name="link"]').attr('disabled',true).hide().prev().hide();
+
+ //init text/link switcher
+ $('input[name="switch"]').on('click',function(){
+ if($(this).val() === 'link'){
+ $(this).parent().next().find('input[name="title"],input[name="text"]').attr('disabled',true).hide().prev().hide();
+ $(this).parent().next().find('input[name="link"]').attr('disabled',false).show().prev().show();
+ }else{
+ $(this).parent().next().find('input[name="title"],input[name="text"]').attr('disabled',false).show().prev().show();
+ $(this).parent().next().find('input[name="link"]').attr('disabled',true).hide().prev().hide();
+ }
+ });
+
+ //init enter keypress to press done on dialog.
+ $('.mapeditor-dialog').on('keypress',function(e){
+ if(e.keyCode === 13){
+ $(this).dialog('option','buttons').Done.call(this);
+ }
+ });
+
+ //init sliders
+ $('input[name="strokeOpacity"],input[name="fillOpacity"]').each(function(){
+ var input = $(this);
+ var dataHolder = $('<span class="opacity-data-holder">'+mw.msg('mapeditor-none-text')+'</span>');
+ dataHolder.css({fontSize: 12});
+ var slider = $('<div></div>').slider({
+ min: -1,
+ slide: function(event, ui){
+ if(ui.value === -1){
+ input.val('');
+ dataHolder.text(mw.msg('mapeditor-none-text'));
+ }else{
+ input.val( ui.value/100 );
+ dataHolder.text(ui.value+'%');
+ }
+ }
+ });
+ input.before(dataHolder);
+ input.after(slider);
+ });
+
+ //init color pickers
+ $('input[name="strokeColor"],input[name="fillColor"]').miniColors().miniColors('value','').val('');
+});
diff --git a/www/wiki/extensions/Maps/resources/leaflet/cluster/m1.png b/www/wiki/extensions/Maps/resources/leaflet/cluster/m1.png
new file mode 100644
index 00000000..3346508f
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/cluster/m1.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/cluster/m2.png b/www/wiki/extensions/Maps/resources/leaflet/cluster/m2.png
new file mode 100644
index 00000000..64786829
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/cluster/m2.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/cluster/m3.png b/www/wiki/extensions/Maps/resources/leaflet/cluster/m3.png
new file mode 100644
index 00000000..772ed91a
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/cluster/m3.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/cluster/m4.png b/www/wiki/extensions/Maps/resources/leaflet/cluster/m4.png
new file mode 100644
index 00000000..eb33557a
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/cluster/m4.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/cluster/m5.png b/www/wiki/extensions/Maps/resources/leaflet/cluster/m5.png
new file mode 100644
index 00000000..6ca9c490
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/cluster/m5.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/ext.maps.leaflet.js b/www/wiki/extensions/Maps/resources/leaflet/ext.maps.leaflet.js
new file mode 100644
index 00000000..087d4acf
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/ext.maps.leaflet.js
@@ -0,0 +1,4 @@
+
+mediaWiki.loader.using( [ 'ext.maps.leaflet' ] ).done( function () {
+ ( new maps.services( jQuery( document ) ) ).leaflet();
+} ); \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/leaflet/ext.sm.leafletajax.js b/www/wiki/extensions/Maps/resources/leaflet/ext.sm.leafletajax.js
new file mode 100644
index 00000000..fb9f7341
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/ext.sm.leafletajax.js
@@ -0,0 +1,44 @@
+/**
+ * JavaScript for Leaflet in the Semantic Maps extension.
+ * @see https://www.mediawiki.org/wiki/Extension:Semantic_Maps
+ *
+ * @licence GNU GPL v2+
+ * @author Peter Grassberger < petertheone@gmail.com >
+ */
+
+
+(function( $, sm ) {
+ var ajaxRequest = null;
+
+ var mapEvents = ['dragend', 'zoomend'];
+
+ $( document ).ready( function() {
+ // todo: find a way to remove setTimeout.
+ setTimeout( function() {
+ $( window.maps.leafletList ).each( function( index, map ) {
+ if( !map.options.ajaxquery || !map.options.ajaxcoordproperty ) {
+ return;
+ }
+ map.map.on( mapEvents.join( ' ' ), function() {
+ var bounds = map.map.getBounds();
+ var query = sm.buildQueryString(
+ decodeURIComponent( map.options.ajaxquery.replace( /\+/g, ' ' ) ),
+ map.options.ajaxcoordproperty,
+ bounds.getNorthEast().lat,
+ bounds.getNorthEast().lng,
+ bounds.getSouthWest().lat,
+ bounds.getSouthWest().lng
+ );
+
+ if( ajaxRequest !== null ) {
+ ajaxRequest.abort();
+ }
+ ajaxRequest = sm.ajaxUpdateMarker( map, query, map.options.icon ).done( function() {
+ map.createMarkerCluster();
+ ajaxRequest = null;
+ } );
+ } );
+ } );
+ }, 1000 );
+ } );
+})( window.jQuery, window.sm );
diff --git a/www/wiki/extensions/Maps/resources/leaflet/jquery.leaflet.js b/www/wiki/extensions/Maps/resources/leaflet/jquery.leaflet.js
new file mode 100644
index 00000000..3483ec3a
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/jquery.leaflet.js
@@ -0,0 +1,434 @@
+/**
+ * JavaScript for Leaflet in the Maps extension.
+ * @see https://www.mediawiki.org/wiki/Extension:Maps
+ *
+ * @author Pavel Astakhov < pastakhov@yandex.ru >
+ * @author Peter Grassberger < petertheone@gmail.com >
+ */
+(function ($, mw, L, MQ) {
+ $.fn.leafletmaps = function ( options ) {
+ var _this = this;
+ this.map = null;
+ this.options = options;
+ this.markers = [];
+ this.markercluster = null;
+ var apikeys = mw.config.get('egMapsLeafletLayersApiKeys') ;
+
+ /**
+ * array point of all map elements (markers, lines, polygons, etc.)
+ * for map fit
+ */
+ this.points = [];
+
+ /**
+ * Creates a new marker with the provided data and returns it.
+ * @param {Object} properties Contains the fields lat, lon, title, text and icon
+ * @return {L.Marker}
+ */
+ this.createMarker = function (properties) {
+ this.points.push( new L.LatLng(properties.lat, properties.lon) );
+
+ var markerOptions = {
+ title:properties.title
+ };
+
+ var marker = L.marker( [properties.lat, properties.lon], markerOptions );
+
+ if (properties.hasOwnProperty('icon') && properties.icon !== '') {
+ marker.setOpacity(0);
+
+ var img = new Image();
+ img.onload = function() {
+ var icon = new L.Icon({
+ iconUrl: properties.icon,
+ iconSize: [ img.width, img.height ],
+ iconAnchor: [ img.width / 2, img.height ],
+ popupAnchor: [ -img.width % 2, -img.height*2/3 ]
+ });
+
+ marker.setIcon(icon);
+ marker.setOpacity(1);
+ };
+ img.src = properties.icon;
+ }
+
+ if( properties.hasOwnProperty('text') && properties.text.length > 0 ) {
+ marker.bindPopup( properties.text );
+ }
+
+ if ( options.copycoords ) {
+ marker.on(
+ 'contextmenu',
+ function( e ) {
+ prompt(mw.msg('maps-copycoords-prompt'), e.latlng.lat + ',' + e.latlng.lng);
+ }
+ );
+ }
+
+ return marker;
+ };
+
+ /**
+ * Creates a new marker with the provided data, adds it to the map
+ * and returns it.
+ * @param {Object} properties Contains the fields lat, lon, title, text and icon
+ * @return {L.Marker}
+ */
+ this.addMarker = function (properties) {
+ var marker = this.createMarker(properties);
+ if (!this.options.markercluster) {
+ marker.addTo( this.map );
+ }
+ this.markers.push( marker );
+ return marker;
+ };
+
+ this.removeMarker = function (marker) {
+ this.map.removeLayer(marker);
+ this.points = [];
+ this.markers = this.markers.filter(function(object) {
+ return object !== marker;
+ });
+ };
+
+ this.removeMarkers = function () {
+ if (this.markercluster) {
+ this.map.removeLayer(this.markercluster);
+ this.markercluster = null;
+ }
+ var map = this.map;
+ $.each(this.markers, function(index, marker) {
+ map.removeLayer(marker);
+ });
+
+ this.points = [];
+ this.markers = [];
+ };
+
+ this.addLine = function (properties) {
+ var options = {
+ color: properties.strokeColor,
+ weight:properties.strokeWeight,
+ opacity:properties.strokeOpacity
+ };
+
+ var latlngs = [];
+ for (var x = 0; x < properties.pos.length; x++) {
+ latlngs.push([properties.pos[x].lat, properties.pos[x].lon]);
+ this.points.push( new L.LatLng(properties.pos[x].lat, properties.pos[x].lon) );
+ }
+
+ var line = L.polyline(latlngs, options).addTo(this.map);
+
+ if( properties.hasOwnProperty('text') && properties.text.trim().length > 0 ) {
+ line.bindPopup( properties.text );
+ }
+ };
+
+ this.addPolygon = function (properties) {
+ properties.pos.forEach(function(position) {
+ _this.points.push( new L.LatLng(position.lat, position.lon) );
+ });
+
+ var polygon = L.polygon(
+ properties.pos.map(function(position) {
+ return [position.lat, position.lon];
+ }),
+ {
+ color: properties.strokeColor,
+ weight:properties.strokeWeight,
+ opacity:properties.strokeOpacity,
+ fillColor:properties.fillColor,
+ fillOpacity:properties.fillOpacity
+ }
+ );
+
+ polygon.addTo(this.map);
+
+ if( properties.hasOwnProperty('text') && properties.text.trim().length > 0 ) {
+ console.log(properties.text);
+ polygon.bindPopup( properties.text );
+ }
+ };
+
+ this.addCircle = function (properties) {
+ var circle = L.circle(
+ [properties.centre.lat, properties.centre.lon],
+ {
+ radius: properties.radius,
+ color: properties.strokeColor,
+ weight:properties.strokeWeight,
+ opacity:properties.strokeOpacity,
+ fillColor:properties.fillColor,
+ fillOpacity:properties.fillOpacity,
+ }
+ ).addTo(this.map);
+
+ this.points.push( new L.LatLng(properties.centre.lat, properties.centre.lon) );
+
+ if( properties.hasOwnProperty('text') && properties.text.trim().length > 0 ) {
+ circle.bindPopup( properties.text );
+ }
+ };
+
+ this.addRectangle = function (properties) {
+ this.points.push( new L.LatLng(properties.sw.lat, properties.sw.lon) );
+ this.points.push( new L.LatLng(properties.ne.lat, properties.ne.lon) );
+
+ var options = {
+ color: properties.strokeColor,
+ weight:properties.strokeWeight,
+ opacity:properties.strokeOpacity,
+ fillColor:properties.fillColor,
+ fillOpacity:properties.fillOpacity
+ };
+
+ var bounds = [[properties.sw.lat, properties.sw.lon], [properties.ne.lat, properties.ne.lon]];
+
+ var rectangle = L.rectangle( bounds, options ).addTo(this.map);
+
+ if( properties.hasOwnProperty('text') && properties.text.trim().length > 0 ) {
+ rectangle.bindPopup( properties.text );
+ }
+ };
+
+ this.createMarkerCluster = function () {
+ if ( !options.markercluster ) {
+ return;
+ }
+ var markers = this.markers;
+
+ var markercluster = new L.MarkerClusterGroup({
+ maxClusterRadius: options.clustermaxradius,
+ disableClusteringAtZoom: options.clustermaxzoom + 1,
+ zoomToBoundsOnClick: options.clusterzoomonclick,
+ spiderfyOnMaxZoom: options.clusterspiderfy,
+ iconCreateFunction: function(cluster) {
+ var childCount = cluster.getChildCount();
+
+ var imagePath = mw.config.get( 'egMapsScriptPath' ) + '/resources/leaflet/cluster/';
+
+ var styles = [
+ {
+ iconUrl: imagePath + 'm1.png',
+ iconSize: [53, 52]
+ },
+ {
+ iconUrl: imagePath + 'm2.png',
+ iconSize: [56, 55]
+ },
+ {
+ iconUrl: imagePath + 'm3.png',
+ iconSize: [66, 65]
+ },
+ {
+ iconUrl: imagePath + 'm4.png',
+ iconSize: [78, 77]
+ },
+ {
+ iconUrl: imagePath + 'm5.png',
+ iconSize: [90, 89]
+ }
+ ];
+
+ var index = 0;
+ var dv = childCount;
+ while (dv !== 0) {
+ dv = parseInt(dv / 10, 10);
+ index++;
+ }
+ var index = Math.min(index, styles.length);
+ index = Math.max(0, index - 1);
+ index = Math.min(styles.length - 1, index);
+ var style = styles[index];
+
+ return new L.divIcon({
+ iconSize: style.iconSize,
+ className: '',
+ html: '<img style="' +
+ '" src="' + style.iconUrl + '" />' +
+ '<span style="' +
+ 'position: absolute; font-size: 11px; font-weight: bold; text-align: center; ' +
+ 'top: 0; left: 0; ' +
+ 'line-height: ' + style.iconSize[1] + 'px;' +
+ 'width: ' + style.iconSize[0] + 'px;' +
+ '">' + childCount + '</span>'
+ });
+ }
+ });
+ $.each(this.markers, function(index, marker) {
+ markercluster.addLayer(marker);
+ });
+ if (this.markercluster) {
+ this.map.removeLayer(this.markercluster);
+ this.markercluster = null;
+ }
+ this.map.addLayer(markercluster);
+ this.markercluster = markercluster;
+ };
+
+ this.addGeoJson = function(options) {
+ if (options.geojson !== '') {
+ var geoJson = options.geojson;
+ var geoJsonLayer = L.geoJSON( geoJson ).addTo( this.map );
+
+ this.points.push( geoJsonLayer.getBounds().getNorthEast() );
+ this.points.push( geoJsonLayer.getBounds().getSouthWest() );
+ }
+ };
+
+ this.setup = function () {
+
+ var mapOptions = {};
+ if (options.minzoom !== false ) mapOptions.minZoom = options.minzoom;
+ if (options.maxzoom !== false ) mapOptions.maxZoom = options.maxzoom;
+
+ if (options.enablefullscreen) {
+ mapOptions.fullscreenControl = true;
+ mapOptions.fullscreenControlOptions= {
+ position: 'topleft'
+ };
+ }
+
+ mapOptions.scrollWheelZoom = options.scrollwheelzoom;
+
+ if (options.static) {
+ mapOptions.scrollWheelZoom = false;
+ mapOptions.doubleClickZoom = false;
+ mapOptions.touchZoom = false;
+ mapOptions.boxZoom = false;
+ mapOptions.tap = false;
+ mapOptions.keyboard = false;
+ mapOptions.zoomControl = false;
+ mapOptions.dragging = false;
+ }
+
+ var map = L.map( this.get(0), mapOptions ).fitWorld();
+ this.map = map;
+
+ var layers = {};
+ $.each(options.layers.reverse(), function(index, layerName) {
+ var options = {} ;
+ var providerName = layerName.split('.')[0] ;
+ if (apikeys.hasOwnProperty(providerName) && apikeys[providerName] !== '') {
+ options.apikey = apikeys[providerName] ;
+ }
+ if (layerName === 'MapQuestOpen') {
+ layers[layerName] = new MQ.TileLayer().addTo(map);
+ } else {
+ layers[layerName] = new L.tileLayer.provider(layerName,options).addTo(map);
+ }
+ });
+
+ var overlaylayers = {};
+ $.each(options.overlaylayers, function(index, overlaylayerName) {
+ overlaylayers[overlaylayerName] = new L.tileLayer.provider(overlaylayerName).addTo(_this.map);
+ });
+
+ if (options.layers.length > 1) {
+ L.control.layers(layers, overlaylayers).addTo(map);
+ }
+
+ if (options.resizable) {
+ //TODO: Fix moving map when resized
+ _this.resizable();
+ }
+
+ if (!options.locations) {
+ options.locations = [];
+ }
+
+ // Add the markers.
+ for (var i = options.locations.length - 1; i >= 0; i--) {
+ this.addMarker(options.locations[i]);
+ }
+
+ // Add markercluster
+ if (options.markercluster) {
+ this.createMarkerCluster();
+ }
+
+ // Add lines
+ if (options.lines) {
+ for (var i = 0; i < options.lines.length; i++) {
+ this.addLine(options.lines[i]);
+ }
+ }
+
+ // Add polygons
+ if (options.polygons) {
+ for (var i = 0; i < options.polygons.length; i++) {
+ this.addPolygon(options.polygons[i]);
+ }
+ }
+
+ // Add circles
+ if (options.circles) {
+ for (var i = 0; i < options.circles.length; i++) {
+ this.addCircle(options.circles[i]);
+ }
+ }
+
+ // Add rectangles
+ if (options.rectangles) {
+ for (var i = 0; i < options.rectangles.length; i++) {
+ this.addRectangle(options.rectangles[i]);
+ }
+ }
+
+ this.addGeoJson(options);
+
+ // Set map position (centre and zoom)
+ var centre;
+ if (options.centre === false) {
+ switch ( this.points.length ) {
+ case 0:
+ centre = new L.LatLng(0, 0);
+ break;
+ case 1:
+ centre = this.points[0];
+ break;
+ default:
+ var bounds = new L.LatLngBounds( this.points );
+ if (options.zoom === false) {
+ map.fitBounds( bounds );
+ centre = false;
+ } else {
+ centre = bounds.getCenter();
+ }
+ break;
+ }
+ this.points = [];
+ } else {
+ centre = new L.LatLng(options.centre.lat, options.centre.lon);
+ }
+ if(centre) {
+ map.setView( centre, options.zoom !== false ? options.zoom : options.defzoom );
+ }
+ };
+
+ this.getDependencies = function ( options ) {
+ var dependencies = [];
+ if (options.layers !== ['MapQuestOpen'] || options.overlaylayers.length > 0) {
+ dependencies.push( 'ext.maps.leaflet.providers' );
+ }
+ if (options.enablefullscreen) {
+ dependencies.push( 'ext.maps.leaflet.fullscreen' );
+ }
+ if (options.resizable) {
+ dependencies.push( 'ext.maps.resizable' );
+ }
+ if (options.markercluster) {
+ dependencies.push( 'ext.maps.leaflet.markercluster' );
+ }
+ return dependencies;
+ };
+
+ mw.loader.using( this.getDependencies( options ) ).then( function() {
+ _this.setup();
+ } );
+
+ return this;
+
+ };
+})(jQuery, window.mediaWiki, L, window.MQ);
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet-providers/leaflet-providers.js b/www/wiki/extensions/Maps/resources/leaflet/leaflet-providers/leaflet-providers.js
new file mode 100644
index 00000000..5437f1f1
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet-providers/leaflet-providers.js
@@ -0,0 +1,774 @@
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define(['leaflet'], factory);
+ } else if (typeof modules === 'object' && module.exports) {
+ // define a Common JS module that relies on 'leaflet'
+ module.exports = factory(require('leaflet'));
+ } else {
+ // Assume Leaflet is loaded into global object L already
+ factory(L);
+ }
+}(this, function (L) {
+ 'use strict';
+
+ L.TileLayer.Provider = L.TileLayer.extend({
+ initialize: function (arg, options) {
+ var providers = L.TileLayer.Provider.providers;
+
+ var parts = arg.split('.');
+
+ var providerName = parts[0];
+ var variantName = parts[1];
+
+ if (!providers[providerName]) {
+ throw 'No such provider (' + providerName + ')';
+ }
+
+ var provider = {
+ url: providers[providerName].url,
+ options: providers[providerName].options
+ };
+
+ // overwrite values in provider from variant.
+ if (variantName && 'variants' in providers[providerName]) {
+ if (!(variantName in providers[providerName].variants)) {
+ throw 'No such variant of ' + providerName + ' (' + variantName + ')';
+ }
+ var variant = providers[providerName].variants[variantName];
+ var variantOptions;
+ if (typeof variant === 'string') {
+ variantOptions = {
+ variant: variant
+ };
+ } else {
+ variantOptions = variant.options;
+ }
+ provider = {
+ url: variant.url || provider.url,
+ options: L.Util.extend({}, provider.options, variantOptions)
+ };
+ }
+
+ // replace attribution placeholders with their values from toplevel provider attribution,
+ // recursively
+ var attributionReplacer = function (attr) {
+ if (attr.indexOf('{attribution.') === -1) {
+ return attr;
+ }
+ return attr.replace(/\{attribution.(\w*)\}/,
+ function (match, attributionName) {
+ return attributionReplacer(providers[attributionName].options.attribution);
+ }
+ );
+ };
+ provider.options.attribution = attributionReplacer(provider.options.attribution);
+
+ // Compute final options combining provider options with any user overrides
+ var layerOpts = L.Util.extend({}, provider.options, options);
+ L.TileLayer.prototype.initialize.call(this, provider.url, layerOpts);
+ }
+ });
+
+ /**
+ * Definition of providers.
+ * see http://leafletjs.com/reference.html#tilelayer for options in the options map.
+ */
+
+ L.TileLayer.Provider.providers = {
+ OpenStreetMap: {
+ url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+ options: {
+ maxZoom: 19,
+ attribution:
+ '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
+ },
+ variants: {
+ Mapnik: {},
+ BlackAndWhite: {
+ url: 'http://{s}.tiles.wmflabs.org/bw-mapnik/{z}/{x}/{y}.png',
+ options: {
+ maxZoom: 18
+ }
+ },
+ DE: {
+ url: 'https://{s}.tile.openstreetmap.de/tiles/osmde/{z}/{x}/{y}.png',
+ options: {
+ maxZoom: 18
+ }
+ },
+ CH: {
+ url: 'https://tile.osm.ch/switzerland/{z}/{x}/{y}.png',
+ options: {
+ maxZoom: 18,
+ bounds: [[45, 5], [48, 11]]
+ }
+ },
+ France: {
+ url: 'https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png',
+ options: {
+ maxZoom: 20,
+ attribution: '&copy; Openstreetmap France | {attribution.OpenStreetMap}'
+ }
+ },
+ HOT: {
+ url: 'https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png',
+ options: {
+ attribution: '{attribution.OpenStreetMap}, Tiles courtesy of <a href="http://hot.openstreetmap.org/" target="_blank">Humanitarian OpenStreetMap Team</a>'
+ }
+ },
+ BZH: {
+ url: 'https://tile.openstreetmap.bzh/br/{z}/{x}/{y}.png',
+ options: {
+ attribution: '{attribution.OpenStreetMap}, Tiles courtesy of <a href="http://www.openstreetmap.bzh/" target="_blank">Breton OpenStreetMap Team</a>',
+ bounds: [[46.2, -5.5], [50, 0.7]]
+ }
+ }
+ }
+ },
+ OpenInfraMap: {
+ url: 'https://tiles-{s}.openinframap.org/{variant}/{z}/{x}/{y}.png',
+ options: {
+ maxZoom: 18,
+ attribution:
+ '{attribution.OpenStreetMap}, <a href="http://www.openinframap.org/about.html">About OpenInfraMap</a>'
+ },
+ variants: {
+ Power: 'power',
+ Telecom: 'telecoms',
+ Petroleum: 'petroleum',
+ Water: 'water'
+ }
+ },
+ OpenSeaMap: {
+ url: 'https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png',
+ options: {
+ attribution: 'Map data: &copy; <a href="http://www.openseamap.org">OpenSeaMap</a> contributors'
+ }
+ },
+ OpenPtMap: {
+ url: 'http://openptmap.org/tiles/{z}/{x}/{y}.png',
+ options: {
+ maxZoom: 17,
+ attribution: 'Map data: &copy; <a href="http://www.openptmap.org">OpenPtMap</a> contributors'
+ }
+ },
+ OpenTopoMap: {
+ url: 'https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png',
+ options: {
+ maxZoom: 17,
+ attribution: 'Map data: {attribution.OpenStreetMap}, <a href="http://viewfinderpanoramas.org">SRTM</a> | Map style: &copy; <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
+ }
+ },
+ OpenRailwayMap: {
+ url: 'https://{s}.tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png',
+ options: {
+ maxZoom: 19,
+ attribution: 'Map data: {attribution.OpenStreetMap} | Map style: &copy; <a href="https://www.OpenRailwayMap.org">OpenRailwayMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
+ }
+ },
+ OpenFireMap: {
+ url: 'http://openfiremap.org/hytiles/{z}/{x}/{y}.png',
+ options: {
+ maxZoom: 19,
+ attribution: 'Map data: {attribution.OpenStreetMap} | Map style: &copy; <a href="http://www.openfiremap.org">OpenFireMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
+ }
+ },
+ SafeCast: {
+ url: 'https://s3.amazonaws.com/te512.safecast.org/{z}/{x}/{y}.png',
+ options: {
+ maxZoom: 16,
+ attribution: 'Map data: {attribution.OpenStreetMap} | Map style: &copy; <a href="https://blog.safecast.org/about/">SafeCast</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
+ }
+ },
+ Thunderforest: {
+ url: 'https://{s}.tile.thunderforest.com/{variant}/{z}/{x}/{y}.png?apikey={apikey}',
+ options: {
+ attribution:
+ '&copy; <a href="http://www.thunderforest.com/">Thunderforest</a>, {attribution.OpenStreetMap}',
+ variant: 'cycle',
+ apikey: '<insert your api key here>',
+ maxZoom: 22
+ },
+ variants: {
+ OpenCycleMap: 'cycle',
+ Transport: {
+ options: {
+ variant: 'transport'
+ }
+ },
+ TransportDark: {
+ options: {
+ variant: 'transport-dark'
+ }
+ },
+ SpinalMap: {
+ options: {
+ variant: 'spinal-map'
+ }
+ },
+ Landscape: 'landscape',
+ Outdoors: 'outdoors',
+ Pioneer: 'pioneer'
+ }
+ },
+ OpenMapSurfer: {
+ url: 'https://korona.geog.uni-heidelberg.de/tiles/{variant}/x={x}&y={y}&z={z}',
+ options: {
+ maxZoom: 20,
+ variant: 'roads',
+ attribution: 'Imagery from <a href="http://giscience.uni-hd.de/">GIScience Research Group @ University of Heidelberg</a> &mdash; Map data {attribution.OpenStreetMap}'
+ },
+ variants: {
+ Roads: 'roads',
+ AdminBounds: {
+ options: {
+ variant: 'adminb',
+ maxZoom: 19
+ }
+ },
+ Grayscale: {
+ options: {
+ variant: 'roadsg',
+ maxZoom: 19
+ }
+ }
+ }
+ },
+ Hydda: {
+ url: 'https://{s}.tile.openstreetmap.se/hydda/{variant}/{z}/{x}/{y}.png',
+ options: {
+ maxZoom: 18,
+ variant: 'full',
+ attribution: 'Tiles courtesy of <a href="http://openstreetmap.se/" target="_blank">OpenStreetMap Sweden</a> &mdash; Map data {attribution.OpenStreetMap}'
+ },
+ variants: {
+ Full: 'full',
+ Base: 'base',
+ RoadsAndLabels: 'roads_and_labels'
+ }
+ },
+ MapBox: {
+ url: 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}{r}.png?access_token={accessToken}',
+ options: {
+ attribution:
+ 'Imagery from <a href="http://mapbox.com/about/maps/">MapBox</a> &mdash; ' +
+ 'Map data {attribution.OpenStreetMap}',
+ subdomains: 'abcd',
+ id: 'streets',
+ accessToken: '<insert your access token here>',
+ }
+ },
+ Stamen: {
+ url: 'https://stamen-tiles-{s}.a.ssl.fastly.net/{variant}/{z}/{x}/{y}{r}.{ext}',
+ options: {
+ attribution:
+ 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, ' +
+ '<a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> &mdash; ' +
+ 'Map data {attribution.OpenStreetMap}',
+ subdomains: 'abcd',
+ minZoom: 0,
+ maxZoom: 20,
+ variant: 'toner',
+ ext: 'png'
+ },
+ variants: {
+ Toner: 'toner',
+ TonerBackground: 'toner-background',
+ TonerHybrid: 'toner-hybrid',
+ TonerLines: 'toner-lines',
+ TonerLabels: 'toner-labels',
+ TonerLite: 'toner-lite',
+ Watercolor: {
+ url: 'https://stamen-tiles-{s}.a.ssl.fastly.net/{variant}/{z}/{x}/{y}.{ext}',
+ options: {
+ variant: 'watercolor',
+ minZoom: 1,
+ maxZoom: 16
+ }
+ },
+ Terrain: {
+ options: {
+ variant: 'terrain',
+ minZoom: 0,
+ maxZoom: 18
+ }
+ },
+ TerrainBackground: {
+ options: {
+ variant: 'terrain-background',
+ minZoom: 0,
+ maxZoom: 18
+ }
+ },
+ TopOSMRelief: {
+ url: 'https://stamen-tiles-{s}.a.ssl.fastly.net/{variant}/{z}/{x}/{y}.{ext}',
+ options: {
+ variant: 'toposm-color-relief',
+ ext: 'jpg',
+ bounds: [[22, -132], [51, -56]]
+ }
+ },
+ TopOSMFeatures: {
+ options: {
+ variant: 'toposm-features',
+ bounds: [[22, -132], [51, -56]],
+ opacity: 0.9
+ }
+ }
+ }
+ },
+ Esri: {
+ url: 'https://server.arcgisonline.com/ArcGIS/rest/services/{variant}/MapServer/tile/{z}/{y}/{x}',
+ options: {
+ variant: 'World_Street_Map',
+ attribution: 'Tiles &copy; Esri'
+ },
+ variants: {
+ WorldStreetMap: {
+ options: {
+ attribution:
+ '{attribution.Esri} &mdash; ' +
+ 'Source: Esri, DeLorme, NAVTEQ, USGS, Intermap, iPC, NRCAN, Esri Japan, METI, Esri China (Hong Kong), Esri (Thailand), TomTom, 2012'
+ }
+ },
+ DeLorme: {
+ options: {
+ variant: 'Specialty/DeLorme_World_Base_Map',
+ minZoom: 1,
+ maxZoom: 11,
+ attribution: '{attribution.Esri} &mdash; Copyright: &copy;2012 DeLorme'
+ }
+ },
+ WorldTopoMap: {
+ options: {
+ variant: 'World_Topo_Map',
+ attribution:
+ '{attribution.Esri} &mdash; ' +
+ 'Esri, DeLorme, NAVTEQ, TomTom, Intermap, iPC, USGS, FAO, NPS, NRCAN, GeoBase, Kadaster NL, Ordnance Survey, Esri Japan, METI, Esri China (Hong Kong), and the GIS User Community'
+ }
+ },
+ WorldImagery: {
+ options: {
+ variant: 'World_Imagery',
+ attribution:
+ '{attribution.Esri} &mdash; ' +
+ 'Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
+ }
+ },
+ WorldTerrain: {
+ options: {
+ variant: 'World_Terrain_Base',
+ maxZoom: 13,
+ attribution:
+ '{attribution.Esri} &mdash; ' +
+ 'Source: USGS, Esri, TANA, DeLorme, and NPS'
+ }
+ },
+ WorldShadedRelief: {
+ options: {
+ variant: 'World_Shaded_Relief',
+ maxZoom: 13,
+ attribution: '{attribution.Esri} &mdash; Source: Esri'
+ }
+ },
+ WorldPhysical: {
+ options: {
+ variant: 'World_Physical_Map',
+ maxZoom: 8,
+ attribution: '{attribution.Esri} &mdash; Source: US National Park Service'
+ }
+ },
+ OceanBasemap: {
+ options: {
+ variant: 'Ocean_Basemap',
+ maxZoom: 13,
+ attribution: '{attribution.Esri} &mdash; Sources: GEBCO, NOAA, CHS, OSU, UNH, CSUMB, National Geographic, DeLorme, NAVTEQ, and Esri'
+ }
+ },
+ NatGeoWorldMap: {
+ options: {
+ variant: 'NatGeo_World_Map',
+ maxZoom: 16,
+ attribution: '{attribution.Esri} &mdash; National Geographic, Esri, DeLorme, NAVTEQ, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, iPC'
+ }
+ },
+ WorldGrayCanvas: {
+ options: {
+ variant: 'Canvas/World_Light_Gray_Base',
+ maxZoom: 16,
+ attribution: '{attribution.Esri} &mdash; Esri, DeLorme, NAVTEQ'
+ }
+ }
+ }
+ },
+ OpenWeatherMap: {
+ url: 'http://{s}.tile.openweathermap.org/map/{variant}/{z}/{x}/{y}.png?appid={apiKey}',
+ options: {
+ maxZoom: 19,
+ attribution: 'Map data &copy; <a href="http://openweathermap.org">OpenWeatherMap</a>',
+ apiKey:'<insert your api key here>',
+ opacity: 0.5
+ },
+ variants: {
+ Clouds: 'clouds',
+ CloudsClassic: 'clouds_cls',
+ Precipitation: 'precipitation',
+ PrecipitationClassic: 'precipitation_cls',
+ Rain: 'rain',
+ RainClassic: 'rain_cls',
+ Pressure: 'pressure',
+ PressureContour: 'pressure_cntr',
+ Wind: 'wind',
+ Temperature: 'temp',
+ Snow: 'snow'
+ }
+ },
+ HERE: {
+ /*
+ * HERE maps, formerly Nokia maps.
+ * These basemaps are free, but you need an API key. Please sign up at
+ * http://developer.here.com/getting-started
+ *
+ * Note that the base urls contain '.cit' whichs is HERE's
+ * 'Customer Integration Testing' environment. Please remove for production
+ * envirionments.
+ */
+ url:
+ 'https://{s}.{base}.maps.cit.api.here.com/maptile/2.1/' +
+ '{type}/{mapID}/{variant}/{z}/{x}/{y}/{size}/{format}?' +
+ 'app_id={app_id}&app_code={app_code}&lg={language}',
+ options: {
+ attribution:
+ 'Map &copy; 1987-2014 <a href="http://developer.here.com">HERE</a>',
+ subdomains: '1234',
+ mapID: 'newest',
+ 'app_id': '<insert your app_id here>',
+ 'app_code': '<insert your app_code here>',
+ base: 'base',
+ variant: 'normal.day',
+ maxZoom: 20,
+ type: 'maptile',
+ language: 'eng',
+ format: 'png8',
+ size: '256'
+ },
+ variants: {
+ normalDay: 'normal.day',
+ normalDayCustom: 'normal.day.custom',
+ normalDayGrey: 'normal.day.grey',
+ normalDayMobile: 'normal.day.mobile',
+ normalDayGreyMobile: 'normal.day.grey.mobile',
+ normalDayTransit: 'normal.day.transit',
+ normalDayTransitMobile: 'normal.day.transit.mobile',
+ normalNight: 'normal.night',
+ normalNightMobile: 'normal.night.mobile',
+ normalNightGrey: 'normal.night.grey',
+ normalNightGreyMobile: 'normal.night.grey.mobile',
+
+ basicMap: {
+ options: {
+ type: 'basetile'
+ }
+ },
+ mapLabels: {
+ options: {
+ type: 'labeltile',
+ format: 'png'
+ }
+ },
+ trafficFlow: {
+ options: {
+ base: 'traffic',
+ type: 'flowtile'
+ }
+ },
+ carnavDayGrey: 'carnav.day.grey',
+ hybridDay: {
+ options: {
+ base: 'aerial',
+ variant: 'hybrid.day'
+ }
+ },
+ hybridDayMobile: {
+ options: {
+ base: 'aerial',
+ variant: 'hybrid.day.mobile'
+ }
+ },
+ pedestrianDay: 'pedestrian.day',
+ pedestrianNight: 'pedestrian.night',
+ satelliteDay: {
+ options: {
+ base: 'aerial',
+ variant: 'satellite.day'
+ }
+ },
+ terrainDay: {
+ options: {
+ base: 'aerial',
+ variant: 'terrain.day'
+ }
+ },
+ terrainDayMobile: {
+ options: {
+ base: 'aerial',
+ variant: 'terrain.day.mobile'
+ }
+ }
+ }
+ },
+ FreeMapSK: {
+ url: 'http://t{s}.freemap.sk/T/{z}/{x}/{y}.jpeg',
+ options: {
+ minZoom: 8,
+ maxZoom: 16,
+ subdomains: '1234',
+ bounds: [[47.204642, 15.996093], [49.830896, 22.576904]],
+ attribution:
+ '{attribution.OpenStreetMap}, vizualization CC-By-SA 2.0 <a href="http://freemap.sk">Freemap.sk</a>'
+ }
+ },
+ MtbMap: {
+ url: 'http://tile.mtbmap.cz/mtbmap_tiles/{z}/{x}/{y}.png',
+ options: {
+ attribution:
+ '{attribution.OpenStreetMap} &amp; USGS'
+ }
+ },
+ CartoDB: {
+ url: 'https://cartodb-basemaps-{s}.global.ssl.fastly.net/{variant}/{z}/{x}/{y}{r}.png',
+ options: {
+ attribution: '{attribution.OpenStreetMap} &copy; <a href="http://cartodb.com/attributions">CartoDB</a>',
+ subdomains: 'abcd',
+ maxZoom: 19,
+ variant: 'light_all'
+ },
+ variants: {
+ Positron: 'light_all',
+ PositronNoLabels: 'light_nolabels',
+ PositronOnlyLabels: 'light_only_labels',
+ DarkMatter: 'dark_all',
+ DarkMatterNoLabels: 'dark_nolabels',
+ DarkMatterOnlyLabels: 'dark_only_labels',
+ Voyager: 'rastertiles/voyager',
+ VoyagerNoLabels: 'rastertiles/voyager_nolabels',
+ VoyagerOnlyLabels: 'rastertiles/voyager_only_labels',
+ VoyagerLabelsUnder: 'rastertiles/voyager_labels_under'
+ }
+ },
+ HikeBike: {
+ url: 'http://{s}.tiles.wmflabs.org/{variant}/{z}/{x}/{y}.png',
+ options: {
+ maxZoom: 19,
+ attribution: '{attribution.OpenStreetMap}',
+ variant: 'hikebike'
+ },
+ variants: {
+ HikeBike: {},
+ HillShading: {
+ options: {
+ maxZoom: 15,
+ variant: 'hillshading'
+ }
+ }
+ }
+ },
+ BasemapAT: {
+ url: 'https://maps{s}.wien.gv.at/basemap/{variant}/normal/google3857/{z}/{y}/{x}.{format}',
+ options: {
+ maxZoom: 19,
+ attribution: 'Datenquelle: <a href="https://www.basemap.at">basemap.at</a>',
+ subdomains: ['', '1', '2', '3', '4'],
+ format: 'png',
+ bounds: [[46.358770, 8.782379], [49.037872, 17.189532]],
+ variant: 'geolandbasemap'
+ },
+ variants: {
+ basemap: {
+ options: {
+ maxZoom: 20, // currently only in Vienna
+ variant: 'geolandbasemap'
+ }
+ },
+ grau: 'bmapgrau',
+ overlay: 'bmapoverlay',
+ highdpi: {
+ options: {
+ variant: 'bmaphidpi',
+ format: 'jpeg'
+ }
+ },
+ orthofoto: {
+ options: {
+ maxZoom: 20, // currently only in Vienna
+ variant: 'bmaporthofoto30cm',
+ format: 'jpeg'
+ }
+ }
+ }
+ },
+ nlmaps: {
+ url: 'https://geodata.nationaalgeoregister.nl/tiles/service/wmts/{variant}/EPSG:3857/{z}/{x}/{y}.png',
+ options: {
+ minZoom: 6,
+ maxZoom: 19,
+ bounds: [[50.5, 3.25], [54, 7.6]],
+ attribution: 'Kaartgegevens &copy; <a href="kadaster.nl">Kadaster</a>'
+ },
+ variants: {
+ 'standaard': 'brtachtergrondkaart',
+ 'pastel': 'brtachtergrondkaartpastel',
+ 'grijs': 'brtachtergrondkaartgrijs',
+ 'luchtfoto': {
+ 'url': 'https://geodata.nationaalgeoregister.nl/luchtfoto/rgb/wmts/1.0.0/2016_ortho25/EPSG:3857/{z}/{x}/{y}.png',
+ }
+ }
+ },
+ NASAGIBS: {
+ url: 'https://map1.vis.earthdata.nasa.gov/wmts-webmerc/{variant}/default/{time}/{tilematrixset}{maxZoom}/{z}/{y}/{x}.{format}',
+ options: {
+ attribution:
+ 'Imagery provided by services from the Global Imagery Browse Services (GIBS), operated by the NASA/GSFC/Earth Science Data and Information System ' +
+ '(<a href="https://earthdata.nasa.gov">ESDIS</a>) with funding provided by NASA/HQ.',
+ bounds: [[-85.0511287776, -179.999999975], [85.0511287776, 179.999999975]],
+ minZoom: 1,
+ maxZoom: 9,
+ format: 'jpg',
+ time: '',
+ tilematrixset: 'GoogleMapsCompatible_Level'
+ },
+ variants: {
+ ModisTerraTrueColorCR: 'MODIS_Terra_CorrectedReflectance_TrueColor',
+ ModisTerraBands367CR: 'MODIS_Terra_CorrectedReflectance_Bands367',
+ ViirsEarthAtNight2012: {
+ options: {
+ variant: 'VIIRS_CityLights_2012',
+ maxZoom: 8
+ }
+ },
+ ModisTerraLSTDay: {
+ options: {
+ variant: 'MODIS_Terra_Land_Surface_Temp_Day',
+ format: 'png',
+ maxZoom: 7,
+ opacity: 0.75
+ }
+ },
+ ModisTerraSnowCover: {
+ options: {
+ variant: 'MODIS_Terra_Snow_Cover',
+ format: 'png',
+ maxZoom: 8,
+ opacity: 0.75
+ }
+ },
+ ModisTerraAOD: {
+ options: {
+ variant: 'MODIS_Terra_Aerosol',
+ format: 'png',
+ maxZoom: 6,
+ opacity: 0.75
+ }
+ },
+ ModisTerraChlorophyll: {
+ options: {
+ variant: 'MODIS_Terra_Chlorophyll_A',
+ format: 'png',
+ maxZoom: 7,
+ opacity: 0.75
+ }
+ }
+ }
+ },
+ NLS: {
+ // NLS maps are copyright National library of Scotland.
+ // http://maps.nls.uk/projects/api/index.html
+ // Please contact NLS for anything other than non-commercial low volume usage
+ //
+ // Map sources: Ordnance Survey 1:1m to 1:63K, 1920s-1940s
+ // z0-9 - 1:1m
+ // z10-11 - quarter inch (1:253440)
+ // z12-18 - one inch (1:63360)
+ url: 'https://nls-{s}.tileserver.com/nls/{z}/{x}/{y}.jpg',
+ options: {
+ attribution: '<a href="http://geo.nls.uk/maps/">National Library of Scotland Historic Maps</a>',
+ bounds: [[49.6, -12], [61.7, 3]],
+ minZoom: 1,
+ maxZoom: 18,
+ subdomains: '0123',
+ }
+ },
+ JusticeMap: {
+ // Justice Map (http://www.justicemap.org/)
+ // Visualize race and income data for your community, county and country.
+ // Includes tools for data journalists, bloggers and community activists.
+ url: 'http://www.justicemap.org/tile/{size}/{variant}/{z}/{x}/{y}.png',
+ options: {
+ attribution: '<a href="http://www.justicemap.org/terms.php">Justice Map</a>',
+ // one of 'county', 'tract', 'block'
+ size: 'county',
+ // Bounds for USA, including Alaska and Hawaii
+ bounds: [[14, -180], [72, -56]]
+ },
+ variants: {
+ income: 'income',
+ americanIndian: 'indian',
+ asian: 'asian',
+ black: 'black',
+ hispanic: 'hispanic',
+ multi: 'multi',
+ nonWhite: 'nonwhite',
+ white: 'white',
+ plurality: 'plural'
+ }
+ },
+ Wikimedia: {
+ url: 'https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}{r}.png',
+ options: {
+ attribution: '<a href="https://wikimediafoundation.org/wiki/Maps_Terms_of_Use">Wikimedia</a>',
+ minZoom: 1,
+ maxZoom: 19
+ }
+ },
+ GeoportailFrance: {
+ url: 'https://wxs.ign.fr/{apikey}/geoportail/wmts?REQUEST=GetTile&SERVICE=WMTS&VERSION=1.0.0&STYLE={style}&TILEMATRIXSET=PM&FORMAT={format}&LAYER={variant}&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}',
+ options: {
+ attribution: '<a target="_blank" href="https://www.geoportail.gouv.fr/">Geoportail France</a>',
+ bounds: [[-75, -180], [81, 180]],
+ minZoom: 2,
+ maxZoom: 18,
+ // Get your own geoportail apikey here : http://professionnels.ign.fr/ign/contrats/
+ // NB : 'choisirgeoportail' is a demonstration key that comes with no guarantee
+ apikey: 'choisirgeoportail',
+ format: 'image/jpeg',
+ style : 'normal',
+ variant: 'GEOGRAPHICALGRIDSYSTEMS.MAPS.SCAN-EXPRESS.STANDARD'
+ },
+ variants: {
+ parcels: {
+ options : {
+ variant: 'CADASTRALPARCELS.PARCELS',
+ maxZoom: 20,
+ style : 'bdparcellaire',
+ format: 'image/png'
+ }
+ },
+ ignMaps: 'GEOGRAPHICALGRIDSYSTEMS.MAPS',
+ maps: 'GEOGRAPHICALGRIDSYSTEMS.MAPS.SCAN-EXPRESS.STANDARD',
+ orthos: {
+ options: {
+ maxZoom: 19,
+ variant: 'ORTHOIMAGERY.ORTHOPHOTOS'
+ }
+ }
+ }
+ }
+ };
+
+ L.tileLayer.provider = function (provider, options) {
+ return new L.TileLayer.Provider(provider, options);
+ };
+
+ return L;
+}));
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.editable/Leaflet.Editable.js b/www/wiki/extensions/Maps/resources/leaflet/leaflet.editable/Leaflet.Editable.js
new file mode 100644
index 00000000..e259a14c
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet.editable/Leaflet.Editable.js
@@ -0,0 +1,1945 @@
+'use strict';
+(function (factory, window) {// 1.2 alpha
+ /*globals define, module, require*/
+
+ // define an AMD module that relies on 'leaflet'
+ if (typeof define === 'function' && define.amd) {
+ define(['leaflet'], factory);
+
+
+ // define a Common JS module that relies on 'leaflet'
+ } else if (typeof exports === 'object') {
+ module.exports = factory(require('leaflet'));
+ }
+
+ // attach your plugin to the global 'L' variable
+ if(typeof window !== 'undefined' && window.L){
+ factory(window.L);
+ }
+
+}(function (L) {
+ // 🍂miniclass CancelableEvent (Event objects)
+ // 🍂method cancel()
+ // Cancel any subsequent action.
+
+ // 🍂miniclass VertexEvent (Event objects)
+ // 🍂property vertex: VertexMarker
+ // The vertex that fires the event.
+
+ // 🍂miniclass ShapeEvent (Event objects)
+ // 🍂property shape: Array
+ // The shape (LatLngs array) subject of the action.
+
+ // 🍂miniclass CancelableVertexEvent (Event objects)
+ // 🍂inherits VertexEvent
+ // 🍂inherits CancelableEvent
+
+ // 🍂miniclass CancelableShapeEvent (Event objects)
+ // 🍂inherits ShapeEvent
+ // 🍂inherits CancelableEvent
+
+ // 🍂miniclass LayerEvent (Event objects)
+ // 🍂property layer: object
+ // The Layer (Marker, Polyline…) subject of the action.
+
+ // 🍂namespace Editable; 🍂class Editable; 🍂aka L.Editable
+ // Main edition handler. By default, it is attached to the map
+ // as `map.editTools` property.
+ // Leaflet.Editable is made to be fully extendable. You have three ways to customize
+ // the behaviour: using options, listening to events, or extending.
+ L.Editable = L.Evented.extend({
+
+ statics: {
+ FORWARD: 1,
+ BACKWARD: -1
+ },
+
+ options: {
+
+ // You can pass them when creating a map using the `editOptions` key.
+ // 🍂option zIndex: int = 1000
+ // The default zIndex of the editing tools.
+ zIndex: 1000,
+
+ // 🍂option polygonClass: class = L.Polygon
+ // Class to be used when creating a new Polygon.
+ polygonClass: L.Polygon,
+
+ // 🍂option polylineClass: class = L.Polyline
+ // Class to be used when creating a new Polyline.
+ polylineClass: L.Polyline,
+
+ // 🍂option markerClass: class = L.Marker
+ // Class to be used when creating a new Marker.
+ markerClass: L.Marker,
+
+ // 🍂option rectangleClass: class = L.Rectangle
+ // Class to be used when creating a new Rectangle.
+ rectangleClass: L.Rectangle,
+
+ // 🍂option circleClass: class = L.Circle
+ // Class to be used when creating a new Circle.
+ circleClass: L.Circle,
+
+ // 🍂option drawingCSSClass: string = 'leaflet-editable-drawing'
+ // CSS class to be added to the map container while drawing.
+ drawingCSSClass: 'leaflet-editable-drawing',
+
+ // 🍂option drawingCursor: const = 'crosshair'
+ // Cursor mode set to the map while drawing.
+ drawingCursor: 'crosshair',
+
+ // 🍂option editLayer: Layer = new L.LayerGroup()
+ // Layer used to store edit tools (vertex, line guide…).
+ editLayer: undefined,
+
+ // 🍂option featuresLayer: Layer = new L.LayerGroup()
+ // Default layer used to store drawn features (Marker, Polyline…).
+ featuresLayer: undefined,
+
+ // 🍂option polylineEditorClass: class = PolylineEditor
+ // Class to be used as Polyline editor.
+ polylineEditorClass: undefined,
+
+ // 🍂option polygonEditorClass: class = PolygonEditor
+ // Class to be used as Polygon editor.
+ polygonEditorClass: undefined,
+
+ // 🍂option markerEditorClass: class = MarkerEditor
+ // Class to be used as Marker editor.
+ markerEditorClass: undefined,
+
+ // 🍂option rectangleEditorClass: class = RectangleEditor
+ // Class to be used as Rectangle editor.
+ rectangleEditorClass: undefined,
+
+ // 🍂option circleEditorClass: class = CircleEditor
+ // Class to be used as Circle editor.
+ circleEditorClass: undefined,
+
+ // 🍂option lineGuideOptions: hash = {}
+ // Options to be passed to the line guides.
+ lineGuideOptions: {},
+
+ // 🍂option skipMiddleMarkers: boolean = false
+ // Set this to true if you don't want middle markers.
+ skipMiddleMarkers: false
+
+ },
+
+ initialize: function (map, options) {
+ L.setOptions(this, options);
+ this._lastZIndex = this.options.zIndex;
+ this.map = map;
+ this.editLayer = this.createEditLayer();
+ this.featuresLayer = this.createFeaturesLayer();
+ this.forwardLineGuide = this.createLineGuide();
+ this.backwardLineGuide = this.createLineGuide();
+ },
+
+ fireAndForward: function (type, e) {
+ e = e || {};
+ e.editTools = this;
+ this.fire(type, e);
+ this.map.fire(type, e);
+ },
+
+ createLineGuide: function () {
+ var options = L.extend({dashArray: '5,10', weight: 1, interactive: false}, this.options.lineGuideOptions);
+ return L.polyline([], options);
+ },
+
+ createVertexIcon: function (options) {
+ return L.Browser.mobile && L.Browser.touch ? new L.Editable.TouchVertexIcon(options) : new L.Editable.VertexIcon(options);
+ },
+
+ createEditLayer: function () {
+ return this.options.editLayer || new L.LayerGroup().addTo(this.map);
+ },
+
+ createFeaturesLayer: function () {
+ return this.options.featuresLayer || new L.LayerGroup().addTo(this.map);
+ },
+
+ moveForwardLineGuide: function (latlng) {
+ if (this.forwardLineGuide._latlngs.length) {
+ this.forwardLineGuide._latlngs[1] = latlng;
+ this.forwardLineGuide._bounds.extend(latlng);
+ this.forwardLineGuide.redraw();
+ }
+ },
+
+ moveBackwardLineGuide: function (latlng) {
+ if (this.backwardLineGuide._latlngs.length) {
+ this.backwardLineGuide._latlngs[1] = latlng;
+ this.backwardLineGuide._bounds.extend(latlng);
+ this.backwardLineGuide.redraw();
+ }
+ },
+
+ anchorForwardLineGuide: function (latlng) {
+ this.forwardLineGuide._latlngs[0] = latlng;
+ this.forwardLineGuide._bounds.extend(latlng);
+ this.forwardLineGuide.redraw();
+ },
+
+ anchorBackwardLineGuide: function (latlng) {
+ this.backwardLineGuide._latlngs[0] = latlng;
+ this.backwardLineGuide._bounds.extend(latlng);
+ this.backwardLineGuide.redraw();
+ },
+
+ attachForwardLineGuide: function () {
+ this.editLayer.addLayer(this.forwardLineGuide);
+ },
+
+ attachBackwardLineGuide: function () {
+ this.editLayer.addLayer(this.backwardLineGuide);
+ },
+
+ detachForwardLineGuide: function () {
+ this.forwardLineGuide.setLatLngs([]);
+ this.editLayer.removeLayer(this.forwardLineGuide);
+ },
+
+ detachBackwardLineGuide: function () {
+ this.backwardLineGuide.setLatLngs([]);
+ this.editLayer.removeLayer(this.backwardLineGuide);
+ },
+
+ blockEvents: function () {
+ // Hack: force map not to listen to other layers events while drawing.
+ if (!this._oldTargets) {
+ this._oldTargets = this.map._targets;
+ this.map._targets = {};
+ }
+ },
+
+ unblockEvents: function () {
+ if (this._oldTargets) {
+ // Reset, but keep targets created while drawing.
+ this.map._targets = L.extend(this.map._targets, this._oldTargets);
+ delete this._oldTargets;
+ }
+ },
+
+ registerForDrawing: function (editor) {
+ if (this._drawingEditor) this.unregisterForDrawing(this._drawingEditor);
+ this.blockEvents();
+ editor.reset(); // Make sure editor tools still receive events.
+ this._drawingEditor = editor;
+ this.map.on('mousemove touchmove', editor.onDrawingMouseMove, editor);
+ this.map.on('mousedown', this.onMousedown, this);
+ this.map.on('mouseup', this.onMouseup, this);
+ L.DomUtil.addClass(this.map._container, this.options.drawingCSSClass);
+ this.defaultMapCursor = this.map._container.style.cursor;
+ this.map._container.style.cursor = this.options.drawingCursor;
+ },
+
+ unregisterForDrawing: function (editor) {
+ this.unblockEvents();
+ L.DomUtil.removeClass(this.map._container, this.options.drawingCSSClass);
+ this.map._container.style.cursor = this.defaultMapCursor;
+ editor = editor || this._drawingEditor;
+ if (!editor) return;
+ this.map.off('mousemove touchmove', editor.onDrawingMouseMove, editor);
+ this.map.off('mousedown', this.onMousedown, this);
+ this.map.off('mouseup', this.onMouseup, this);
+ if (editor !== this._drawingEditor) return;
+ delete this._drawingEditor;
+ if (editor._drawing) editor.cancelDrawing();
+ },
+
+ onMousedown: function (e) {
+ this._mouseDown = e;
+ this._drawingEditor.onDrawingMouseDown(e);
+ },
+
+ onMouseup: function (e) {
+ if (this._mouseDown) {
+ var editor = this._drawingEditor,
+ mouseDown = this._mouseDown;
+ this._mouseDown = null;
+ editor.onDrawingMouseUp(e);
+ if (this._drawingEditor !== editor) return; // onDrawingMouseUp may call unregisterFromDrawing.
+ var origin = L.point(mouseDown.originalEvent.clientX, mouseDown.originalEvent.clientY);
+ var distance = L.point(e.originalEvent.clientX, e.originalEvent.clientY).distanceTo(origin);
+ if (Math.abs(distance) < 9 * (window.devicePixelRatio || 1)) this._drawingEditor.onDrawingClick(e);
+ }
+ },
+
+ // 🍂section Public methods
+ // You will generally access them by the `map.editTools`
+ // instance:
+ //
+ // `map.editTools.startPolyline();`
+
+ // 🍂method drawing(): boolean
+ // Return true if any drawing action is ongoing.
+ drawing: function () {
+ return this._drawingEditor && this._drawingEditor.drawing();
+ },
+
+ // 🍂method stopDrawing()
+ // When you need to stop any ongoing drawing, without needing to know which editor is active.
+ stopDrawing: function () {
+ this.unregisterForDrawing();
+ },
+
+ // 🍂method commitDrawing()
+ // When you need to commit any ongoing drawing, without needing to know which editor is active.
+ commitDrawing: function (e) {
+ if (!this._drawingEditor) return;
+ this._drawingEditor.commitDrawing(e);
+ },
+
+ connectCreatedToMap: function (layer) {
+ return this.featuresLayer.addLayer(layer);
+ },
+
+ // 🍂method startPolyline(latlng: L.LatLng, options: hash): L.Polyline
+ // Start drawing a Polyline. If `latlng` is given, a first point will be added. In any case, continuing on user click.
+ // If `options` is given, it will be passed to the Polyline class constructor.
+ startPolyline: function (latlng, options) {
+ var line = this.createPolyline([], options);
+ line.enableEdit(this.map).newShape(latlng);
+ return line;
+ },
+
+ // 🍂method startPolygon(latlng: L.LatLng, options: hash): L.Polygon
+ // Start drawing a Polygon. If `latlng` is given, a first point will be added. In any case, continuing on user click.
+ // If `options` is given, it will be passed to the Polygon class constructor.
+ startPolygon: function (latlng, options) {
+ var polygon = this.createPolygon([], options);
+ polygon.enableEdit(this.map).newShape(latlng);
+ return polygon;
+ },
+
+ // 🍂method startMarker(latlng: L.LatLng, options: hash): L.Marker
+ // Start adding a Marker. If `latlng` is given, the Marker will be shown first at this point.
+ // In any case, it will follow the user mouse, and will have a final `latlng` on next click (or touch).
+ // If `options` is given, it will be passed to the Marker class constructor.
+ startMarker: function (latlng, options) {
+ latlng = latlng || this.map.getCenter().clone();
+ var marker = this.createMarker(latlng, options);
+ marker.enableEdit(this.map).startDrawing();
+ return marker;
+ },
+
+ // 🍂method startRectangle(latlng: L.LatLng, options: hash): L.Rectangle
+ // Start drawing a Rectangle. If `latlng` is given, the Rectangle anchor will be added. In any case, continuing on user drag.
+ // If `options` is given, it will be passed to the Rectangle class constructor.
+ startRectangle: function(latlng, options) {
+ var corner = latlng || L.latLng([0, 0]);
+ var bounds = new L.LatLngBounds(corner, corner);
+ var rectangle = this.createRectangle(bounds, options);
+ rectangle.enableEdit(this.map).startDrawing();
+ return rectangle;
+ },
+
+ // 🍂method startCircle(latlng: L.LatLng, options: hash): L.Circle
+ // Start drawing a Circle. If `latlng` is given, the Circle anchor will be added. In any case, continuing on user drag.
+ // If `options` is given, it will be passed to the Circle class constructor.
+ startCircle: function (latlng, options) {
+ latlng = latlng || this.map.getCenter().clone();
+ var circle = this.createCircle(latlng, options);
+ circle.enableEdit(this.map).startDrawing();
+ return circle;
+ },
+
+ startHole: function (editor, latlng) {
+ editor.newHole(latlng);
+ },
+
+ createLayer: function (klass, latlngs, options) {
+ options = L.Util.extend({editOptions: {editTools: this}}, options);
+ var layer = new klass(latlngs, options);
+ // 🍂namespace Editable
+ // 🍂event editable:created: LayerEvent
+ // Fired when a new feature (Marker, Polyline…) is created.
+ this.fireAndForward('editable:created', {layer: layer});
+ return layer;
+ },
+
+ createPolyline: function (latlngs, options) {
+ return this.createLayer(options && options.polylineClass || this.options.polylineClass, latlngs, options);
+ },
+
+ createPolygon: function (latlngs, options) {
+ return this.createLayer(options && options.polygonClass || this.options.polygonClass, latlngs, options);
+ },
+
+ createMarker: function (latlng, options) {
+ return this.createLayer(options && options.markerClass || this.options.markerClass, latlng, options);
+ },
+
+ createRectangle: function (bounds, options) {
+ return this.createLayer(options && options.rectangleClass || this.options.rectangleClass, bounds, options);
+ },
+
+ createCircle: function (latlng, options) {
+ return this.createLayer(options && options.circleClass || this.options.circleClass, latlng, options);
+ }
+
+ });
+
+ L.extend(L.Editable, {
+
+ makeCancellable: function (e) {
+ e.cancel = function () {
+ e._cancelled = true;
+ };
+ }
+
+ });
+
+ // 🍂namespace Map; 🍂class Map
+ // Leaflet.Editable add options and events to the `L.Map` object.
+ // See `Editable` events for the list of events fired on the Map.
+ // 🍂example
+ //
+ // ```js
+ // var map = L.map('map', {
+ // editable: true,
+ // editOptions: {
+ // …
+ // }
+ // });
+ // ```
+ // 🍂section Editable Map Options
+ L.Map.mergeOptions({
+
+ // 🍂namespace Map
+ // 🍂section Map Options
+ // 🍂option editToolsClass: class = L.Editable
+ // Class to be used as vertex, for path editing.
+ editToolsClass: L.Editable,
+
+ // 🍂option editable: boolean = false
+ // Whether to create a L.Editable instance at map init.
+ editable: false,
+
+ // 🍂option editOptions: hash = {}
+ // Options to pass to L.Editable when instantiating.
+ editOptions: {}
+
+ });
+
+ L.Map.addInitHook(function () {
+
+ this.whenReady(function () {
+ if (this.options.editable) {
+ this.editTools = new this.options.editToolsClass(this, this.options.editOptions);
+ }
+ });
+
+ });
+
+ L.Editable.VertexIcon = L.DivIcon.extend({
+
+ options: {
+ iconSize: new L.Point(8, 8)
+ }
+
+ });
+
+ L.Editable.TouchVertexIcon = L.Editable.VertexIcon.extend({
+
+ options: {
+ iconSize: new L.Point(20, 20)
+ }
+
+ });
+
+
+ // 🍂namespace Editable; 🍂class VertexMarker; Handler for dragging path vertices.
+ L.Editable.VertexMarker = L.Marker.extend({
+
+ options: {
+ draggable: true,
+ className: 'leaflet-div-icon leaflet-vertex-icon'
+ },
+
+
+ // 🍂section Public methods
+ // The marker used to handle path vertex. You will usually interact with a `VertexMarker`
+ // instance when listening for events like `editable:vertex:ctrlclick`.
+
+ initialize: function (latlng, latlngs, editor, options) {
+ // We don't use this._latlng, because on drag Leaflet replace it while
+ // we want to keep reference.
+ this.latlng = latlng;
+ this.latlngs = latlngs;
+ this.editor = editor;
+ L.Marker.prototype.initialize.call(this, latlng, options);
+ this.options.icon = this.editor.tools.createVertexIcon({className: this.options.className});
+ this.latlng.__vertex = this;
+ this.editor.editLayer.addLayer(this);
+ this.setZIndexOffset(editor.tools._lastZIndex + 1);
+ },
+
+ onAdd: function (map) {
+ L.Marker.prototype.onAdd.call(this, map);
+ this.on('drag', this.onDrag);
+ this.on('dragstart', this.onDragStart);
+ this.on('dragend', this.onDragEnd);
+ this.on('mouseup', this.onMouseup);
+ this.on('click', this.onClick);
+ this.on('contextmenu', this.onContextMenu);
+ this.on('mousedown touchstart', this.onMouseDown);
+ this.on('mouseover', this.onMouseOver);
+ this.on('mouseout', this.onMouseOut);
+ this.addMiddleMarkers();
+ },
+
+ onRemove: function (map) {
+ if (this.middleMarker) this.middleMarker.delete();
+ delete this.latlng.__vertex;
+ this.off('drag', this.onDrag);
+ this.off('dragstart', this.onDragStart);
+ this.off('dragend', this.onDragEnd);
+ this.off('mouseup', this.onMouseup);
+ this.off('click', this.onClick);
+ this.off('contextmenu', this.onContextMenu);
+ this.off('mousedown touchstart', this.onMouseDown);
+ this.off('mouseover', this.onMouseOver);
+ this.off('mouseout', this.onMouseOut);
+ L.Marker.prototype.onRemove.call(this, map);
+ },
+
+ onDrag: function (e) {
+ e.vertex = this;
+ this.editor.onVertexMarkerDrag(e);
+ var iconPos = L.DomUtil.getPosition(this._icon),
+ latlng = this._map.layerPointToLatLng(iconPos);
+ this.latlng.update(latlng);
+ this._latlng = this.latlng; // Push back to Leaflet our reference.
+ this.editor.refresh();
+ if (this.middleMarker) this.middleMarker.updateLatLng();
+ var next = this.getNext();
+ if (next && next.middleMarker) next.middleMarker.updateLatLng();
+ },
+
+ onDragStart: function (e) {
+ e.vertex = this;
+ this.editor.onVertexMarkerDragStart(e);
+ },
+
+ onDragEnd: function (e) {
+ e.vertex = this;
+ this.editor.onVertexMarkerDragEnd(e);
+ },
+
+ onClick: function (e) {
+ e.vertex = this;
+ this.editor.onVertexMarkerClick(e);
+ },
+
+ onMouseup: function (e) {
+ L.DomEvent.stop(e);
+ e.vertex = this;
+ this.editor.map.fire('mouseup', e);
+ },
+
+ onContextMenu: function (e) {
+ e.vertex = this;
+ this.editor.onVertexMarkerContextMenu(e);
+ },
+
+ onMouseDown: function (e) {
+ e.vertex = this;
+ this.editor.onVertexMarkerMouseDown(e);
+ },
+
+ onMouseOver: function (e) {
+ e.vertex = this;
+ this.editor.onVertexMarkerMouseOver(e);
+ },
+
+ onMouseOut: function (e) {
+ e.vertex = this;
+ this.editor.onVertexMarkerMouseOut(e);
+ },
+
+ // 🍂method delete()
+ // Delete a vertex and the related LatLng.
+ delete: function () {
+ var next = this.getNext(); // Compute before changing latlng
+ this.latlngs.splice(this.getIndex(), 1);
+ this.editor.editLayer.removeLayer(this);
+ this.editor.onVertexDeleted({latlng: this.latlng, vertex: this});
+ if (!this.latlngs.length) this.editor.deleteShape(this.latlngs);
+ if (next) next.resetMiddleMarker();
+ this.editor.refresh();
+ },
+
+ // 🍂method getIndex(): int
+ // Get the index of the current vertex among others of the same LatLngs group.
+ getIndex: function () {
+ return this.latlngs.indexOf(this.latlng);
+ },
+
+ // 🍂method getLastIndex(): int
+ // Get last vertex index of the LatLngs group of the current vertex.
+ getLastIndex: function () {
+ return this.latlngs.length - 1;
+ },
+
+ // 🍂method getPrevious(): VertexMarker
+ // Get the previous VertexMarker in the same LatLngs group.
+ getPrevious: function () {
+ if (this.latlngs.length < 2) return;
+ var index = this.getIndex(),
+ previousIndex = index - 1;
+ if (index === 0 && this.editor.CLOSED) previousIndex = this.getLastIndex();
+ var previous = this.latlngs[previousIndex];
+ if (previous) return previous.__vertex;
+ },
+
+ // 🍂method getNext(): VertexMarker
+ // Get the next VertexMarker in the same LatLngs group.
+ getNext: function () {
+ if (this.latlngs.length < 2) return;
+ var index = this.getIndex(),
+ nextIndex = index + 1;
+ if (index === this.getLastIndex() && this.editor.CLOSED) nextIndex = 0;
+ var next = this.latlngs[nextIndex];
+ if (next) return next.__vertex;
+ },
+
+ addMiddleMarker: function (previous) {
+ if (!this.editor.hasMiddleMarkers()) return;
+ previous = previous || this.getPrevious();
+ if (previous && !this.middleMarker) this.middleMarker = this.editor.addMiddleMarker(previous, this, this.latlngs, this.editor);
+ },
+
+ addMiddleMarkers: function () {
+ if (!this.editor.hasMiddleMarkers()) return;
+ var previous = this.getPrevious();
+ if (previous) this.addMiddleMarker(previous);
+ var next = this.getNext();
+ if (next) next.resetMiddleMarker();
+ },
+
+ resetMiddleMarker: function () {
+ if (this.middleMarker) this.middleMarker.delete();
+ this.addMiddleMarker();
+ },
+
+ // 🍂method split()
+ // Split the vertex LatLngs group at its index, if possible.
+ split: function () {
+ if (!this.editor.splitShape) return; // Only for PolylineEditor
+ this.editor.splitShape(this.latlngs, this.getIndex());
+ },
+
+ // 🍂method continue()
+ // Continue the vertex LatLngs from this vertex. Only active for first and last vertices of a Polyline.
+ continue: function () {
+ if (!this.editor.continueBackward) return; // Only for PolylineEditor
+ var index = this.getIndex();
+ if (index === 0) this.editor.continueBackward(this.latlngs);
+ else if (index === this.getLastIndex()) this.editor.continueForward(this.latlngs);
+ }
+
+ });
+
+ L.Editable.mergeOptions({
+
+ // 🍂namespace Editable
+ // 🍂option vertexMarkerClass: class = VertexMarker
+ // Class to be used as vertex, for path editing.
+ vertexMarkerClass: L.Editable.VertexMarker
+
+ });
+
+ L.Editable.MiddleMarker = L.Marker.extend({
+
+ options: {
+ opacity: 0.5,
+ className: 'leaflet-div-icon leaflet-middle-icon',
+ draggable: true
+ },
+
+ initialize: function (left, right, latlngs, editor, options) {
+ this.left = left;
+ this.right = right;
+ this.editor = editor;
+ this.latlngs = latlngs;
+ L.Marker.prototype.initialize.call(this, this.computeLatLng(), options);
+ this._opacity = this.options.opacity;
+ this.options.icon = this.editor.tools.createVertexIcon({className: this.options.className});
+ this.editor.editLayer.addLayer(this);
+ this.setVisibility();
+ },
+
+ setVisibility: function () {
+ var leftPoint = this._map.latLngToContainerPoint(this.left.latlng),
+ rightPoint = this._map.latLngToContainerPoint(this.right.latlng),
+ size = L.point(this.options.icon.options.iconSize);
+ if (leftPoint.distanceTo(rightPoint) < size.x * 3) this.hide();
+ else this.show();
+ },
+
+ show: function () {
+ this.setOpacity(this._opacity);
+ },
+
+ hide: function () {
+ this.setOpacity(0);
+ },
+
+ updateLatLng: function () {
+ this.setLatLng(this.computeLatLng());
+ this.setVisibility();
+ },
+
+ computeLatLng: function () {
+ var leftPoint = this.editor.map.latLngToContainerPoint(this.left.latlng),
+ rightPoint = this.editor.map.latLngToContainerPoint(this.right.latlng),
+ y = (leftPoint.y + rightPoint.y) / 2,
+ x = (leftPoint.x + rightPoint.x) / 2;
+ return this.editor.map.containerPointToLatLng([x, y]);
+ },
+
+ onAdd: function (map) {
+ L.Marker.prototype.onAdd.call(this, map);
+ L.DomEvent.on(this._icon, 'mousedown touchstart', this.onMouseDown, this);
+ map.on('zoomend', this.setVisibility, this);
+ },
+
+ onRemove: function (map) {
+ delete this.right.middleMarker;
+ L.DomEvent.off(this._icon, 'mousedown touchstart', this.onMouseDown, this);
+ map.off('zoomend', this.setVisibility, this);
+ L.Marker.prototype.onRemove.call(this, map);
+ },
+
+ onMouseDown: function (e) {
+ var iconPos = L.DomUtil.getPosition(this._icon),
+ latlng = this.editor.map.layerPointToLatLng(iconPos);
+ e = {
+ originalEvent: e,
+ latlng: latlng
+ };
+ if (this.options.opacity === 0) return;
+ L.Editable.makeCancellable(e);
+ this.editor.onMiddleMarkerMouseDown(e);
+ if (e._cancelled) return;
+ this.latlngs.splice(this.index(), 0, e.latlng);
+ this.editor.refresh();
+ var icon = this._icon;
+ var marker = this.editor.addVertexMarker(e.latlng, this.latlngs);
+ this.editor.onNewVertex(marker);
+ /* Hack to workaround browser not firing touchend when element is no more on DOM */
+ var parent = marker._icon.parentNode;
+ parent.removeChild(marker._icon);
+ marker._icon = icon;
+ parent.appendChild(marker._icon);
+ marker._initIcon();
+ marker._initInteraction();
+ marker.setOpacity(1);
+ /* End hack */
+ // Transfer ongoing dragging to real marker
+ L.Draggable._dragging = false;
+ marker.dragging._draggable._onDown(e.originalEvent);
+ this.delete();
+ },
+
+ delete: function () {
+ this.editor.editLayer.removeLayer(this);
+ },
+
+ index: function () {
+ return this.latlngs.indexOf(this.right.latlng);
+ }
+
+ });
+
+ L.Editable.mergeOptions({
+
+ // 🍂namespace Editable
+ // 🍂option middleMarkerClass: class = VertexMarker
+ // Class to be used as middle vertex, pulled by the user to create a new point in the middle of a path.
+ middleMarkerClass: L.Editable.MiddleMarker
+
+ });
+
+ // 🍂namespace Editable; 🍂class BaseEditor; 🍂aka L.Editable.BaseEditor
+ // When editing a feature (Marker, Polyline…), an editor is attached to it. This
+ // editor basically knows how to handle the edition.
+ L.Editable.BaseEditor = L.Handler.extend({
+
+ initialize: function (map, feature, options) {
+ L.setOptions(this, options);
+ this.map = map;
+ this.feature = feature;
+ this.feature.editor = this;
+ this.editLayer = new L.LayerGroup();
+ this.tools = this.options.editTools || map.editTools;
+ },
+
+ // 🍂method enable(): this
+ // Set up the drawing tools for the feature to be editable.
+ addHooks: function () {
+ if (this.isConnected()) this.onFeatureAdd();
+ else this.feature.once('add', this.onFeatureAdd, this);
+ this.onEnable();
+ this.feature.on(this._getEvents(), this);
+ },
+
+ // 🍂method disable(): this
+ // Remove the drawing tools for the feature.
+ removeHooks: function () {
+ this.feature.off(this._getEvents(), this);
+ if (this.feature.dragging) this.feature.dragging.disable();
+ this.editLayer.clearLayers();
+ this.tools.editLayer.removeLayer(this.editLayer);
+ this.onDisable();
+ if (this._drawing) this.cancelDrawing();
+ },
+
+ // 🍂method drawing(): boolean
+ // Return true if any drawing action is ongoing with this editor.
+ drawing: function () {
+ return !!this._drawing;
+ },
+
+ reset: function () {},
+
+ onFeatureAdd: function () {
+ this.tools.editLayer.addLayer(this.editLayer);
+ if (this.feature.dragging) this.feature.dragging.enable();
+ },
+
+ hasMiddleMarkers: function () {
+ return !this.options.skipMiddleMarkers && !this.tools.options.skipMiddleMarkers;
+ },
+
+ fireAndForward: function (type, e) {
+ e = e || {};
+ e.layer = this.feature;
+ this.feature.fire(type, e);
+ this.tools.fireAndForward(type, e);
+ },
+
+ onEnable: function () {
+ // 🍂namespace Editable
+ // 🍂event editable:enable: Event
+ // Fired when an existing feature is ready to be edited.
+ this.fireAndForward('editable:enable');
+ },
+
+ onDisable: function () {
+ // 🍂namespace Editable
+ // 🍂event editable:disable: Event
+ // Fired when an existing feature is not ready anymore to be edited.
+ this.fireAndForward('editable:disable');
+ },
+
+ onEditing: function () {
+ // 🍂namespace Editable
+ // 🍂event editable:editing: Event
+ // Fired as soon as any change is made to the feature geometry.
+ this.fireAndForward('editable:editing');
+ },
+
+ onStartDrawing: function () {
+ // 🍂namespace Editable
+ // 🍂section Drawing events
+ // 🍂event editable:drawing:start: Event
+ // Fired when a feature is to be drawn.
+ this.fireAndForward('editable:drawing:start');
+ },
+
+ onEndDrawing: function () {
+ // 🍂namespace Editable
+ // 🍂section Drawing events
+ // 🍂event editable:drawing:end: Event
+ // Fired when a feature is not drawn anymore.
+ this.fireAndForward('editable:drawing:end');
+ },
+
+ onCancelDrawing: function () {
+ // 🍂namespace Editable
+ // 🍂section Drawing events
+ // 🍂event editable:drawing:cancel: Event
+ // Fired when user cancel drawing while a feature is being drawn.
+ this.fireAndForward('editable:drawing:cancel');
+ },
+
+ onCommitDrawing: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Drawing events
+ // 🍂event editable:drawing:commit: Event
+ // Fired when user finish drawing a feature.
+ this.fireAndForward('editable:drawing:commit', e);
+ },
+
+ onDrawingMouseDown: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Drawing events
+ // 🍂event editable:drawing:mousedown: Event
+ // Fired when user `mousedown` while drawing.
+ this.fireAndForward('editable:drawing:mousedown', e);
+ },
+
+ onDrawingMouseUp: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Drawing events
+ // 🍂event editable:drawing:mouseup: Event
+ // Fired when user `mouseup` while drawing.
+ this.fireAndForward('editable:drawing:mouseup', e);
+ },
+
+ startDrawing: function () {
+ if (!this._drawing) this._drawing = L.Editable.FORWARD;
+ this.tools.registerForDrawing(this);
+ this.onStartDrawing();
+ },
+
+ commitDrawing: function (e) {
+ this.onCommitDrawing(e);
+ this.endDrawing();
+ },
+
+ cancelDrawing: function () {
+ // If called during a vertex drag, the vertex will be removed before
+ // the mouseup fires on it. This is a workaround. Maybe better fix is
+ // To have L.Draggable reset it's status on disable (Leaflet side).
+ L.Draggable._dragging = false;
+ this.onCancelDrawing();
+ this.endDrawing();
+ },
+
+ endDrawing: function () {
+ this._drawing = false;
+ this.tools.unregisterForDrawing(this);
+ this.onEndDrawing();
+ },
+
+ onDrawingClick: function (e) {
+ if (!this.drawing()) return;
+ L.Editable.makeCancellable(e);
+ // 🍂namespace Editable
+ // 🍂section Drawing events
+ // 🍂event editable:drawing:click: CancelableEvent
+ // Fired when user `click` while drawing, before any internal action is being processed.
+ this.fireAndForward('editable:drawing:click', e);
+ if (e._cancelled) return;
+ if (!this.isConnected()) this.connect(e);
+ this.processDrawingClick(e);
+ },
+
+ isConnected: function () {
+ return this.map.hasLayer(this.feature);
+ },
+
+ connect: function () {
+ this.tools.connectCreatedToMap(this.feature);
+ this.tools.editLayer.addLayer(this.editLayer);
+ },
+
+ onMove: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Drawing events
+ // 🍂event editable:drawing:move: Event
+ // Fired when `move` mouse while drawing, while dragging a marker, and while dragging a vertex.
+ this.fireAndForward('editable:drawing:move', e);
+ },
+
+ onDrawingMouseMove: function (e) {
+ this.onMove(e);
+ },
+
+ _getEvents: function () {
+ return {
+ dragstart: this.onDragStart,
+ drag: this.onDrag,
+ dragend: this.onDragEnd,
+ remove: this.disable
+ };
+ },
+
+ onDragStart: function (e) {
+ this.onEditing();
+ // 🍂namespace Editable
+ // 🍂event editable:dragstart: Event
+ // Fired before a path feature is dragged.
+ this.fireAndForward('editable:dragstart', e);
+ },
+
+ onDrag: function (e) {
+ this.onMove(e);
+ // 🍂namespace Editable
+ // 🍂event editable:drag: Event
+ // Fired when a path feature is being dragged.
+ this.fireAndForward('editable:drag', e);
+ },
+
+ onDragEnd: function (e) {
+ // 🍂namespace Editable
+ // 🍂event editable:dragend: Event
+ // Fired after a path feature has been dragged.
+ this.fireAndForward('editable:dragend', e);
+ }
+
+ });
+
+ // 🍂namespace Editable; 🍂class MarkerEditor; 🍂aka L.Editable.MarkerEditor
+ // 🍂inherits BaseEditor
+ // Editor for Marker.
+ L.Editable.MarkerEditor = L.Editable.BaseEditor.extend({
+
+ onDrawingMouseMove: function (e) {
+ L.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e);
+ if (this._drawing) this.feature.setLatLng(e.latlng);
+ },
+
+ processDrawingClick: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Drawing events
+ // 🍂event editable:drawing:clicked: Event
+ // Fired when user `click` while drawing, after all internal actions.
+ this.fireAndForward('editable:drawing:clicked', e);
+ this.commitDrawing(e);
+ },
+
+ connect: function (e) {
+ // On touch, the latlng has not been updated because there is
+ // no mousemove.
+ if (e) this.feature._latlng = e.latlng;
+ L.Editable.BaseEditor.prototype.connect.call(this, e);
+ }
+
+ });
+
+ // 🍂namespace Editable; 🍂class PathEditor; 🍂aka L.Editable.PathEditor
+ // 🍂inherits BaseEditor
+ // Base class for all path editors.
+ L.Editable.PathEditor = L.Editable.BaseEditor.extend({
+
+ CLOSED: false,
+ MIN_VERTEX: 2,
+
+ addHooks: function () {
+ L.Editable.BaseEditor.prototype.addHooks.call(this);
+ if (this.feature) this.initVertexMarkers();
+ return this;
+ },
+
+ initVertexMarkers: function (latlngs) {
+ if (!this.enabled()) return;
+ latlngs = latlngs || this.getLatLngs();
+ if (isFlat(latlngs)) this.addVertexMarkers(latlngs);
+ else for (var i = 0; i < latlngs.length; i++) this.initVertexMarkers(latlngs[i]);
+ },
+
+ getLatLngs: function () {
+ return this.feature.getLatLngs();
+ },
+
+ // 🍂method reset()
+ // Rebuild edit elements (Vertex, MiddleMarker, etc.).
+ reset: function () {
+ this.editLayer.clearLayers();
+ this.initVertexMarkers();
+ },
+
+ addVertexMarker: function (latlng, latlngs) {
+ return new this.tools.options.vertexMarkerClass(latlng, latlngs, this);
+ },
+
+ onNewVertex: function (vertex) {
+ // 🍂namespace Editable
+ // 🍂section Vertex events
+ // 🍂event editable:vertex:new: VertexEvent
+ // Fired when a new vertex is created.
+ this.fireAndForward('editable:vertex:new', {latlng: vertex.latlng, vertex: vertex});
+ },
+
+ addVertexMarkers: function (latlngs) {
+ for (var i = 0; i < latlngs.length; i++) {
+ this.addVertexMarker(latlngs[i], latlngs);
+ }
+ },
+
+ refreshVertexMarkers: function (latlngs) {
+ latlngs = latlngs || this.getDefaultLatLngs();
+ for (var i = 0; i < latlngs.length; i++) {
+ latlngs[i].__vertex.update();
+ }
+ },
+
+ addMiddleMarker: function (left, right, latlngs) {
+ return new this.tools.options.middleMarkerClass(left, right, latlngs, this);
+ },
+
+ onVertexMarkerClick: function (e) {
+ L.Editable.makeCancellable(e);
+ // 🍂namespace Editable
+ // 🍂section Vertex events
+ // 🍂event editable:vertex:click: CancelableVertexEvent
+ // Fired when a `click` is issued on a vertex, before any internal action is being processed.
+ this.fireAndForward('editable:vertex:click', e);
+ if (e._cancelled) return;
+ if (this.tools.drawing() && this.tools._drawingEditor !== this) return;
+ var index = e.vertex.getIndex(), commit;
+ if (e.originalEvent.ctrlKey) {
+ this.onVertexMarkerCtrlClick(e);
+ } else if (e.originalEvent.altKey) {
+ this.onVertexMarkerAltClick(e);
+ } else if (e.originalEvent.shiftKey) {
+ this.onVertexMarkerShiftClick(e);
+ } else if (e.originalEvent.metaKey) {
+ this.onVertexMarkerMetaKeyClick(e);
+ } else if (index === e.vertex.getLastIndex() && this._drawing === L.Editable.FORWARD) {
+ if (index >= this.MIN_VERTEX - 1) commit = true;
+ } else if (index === 0 && this._drawing === L.Editable.BACKWARD && this._drawnLatLngs.length >= this.MIN_VERTEX) {
+ commit = true;
+ } else if (index === 0 && this._drawing === L.Editable.FORWARD && this._drawnLatLngs.length >= this.MIN_VERTEX && this.CLOSED) {
+ commit = true; // Allow to close on first point also for polygons
+ } else {
+ this.onVertexRawMarkerClick(e);
+ }
+ // 🍂namespace Editable
+ // 🍂section Vertex events
+ // 🍂event editable:vertex:clicked: VertexEvent
+ // Fired when a `click` is issued on a vertex, after all internal actions.
+ this.fireAndForward('editable:vertex:clicked', e);
+ if (commit) this.commitDrawing(e);
+ },
+
+ onVertexRawMarkerClick: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Vertex events
+ // 🍂event editable:vertex:rawclick: CancelableVertexEvent
+ // Fired when a `click` is issued on a vertex without any special key and without being in drawing mode.
+ this.fireAndForward('editable:vertex:rawclick', e);
+ if (e._cancelled) return;
+ if (!this.vertexCanBeDeleted(e.vertex)) return;
+ e.vertex.delete();
+ },
+
+ vertexCanBeDeleted: function (vertex) {
+ return vertex.latlngs.length > this.MIN_VERTEX;
+ },
+
+ onVertexDeleted: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Vertex events
+ // 🍂event editable:vertex:deleted: VertexEvent
+ // Fired after a vertex has been deleted by user.
+ this.fireAndForward('editable:vertex:deleted', e);
+ },
+
+ onVertexMarkerCtrlClick: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Vertex events
+ // 🍂event editable:vertex:ctrlclick: VertexEvent
+ // Fired when a `click` with `ctrlKey` is issued on a vertex.
+ this.fireAndForward('editable:vertex:ctrlclick', e);
+ },
+
+ onVertexMarkerShiftClick: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Vertex events
+ // 🍂event editable:vertex:shiftclick: VertexEvent
+ // Fired when a `click` with `shiftKey` is issued on a vertex.
+ this.fireAndForward('editable:vertex:shiftclick', e);
+ },
+
+ onVertexMarkerMetaKeyClick: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Vertex events
+ // 🍂event editable:vertex:metakeyclick: VertexEvent
+ // Fired when a `click` with `metaKey` is issued on a vertex.
+ this.fireAndForward('editable:vertex:metakeyclick', e);
+ },
+
+ onVertexMarkerAltClick: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Vertex events
+ // 🍂event editable:vertex:altclick: VertexEvent
+ // Fired when a `click` with `altKey` is issued on a vertex.
+ this.fireAndForward('editable:vertex:altclick', e);
+ },
+
+ onVertexMarkerContextMenu: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Vertex events
+ // 🍂event editable:vertex:contextmenu: VertexEvent
+ // Fired when a `contextmenu` is issued on a vertex.
+ this.fireAndForward('editable:vertex:contextmenu', e);
+ },
+
+ onVertexMarkerMouseDown: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Vertex events
+ // 🍂event editable:vertex:mousedown: VertexEvent
+ // Fired when user `mousedown` a vertex.
+ this.fireAndForward('editable:vertex:mousedown', e);
+ },
+
+ onVertexMarkerMouseOver: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Vertex events
+ // 🍂event editable:vertex:mouseover: VertexEvent
+ // Fired when a user's mouse enters the vertex
+ this.fireAndForward('editable:vertex:mouseover', e);
+ },
+
+ onVertexMarkerMouseOut: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Vertex events
+ // 🍂event editable:vertex:mouseout: VertexEvent
+ // Fired when a user's mouse leaves the vertex
+ this.fireAndForward('editable:vertex:mouseout', e);
+ },
+
+ onMiddleMarkerMouseDown: function (e) {
+ // 🍂namespace Editable
+ // 🍂section MiddleMarker events
+ // 🍂event editable:middlemarker:mousedown: VertexEvent
+ // Fired when user `mousedown` a middle marker.
+ this.fireAndForward('editable:middlemarker:mousedown', e);
+ },
+
+ onVertexMarkerDrag: function (e) {
+ this.onMove(e);
+ if (this.feature._bounds) this.extendBounds(e);
+ // 🍂namespace Editable
+ // 🍂section Vertex events
+ // 🍂event editable:vertex:drag: VertexEvent
+ // Fired when a vertex is dragged by user.
+ this.fireAndForward('editable:vertex:drag', e);
+ },
+
+ onVertexMarkerDragStart: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Vertex events
+ // 🍂event editable:vertex:dragstart: VertexEvent
+ // Fired before a vertex is dragged by user.
+ this.fireAndForward('editable:vertex:dragstart', e);
+ },
+
+ onVertexMarkerDragEnd: function (e) {
+ // 🍂namespace Editable
+ // 🍂section Vertex events
+ // 🍂event editable:vertex:dragend: VertexEvent
+ // Fired after a vertex is dragged by user.
+ this.fireAndForward('editable:vertex:dragend', e);
+ },
+
+ setDrawnLatLngs: function (latlngs) {
+ this._drawnLatLngs = latlngs || this.getDefaultLatLngs();
+ },
+
+ startDrawing: function () {
+ if (!this._drawnLatLngs) this.setDrawnLatLngs();
+ L.Editable.BaseEditor.prototype.startDrawing.call(this);
+ },
+
+ startDrawingForward: function () {
+ this.startDrawing();
+ },
+
+ endDrawing: function () {
+ this.tools.detachForwardLineGuide();
+ this.tools.detachBackwardLineGuide();
+ if (this._drawnLatLngs && this._drawnLatLngs.length < this.MIN_VERTEX) this.deleteShape(this._drawnLatLngs);
+ L.Editable.BaseEditor.prototype.endDrawing.call(this);
+ delete this._drawnLatLngs;
+ },
+
+ addLatLng: function (latlng) {
+ if (this._drawing === L.Editable.FORWARD) this._drawnLatLngs.push(latlng);
+ else this._drawnLatLngs.unshift(latlng);
+ this.feature._bounds.extend(latlng);
+ var vertex = this.addVertexMarker(latlng, this._drawnLatLngs);
+ this.onNewVertex(vertex);
+ this.refresh();
+ },
+
+ newPointForward: function (latlng) {
+ this.addLatLng(latlng);
+ this.tools.attachForwardLineGuide();
+ this.tools.anchorForwardLineGuide(latlng);
+ },
+
+ newPointBackward: function (latlng) {
+ this.addLatLng(latlng);
+ this.tools.anchorBackwardLineGuide(latlng);
+ },
+
+ // 🍂namespace PathEditor
+ // 🍂method push()
+ // Programmatically add a point while drawing.
+ push: function (latlng) {
+ if (!latlng) return console.error('L.Editable.PathEditor.push expect a valid latlng as parameter');
+ if (this._drawing === L.Editable.FORWARD) this.newPointForward(latlng);
+ else this.newPointBackward(latlng);
+ },
+
+ removeLatLng: function (latlng) {
+ latlng.__vertex.delete();
+ this.refresh();
+ },
+
+ // 🍂method pop(): L.LatLng or null
+ // Programmatically remove last point (if any) while drawing.
+ pop: function () {
+ if (this._drawnLatLngs.length <= 1) return;
+ var latlng;
+ if (this._drawing === L.Editable.FORWARD) latlng = this._drawnLatLngs[this._drawnLatLngs.length - 1];
+ else latlng = this._drawnLatLngs[0];
+ this.removeLatLng(latlng);
+ if (this._drawing === L.Editable.FORWARD) this.tools.anchorForwardLineGuide(this._drawnLatLngs[this._drawnLatLngs.length - 1]);
+ else this.tools.anchorForwardLineGuide(this._drawnLatLngs[0]);
+ return latlng;
+ },
+
+ processDrawingClick: function (e) {
+ if (e.vertex && e.vertex.editor === this) return;
+ if (this._drawing === L.Editable.FORWARD) this.newPointForward(e.latlng);
+ else this.newPointBackward(e.latlng);
+ this.fireAndForward('editable:drawing:clicked', e);
+ },
+
+ onDrawingMouseMove: function (e) {
+ L.Editable.BaseEditor.prototype.onDrawingMouseMove.call(this, e);
+ if (this._drawing) {
+ this.tools.moveForwardLineGuide(e.latlng);
+ this.tools.moveBackwardLineGuide(e.latlng);
+ }
+ },
+
+ refresh: function () {
+ this.feature.redraw();
+ this.onEditing();
+ },
+
+ // 🍂namespace PathEditor
+ // 🍂method newShape(latlng?: L.LatLng)
+ // Add a new shape (Polyline, Polygon) in a multi, and setup up drawing tools to draw it;
+ // if optional `latlng` is given, start a path at this point.
+ newShape: function (latlng) {
+ var shape = this.addNewEmptyShape();
+ if (!shape) return;
+ this.setDrawnLatLngs(shape[0] || shape); // Polygon or polyline
+ this.startDrawingForward();
+ // 🍂namespace Editable
+ // 🍂section Shape events
+ // 🍂event editable:shape:new: ShapeEvent
+ // Fired when a new shape is created in a multi (Polygon or Polyline).
+ this.fireAndForward('editable:shape:new', {shape: shape});
+ if (latlng) this.newPointForward(latlng);
+ },
+
+ deleteShape: function (shape, latlngs) {
+ var e = {shape: shape};
+ L.Editable.makeCancellable(e);
+ // 🍂namespace Editable
+ // 🍂section Shape events
+ // 🍂event editable:shape:delete: CancelableShapeEvent
+ // Fired before a new shape is deleted in a multi (Polygon or Polyline).
+ this.fireAndForward('editable:shape:delete', e);
+ if (e._cancelled) return;
+ shape = this._deleteShape(shape, latlngs);
+ if (this.ensureNotFlat) this.ensureNotFlat(); // Polygon.
+ this.feature.setLatLngs(this.getLatLngs()); // Force bounds reset.
+ this.refresh();
+ this.reset();
+ // 🍂namespace Editable
+ // 🍂section Shape events
+ // 🍂event editable:shape:deleted: ShapeEvent
+ // Fired after a new shape is deleted in a multi (Polygon or Polyline).
+ this.fireAndForward('editable:shape:deleted', {shape: shape});
+ return shape;
+ },
+
+ _deleteShape: function (shape, latlngs) {
+ latlngs = latlngs || this.getLatLngs();
+ if (!latlngs.length) return;
+ var self = this,
+ inplaceDelete = function (latlngs, shape) {
+ // Called when deleting a flat latlngs
+ shape = latlngs.splice(0, Number.MAX_VALUE);
+ return shape;
+ },
+ spliceDelete = function (latlngs, shape) {
+ // Called when removing a latlngs inside an array
+ latlngs.splice(latlngs.indexOf(shape), 1);
+ if (!latlngs.length) self._deleteShape(latlngs);
+ return shape;
+ };
+ if (latlngs === shape) return inplaceDelete(latlngs, shape);
+ for (var i = 0; i < latlngs.length; i++) {
+ if (latlngs[i] === shape) return spliceDelete(latlngs, shape);
+ else if (latlngs[i].indexOf(shape) !== -1) return spliceDelete(latlngs[i], shape);
+ }
+ },
+
+ // 🍂namespace PathEditor
+ // 🍂method deleteShapeAt(latlng: L.LatLng): Array
+ // Remove a path shape at the given `latlng`.
+ deleteShapeAt: function (latlng) {
+ var shape = this.feature.shapeAt(latlng);
+ if (shape) return this.deleteShape(shape);
+ },
+
+ // 🍂method appendShape(shape: Array)
+ // Append a new shape to the Polygon or Polyline.
+ appendShape: function (shape) {
+ this.insertShape(shape);
+ },
+
+ // 🍂method prependShape(shape: Array)
+ // Prepend a new shape to the Polygon or Polyline.
+ prependShape: function (shape) {
+ this.insertShape(shape, 0);
+ },
+
+ // 🍂method insertShape(shape: Array, index: int)
+ // Insert a new shape to the Polygon or Polyline at given index (default is to append).
+ insertShape: function (shape, index) {
+ this.ensureMulti();
+ shape = this.formatShape(shape);
+ if (typeof index === 'undefined') index = this.feature._latlngs.length;
+ this.feature._latlngs.splice(index, 0, shape);
+ this.feature.redraw();
+ if (this._enabled) this.reset();
+ },
+
+ extendBounds: function (e) {
+ this.feature._bounds.extend(e.vertex.latlng);
+ },
+
+ onDragStart: function (e) {
+ this.editLayer.clearLayers();
+ L.Editable.BaseEditor.prototype.onDragStart.call(this, e);
+ },
+
+ onDragEnd: function (e) {
+ this.initVertexMarkers();
+ L.Editable.BaseEditor.prototype.onDragEnd.call(this, e);
+ }
+
+ });
+
+ // 🍂namespace Editable; 🍂class PolylineEditor; 🍂aka L.Editable.PolylineEditor
+ // 🍂inherits PathEditor
+ L.Editable.PolylineEditor = L.Editable.PathEditor.extend({
+
+ startDrawingBackward: function () {
+ this._drawing = L.Editable.BACKWARD;
+ this.startDrawing();
+ },
+
+ // 🍂method continueBackward(latlngs?: Array)
+ // Set up drawing tools to continue the line backward.
+ continueBackward: function (latlngs) {
+ if (this.drawing()) return;
+ latlngs = latlngs || this.getDefaultLatLngs();
+ this.setDrawnLatLngs(latlngs);
+ if (latlngs.length > 0) {
+ this.tools.attachBackwardLineGuide();
+ this.tools.anchorBackwardLineGuide(latlngs[0]);
+ }
+ this.startDrawingBackward();
+ },
+
+ // 🍂method continueForward(latlngs?: Array)
+ // Set up drawing tools to continue the line forward.
+ continueForward: function (latlngs) {
+ if (this.drawing()) return;
+ latlngs = latlngs || this.getDefaultLatLngs();
+ this.setDrawnLatLngs(latlngs);
+ if (latlngs.length > 0) {
+ this.tools.attachForwardLineGuide();
+ this.tools.anchorForwardLineGuide(latlngs[latlngs.length - 1]);
+ }
+ this.startDrawingForward();
+ },
+
+ getDefaultLatLngs: function (latlngs) {
+ latlngs = latlngs || this.feature._latlngs;
+ if (!latlngs.length || latlngs[0] instanceof L.LatLng) return latlngs;
+ else return this.getDefaultLatLngs(latlngs[0]);
+ },
+
+ ensureMulti: function () {
+ if (this.feature._latlngs.length && isFlat(this.feature._latlngs)) {
+ this.feature._latlngs = [this.feature._latlngs];
+ }
+ },
+
+ addNewEmptyShape: function () {
+ if (this.feature._latlngs.length) {
+ var shape = [];
+ this.appendShape(shape);
+ return shape;
+ } else {
+ return this.feature._latlngs;
+ }
+ },
+
+ formatShape: function (shape) {
+ if (isFlat(shape)) return shape;
+ else if (shape[0]) return this.formatShape(shape[0]);
+ },
+
+ // 🍂method splitShape(latlngs?: Array, index: int)
+ // Split the given `latlngs` shape at index `index` and integrate new shape in instance `latlngs`.
+ splitShape: function (shape, index) {
+ if (!index || index >= shape.length - 1) return;
+ this.ensureMulti();
+ var shapeIndex = this.feature._latlngs.indexOf(shape);
+ if (shapeIndex === -1) return;
+ var first = shape.slice(0, index + 1),
+ second = shape.slice(index);
+ // We deal with reference, we don't want twice the same latlng around.
+ second[0] = L.latLng(second[0].lat, second[0].lng, second[0].alt);
+ this.feature._latlngs.splice(shapeIndex, 1, first, second);
+ this.refresh();
+ this.reset();
+ }
+
+ });
+
+ // 🍂namespace Editable; 🍂class PolygonEditor; 🍂aka L.Editable.PolygonEditor
+ // 🍂inherits PathEditor
+ L.Editable.PolygonEditor = L.Editable.PathEditor.extend({
+
+ CLOSED: true,
+ MIN_VERTEX: 3,
+
+ newPointForward: function (latlng) {
+ L.Editable.PathEditor.prototype.newPointForward.call(this, latlng);
+ if (!this.tools.backwardLineGuide._latlngs.length) this.tools.anchorBackwardLineGuide(latlng);
+ if (this._drawnLatLngs.length === 2) this.tools.attachBackwardLineGuide();
+ },
+
+ addNewEmptyHole: function (latlng) {
+ this.ensureNotFlat();
+ var latlngs = this.feature.shapeAt(latlng);
+ if (!latlngs) return;
+ var holes = [];
+ latlngs.push(holes);
+ return holes;
+ },
+
+ // 🍂method newHole(latlng?: L.LatLng, index: int)
+ // Set up drawing tools for creating a new hole on the Polygon. If the `latlng` param is given, a first point is created.
+ newHole: function (latlng) {
+ var holes = this.addNewEmptyHole(latlng);
+ if (!holes) return;
+ this.setDrawnLatLngs(holes);
+ this.startDrawingForward();
+ if (latlng) this.newPointForward(latlng);
+ },
+
+ addNewEmptyShape: function () {
+ if (this.feature._latlngs.length && this.feature._latlngs[0].length) {
+ var shape = [];
+ this.appendShape(shape);
+ return shape;
+ } else {
+ return this.feature._latlngs;
+ }
+ },
+
+ ensureMulti: function () {
+ if (this.feature._latlngs.length && isFlat(this.feature._latlngs[0])) {
+ this.feature._latlngs = [this.feature._latlngs];
+ }
+ },
+
+ ensureNotFlat: function () {
+ if (!this.feature._latlngs.length || isFlat(this.feature._latlngs)) this.feature._latlngs = [this.feature._latlngs];
+ },
+
+ vertexCanBeDeleted: function (vertex) {
+ var parent = this.feature.parentShape(vertex.latlngs),
+ idx = L.Util.indexOf(parent, vertex.latlngs);
+ if (idx > 0) return true; // Holes can be totally deleted without removing the layer itself.
+ return L.Editable.PathEditor.prototype.vertexCanBeDeleted.call(this, vertex);
+ },
+
+ getDefaultLatLngs: function () {
+ if (!this.feature._latlngs.length) this.feature._latlngs.push([]);
+ return this.feature._latlngs[0];
+ },
+
+ formatShape: function (shape) {
+ // [[1, 2], [3, 4]] => must be nested
+ // [] => must be nested
+ // [[]] => is already nested
+ if (isFlat(shape) && (!shape[0] || shape[0].length !== 0)) return [shape];
+ else return shape;
+ }
+
+ });
+
+ // 🍂namespace Editable; 🍂class RectangleEditor; 🍂aka L.Editable.RectangleEditor
+ // 🍂inherits PathEditor
+ L.Editable.RectangleEditor = L.Editable.PathEditor.extend({
+
+ CLOSED: true,
+ MIN_VERTEX: 4,
+
+ options: {
+ skipMiddleMarkers: true
+ },
+
+ extendBounds: function (e) {
+ var index = e.vertex.getIndex(),
+ next = e.vertex.getNext(),
+ previous = e.vertex.getPrevious(),
+ oppositeIndex = (index + 2) % 4,
+ opposite = e.vertex.latlngs[oppositeIndex],
+ bounds = new L.LatLngBounds(e.latlng, opposite);
+ // Update latlngs by hand to preserve order.
+ previous.latlng.update([e.latlng.lat, opposite.lng]);
+ next.latlng.update([opposite.lat, e.latlng.lng]);
+ this.updateBounds(bounds);
+ this.refreshVertexMarkers();
+ },
+
+ onDrawingMouseDown: function (e) {
+ L.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e);
+ this.connect();
+ var latlngs = this.getDefaultLatLngs();
+ // L.Polygon._convertLatLngs removes last latlng if it equals first point,
+ // which is the case here as all latlngs are [0, 0]
+ if (latlngs.length === 3) latlngs.push(e.latlng);
+ var bounds = new L.LatLngBounds(e.latlng, e.latlng);
+ this.updateBounds(bounds);
+ this.updateLatLngs(bounds);
+ this.refresh();
+ this.reset();
+ // Stop dragging map.
+ // L.Draggable has two workflows:
+ // - mousedown => mousemove => mouseup
+ // - touchstart => touchmove => touchend
+ // Problem: L.Map.Tap does not allow us to listen to touchstart, so we only
+ // can deal with mousedown, but then when in a touch device, we are dealing with
+ // simulated events (actually simulated by L.Map.Tap), which are no more taken
+ // into account by L.Draggable.
+ // Ref.: https://github.com/Leaflet/Leaflet.Editable/issues/103
+ e.originalEvent._simulated = false;
+ this.map.dragging._draggable._onUp(e.originalEvent);
+ // Now transfer ongoing drag action to the bottom right corner.
+ // Should we refine which corner will handle the drag according to
+ // drag direction?
+ latlngs[3].__vertex.dragging._draggable._onDown(e.originalEvent);
+ },
+
+ onDrawingMouseUp: function (e) {
+ this.commitDrawing(e);
+ e.originalEvent._simulated = false;
+ L.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e);
+ },
+
+ onDrawingMouseMove: function (e) {
+ e.originalEvent._simulated = false;
+ L.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e);
+ },
+
+
+ getDefaultLatLngs: function (latlngs) {
+ return latlngs || this.feature._latlngs[0];
+ },
+
+ updateBounds: function (bounds) {
+ this.feature._bounds = bounds;
+ },
+
+ updateLatLngs: function (bounds) {
+ var latlngs = this.getDefaultLatLngs(),
+ newLatlngs = this.feature._boundsToLatLngs(bounds);
+ // Keep references.
+ for (var i = 0; i < latlngs.length; i++) {
+ latlngs[i].update(newLatlngs[i]);
+ }
+ }
+
+ });
+
+ // 🍂namespace Editable; 🍂class CircleEditor; 🍂aka L.Editable.CircleEditor
+ // 🍂inherits PathEditor
+ L.Editable.CircleEditor = L.Editable.PathEditor.extend({
+
+ MIN_VERTEX: 2,
+
+ options: {
+ skipMiddleMarkers: true
+ },
+
+ initialize: function (map, feature, options) {
+ L.Editable.PathEditor.prototype.initialize.call(this, map, feature, options);
+ this._resizeLatLng = this.computeResizeLatLng();
+ },
+
+ computeResizeLatLng: function () {
+ // While circle is not added to the map, _radius is not set.
+ var delta = (this.feature._radius || this.feature._mRadius) * Math.cos(Math.PI / 4),
+ point = this.map.project(this.feature._latlng);
+ return this.map.unproject([point.x + delta, point.y - delta]);
+ },
+
+ updateResizeLatLng: function () {
+ this._resizeLatLng.update(this.computeResizeLatLng());
+ this._resizeLatLng.__vertex.update();
+ },
+
+ getLatLngs: function () {
+ return [this.feature._latlng, this._resizeLatLng];
+ },
+
+ getDefaultLatLngs: function () {
+ return this.getLatLngs();
+ },
+
+ onVertexMarkerDrag: function (e) {
+ if (e.vertex.getIndex() === 1) this.resize(e);
+ else this.updateResizeLatLng(e);
+ L.Editable.PathEditor.prototype.onVertexMarkerDrag.call(this, e);
+ },
+
+ resize: function (e) {
+ var radius = this.feature._latlng.distanceTo(e.latlng);
+ this.feature.setRadius(radius);
+ },
+
+ onDrawingMouseDown: function (e) {
+ L.Editable.PathEditor.prototype.onDrawingMouseDown.call(this, e);
+ this._resizeLatLng.update(e.latlng);
+ this.feature._latlng.update(e.latlng);
+ this.connect();
+ // Stop dragging map.
+ e.originalEvent._simulated = false;
+ this.map.dragging._draggable._onUp(e.originalEvent);
+ // Now transfer ongoing drag action to the radius handler.
+ this._resizeLatLng.__vertex.dragging._draggable._onDown(e.originalEvent);
+ },
+
+ onDrawingMouseUp: function (e) {
+ this.commitDrawing(e);
+ e.originalEvent._simulated = false;
+ L.Editable.PathEditor.prototype.onDrawingMouseUp.call(this, e);
+ },
+
+ onDrawingMouseMove: function (e) {
+ e.originalEvent._simulated = false;
+ L.Editable.PathEditor.prototype.onDrawingMouseMove.call(this, e);
+ },
+
+ onDrag: function (e) {
+ L.Editable.PathEditor.prototype.onDrag.call(this, e);
+ this.feature.dragging.updateLatLng(this._resizeLatLng);
+ }
+
+ });
+
+ // 🍂namespace Editable; 🍂class EditableMixin
+ // `EditableMixin` is included to `L.Polyline`, `L.Polygon`, `L.Rectangle`, `L.Circle`
+ // and `L.Marker`. It adds some methods to them.
+ // *When editing is enabled, the editor is accessible on the instance with the
+ // `editor` property.*
+ var EditableMixin = {
+
+ createEditor: function (map) {
+ map = map || this._map;
+ var tools = (this.options.editOptions || {}).editTools || map.editTools;
+ if (!tools) throw Error('Unable to detect Editable instance.');
+ var Klass = this.options.editorClass || this.getEditorClass(tools);
+ return new Klass(map, this, this.options.editOptions);
+ },
+
+ // 🍂method enableEdit(map?: L.Map): this.editor
+ // Enable editing, by creating an editor if not existing, and then calling `enable` on it.
+ enableEdit: function (map) {
+ if (!this.editor) this.createEditor(map);
+ this.editor.enable();
+ return this.editor;
+ },
+
+ // 🍂method editEnabled(): boolean
+ // Return true if current instance has an editor attached, and this editor is enabled.
+ editEnabled: function () {
+ return this.editor && this.editor.enabled();
+ },
+
+ // 🍂method disableEdit()
+ // Disable editing, also remove the editor property reference.
+ disableEdit: function () {
+ if (this.editor) {
+ this.editor.disable();
+ delete this.editor;
+ }
+ },
+
+ // 🍂method toggleEdit()
+ // Enable or disable editing, according to current status.
+ toggleEdit: function () {
+ if (this.editEnabled()) this.disableEdit();
+ else this.enableEdit();
+ },
+
+ _onEditableAdd: function () {
+ if (this.editor) this.enableEdit();
+ }
+
+ };
+
+ var PolylineMixin = {
+
+ getEditorClass: function (tools) {
+ return (tools && tools.options.polylineEditorClass) ? tools.options.polylineEditorClass : L.Editable.PolylineEditor;
+ },
+
+ shapeAt: function (latlng, latlngs) {
+ // We can have those cases:
+ // - latlngs are just a flat array of latlngs, use this
+ // - latlngs is an array of arrays of latlngs, loop over
+ var shape = null;
+ latlngs = latlngs || this._latlngs;
+ if (!latlngs.length) return shape;
+ else if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs;
+ else for (var i = 0; i < latlngs.length; i++) if (this.isInLatLngs(latlng, latlngs[i])) return latlngs[i];
+ return shape;
+ },
+
+ isInLatLngs: function (l, latlngs) {
+ if (!latlngs) return false;
+ var i, k, len, part = [], p,
+ w = this._clickTolerance();
+ this._projectLatlngs(latlngs, part, this._pxBounds);
+ part = part[0];
+ p = this._map.latLngToLayerPoint(l);
+
+ if (!this._pxBounds.contains(p)) { return false; }
+ for (i = 1, len = part.length, k = 0; i < len; k = i++) {
+
+ if (L.LineUtil.pointToSegmentDistance(p, part[k], part[i]) <= w) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ };
+
+ var PolygonMixin = {
+
+ getEditorClass: function (tools) {
+ return (tools && tools.options.polygonEditorClass) ? tools.options.polygonEditorClass : L.Editable.PolygonEditor;
+ },
+
+ shapeAt: function (latlng, latlngs) {
+ // We can have those cases:
+ // - latlngs are just a flat array of latlngs, use this
+ // - latlngs is an array of arrays of latlngs, this is a simple polygon (maybe with holes), use the first
+ // - latlngs is an array of arrays of arrays, this is a multi, loop over
+ var shape = null;
+ latlngs = latlngs || this._latlngs;
+ if (!latlngs.length) return shape;
+ else if (isFlat(latlngs) && this.isInLatLngs(latlng, latlngs)) shape = latlngs;
+ else if (isFlat(latlngs[0]) && this.isInLatLngs(latlng, latlngs[0])) shape = latlngs;
+ else for (var i = 0; i < latlngs.length; i++) if (this.isInLatLngs(latlng, latlngs[i][0])) return latlngs[i];
+ return shape;
+ },
+
+ isInLatLngs: function (l, latlngs) {
+ var inside = false, l1, l2, j, k, len2;
+
+ for (j = 0, len2 = latlngs.length, k = len2 - 1; j < len2; k = j++) {
+ l1 = latlngs[j];
+ l2 = latlngs[k];
+
+ if (((l1.lat > l.lat) !== (l2.lat > l.lat)) &&
+ (l.lng < (l2.lng - l1.lng) * (l.lat - l1.lat) / (l2.lat - l1.lat) + l1.lng)) {
+ inside = !inside;
+ }
+ }
+
+ return inside;
+ },
+
+ parentShape: function (shape, latlngs) {
+ latlngs = latlngs || this._latlngs;
+ if (!latlngs) return;
+ var idx = L.Util.indexOf(latlngs, shape);
+ if (idx !== -1) return latlngs;
+ for (var i = 0; i < latlngs.length; i++) {
+ idx = L.Util.indexOf(latlngs[i], shape);
+ if (idx !== -1) return latlngs[i];
+ }
+ }
+
+ };
+
+
+ var MarkerMixin = {
+
+ getEditorClass: function (tools) {
+ return (tools && tools.options.markerEditorClass) ? tools.options.markerEditorClass : L.Editable.MarkerEditor;
+ }
+
+ };
+
+ var RectangleMixin = {
+
+ getEditorClass: function (tools) {
+ return (tools && tools.options.rectangleEditorClass) ? tools.options.rectangleEditorClass : L.Editable.RectangleEditor;
+ }
+
+ };
+
+ var CircleMixin = {
+
+ getEditorClass: function (tools) {
+ return (tools && tools.options.circleEditorClass) ? tools.options.circleEditorClass : L.Editable.CircleEditor;
+ }
+
+ };
+
+ var keepEditable = function () {
+ // Make sure you can remove/readd an editable layer.
+ this.on('add', this._onEditableAdd);
+ };
+
+ var isFlat = L.LineUtil.isFlat || L.LineUtil._flat || L.Polyline._flat; // <=> 1.1 compat.
+
+
+ if (L.Polyline) {
+ L.Polyline.include(EditableMixin);
+ L.Polyline.include(PolylineMixin);
+ L.Polyline.addInitHook(keepEditable);
+ }
+ if (L.Polygon) {
+ L.Polygon.include(EditableMixin);
+ L.Polygon.include(PolygonMixin);
+ }
+ if (L.Marker) {
+ L.Marker.include(EditableMixin);
+ L.Marker.include(MarkerMixin);
+ L.Marker.addInitHook(keepEditable);
+ }
+ if (L.Rectangle) {
+ L.Rectangle.include(EditableMixin);
+ L.Rectangle.include(RectangleMixin);
+ }
+ if (L.Circle) {
+ L.Circle.include(EditableMixin);
+ L.Circle.include(CircleMixin);
+ }
+
+ L.LatLng.prototype.update = function (latlng) {
+ latlng = L.latLng(latlng);
+ this.lat = latlng.lat;
+ this.lng = latlng.lng;
+ }
+
+}, window));
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.editor.js b/www/wiki/extensions/Maps/resources/leaflet/leaflet.editor.js
new file mode 100644
index 00000000..6aadad18
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet.editor.js
@@ -0,0 +1,15 @@
+(function( $, mw ) {
+
+ $( document ).ready( function() {
+ var map = L.map('GeoJsonMap'/*, {editable: true}*/);
+
+ L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
+ }).addTo(map);
+
+ var geoJsonLayer = L.geoJSON(GeoJson).addTo(map);
+
+ map.fitBounds(geoJsonLayer.getBounds());
+ } );
+
+})( window.jQuery, mediaWiki );
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/.jshintrc b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/.jshintrc
new file mode 100644
index 00000000..b7636119
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/.jshintrc
@@ -0,0 +1,12 @@
+{
+ "browser": true,
+ "curly": true,
+ "eqeqeq": true,
+ "undef": true,
+ "quotmark": "single",
+ "trailing": true,
+ "globals": {
+ "L": true,
+ "jQuery": true
+ }
+} \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/Control.FullScreen.css b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/Control.FullScreen.css
new file mode 100644
index 00000000..c93b1bf9
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/Control.FullScreen.css
@@ -0,0 +1,4 @@
+.leaflet-control-zoom-fullscreen { background-image: url(icon-fullscreen.png); }
+.leaflet-retina .leaflet-control-zoom-fullscreen { background-image: url(icon-fullscreen-2x.png); background-size: 26px 26px; }
+.leaflet-container:-webkit-full-screen { width: 100% !important; height: 100% !important; z-index: 99999; }
+.leaflet-pseudo-fullscreen { position: fixed !important; width: 100% !important; height: 100% !important; top: 0px !important; left: 0px !important; z-index: 99999; } \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/Control.FullScreen.js b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/Control.FullScreen.js
new file mode 100644
index 00000000..f1cd7ccc
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/Control.FullScreen.js
@@ -0,0 +1,164 @@
+(function() {
+
+L.Control.FullScreen = L.Control.extend({
+ options: {
+ position: 'topleft',
+ title: 'Full Screen',
+ forceSeparateButton: false,
+ forcePseudoFullscreen: false
+ },
+
+ onAdd: function (map) {
+ var className = 'leaflet-control-zoom-fullscreen', container;
+
+ if (map.zoomControl && !this.options.forceSeparateButton) {
+ container = map.zoomControl._container;
+ } else {
+ container = L.DomUtil.create('div', 'leaflet-bar');
+ }
+
+ this._createButton(this.options.title, className, container, this.toggleFullScreen, this);
+
+ return container;
+ },
+
+ _createButton: function (title, className, container, fn, context) {
+ var link = L.DomUtil.create('a', className, container);
+ link.href = '#';
+ link.title = title;
+
+ L.DomEvent
+ .addListener(link, 'click', L.DomEvent.stopPropagation)
+ .addListener(link, 'click', L.DomEvent.preventDefault)
+ .addListener(link, 'click', fn, context);
+
+ L.DomEvent
+ .addListener(container, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation)
+ .addListener(container, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault)
+ .addListener(container, fullScreenApi.fullScreenEventName, this._handleEscKey, context);
+
+ L.DomEvent
+ .addListener(document, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation)
+ .addListener(document, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault)
+ .addListener(document, fullScreenApi.fullScreenEventName, this._handleEscKey, context);
+
+ return link;
+ },
+
+ toggleFullScreen: function () {
+ var map = this._map;
+ map._exitFired = false;
+ if (map._isFullscreen) {
+ if (fullScreenApi.supportsFullScreen && !this.options.forcePseudoFullscreen) {
+ fullScreenApi.cancelFullScreen(map._container);
+ } else {
+ L.DomUtil.removeClass(map._container, 'leaflet-pseudo-fullscreen');
+ }
+ map.invalidateSize();
+ map.fire('exitFullscreen');
+ map._exitFired = true;
+ map._isFullscreen = false;
+ }
+ else {
+ if (fullScreenApi.supportsFullScreen && !this.options.forcePseudoFullscreen) {
+ fullScreenApi.requestFullScreen(map._container);
+ } else {
+ L.DomUtil.addClass(map._container, 'leaflet-pseudo-fullscreen');
+ }
+ map.invalidateSize();
+ map.fire('enterFullscreen');
+ map._isFullscreen = true;
+ }
+ },
+
+ _handleEscKey: function () {
+ var map = this._map;
+ if (!fullScreenApi.isFullScreen(map) && !map._exitFired) {
+ map.fire('exitFullscreen');
+ map._exitFired = true;
+ map._isFullscreen = false;
+ }
+ }
+});
+
+L.Map.addInitHook(function () {
+ if (this.options.fullscreenControl) {
+ this.fullscreenControl = L.control.fullscreen(this.options.fullscreenControlOptions);
+ this.addControl(this.fullscreenControl);
+ }
+});
+
+L.control.fullscreen = function (options) {
+ return new L.Control.FullScreen(options);
+};
+
+/*
+Native FullScreen JavaScript API
+-------------
+Assumes Mozilla naming conventions instead of W3C for now
+
+source : http://johndyer.name/native-fullscreen-javascript-api-plus-jquery-plugin/
+
+*/
+
+ var
+ fullScreenApi = {
+ supportsFullScreen: false,
+ isFullScreen: function() { return false; },
+ requestFullScreen: function() {},
+ cancelFullScreen: function() {},
+ fullScreenEventName: '',
+ prefix: ''
+ },
+ browserPrefixes = 'webkit moz o ms khtml'.split(' ');
+
+ // check for native support
+ if (typeof document.exitFullscreen !== 'undefined') {
+ fullScreenApi.supportsFullScreen = true;
+ } else {
+ // check for fullscreen support by vendor prefix
+ for (var i = 0, il = browserPrefixes.length; i < il; i++ ) {
+ fullScreenApi.prefix = browserPrefixes[i];
+ if (typeof document[fullScreenApi.prefix + 'CancelFullScreen' ] !== 'undefined' ) {
+ fullScreenApi.supportsFullScreen = true;
+ break;
+ }
+ }
+ }
+
+ // update methods to do something useful
+ if (fullScreenApi.supportsFullScreen) {
+ fullScreenApi.fullScreenEventName = fullScreenApi.prefix + 'fullscreenchange';
+ fullScreenApi.isFullScreen = function() {
+ switch (this.prefix) {
+ case '':
+ return document.fullScreen;
+ case 'webkit':
+ return document.webkitIsFullScreen;
+ default:
+ return document[this.prefix + 'FullScreen'];
+ }
+ };
+ fullScreenApi.requestFullScreen = function(el) {
+ return (this.prefix === '') ? el.requestFullscreen() : el[this.prefix + 'RequestFullScreen']();
+ };
+ fullScreenApi.cancelFullScreen = function(el) {
+ return (this.prefix === '') ? document.exitFullscreen() : document[this.prefix + 'CancelFullScreen']();
+ };
+ }
+
+ // jQuery plugin
+ if (typeof jQuery !== 'undefined') {
+ jQuery.fn.requestFullScreen = function() {
+ return this.each(function() {
+ var el = jQuery(this);
+ if (fullScreenApi.supportsFullScreen) {
+ fullScreenApi.requestFullScreen(el);
+ }
+ });
+ };
+ }
+
+ // export api
+ window.fullScreenApi = fullScreenApi;
+})();
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/LICENSE b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/LICENSE
new file mode 100644
index 00000000..07ddddcc
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2013, Bruno Bergot
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ of conditions and the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/README.md b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/README.md
new file mode 100644
index 00000000..a111801f
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/README.md
@@ -0,0 +1,68 @@
+Leaflet.Control.FullScreen
+============
+
+What ?
+------
+
+Simple plugin for Leaflet that adds fullscreen button to your maps.
+
+Inspired by http://elidupuis.github.com/leaflet.zoomfs/
+
+Use the native javascript fullscreen API http://johndyer.name/native-fullscreen-javascript-api-plus-jquery-plugin/
+
+Released under the MIT License http://opensource.org/licenses/mit-license.php
+
+How ?
+------
+
+Include Control.FullScreen.js and Control.FullScreen.css in your page:
+
+``` html
+ <link rel="stylesheet" href="Control.FullScreen.css" />
+ <script src="Control.FullScreen.js"></script>
+```
+
+Add the fullscreen control to the map:
+
+``` js
+var map = new L.Map('map', {
+ fullscreenControl: true,
+ fullscreenControlOptions: {
+ position: 'topleft'
+ }
+});
+```
+
+If your map have a zoomControl the fullscreen button will be added at the bottom of this one.
+
+If your map doesn't have a zoomContron the fullscreen button will be added to topleft corner of the map (same as the zoomcontrol).
+
+__Events and options__:
+
+``` js
+// create a fullscreen button and add it to the map
+L.control.fullscreen({
+ position: 'topleft', // change the position of the button can be topleft, topright, bottomright or bottomleft, defaut topleft
+ title: 'Show me the fullscreen !', // change the title of the button, default Full Screen
+ forceSeparateButton: true, // force seperate button to detach from zoom buttons, default false
+ forcePseudoFullscreen: true // force use of pseudo full screen even if full screen API is available, default false
+}).addTo(map);
+
+// events are fired when entering or exiting fullscreen.
+map.on('enterFullscreen', function(){
+ console.log('entered fullscreen');
+});
+
+map.on('exitFullscreen', function(){
+ console.log('exited fullscreen');
+});
+```
+
+Where ?
+------
+
+Source code : https://github.com/brunob/leaflet.fullscreen
+
+Downloads : https://github.com/brunob/leaflet.fullscreen/releases
+
+Demo : http://brunob.github.com/leaflet.fullscreen/
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/bower.json b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/bower.json
new file mode 100644
index 00000000..6de9eee8
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/bower.json
@@ -0,0 +1,30 @@
+{
+ "name": "leaflet.fullscreen",
+ "version": "1.1.4",
+ "homepage": "https://github.com/brunob/leaflet.fullscreen",
+ "authors": [
+ "brunob <brunobergot@gmail.com>"
+ ],
+ "description": "Leaflet.Control.FullScreen for Leaflet",
+ "main": [
+ "Control.FullScreen.js",
+ "Control.FullScreen.css",
+ "icon-fullscreen.png",
+ "icon-fullscreen-2x.png"
+ ],
+ "keywords": [
+ "leaflet",
+ "plugins",
+ "maps",
+ "fullscreen"
+ ],
+ "license": "MIT",
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components",
+ "test",
+ "tests",
+ "index.html"
+ ]
+}
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/icon-fullscreen-2x.png b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/icon-fullscreen-2x.png
new file mode 100644
index 00000000..7320d953
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/icon-fullscreen-2x.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/icon-fullscreen.png b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/icon-fullscreen.png
new file mode 100644
index 00000000..17478145
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/icon-fullscreen.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/index.html b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/index.html
new file mode 100644
index 00000000..87b345c4
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/index.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset='utf-8'>
+ <title>Leaflet.Control.FullScreen Demo</title>
+ <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7/leaflet.css" />
+ <style type="text/css">
+ #map { width: 700px; height: 433px; }
+ .leaflet-control-zoom-fullscreen { background-image: url(icon-fullscreen.png); }
+ /* on selector per rule as explained here : http://www.sitepoint.com/html5-full-screen-api/ */
+ #map:-webkit-full-screen { width: 100% !important; height: 100% !important; z-index: 99999; }
+ #map:-moz-full-screen { width: 100% !important; height: 100% !important; z-index: 99999; }
+ #map:full-screen { width: 100% !important; height: 100% !important; z-index: 99999; }
+ .leaflet-pseudo-fullscreen { position: fixed !important; width: 100% !important; height: 100% !important; top: 0px !important; left: 0px !important; z-index: 99999; }
+ </style>
+ <script src="http://cdn.leafletjs.com/leaflet-0.7/leaflet.js"></script>
+ <script src="Control.FullScreen.js"></script>
+</head>
+<body>
+
+ <div id="map"></div>
+
+ <script>
+ var base = new L.TileLayer('http://{s}.www.toolserver.org/tiles/bw-mapnik/{z}/{x}/{y}.png', {
+ maxZoom: 18,
+ attribution: '&copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'
+ });
+
+ var map = new L.Map('map', {
+ layers: [base],
+ center: new L.LatLng(48.5, -4.5),
+ zoom: 5,
+ fullscreenControl: true,
+ fullscreenControlOptions: { // optional
+ title:"Show me the fullscreen !"
+ }
+ });
+
+ // detect fullscreen toggling
+ map.on('enterFullscreen', function(){
+ if(window.console) window.console.log('enterFullscreen');
+ });
+ map.on('exitFullscreen', function(){
+ if(window.console) window.console.log('exitFullscreen');
+ });
+ </script>
+</body>
+</html>
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/package.json b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/package.json
new file mode 100644
index 00000000..e0c9aa7b
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet.fullscreen/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "leaflet.fullscreen",
+ "version": "1.1.4",
+ "description": "Simple plugin for Leaflet that adds fullscreen button to your maps.",
+ "main": "Control.FullScreen.js",
+ "scripts": {
+ "test": "jshint Control.FullScreen.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/brunob/leaflet.fullscreen.git"
+ },
+ "keywords": [
+ "leaflet",
+ "plugins",
+ "maps",
+ "fullscreen"
+ ],
+ "devDependencies": {
+ "jshint": "2.5.0"
+ },
+ "author": "b_b",
+ "license": "MIT License",
+ "readmeFilename": "README.md"
+}
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.markercluster/MarkerCluster.css b/www/wiki/extensions/Maps/resources/leaflet/leaflet.markercluster/MarkerCluster.css
new file mode 100644
index 00000000..c60d71b7
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet.markercluster/MarkerCluster.css
@@ -0,0 +1,14 @@
+.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
+ -webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in;
+ -moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in;
+ -o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in;
+ transition: transform 0.3s ease-out, opacity 0.3s ease-in;
+}
+
+.leaflet-cluster-spider-leg {
+ /* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */
+ -webkit-transition: -webkit-stroke-dashoffset 0.3s ease-out, -webkit-stroke-opacity 0.3s ease-in;
+ -moz-transition: -moz-stroke-dashoffset 0.3s ease-out, -moz-stroke-opacity 0.3s ease-in;
+ -o-transition: -o-stroke-dashoffset 0.3s ease-out, -o-stroke-opacity 0.3s ease-in;
+ transition: stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in;
+}
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet.markercluster/leaflet.markercluster.js b/www/wiki/extensions/Maps/resources/leaflet/leaflet.markercluster/leaflet.markercluster.js
new file mode 100644
index 00000000..ed22dc07
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet.markercluster/leaflet.markercluster.js
@@ -0,0 +1,3 @@
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e.Leaflet=e.Leaflet||{},e.Leaflet.markercluster=e.Leaflet.markercluster||{}))}(this,function(e){"use strict";var t=L.MarkerClusterGroup=L.FeatureGroup.extend({options:{maxClusterRadius:80,iconCreateFunction:null,clusterPane:L.Marker.prototype.options.pane,spiderfyOnMaxZoom:!0,showCoverageOnHover:!0,zoomToBoundsOnClick:!0,singleMarkerMode:!1,disableClusteringAtZoom:null,removeOutsideVisibleBounds:!0,animate:!0,animateAddingMarkers:!1,spiderfyDistanceMultiplier:1,spiderLegPolylineOptions:{weight:1.5,color:"#222",opacity:.5},chunkedLoading:!1,chunkInterval:200,chunkDelay:50,chunkProgress:null,polygonOptions:{}},initialize:function(e){L.Util.setOptions(this,e),this.options.iconCreateFunction||(this.options.iconCreateFunction=this._defaultIconCreateFunction),this._featureGroup=L.featureGroup(),this._featureGroup.addEventParent(this),this._nonPointGroup=L.featureGroup(),this._nonPointGroup.addEventParent(this),this._inZoomAnimation=0,this._needsClustering=[],this._needsRemoving=[],this._currentShownBounds=null,this._queue=[],this._childMarkerEventHandlers={dragstart:this._childMarkerDragStart,move:this._childMarkerMoved,dragend:this._childMarkerDragEnd};var t=L.DomUtil.TRANSITION&&this.options.animate;L.extend(this,t?this._withAnimation:this._noAnimation),this._markerCluster=t?L.MarkerCluster:L.MarkerClusterNonAnimated},addLayer:function(e){if(e instanceof L.LayerGroup)return this.addLayers([e]);if(!e.getLatLng)return this._nonPointGroup.addLayer(e),this.fire("layeradd",{layer:e}),this;if(!this._map)return this._needsClustering.push(e),this.fire("layeradd",{layer:e}),this;if(this.hasLayer(e))return this;this._unspiderfy&&this._unspiderfy(),this._addLayer(e,this._maxZoom),this.fire("layeradd",{layer:e}),this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons();var t=e,i=this._zoom;if(e.__parent)for(;t.__parent._zoom>=i;)t=t.__parent;return this._currentShownBounds.contains(t.getLatLng())&&(this.options.animateAddingMarkers?this._animationAddLayer(e,t):this._animationAddLayerNonAnimated(e,t)),this},removeLayer:function(e){return e instanceof L.LayerGroup?this.removeLayers([e]):e.getLatLng?this._map?e.__parent?(this._unspiderfy&&(this._unspiderfy(),this._unspiderfyLayer(e)),this._removeLayer(e,!0),this.fire("layerremove",{layer:e}),this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons(),e.off(this._childMarkerEventHandlers,this),this._featureGroup.hasLayer(e)&&(this._featureGroup.removeLayer(e),e.clusterShow&&e.clusterShow()),this):this:(!this._arraySplice(this._needsClustering,e)&&this.hasLayer(e)&&this._needsRemoving.push({layer:e,latlng:e._latlng}),this.fire("layerremove",{layer:e}),this):(this._nonPointGroup.removeLayer(e),this.fire("layerremove",{layer:e}),this)},addLayers:function(e,t){if(!L.Util.isArray(e))return this.addLayer(e);var i,n=this._featureGroup,r=this._nonPointGroup,s=this.options.chunkedLoading,o=this.options.chunkInterval,a=this.options.chunkProgress,h=e.length,l=0,u=!0;if(this._map){var _=(new Date).getTime(),d=L.bind(function(){for(var c=(new Date).getTime();h>l;l++){if(s&&0===l%200){var p=(new Date).getTime()-c;if(p>o)break}if(i=e[l],i instanceof L.LayerGroup)u&&(e=e.slice(),u=!1),this._extractNonGroupLayers(i,e),h=e.length;else if(i.getLatLng){if(!this.hasLayer(i)&&(this._addLayer(i,this._maxZoom),t||this.fire("layeradd",{layer:i}),i.__parent&&2===i.__parent.getChildCount())){var f=i.__parent.getAllChildMarkers(),m=f[0]===i?f[1]:f[0];n.removeLayer(m)}}else r.addLayer(i),t||this.fire("layeradd",{layer:i})}a&&a(l,h,(new Date).getTime()-_),l===h?(this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons(),this._topClusterLevel._recursivelyAddChildrenToMap(null,this._zoom,this._currentShownBounds)):setTimeout(d,this.options.chunkDelay)},this);d()}else for(var c=this._needsClustering;h>l;l++)i=e[l],i instanceof L.LayerGroup?(u&&(e=e.slice(),u=!1),this._extractNonGroupLayers(i,e),h=e.length):i.getLatLng?this.hasLayer(i)||c.push(i):r.addLayer(i);return this},removeLayers:function(e){var t,i,n=e.length,r=this._featureGroup,s=this._nonPointGroup,o=!0;if(!this._map){for(t=0;n>t;t++)i=e[t],i instanceof L.LayerGroup?(o&&(e=e.slice(),o=!1),this._extractNonGroupLayers(i,e),n=e.length):(this._arraySplice(this._needsClustering,i),s.removeLayer(i),this.hasLayer(i)&&this._needsRemoving.push({layer:i,latlng:i._latlng}),this.fire("layerremove",{layer:i}));return this}if(this._unspiderfy){this._unspiderfy();var a=e.slice(),h=n;for(t=0;h>t;t++)i=a[t],i instanceof L.LayerGroup?(this._extractNonGroupLayers(i,a),h=a.length):this._unspiderfyLayer(i)}for(t=0;n>t;t++)i=e[t],i instanceof L.LayerGroup?(o&&(e=e.slice(),o=!1),this._extractNonGroupLayers(i,e),n=e.length):i.__parent?(this._removeLayer(i,!0,!0),this.fire("layerremove",{layer:i}),r.hasLayer(i)&&(r.removeLayer(i),i.clusterShow&&i.clusterShow())):(s.removeLayer(i),this.fire("layerremove",{layer:i}));return this._topClusterLevel._recalculateBounds(),this._refreshClustersIcons(),this._topClusterLevel._recursivelyAddChildrenToMap(null,this._zoom,this._currentShownBounds),this},clearLayers:function(){return this._map||(this._needsClustering=[],delete this._gridClusters,delete this._gridUnclustered),this._noanimationUnspiderfy&&this._noanimationUnspiderfy(),this._featureGroup.clearLayers(),this._nonPointGroup.clearLayers(),this.eachLayer(function(e){e.off(this._childMarkerEventHandlers,this),delete e.__parent},this),this._map&&this._generateInitialClusters(),this},getBounds:function(){var e=new L.LatLngBounds;this._topClusterLevel&&e.extend(this._topClusterLevel._bounds);for(var t=this._needsClustering.length-1;t>=0;t--)e.extend(this._needsClustering[t].getLatLng());return e.extend(this._nonPointGroup.getBounds()),e},eachLayer:function(e,t){var i,n,r,s=this._needsClustering.slice(),o=this._needsRemoving;for(this._topClusterLevel&&this._topClusterLevel.getAllChildMarkers(s),n=s.length-1;n>=0;n--){for(i=!0,r=o.length-1;r>=0;r--)if(o[r].layer===s[n]){i=!1;break}i&&e.call(t,s[n])}this._nonPointGroup.eachLayer(e,t)},getLayers:function(){var e=[];return this.eachLayer(function(t){e.push(t)}),e},getLayer:function(e){var t=null;return e=parseInt(e,10),this.eachLayer(function(i){L.stamp(i)===e&&(t=i)}),t},hasLayer:function(e){if(!e)return!1;var t,i=this._needsClustering;for(t=i.length-1;t>=0;t--)if(i[t]===e)return!0;for(i=this._needsRemoving,t=i.length-1;t>=0;t--)if(i[t].layer===e)return!1;return!(!e.__parent||e.__parent._group!==this)||this._nonPointGroup.hasLayer(e)},zoomToShowLayer:function(e,t){"function"!=typeof t&&(t=function(){});var i=function(){!e._icon&&!e.__parent._icon||this._inZoomAnimation||(this._map.off("moveend",i,this),this.off("animationend",i,this),e._icon?t():e.__parent._icon&&(this.once("spiderfied",t,this),e.__parent.spiderfy()))};e._icon&&this._map.getBounds().contains(e.getLatLng())?t():e.__parent._zoom<Math.round(this._map._zoom)?(this._map.on("moveend",i,this),this._map.panTo(e.getLatLng())):(this._map.on("moveend",i,this),this.on("animationend",i,this),e.__parent.zoomToBounds())},onAdd:function(e){this._map=e;var t,i,n;if(!isFinite(this._map.getMaxZoom()))throw"Map has no maxZoom specified";for(this._featureGroup.addTo(e),this._nonPointGroup.addTo(e),this._gridClusters||this._generateInitialClusters(),this._maxLat=e.options.crs.projection.MAX_LATITUDE,t=0,i=this._needsRemoving.length;i>t;t++)n=this._needsRemoving[t],n.newlatlng=n.layer._latlng,n.layer._latlng=n.latlng;for(t=0,i=this._needsRemoving.length;i>t;t++)n=this._needsRemoving[t],this._removeLayer(n.layer,!0),n.layer._latlng=n.newlatlng;this._needsRemoving=[],this._zoom=Math.round(this._map._zoom),this._currentShownBounds=this._getExpandedVisibleBounds(),this._map.on("zoomend",this._zoomEnd,this),this._map.on("moveend",this._moveEnd,this),this._spiderfierOnAdd&&this._spiderfierOnAdd(),this._bindEvents(),i=this._needsClustering,this._needsClustering=[],this.addLayers(i,!0)},onRemove:function(e){e.off("zoomend",this._zoomEnd,this),e.off("moveend",this._moveEnd,this),this._unbindEvents(),this._map._mapPane.className=this._map._mapPane.className.replace(" leaflet-cluster-anim",""),this._spiderfierOnRemove&&this._spiderfierOnRemove(),delete this._maxLat,this._hideCoverage(),this._featureGroup.remove(),this._nonPointGroup.remove(),this._featureGroup.clearLayers(),this._map=null},getVisibleParent:function(e){for(var t=e;t&&!t._icon;)t=t.__parent;return t||null},_arraySplice:function(e,t){for(var i=e.length-1;i>=0;i--)if(e[i]===t)return e.splice(i,1),!0},_removeFromGridUnclustered:function(e,t){for(var i=this._map,n=this._gridUnclustered,r=Math.floor(this._map.getMinZoom());t>=r&&n[t].removeObject(e,i.project(e.getLatLng(),t));t--);},_childMarkerDragStart:function(e){e.target.__dragStart=e.target._latlng},_childMarkerMoved:function(e){if(!this._ignoreMove&&!e.target.__dragStart){var t=e.target._popup&&e.target._popup.isOpen();this._moveChild(e.target,e.oldLatLng,e.latlng),t&&e.target.openPopup()}},_moveChild:function(e,t,i){e._latlng=t,this.removeLayer(e),e._latlng=i,this.addLayer(e)},_childMarkerDragEnd:function(e){e.target.__dragStart&&this._moveChild(e.target,e.target.__dragStart,e.target._latlng),delete e.target.__dragStart},_removeLayer:function(e,t,i){var n=this._gridClusters,r=this._gridUnclustered,s=this._featureGroup,o=this._map,a=Math.floor(this._map.getMinZoom());t&&this._removeFromGridUnclustered(e,this._maxZoom);var h,l=e.__parent,u=l._markers;for(this._arraySplice(u,e);l&&(l._childCount--,l._boundsNeedUpdate=!0,!(l._zoom<a));)t&&l._childCount<=1?(h=l._markers[0]===e?l._markers[1]:l._markers[0],n[l._zoom].removeObject(l,o.project(l._cLatLng,l._zoom)),r[l._zoom].addObject(h,o.project(h.getLatLng(),l._zoom)),this._arraySplice(l.__parent._childClusters,l),l.__parent._markers.push(h),h.__parent=l.__parent,l._icon&&(s.removeLayer(l),i||s.addLayer(h))):l._iconNeedsUpdate=!0,l=l.__parent;delete e.__parent},_isOrIsParent:function(e,t){for(;t;){if(e===t)return!0;t=t.parentNode}return!1},fire:function(e,t,i){if(t&&t.layer instanceof L.MarkerCluster){if(t.originalEvent&&this._isOrIsParent(t.layer._icon,t.originalEvent.relatedTarget))return;e="cluster"+e}L.FeatureGroup.prototype.fire.call(this,e,t,i)},listens:function(e,t){return L.FeatureGroup.prototype.listens.call(this,e,t)||L.FeatureGroup.prototype.listens.call(this,"cluster"+e,t)},_defaultIconCreateFunction:function(e){var t=e.getChildCount(),i=" marker-cluster-";return i+=10>t?"small":100>t?"medium":"large",new L.DivIcon({html:"<div><span>"+t+"</span></div>",className:"marker-cluster"+i,iconSize:new L.Point(40,40)})},_bindEvents:function(){var e=this._map,t=this.options.spiderfyOnMaxZoom,i=this.options.showCoverageOnHover,n=this.options.zoomToBoundsOnClick;(t||n)&&this.on("clusterclick",this._zoomOrSpiderfy,this),i&&(this.on("clustermouseover",this._showCoverage,this),this.on("clustermouseout",this._hideCoverage,this),e.on("zoomend",this._hideCoverage,this))},_zoomOrSpiderfy:function(e){for(var t=e.layer,i=t;1===i._childClusters.length;)i=i._childClusters[0];i._zoom===this._maxZoom&&i._childCount===t._childCount&&this.options.spiderfyOnMaxZoom?t.spiderfy():this.options.zoomToBoundsOnClick&&t.zoomToBounds(),e.originalEvent&&13===e.originalEvent.keyCode&&this._map._container.focus()},_showCoverage:function(e){var t=this._map;this._inZoomAnimation||(this._shownPolygon&&t.removeLayer(this._shownPolygon),e.layer.getChildCount()>2&&e.layer!==this._spiderfied&&(this._shownPolygon=new L.Polygon(e.layer.getConvexHull(),this.options.polygonOptions),t.addLayer(this._shownPolygon)))},_hideCoverage:function(){this._shownPolygon&&(this._map.removeLayer(this._shownPolygon),this._shownPolygon=null)},_unbindEvents:function(){var e=this.options.spiderfyOnMaxZoom,t=this.options.showCoverageOnHover,i=this.options.zoomToBoundsOnClick,n=this._map;(e||i)&&this.off("clusterclick",this._zoomOrSpiderfy,this),t&&(this.off("clustermouseover",this._showCoverage,this),this.off("clustermouseout",this._hideCoverage,this),n.off("zoomend",this._hideCoverage,this))},_zoomEnd:function(){this._map&&(this._mergeSplitClusters(),this._zoom=Math.round(this._map._zoom),this._currentShownBounds=this._getExpandedVisibleBounds())},_moveEnd:function(){if(!this._inZoomAnimation){var e=this._getExpandedVisibleBounds();this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,Math.floor(this._map.getMinZoom()),this._zoom,e),this._topClusterLevel._recursivelyAddChildrenToMap(null,Math.round(this._map._zoom),e),this._currentShownBounds=e}},_generateInitialClusters:function(){var e=Math.ceil(this._map.getMaxZoom()),t=Math.floor(this._map.getMinZoom()),i=this.options.maxClusterRadius,n=i;"function"!=typeof i&&(n=function(){return i}),null!==this.options.disableClusteringAtZoom&&(e=this.options.disableClusteringAtZoom-1),this._maxZoom=e,this._gridClusters={},this._gridUnclustered={};for(var r=e;r>=t;r--)this._gridClusters[r]=new L.DistanceGrid(n(r)),this._gridUnclustered[r]=new L.DistanceGrid(n(r));this._topClusterLevel=new this._markerCluster(this,t-1)},_addLayer:function(e,t){var i,n,r=this._gridClusters,s=this._gridUnclustered,o=Math.floor(this._map.getMinZoom());for(this.options.singleMarkerMode&&this._overrideMarkerIcon(e),e.on(this._childMarkerEventHandlers,this);t>=o;t--){i=this._map.project(e.getLatLng(),t);var a=r[t].getNearObject(i);if(a)return a._addChild(e),e.__parent=a,void 0;if(a=s[t].getNearObject(i)){var h=a.__parent;h&&this._removeLayer(a,!1);var l=new this._markerCluster(this,t,a,e);r[t].addObject(l,this._map.project(l._cLatLng,t)),a.__parent=l,e.__parent=l;var u=l;for(n=t-1;n>h._zoom;n--)u=new this._markerCluster(this,n,u),r[n].addObject(u,this._map.project(a.getLatLng(),n));return h._addChild(u),this._removeFromGridUnclustered(a,t),void 0}s[t].addObject(e,i)}this._topClusterLevel._addChild(e),e.__parent=this._topClusterLevel},_refreshClustersIcons:function(){this._featureGroup.eachLayer(function(e){e instanceof L.MarkerCluster&&e._iconNeedsUpdate&&e._updateIcon()})},_enqueue:function(e){this._queue.push(e),this._queueTimeout||(this._queueTimeout=setTimeout(L.bind(this._processQueue,this),300))},_processQueue:function(){for(var e=0;e<this._queue.length;e++)this._queue[e].call(this);this._queue.length=0,clearTimeout(this._queueTimeout),this._queueTimeout=null},_mergeSplitClusters:function(){var e=Math.round(this._map._zoom);this._processQueue(),this._zoom<e&&this._currentShownBounds.intersects(this._getExpandedVisibleBounds())?(this._animationStart(),this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,Math.floor(this._map.getMinZoom()),this._zoom,this._getExpandedVisibleBounds()),this._animationZoomIn(this._zoom,e)):this._zoom>e?(this._animationStart(),this._animationZoomOut(this._zoom,e)):this._moveEnd()},_getExpandedVisibleBounds:function(){return this.options.removeOutsideVisibleBounds?L.Browser.mobile?this._checkBoundsMaxLat(this._map.getBounds()):this._checkBoundsMaxLat(this._map.getBounds().pad(1)):this._mapBoundsInfinite},_checkBoundsMaxLat:function(e){var t=this._maxLat;return void 0!==t&&(e.getNorth()>=t&&(e._northEast.lat=1/0),e.getSouth()<=-t&&(e._southWest.lat=-1/0)),e},_animationAddLayerNonAnimated:function(e,t){if(t===e)this._featureGroup.addLayer(e);else if(2===t._childCount){t._addToMap();var i=t.getAllChildMarkers();this._featureGroup.removeLayer(i[0]),this._featureGroup.removeLayer(i[1])}else t._updateIcon()},_extractNonGroupLayers:function(e,t){var i,n=e.getLayers(),r=0;for(t=t||[];r<n.length;r++)i=n[r],i instanceof L.LayerGroup?this._extractNonGroupLayers(i,t):t.push(i);return t},_overrideMarkerIcon:function(e){var t=e.options.icon=this.options.iconCreateFunction({getChildCount:function(){return 1},getAllChildMarkers:function(){return[e]}});return t}});L.MarkerClusterGroup.include({_mapBoundsInfinite:new L.LatLngBounds(new L.LatLng(-1/0,-1/0),new L.LatLng(1/0,1/0))}),L.MarkerClusterGroup.include({_noAnimation:{_animationStart:function(){},_animationZoomIn:function(e,t){this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,Math.floor(this._map.getMinZoom()),e),this._topClusterLevel._recursivelyAddChildrenToMap(null,t,this._getExpandedVisibleBounds()),this.fire("animationend")},_animationZoomOut:function(e,t){this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,Math.floor(this._map.getMinZoom()),e),this._topClusterLevel._recursivelyAddChildrenToMap(null,t,this._getExpandedVisibleBounds()),this.fire("animationend")},_animationAddLayer:function(e,t){this._animationAddLayerNonAnimated(e,t)}},_withAnimation:{_animationStart:function(){this._map._mapPane.className+=" leaflet-cluster-anim",this._inZoomAnimation++},_animationZoomIn:function(e,t){var i,n=this._getExpandedVisibleBounds(),r=this._featureGroup,s=Math.floor(this._map.getMinZoom());this._ignoreMove=!0,this._topClusterLevel._recursively(n,e,s,function(s){var o,a=s._latlng,h=s._markers;for(n.contains(a)||(a=null),s._isSingleParent()&&e+1===t?(r.removeLayer(s),s._recursivelyAddChildrenToMap(null,t,n)):(s.clusterHide(),s._recursivelyAddChildrenToMap(a,t,n)),i=h.length-1;i>=0;i--)o=h[i],n.contains(o._latlng)||r.removeLayer(o)}),this._forceLayout(),this._topClusterLevel._recursivelyBecomeVisible(n,t),r.eachLayer(function(e){e instanceof L.MarkerCluster||!e._icon||e.clusterShow()}),this._topClusterLevel._recursively(n,e,t,function(e){e._recursivelyRestoreChildPositions(t)}),this._ignoreMove=!1,this._enqueue(function(){this._topClusterLevel._recursively(n,e,s,function(e){r.removeLayer(e),e.clusterShow()}),this._animationEnd()})},_animationZoomOut:function(e,t){this._animationZoomOutSingle(this._topClusterLevel,e-1,t),this._topClusterLevel._recursivelyAddChildrenToMap(null,t,this._getExpandedVisibleBounds()),this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds,Math.floor(this._map.getMinZoom()),e,this._getExpandedVisibleBounds())},_animationAddLayer:function(e,t){var i=this,n=this._featureGroup;n.addLayer(e),t!==e&&(t._childCount>2?(t._updateIcon(),this._forceLayout(),this._animationStart(),e._setPos(this._map.latLngToLayerPoint(t.getLatLng())),e.clusterHide(),this._enqueue(function(){n.removeLayer(e),e.clusterShow(),i._animationEnd()})):(this._forceLayout(),i._animationStart(),i._animationZoomOutSingle(t,this._map.getMaxZoom(),this._zoom)))}},_animationZoomOutSingle:function(e,t,i){var n=this._getExpandedVisibleBounds(),r=Math.floor(this._map.getMinZoom());e._recursivelyAnimateChildrenInAndAddSelfToMap(n,r,t+1,i);var s=this;this._forceLayout(),e._recursivelyBecomeVisible(n,i),this._enqueue(function(){if(1===e._childCount){var o=e._markers[0];this._ignoreMove=!0,o.setLatLng(o.getLatLng()),this._ignoreMove=!1,o.clusterShow&&o.clusterShow()}else e._recursively(n,i,r,function(e){e._recursivelyRemoveChildrenFromMap(n,r,t+1)});s._animationEnd()})},_animationEnd:function(){this._map&&(this._map._mapPane.className=this._map._mapPane.className.replace(" leaflet-cluster-anim","")),this._inZoomAnimation--,this.fire("animationend")},_forceLayout:function(){L.Util.falseFn(document.body.offsetWidth)}}),L.markerClusterGroup=function(e){return new L.MarkerClusterGroup(e)};var i=L.MarkerCluster=L.Marker.extend({options:L.Icon.prototype.options,initialize:function(e,t,i,n){L.Marker.prototype.initialize.call(this,i?i._cLatLng||i.getLatLng():new L.LatLng(0,0),{icon:this,pane:e.options.clusterPane}),this._group=e,this._zoom=t,this._markers=[],this._childClusters=[],this._childCount=0,this._iconNeedsUpdate=!0,this._boundsNeedUpdate=!0,this._bounds=new L.LatLngBounds,i&&this._addChild(i),n&&this._addChild(n)},getAllChildMarkers:function(e){e=e||[];for(var t=this._childClusters.length-1;t>=0;t--)this._childClusters[t].getAllChildMarkers(e);for(var i=this._markers.length-1;i>=0;i--)e.push(this._markers[i]);return e},getChildCount:function(){return this._childCount},zoomToBounds:function(e){for(var t,i=this._childClusters.slice(),n=this._group._map,r=n.getBoundsZoom(this._bounds),s=this._zoom+1,o=n.getZoom();i.length>0&&r>s;){s++;var a=[];for(t=0;t<i.length;t++)a=a.concat(i[t]._childClusters);i=a}r>s?this._group._map.setView(this._latlng,s):o>=r?this._group._map.setView(this._latlng,o+1):this._group._map.fitBounds(this._bounds,e)},getBounds:function(){var e=new L.LatLngBounds;return e.extend(this._bounds),e},_updateIcon:function(){this._iconNeedsUpdate=!0,this._icon&&this.setIcon(this)},createIcon:function(){return this._iconNeedsUpdate&&(this._iconObj=this._group.options.iconCreateFunction(this),this._iconNeedsUpdate=!1),this._iconObj.createIcon()},createShadow:function(){return this._iconObj.createShadow()},_addChild:function(e,t){this._iconNeedsUpdate=!0,this._boundsNeedUpdate=!0,this._setClusterCenter(e),e instanceof L.MarkerCluster?(t||(this._childClusters.push(e),e.__parent=this),this._childCount+=e._childCount):(t||this._markers.push(e),this._childCount++),this.__parent&&this.__parent._addChild(e,!0)},_setClusterCenter:function(e){this._cLatLng||(this._cLatLng=e._cLatLng||e._latlng)},_resetBounds:function(){var e=this._bounds;e._southWest&&(e._southWest.lat=1/0,e._southWest.lng=1/0),e._northEast&&(e._northEast.lat=-1/0,e._northEast.lng=-1/0)},_recalculateBounds:function(){var e,t,i,n,r=this._markers,s=this._childClusters,o=0,a=0,h=this._childCount;if(0!==h){for(this._resetBounds(),e=0;e<r.length;e++)i=r[e]._latlng,this._bounds.extend(i),o+=i.lat,a+=i.lng;for(e=0;e<s.length;e++)t=s[e],t._boundsNeedUpdate&&t._recalculateBounds(),this._bounds.extend(t._bounds),i=t._wLatLng,n=t._childCount,o+=i.lat*n,a+=i.lng*n;this._latlng=this._wLatLng=new L.LatLng(o/h,a/h),this._boundsNeedUpdate=!1}},_addToMap:function(e){e&&(this._backupLatlng=this._latlng,this.setLatLng(e)),this._group._featureGroup.addLayer(this)},_recursivelyAnimateChildrenIn:function(e,t,i){this._recursively(e,this._group._map.getMinZoom(),i-1,function(e){var i,n,r=e._markers;for(i=r.length-1;i>=0;i--)n=r[i],n._icon&&(n._setPos(t),n.clusterHide())},function(e){var i,n,r=e._childClusters;for(i=r.length-1;i>=0;i--)n=r[i],n._icon&&(n._setPos(t),n.clusterHide())})},_recursivelyAnimateChildrenInAndAddSelfToMap:function(e,t,i,n){this._recursively(e,n,t,function(r){r._recursivelyAnimateChildrenIn(e,r._group._map.latLngToLayerPoint(r.getLatLng()).round(),i),r._isSingleParent()&&i-1===n?(r.clusterShow(),r._recursivelyRemoveChildrenFromMap(e,t,i)):r.clusterHide(),r._addToMap()})},_recursivelyBecomeVisible:function(e,t){this._recursively(e,this._group._map.getMinZoom(),t,null,function(e){e.clusterShow()})},_recursivelyAddChildrenToMap:function(e,t,i){this._recursively(i,this._group._map.getMinZoom()-1,t,function(n){if(t!==n._zoom)for(var r=n._markers.length-1;r>=0;r--){var s=n._markers[r];i.contains(s._latlng)&&(e&&(s._backupLatlng=s.getLatLng(),s.setLatLng(e),s.clusterHide&&s.clusterHide()),n._group._featureGroup.addLayer(s))}},function(t){t._addToMap(e)})},_recursivelyRestoreChildPositions:function(e){for(var t=this._markers.length-1;t>=0;t--){var i=this._markers[t];i._backupLatlng&&(i.setLatLng(i._backupLatlng),delete i._backupLatlng)}if(e-1===this._zoom)for(var n=this._childClusters.length-1;n>=0;n--)this._childClusters[n]._restorePosition();else for(var r=this._childClusters.length-1;r>=0;r--)this._childClusters[r]._recursivelyRestoreChildPositions(e)},_restorePosition:function(){this._backupLatlng&&(this.setLatLng(this._backupLatlng),delete this._backupLatlng)},_recursivelyRemoveChildrenFromMap:function(e,t,i,n){var r,s;this._recursively(e,t-1,i-1,function(e){for(s=e._markers.length-1;s>=0;s--)r=e._markers[s],n&&n.contains(r._latlng)||(e._group._featureGroup.removeLayer(r),r.clusterShow&&r.clusterShow())},function(e){for(s=e._childClusters.length-1;s>=0;s--)r=e._childClusters[s],n&&n.contains(r._latlng)||(e._group._featureGroup.removeLayer(r),r.clusterShow&&r.clusterShow())})},_recursively:function(e,t,i,n,r){var s,o,a=this._childClusters,h=this._zoom;if(h>=t&&(n&&n(this),r&&h===i&&r(this)),t>h||i>h)for(s=a.length-1;s>=0;s--)o=a[s],e.intersects(o._bounds)&&o._recursively(e,t,i,n,r)},_isSingleParent:function(){return this._childClusters.length>0&&this._childClusters[0]._childCount===this._childCount}});L.Marker.include({clusterHide:function(){return this.options.opacityWhenUnclustered=this.options.opacity||1,this.setOpacity(0)},clusterShow:function(){var e=this.setOpacity(this.options.opacity||this.options.opacityWhenUnclustered);return delete this.options.opacityWhenUnclustered,e}}),L.DistanceGrid=function(e){this._cellSize=e,this._sqCellSize=e*e,this._grid={},this._objectPoint={}},L.DistanceGrid.prototype={addObject:function(e,t){var i=this._getCoord(t.x),n=this._getCoord(t.y),r=this._grid,s=r[n]=r[n]||{},o=s[i]=s[i]||[],a=L.Util.stamp(e);this._objectPoint[a]=t,o.push(e)},updateObject:function(e,t){this.removeObject(e),this.addObject(e,t)},removeObject:function(e,t){var i,n,r=this._getCoord(t.x),s=this._getCoord(t.y),o=this._grid,a=o[s]=o[s]||{},h=a[r]=a[r]||[];for(delete this._objectPoint[L.Util.stamp(e)],i=0,n=h.length;n>i;i++)if(h[i]===e)return h.splice(i,1),1===n&&delete a[r],!0},eachObject:function(e,t){var i,n,r,s,o,a,h,l=this._grid;for(i in l){o=l[i];for(n in o)for(a=o[n],r=0,s=a.length;s>r;r++)h=e.call(t,a[r]),h&&(r--,s--)}},getNearObject:function(e){var t,i,n,r,s,o,a,h,l=this._getCoord(e.x),u=this._getCoord(e.y),_=this._objectPoint,d=this._sqCellSize,c=null;for(t=u-1;u+1>=t;t++)if(r=this._grid[t])for(i=l-1;l+1>=i;i++)if(s=r[i])for(n=0,o=s.length;o>n;n++)a=s[n],h=this._sqDist(_[L.Util.stamp(a)],e),(d>h||d>=h&&null===c)&&(d=h,c=a);return c},_getCoord:function(e){var t=Math.floor(e/this._cellSize);return isFinite(t)?t:e},_sqDist:function(e,t){var i=t.x-e.x,n=t.y-e.y;return i*i+n*n}},function(){L.QuickHull={getDistant:function(e,t){var i=t[1].lat-t[0].lat,n=t[0].lng-t[1].lng;return n*(e.lat-t[0].lat)+i*(e.lng-t[0].lng)},findMostDistantPointFromBaseLine:function(e,t){var i,n,r,s=0,o=null,a=[];for(i=t.length-1;i>=0;i--)n=t[i],r=this.getDistant(n,e),r>0&&(a.push(n),r>s&&(s=r,o=n));return{maxPoint:o,newPoints:a}},buildConvexHull:function(e,t){var i=[],n=this.findMostDistantPointFromBaseLine(e,t);return n.maxPoint?(i=i.concat(this.buildConvexHull([e[0],n.maxPoint],n.newPoints)),i=i.concat(this.buildConvexHull([n.maxPoint,e[1]],n.newPoints))):[e[0]]},getConvexHull:function(e){var t,i=!1,n=!1,r=!1,s=!1,o=null,a=null,h=null,l=null,u=null,_=null;for(t=e.length-1;t>=0;t--){var d=e[t];(i===!1||d.lat>i)&&(o=d,i=d.lat),(n===!1||d.lat<n)&&(a=d,n=d.lat),(r===!1||d.lng>r)&&(h=d,r=d.lng),(s===!1||d.lng<s)&&(l=d,s=d.lng)}n!==i?(_=a,u=o):(_=l,u=h);var c=[].concat(this.buildConvexHull([_,u],e),this.buildConvexHull([u,_],e));return c}}}(),L.MarkerCluster.include({getConvexHull:function(){var e,t,i=this.getAllChildMarkers(),n=[];for(t=i.length-1;t>=0;t--)e=i[t].getLatLng(),n.push(e);return L.QuickHull.getConvexHull(n)}}),L.MarkerCluster.include({_2PI:2*Math.PI,_circleFootSeparation:25,_circleStartAngle:0,_spiralFootSeparation:28,_spiralLengthStart:11,_spiralLengthFactor:5,_circleSpiralSwitchover:9,spiderfy:function(){if(this._group._spiderfied!==this&&!this._group._inZoomAnimation){var e,t=this.getAllChildMarkers(),i=this._group,n=i._map,r=n.latLngToLayerPoint(this._latlng);this._group._unspiderfy(),this._group._spiderfied=this,t.length>=this._circleSpiralSwitchover?e=this._generatePointsSpiral(t.length,r):(r.y+=10,e=this._generatePointsCircle(t.length,r)),this._animationSpiderfy(t,e)}},unspiderfy:function(e){this._group._inZoomAnimation||(this._animationUnspiderfy(e),this._group._spiderfied=null)},_generatePointsCircle:function(e,t){var i,n,r=this._group.options.spiderfyDistanceMultiplier*this._circleFootSeparation*(2+e),s=r/this._2PI,o=this._2PI/e,a=[];for(s=Math.max(s,35),a.length=e,i=0;e>i;i++)n=this._circleStartAngle+i*o,a[i]=new L.Point(t.x+s*Math.cos(n),t.y+s*Math.sin(n))._round();return a},_generatePointsSpiral:function(e,t){var i,n=this._group.options.spiderfyDistanceMultiplier,r=n*this._spiralLengthStart,s=n*this._spiralFootSeparation,o=n*this._spiralLengthFactor*this._2PI,a=0,h=[];for(h.length=e,i=e;i>=0;i--)e>i&&(h[i]=new L.Point(t.x+r*Math.cos(a),t.y+r*Math.sin(a))._round()),a+=s/r+5e-4*i,r+=o/a;return h},_noanimationUnspiderfy:function(){var e,t,i=this._group,n=i._map,r=i._featureGroup,s=this.getAllChildMarkers();for(i._ignoreMove=!0,this.setOpacity(1),t=s.length-1;t>=0;t--)e=s[t],r.removeLayer(e),e._preSpiderfyLatlng&&(e.setLatLng(e._preSpiderfyLatlng),delete e._preSpiderfyLatlng),e.setZIndexOffset&&e.setZIndexOffset(0),e._spiderLeg&&(n.removeLayer(e._spiderLeg),delete e._spiderLeg);i.fire("unspiderfied",{cluster:this,markers:s}),i._ignoreMove=!1,i._spiderfied=null}}),L.MarkerClusterNonAnimated=L.MarkerCluster.extend({_animationSpiderfy:function(e,t){var i,n,r,s,o=this._group,a=o._map,h=o._featureGroup,l=this._group.options.spiderLegPolylineOptions;for(o._ignoreMove=!0,i=0;i<e.length;i++)s=a.layerPointToLatLng(t[i]),n=e[i],r=new L.Polyline([this._latlng,s],l),a.addLayer(r),n._spiderLeg=r,n._preSpiderfyLatlng=n._latlng,n.setLatLng(s),n.setZIndexOffset&&n.setZIndexOffset(1e6),h.addLayer(n);this.setOpacity(.3),o._ignoreMove=!1,o.fire("spiderfied",{cluster:this,markers:e})},_animationUnspiderfy:function(){this._noanimationUnspiderfy()}}),L.MarkerCluster.include({_animationSpiderfy:function(e,t){var i,n,r,s,o,a,h=this,l=this._group,u=l._map,_=l._featureGroup,d=this._latlng,c=u.latLngToLayerPoint(d),p=L.Path.SVG,f=L.extend({},this._group.options.spiderLegPolylineOptions),m=f.opacity;for(void 0===m&&(m=L.MarkerClusterGroup.prototype.options.spiderLegPolylineOptions.opacity),p?(f.opacity=0,f.className=(f.className||"")+" leaflet-cluster-spider-leg"):f.opacity=m,l._ignoreMove=!0,i=0;i<e.length;i++)n=e[i],a=u.layerPointToLatLng(t[i]),r=new L.Polyline([d,a],f),u.addLayer(r),n._spiderLeg=r,p&&(s=r._path,o=s.getTotalLength()+.1,s.style.strokeDasharray=o,s.style.strokeDashoffset=o),n.setZIndexOffset&&n.setZIndexOffset(1e6),n.clusterHide&&n.clusterHide(),_.addLayer(n),n._setPos&&n._setPos(c);for(l._forceLayout(),l._animationStart(),i=e.length-1;i>=0;i--)a=u.layerPointToLatLng(t[i]),n=e[i],n._preSpiderfyLatlng=n._latlng,n.setLatLng(a),n.clusterShow&&n.clusterShow(),p&&(r=n._spiderLeg,s=r._path,s.style.strokeDashoffset=0,r.setStyle({opacity:m}));this.setOpacity(.3),l._ignoreMove=!1,setTimeout(function(){l._animationEnd(),l.fire("spiderfied",{cluster:h,markers:e})},200)},_animationUnspiderfy:function(e){var t,i,n,r,s,o,a=this,h=this._group,l=h._map,u=h._featureGroup,_=e?l._latLngToNewLayerPoint(this._latlng,e.zoom,e.center):l.latLngToLayerPoint(this._latlng),d=this.getAllChildMarkers(),c=L.Path.SVG;for(h._ignoreMove=!0,h._animationStart(),this.setOpacity(1),i=d.length-1;i>=0;i--)t=d[i],t._preSpiderfyLatlng&&(t.closePopup(),t.setLatLng(t._preSpiderfyLatlng),delete t._preSpiderfyLatlng,o=!0,t._setPos&&(t._setPos(_),o=!1),t.clusterHide&&(t.clusterHide(),o=!1),o&&u.removeLayer(t),c&&(n=t._spiderLeg,r=n._path,s=r.getTotalLength()+.1,r.style.strokeDashoffset=s,n.setStyle({opacity:0})));h._ignoreMove=!1,setTimeout(function(){var e=0;for(i=d.length-1;i>=0;i--)t=d[i],t._spiderLeg&&e++;for(i=d.length-1;i>=0;i--)t=d[i],t._spiderLeg&&(t.clusterShow&&t.clusterShow(),t.setZIndexOffset&&t.setZIndexOffset(0),e>1&&u.removeLayer(t),l.removeLayer(t._spiderLeg),delete t._spiderLeg);h._animationEnd(),h.fire("unspiderfied",{cluster:a,markers:d})},200)}}),L.MarkerClusterGroup.include({_spiderfied:null,unspiderfy:function(){this._unspiderfy.apply(this,arguments)},_spiderfierOnAdd:function(){this._map.on("click",this._unspiderfyWrapper,this),this._map.options.zoomAnimation&&this._map.on("zoomstart",this._unspiderfyZoomStart,this),this._map.on("zoomend",this._noanimationUnspiderfy,this),L.Browser.touch||this._map.getRenderer(this)},_spiderfierOnRemove:function(){this._map.off("click",this._unspiderfyWrapper,this),this._map.off("zoomstart",this._unspiderfyZoomStart,this),this._map.off("zoomanim",this._unspiderfyZoomAnim,this),this._map.off("zoomend",this._noanimationUnspiderfy,this),this._noanimationUnspiderfy()
+},_unspiderfyZoomStart:function(){this._map&&this._map.on("zoomanim",this._unspiderfyZoomAnim,this)},_unspiderfyZoomAnim:function(e){L.DomUtil.hasClass(this._map._mapPane,"leaflet-touching")||(this._map.off("zoomanim",this._unspiderfyZoomAnim,this),this._unspiderfy(e))},_unspiderfyWrapper:function(){this._unspiderfy()},_unspiderfy:function(e){this._spiderfied&&this._spiderfied.unspiderfy(e)},_noanimationUnspiderfy:function(){this._spiderfied&&this._spiderfied._noanimationUnspiderfy()},_unspiderfyLayer:function(e){e._spiderLeg&&(this._featureGroup.removeLayer(e),e.clusterShow&&e.clusterShow(),e.setZIndexOffset&&e.setZIndexOffset(0),this._map.removeLayer(e._spiderLeg),delete e._spiderLeg)}}),L.MarkerClusterGroup.include({refreshClusters:function(e){return e?e instanceof L.MarkerClusterGroup?e=e._topClusterLevel.getAllChildMarkers():e instanceof L.LayerGroup?e=e._layers:e instanceof L.MarkerCluster?e=e.getAllChildMarkers():e instanceof L.Marker&&(e=[e]):e=this._topClusterLevel.getAllChildMarkers(),this._flagParentsIconsNeedUpdate(e),this._refreshClustersIcons(),this.options.singleMarkerMode&&this._refreshSingleMarkerModeMarkers(e),this},_flagParentsIconsNeedUpdate:function(e){var t,i;for(t in e)for(i=e[t].__parent;i;)i._iconNeedsUpdate=!0,i=i.__parent},_refreshSingleMarkerModeMarkers:function(e){var t,i;for(t in e)i=e[t],this.hasLayer(i)&&i.setIcon(this._overrideMarkerIcon(i))}}),L.Marker.include({refreshIconOptions:function(e,t){var i=this.options.icon;return L.setOptions(i,e),this.setIcon(i),t&&this.__parent&&this.__parent._group.refreshClusters(this),this}}),e.MarkerClusterGroup=t,e.MarkerCluster=i});
+//# sourceMappingURL=leaflet.markercluster.js.map \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/layers-2x.png b/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/layers-2x.png
new file mode 100644
index 00000000..200c333d
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/layers-2x.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/layers.png b/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/layers.png
new file mode 100644
index 00000000..1a72e578
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/layers.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-icon-2x.png b/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-icon-2x.png
new file mode 100644
index 00000000..1c26e9fc
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-icon-2x.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-icon.png b/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-icon.png
new file mode 100644
index 00000000..3e64e06d
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-icon.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-shadow.png b/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-shadow.png
new file mode 100644
index 00000000..9fd29795
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet/images/marker-shadow.png
Binary files differ
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet/leaflet.css b/www/wiki/extensions/Maps/resources/leaflet/leaflet/leaflet.css
new file mode 100644
index 00000000..a0932d57
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet/leaflet.css
@@ -0,0 +1,635 @@
+/* required styles */
+
+.leaflet-pane,
+.leaflet-tile,
+.leaflet-marker-icon,
+.leaflet-marker-shadow,
+.leaflet-tile-container,
+.leaflet-pane > svg,
+.leaflet-pane > canvas,
+.leaflet-zoom-box,
+.leaflet-image-layer,
+.leaflet-layer {
+ position: absolute;
+ left: 0;
+ top: 0;
+ }
+.leaflet-container {
+ overflow: hidden;
+ }
+.leaflet-tile,
+.leaflet-marker-icon,
+.leaflet-marker-shadow {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ -webkit-user-drag: none;
+ }
+/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
+.leaflet-safari .leaflet-tile {
+ image-rendering: -webkit-optimize-contrast;
+ }
+/* hack that prevents hw layers "stretching" when loading new tiles */
+.leaflet-safari .leaflet-tile-container {
+ width: 1600px;
+ height: 1600px;
+ -webkit-transform-origin: 0 0;
+ }
+.leaflet-marker-icon,
+.leaflet-marker-shadow {
+ display: block;
+ }
+/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
+/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
+.leaflet-container .leaflet-overlay-pane svg,
+.leaflet-container .leaflet-marker-pane img,
+.leaflet-container .leaflet-shadow-pane img,
+.leaflet-container .leaflet-tile-pane img,
+.leaflet-container img.leaflet-image-layer,
+.leaflet-container .leaflet-tile {
+ max-width: none !important;
+ max-height: none !important;
+ }
+
+.leaflet-container.leaflet-touch-zoom {
+ -ms-touch-action: pan-x pan-y;
+ touch-action: pan-x pan-y;
+ }
+.leaflet-container.leaflet-touch-drag {
+ -ms-touch-action: pinch-zoom;
+ /* Fallback for FF which doesn't support pinch-zoom */
+ touch-action: none;
+ touch-action: pinch-zoom;
+}
+.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
+ -ms-touch-action: none;
+ touch-action: none;
+}
+.leaflet-container {
+ -webkit-tap-highlight-color: transparent;
+}
+.leaflet-container a {
+ -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
+}
+.leaflet-tile {
+ filter: inherit;
+ visibility: hidden;
+ }
+.leaflet-tile-loaded {
+ visibility: inherit;
+ }
+.leaflet-zoom-box {
+ width: 0;
+ height: 0;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ z-index: 800;
+ }
+/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
+.leaflet-overlay-pane svg {
+ -moz-user-select: none;
+ }
+
+.leaflet-pane { z-index: 400; }
+
+.leaflet-tile-pane { z-index: 200; }
+.leaflet-overlay-pane { z-index: 400; }
+.leaflet-shadow-pane { z-index: 500; }
+.leaflet-marker-pane { z-index: 600; }
+.leaflet-tooltip-pane { z-index: 650; }
+.leaflet-popup-pane { z-index: 700; }
+
+.leaflet-map-pane canvas { z-index: 100; }
+.leaflet-map-pane svg { z-index: 200; }
+
+.leaflet-vml-shape {
+ width: 1px;
+ height: 1px;
+ }
+.lvml {
+ behavior: url(#default#VML);
+ display: inline-block;
+ position: absolute;
+ }
+
+
+/* control positioning */
+
+.leaflet-control {
+ position: relative;
+ z-index: 800;
+ pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
+ pointer-events: auto;
+ }
+.leaflet-top,
+.leaflet-bottom {
+ position: absolute;
+ z-index: 1000;
+ pointer-events: none;
+ }
+.leaflet-top {
+ top: 0;
+ }
+.leaflet-right {
+ right: 0;
+ }
+.leaflet-bottom {
+ bottom: 0;
+ }
+.leaflet-left {
+ left: 0;
+ }
+.leaflet-control {
+ float: left;
+ clear: both;
+ }
+.leaflet-right .leaflet-control {
+ float: right;
+ }
+.leaflet-top .leaflet-control {
+ margin-top: 10px;
+ }
+.leaflet-bottom .leaflet-control {
+ margin-bottom: 10px;
+ }
+.leaflet-left .leaflet-control {
+ margin-left: 10px;
+ }
+.leaflet-right .leaflet-control {
+ margin-right: 10px;
+ }
+
+
+/* zoom and fade animations */
+
+.leaflet-fade-anim .leaflet-tile {
+ will-change: opacity;
+ }
+.leaflet-fade-anim .leaflet-popup {
+ opacity: 0;
+ -webkit-transition: opacity 0.2s linear;
+ -moz-transition: opacity 0.2s linear;
+ transition: opacity 0.2s linear;
+ }
+.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
+ opacity: 1;
+ }
+.leaflet-zoom-animated {
+ -webkit-transform-origin: 0 0;
+ -ms-transform-origin: 0 0;
+ transform-origin: 0 0;
+ }
+.leaflet-zoom-anim .leaflet-zoom-animated {
+ will-change: transform;
+ }
+.leaflet-zoom-anim .leaflet-zoom-animated {
+ -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
+ -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
+ transition: transform 0.25s cubic-bezier(0,0,0.25,1);
+ }
+.leaflet-zoom-anim .leaflet-tile,
+.leaflet-pan-anim .leaflet-tile {
+ -webkit-transition: none;
+ -moz-transition: none;
+ transition: none;
+ }
+
+.leaflet-zoom-anim .leaflet-zoom-hide {
+ visibility: hidden;
+ }
+
+
+/* cursors */
+
+.leaflet-interactive {
+ cursor: pointer;
+ }
+.leaflet-grab {
+ cursor: -webkit-grab;
+ cursor: -moz-grab;
+ cursor: grab;
+ }
+.leaflet-crosshair,
+.leaflet-crosshair .leaflet-interactive {
+ cursor: crosshair;
+ }
+.leaflet-popup-pane,
+.leaflet-control {
+ cursor: auto;
+ }
+.leaflet-dragging .leaflet-grab,
+.leaflet-dragging .leaflet-grab .leaflet-interactive,
+.leaflet-dragging .leaflet-marker-draggable {
+ cursor: move;
+ cursor: -webkit-grabbing;
+ cursor: -moz-grabbing;
+ cursor: grabbing;
+ }
+
+/* marker & overlays interactivity */
+.leaflet-marker-icon,
+.leaflet-marker-shadow,
+.leaflet-image-layer,
+.leaflet-pane > svg path,
+.leaflet-tile-container {
+ pointer-events: none;
+ }
+
+.leaflet-marker-icon.leaflet-interactive,
+.leaflet-image-layer.leaflet-interactive,
+.leaflet-pane > svg path.leaflet-interactive {
+ pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
+ pointer-events: auto;
+ }
+
+/* visual tweaks */
+
+.leaflet-container {
+ background: #ddd;
+ outline: 0;
+ }
+.leaflet-container a {
+ color: #0078A8;
+ }
+.leaflet-container a.leaflet-active {
+ outline: 2px solid orange;
+ }
+.leaflet-zoom-box {
+ border: 2px dotted #38f;
+ background: rgba(255,255,255,0.5);
+ }
+
+
+/* general typography */
+.leaflet-container {
+ font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
+ }
+
+
+/* general toolbar styles */
+
+.leaflet-bar {
+ box-shadow: 0 1px 5px rgba(0,0,0,0.65);
+ border-radius: 4px;
+ }
+.leaflet-bar a,
+.leaflet-bar a:hover {
+ background-color: #fff;
+ border-bottom: 1px solid #ccc;
+ width: 26px;
+ height: 26px;
+ line-height: 26px;
+ display: block;
+ text-align: center;
+ text-decoration: none;
+ color: black;
+ }
+.leaflet-bar a,
+.leaflet-control-layers-toggle {
+ background-position: 50% 50%;
+ background-repeat: no-repeat;
+ display: block;
+ }
+.leaflet-bar a:hover {
+ background-color: #f4f4f4;
+ }
+.leaflet-bar a:first-child {
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+ }
+.leaflet-bar a:last-child {
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ border-bottom: none;
+ }
+.leaflet-bar a.leaflet-disabled {
+ cursor: default;
+ background-color: #f4f4f4;
+ color: #bbb;
+ }
+
+.leaflet-touch .leaflet-bar a {
+ width: 30px;
+ height: 30px;
+ line-height: 30px;
+ }
+.leaflet-touch .leaflet-bar a:first-child {
+ border-top-left-radius: 2px;
+ border-top-right-radius: 2px;
+ }
+.leaflet-touch .leaflet-bar a:last-child {
+ border-bottom-left-radius: 2px;
+ border-bottom-right-radius: 2px;
+ }
+
+/* zoom control */
+
+.leaflet-control-zoom-in,
+.leaflet-control-zoom-out {
+ font: bold 18px 'Lucida Console', Monaco, monospace;
+ text-indent: 1px;
+ }
+
+.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
+ font-size: 22px;
+ }
+
+
+/* layers control */
+
+.leaflet-control-layers {
+ box-shadow: 0 1px 5px rgba(0,0,0,0.4);
+ background: #fff;
+ border-radius: 5px;
+ }
+.leaflet-control-layers-toggle {
+ background-image: url(images/layers.png);
+ width: 36px;
+ height: 36px;
+ }
+.leaflet-retina .leaflet-control-layers-toggle {
+ background-image: url(images/layers-2x.png);
+ background-size: 26px 26px;
+ }
+.leaflet-touch .leaflet-control-layers-toggle {
+ width: 44px;
+ height: 44px;
+ }
+.leaflet-control-layers .leaflet-control-layers-list,
+.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
+ display: none;
+ }
+.leaflet-control-layers-expanded .leaflet-control-layers-list {
+ display: block;
+ position: relative;
+ }
+.leaflet-control-layers-expanded {
+ padding: 6px 10px 6px 6px;
+ color: #333;
+ background: #fff;
+ }
+.leaflet-control-layers-scrollbar {
+ overflow-y: scroll;
+ overflow-x: hidden;
+ padding-right: 5px;
+ }
+.leaflet-control-layers-selector {
+ margin-top: 2px;
+ position: relative;
+ top: 1px;
+ }
+.leaflet-control-layers label {
+ display: block;
+ }
+.leaflet-control-layers-separator {
+ height: 0;
+ border-top: 1px solid #ddd;
+ margin: 5px -10px 5px -6px;
+ }
+
+/* Default icon URLs */
+.leaflet-default-icon-path {
+ background-image: url(images/marker-icon.png);
+ }
+
+
+/* attribution and scale controls */
+
+.leaflet-container .leaflet-control-attribution {
+ background: #fff;
+ background: rgba(255, 255, 255, 0.7);
+ margin: 0;
+ }
+.leaflet-control-attribution,
+.leaflet-control-scale-line {
+ padding: 0 5px;
+ color: #333;
+ }
+.leaflet-control-attribution a {
+ text-decoration: none;
+ }
+.leaflet-control-attribution a:hover {
+ text-decoration: underline;
+ }
+.leaflet-container .leaflet-control-attribution,
+.leaflet-container .leaflet-control-scale {
+ font-size: 11px;
+ }
+.leaflet-left .leaflet-control-scale {
+ margin-left: 5px;
+ }
+.leaflet-bottom .leaflet-control-scale {
+ margin-bottom: 5px;
+ }
+.leaflet-control-scale-line {
+ border: 2px solid #777;
+ border-top: none;
+ line-height: 1.1;
+ padding: 2px 5px 1px;
+ font-size: 11px;
+ white-space: nowrap;
+ overflow: hidden;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+
+ background: #fff;
+ background: rgba(255, 255, 255, 0.5);
+ }
+.leaflet-control-scale-line:not(:first-child) {
+ border-top: 2px solid #777;
+ border-bottom: none;
+ margin-top: -2px;
+ }
+.leaflet-control-scale-line:not(:first-child):not(:last-child) {
+ border-bottom: 2px solid #777;
+ }
+
+.leaflet-touch .leaflet-control-attribution,
+.leaflet-touch .leaflet-control-layers,
+.leaflet-touch .leaflet-bar {
+ box-shadow: none;
+ }
+.leaflet-touch .leaflet-control-layers,
+.leaflet-touch .leaflet-bar {
+ border: 2px solid rgba(0,0,0,0.2);
+ background-clip: padding-box;
+ }
+
+
+/* popup */
+
+.leaflet-popup {
+ position: absolute;
+ text-align: center;
+ margin-bottom: 20px;
+ }
+.leaflet-popup-content-wrapper {
+ padding: 1px;
+ text-align: left;
+ border-radius: 12px;
+ }
+.leaflet-popup-content {
+ margin: 13px 19px;
+ line-height: 1.4;
+ }
+.leaflet-popup-content p {
+ margin: 18px 0;
+ }
+.leaflet-popup-tip-container {
+ width: 40px;
+ height: 20px;
+ position: absolute;
+ left: 50%;
+ margin-left: -20px;
+ overflow: hidden;
+ pointer-events: none;
+ }
+.leaflet-popup-tip {
+ width: 17px;
+ height: 17px;
+ padding: 1px;
+
+ margin: -10px auto 0;
+
+ -webkit-transform: rotate(45deg);
+ -moz-transform: rotate(45deg);
+ -ms-transform: rotate(45deg);
+ transform: rotate(45deg);
+ }
+.leaflet-popup-content-wrapper,
+.leaflet-popup-tip {
+ background: white;
+ color: #333;
+ box-shadow: 0 3px 14px rgba(0,0,0,0.4);
+ }
+.leaflet-container a.leaflet-popup-close-button {
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding: 4px 4px 0 0;
+ border: none;
+ text-align: center;
+ width: 18px;
+ height: 14px;
+ font: 16px/14px Tahoma, Verdana, sans-serif;
+ color: #c3c3c3;
+ text-decoration: none;
+ font-weight: bold;
+ background: transparent;
+ }
+.leaflet-container a.leaflet-popup-close-button:hover {
+ color: #999;
+ }
+.leaflet-popup-scrolled {
+ overflow: auto;
+ border-bottom: 1px solid #ddd;
+ border-top: 1px solid #ddd;
+ }
+
+.leaflet-oldie .leaflet-popup-content-wrapper {
+ zoom: 1;
+ }
+.leaflet-oldie .leaflet-popup-tip {
+ width: 24px;
+ margin: 0 auto;
+
+ -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
+ filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
+ }
+.leaflet-oldie .leaflet-popup-tip-container {
+ margin-top: -1px;
+ }
+
+.leaflet-oldie .leaflet-control-zoom,
+.leaflet-oldie .leaflet-control-layers,
+.leaflet-oldie .leaflet-popup-content-wrapper,
+.leaflet-oldie .leaflet-popup-tip {
+ border: 1px solid #999;
+ }
+
+
+/* div icon */
+
+.leaflet-div-icon {
+ background: #fff;
+ border: 1px solid #666;
+ }
+
+
+/* Tooltip */
+/* Base styles for the element that has a tooltip */
+.leaflet-tooltip {
+ position: absolute;
+ padding: 6px;
+ background-color: #fff;
+ border: 1px solid #fff;
+ border-radius: 3px;
+ color: #222;
+ white-space: nowrap;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ pointer-events: none;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.4);
+ }
+.leaflet-tooltip.leaflet-clickable {
+ cursor: pointer;
+ pointer-events: auto;
+ }
+.leaflet-tooltip-top:before,
+.leaflet-tooltip-bottom:before,
+.leaflet-tooltip-left:before,
+.leaflet-tooltip-right:before {
+ position: absolute;
+ pointer-events: none;
+ border: 6px solid transparent;
+ background: transparent;
+ content: "";
+ }
+
+/* Directions */
+
+.leaflet-tooltip-bottom {
+ margin-top: 6px;
+}
+.leaflet-tooltip-top {
+ margin-top: -6px;
+}
+.leaflet-tooltip-bottom:before,
+.leaflet-tooltip-top:before {
+ left: 50%;
+ margin-left: -6px;
+ }
+.leaflet-tooltip-top:before {
+ bottom: 0;
+ margin-bottom: -12px;
+ border-top-color: #fff;
+ }
+.leaflet-tooltip-bottom:before {
+ top: 0;
+ margin-top: -12px;
+ margin-left: -6px;
+ border-bottom-color: #fff;
+ }
+.leaflet-tooltip-left {
+ margin-left: -6px;
+}
+.leaflet-tooltip-right {
+ margin-left: 6px;
+}
+.leaflet-tooltip-left:before,
+.leaflet-tooltip-right:before {
+ top: 50%;
+ margin-top: -6px;
+ }
+.leaflet-tooltip-left:before {
+ right: 0;
+ margin-right: -12px;
+ border-left-color: #fff;
+ }
+.leaflet-tooltip-right:before {
+ left: 0;
+ margin-left: -12px;
+ border-right-color: #fff;
+ }
diff --git a/www/wiki/extensions/Maps/resources/leaflet/leaflet/leaflet.js b/www/wiki/extensions/Maps/resources/leaflet/leaflet/leaflet.js
new file mode 100644
index 00000000..3b628aba
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/leaflet/leaflet/leaflet.js
@@ -0,0 +1,5 @@
+/* @preserve
+ * Leaflet 1.3.4+Detached: 0e566b2ad5e696ba9f79a9d48a7e51c8f4892441.0e566b2, a JS library for interactive maps. http://leafletjs.com
+ * (c) 2010-2018 Vladimir Agafonkin, (c) 2010-2011 CloudMade
+ */
+!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i(t.L={})}(this,function(t){"use strict";function i(t){var i,e,n,o;for(e=1,n=arguments.length;e<n;e++){o=arguments[e];for(i in o)t[i]=o[i]}return t}function e(t,i){var e=Array.prototype.slice;if(t.bind)return t.bind.apply(t,e.call(arguments,1));var n=e.call(arguments,2);return function(){return t.apply(i,n.length?n.concat(e.call(arguments)):arguments)}}function n(t){return t._leaflet_id=t._leaflet_id||++ei,t._leaflet_id}function o(t,i,e){var n,o,s,r;return r=function(){n=!1,o&&(s.apply(e,o),o=!1)},s=function(){n?o=arguments:(t.apply(e,arguments),setTimeout(r,i),n=!0)}}function s(t,i,e){var n=i[1],o=i[0],s=n-o;return t===n&&e?t:((t-o)%s+s)%s+o}function r(){return!1}function a(t,i){var e=Math.pow(10,void 0===i?6:i);return Math.round(t*e)/e}function h(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}function u(t){return h(t).split(/\s+/)}function l(t,i){t.hasOwnProperty("options")||(t.options=t.options?ii(t.options):{});for(var e in i)t.options[e]=i[e];return t.options}function c(t,i,e){var n=[];for(var o in t)n.push(encodeURIComponent(e?o.toUpperCase():o)+"="+encodeURIComponent(t[o]));return(i&&-1!==i.indexOf("?")?"&":"?")+n.join("&")}function _(t,i){return t.replace(ni,function(t,e){var n=i[e];if(void 0===n)throw new Error("No value provided for variable "+t);return"function"==typeof n&&(n=n(i)),n})}function d(t,i){for(var e=0;e<t.length;e++)if(t[e]===i)return e;return-1}function p(t){return window["webkit"+t]||window["moz"+t]||window["ms"+t]}function m(t){var i=+new Date,e=Math.max(0,16-(i-ri));return ri=i+e,window.setTimeout(t,e)}function f(t,i,n){if(!n||ai!==m)return ai.call(window,e(t,i));t.call(i)}function g(t){t&&hi.call(window,t)}function v(){}function y(t){if("undefined"!=typeof L&&L&&L.Mixin){t=oi(t)?t:[t];for(var i=0;i<t.length;i++)t[i]===L.Mixin.Events&&console.warn("Deprecated include of L.Mixin.Events: this property will be removed in future releases, please inherit from L.Evented instead.",(new Error).stack)}}function x(t,i,e){this.x=e?Math.round(t):t,this.y=e?Math.round(i):i}function w(t,i,e){return t instanceof x?t:oi(t)?new x(t[0],t[1]):void 0===t||null===t?t:"object"==typeof t&&"x"in t&&"y"in t?new x(t.x,t.y):new x(t,i,e)}function P(t,i){if(t)for(var e=i?[t,i]:t,n=0,o=e.length;n<o;n++)this.extend(e[n])}function b(t,i){return!t||t instanceof P?t:new P(t,i)}function T(t,i){if(t)for(var e=i?[t,i]:t,n=0,o=e.length;n<o;n++)this.extend(e[n])}function z(t,i){return t instanceof T?t:new T(t,i)}function M(t,i,e){if(isNaN(t)||isNaN(i))throw new Error("Invalid LatLng object: ("+t+", "+i+")");this.lat=+t,this.lng=+i,void 0!==e&&(this.alt=+e)}function C(t,i,e){return t instanceof M?t:oi(t)&&"object"!=typeof t[0]?3===t.length?new M(t[0],t[1],t[2]):2===t.length?new M(t[0],t[1]):null:void 0===t||null===t?t:"object"==typeof t&&"lat"in t?new M(t.lat,"lng"in t?t.lng:t.lon,t.alt):void 0===i?null:new M(t,i,e)}function S(t,i,e,n){if(oi(t))return this._a=t[0],this._b=t[1],this._c=t[2],void(this._d=t[3]);this._a=t,this._b=i,this._c=e,this._d=n}function Z(t,i,e,n){return new S(t,i,e,n)}function E(t){return document.createElementNS("http://www.w3.org/2000/svg",t)}function k(t,i){var e,n,o,s,r,a,h="";for(e=0,o=t.length;e<o;e++){for(n=0,s=(r=t[e]).length;n<s;n++)a=r[n],h+=(n?"L":"M")+a.x+" "+a.y;h+=i?Ji?"z":"x":""}return h||"M0 0"}function A(t){return navigator.userAgent.toLowerCase().indexOf(t)>=0}function B(t,i,e,n){return"touchstart"===i?O(t,e,n):"touchmove"===i?W(t,e,n):"touchend"===i&&H(t,e,n),this}function I(t,i,e){var n=t["_leaflet_"+i+e];return"touchstart"===i?t.removeEventListener(te,n,!1):"touchmove"===i?t.removeEventListener(ie,n,!1):"touchend"===i&&(t.removeEventListener(ee,n,!1),t.removeEventListener(ne,n,!1)),this}function O(t,i,n){var o=e(function(t){if("mouse"!==t.pointerType&&t.MSPOINTER_TYPE_MOUSE&&t.pointerType!==t.MSPOINTER_TYPE_MOUSE){if(!(oe.indexOf(t.target.tagName)<0))return;Pt(t)}j(t,i)});t["_leaflet_touchstart"+n]=o,t.addEventListener(te,o,!1),re||(document.documentElement.addEventListener(te,R,!0),document.documentElement.addEventListener(ie,N,!0),document.documentElement.addEventListener(ee,D,!0),document.documentElement.addEventListener(ne,D,!0),re=!0)}function R(t){se[t.pointerId]=t,ae++}function N(t){se[t.pointerId]&&(se[t.pointerId]=t)}function D(t){delete se[t.pointerId],ae--}function j(t,i){t.touches=[];for(var e in se)t.touches.push(se[e]);t.changedTouches=[t],i(t)}function W(t,i,e){var n=function(t){(t.pointerType!==t.MSPOINTER_TYPE_MOUSE&&"mouse"!==t.pointerType||0!==t.buttons)&&j(t,i)};t["_leaflet_touchmove"+e]=n,t.addEventListener(ie,n,!1)}function H(t,i,e){var n=function(t){j(t,i)};t["_leaflet_touchend"+e]=n,t.addEventListener(ee,n,!1),t.addEventListener(ne,n,!1)}function F(t,i,e){function n(t){var i;if(Vi){if(!bi||"mouse"===t.pointerType)return;i=ae}else i=t.touches.length;if(!(i>1)){var e=Date.now(),n=e-(s||e);r=t.touches?t.touches[0]:t,a=n>0&&n<=h,s=e}}function o(t){if(a&&!r.cancelBubble){if(Vi){if(!bi||"mouse"===t.pointerType)return;var e,n,o={};for(n in r)e=r[n],o[n]=e&&e.bind?e.bind(r):e;r=o}r.type="dblclick",i(r),s=null}}var s,r,a=!1,h=250;return t[le+he+e]=n,t[le+ue+e]=o,t[le+"dblclick"+e]=i,t.addEventListener(he,n,!1),t.addEventListener(ue,o,!1),t.addEventListener("dblclick",i,!1),this}function U(t,i){var e=t[le+he+i],n=t[le+ue+i],o=t[le+"dblclick"+i];return t.removeEventListener(he,e,!1),t.removeEventListener(ue,n,!1),bi||t.removeEventListener("dblclick",o,!1),this}function V(t){return"string"==typeof t?document.getElementById(t):t}function q(t,i){var e=t.style[i]||t.currentStyle&&t.currentStyle[i];if((!e||"auto"===e)&&document.defaultView){var n=document.defaultView.getComputedStyle(t,null);e=n?n[i]:null}return"auto"===e?null:e}function G(t,i,e){var n=document.createElement(t);return n.className=i||"",e&&e.appendChild(n),n}function K(t){var i=t.parentNode;i&&i.removeChild(t)}function Y(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function X(t){var i=t.parentNode;i.lastChild!==t&&i.appendChild(t)}function J(t){var i=t.parentNode;i.firstChild!==t&&i.insertBefore(t,i.firstChild)}function $(t,i){if(void 0!==t.classList)return t.classList.contains(i);var e=et(t);return e.length>0&&new RegExp("(^|\\s)"+i+"(\\s|$)").test(e)}function Q(t,i){if(void 0!==t.classList)for(var e=u(i),n=0,o=e.length;n<o;n++)t.classList.add(e[n]);else if(!$(t,i)){var s=et(t);it(t,(s?s+" ":"")+i)}}function tt(t,i){void 0!==t.classList?t.classList.remove(i):it(t,h((" "+et(t)+" ").replace(" "+i+" "," ")))}function it(t,i){void 0===t.className.baseVal?t.className=i:t.className.baseVal=i}function et(t){return void 0===t.className.baseVal?t.className:t.className.baseVal}function nt(t,i){"opacity"in t.style?t.style.opacity=i:"filter"in t.style&&ot(t,i)}function ot(t,i){var e=!1,n="DXImageTransform.Microsoft.Alpha";try{e=t.filters.item(n)}catch(t){if(1===i)return}i=Math.round(100*i),e?(e.Enabled=100!==i,e.Opacity=i):t.style.filter+=" progid:"+n+"(opacity="+i+")"}function st(t){for(var i=document.documentElement.style,e=0;e<t.length;e++)if(t[e]in i)return t[e];return!1}function rt(t,i,e){var n=i||new x(0,0);t.style[ce]=(Ri?"translate("+n.x+"px,"+n.y+"px)":"translate3d("+n.x+"px,"+n.y+"px,0)")+(e?" scale("+e+")":"")}function at(t,i){t._leaflet_pos=i,ji?rt(t,i):(t.style.left=i.x+"px",t.style.top=i.y+"px")}function ht(t){return t._leaflet_pos||new x(0,0)}function ut(){mt(window,"dragstart",Pt)}function lt(){ft(window,"dragstart",Pt)}function ct(t){for(;-1===t.tabIndex;)t=t.parentNode;t.style&&(_t(),me=t,fe=t.style.outline,t.style.outline="none",mt(window,"keydown",_t))}function _t(){me&&(me.style.outline=fe,me=void 0,fe=void 0,ft(window,"keydown",_t))}function dt(t){do{t=t.parentNode}while(!(t.offsetWidth&&t.offsetHeight||t===document.body));return t}function pt(t){var i=t.getBoundingClientRect();return{x:i.width/t.offsetWidth||1,y:i.height/t.offsetHeight||1,boundingClientRect:i}}function mt(t,i,e,n){if("object"==typeof i)for(var o in i)gt(t,o,i[o],e);else for(var s=0,r=(i=u(i)).length;s<r;s++)gt(t,i[s],e,n);return this}function ft(t,i,e,n){if("object"==typeof i)for(var o in i)vt(t,o,i[o],e);else if(i)for(var s=0,r=(i=u(i)).length;s<r;s++)vt(t,i[s],e,n);else{for(var a in t[ye])vt(t,a,t[ye][a]);delete t[ye]}return this}function gt(t,i,e,o){var s=i+n(e)+(o?"_"+n(o):"");if(t[ye]&&t[ye][s])return this;var r=function(i){return e.call(o||t,i||window.event)},a=r;Vi&&0===i.indexOf("touch")?B(t,i,r,s):!qi||"dblclick"!==i||!F||Vi&&Ei?"addEventListener"in t?"mousewheel"===i?t.addEventListener("onwheel"in t?"wheel":"mousewheel",r,!1):"mouseenter"===i||"mouseleave"===i?(r=function(i){i=i||window.event,Ct(t,i)&&a(i)},t.addEventListener("mouseenter"===i?"mouseover":"mouseout",r,!1)):("click"===i&&zi&&(r=function(t){St(t,a)}),t.addEventListener(i,r,!1)):"attachEvent"in t&&t.attachEvent("on"+i,r):F(t,r,s),t[ye]=t[ye]||{},t[ye][s]=r}function vt(t,i,e,o){var s=i+n(e)+(o?"_"+n(o):""),r=t[ye]&&t[ye][s];if(!r)return this;Vi&&0===i.indexOf("touch")?I(t,i,s):!qi||"dblclick"!==i||!U||Vi&&Ei?"removeEventListener"in t?"mousewheel"===i?t.removeEventListener("onwheel"in t?"wheel":"mousewheel",r,!1):t.removeEventListener("mouseenter"===i?"mouseover":"mouseleave"===i?"mouseout":i,r,!1):"detachEvent"in t&&t.detachEvent("on"+i,r):U(t,s),t[ye][s]=null}function yt(t){return t.stopPropagation?t.stopPropagation():t.originalEvent?t.originalEvent._stopped=!0:t.cancelBubble=!0,Mt(t),this}function xt(t){return gt(t,"mousewheel",yt),this}function wt(t){return mt(t,"mousedown touchstart dblclick",yt),gt(t,"click",zt),this}function Pt(t){return t.preventDefault?t.preventDefault():t.returnValue=!1,this}function Lt(t){return Pt(t),yt(t),this}function bt(t,i){if(!i)return new x(t.clientX,t.clientY);var e=pt(i),n=e.boundingClientRect;return new x((t.clientX-n.left)/e.x-i.clientLeft,(t.clientY-n.top)/e.y-i.clientTop)}function Tt(t){return bi?t.wheelDeltaY/2:t.deltaY&&0===t.deltaMode?-t.deltaY/xe:t.deltaY&&1===t.deltaMode?20*-t.deltaY:t.deltaY&&2===t.deltaMode?60*-t.deltaY:t.deltaX||t.deltaZ?0:t.wheelDelta?(t.wheelDeltaY||t.wheelDelta)/2:t.detail&&Math.abs(t.detail)<32765?20*-t.detail:t.detail?t.detail/-32765*60:0}function zt(t){we[t.type]=!0}function Mt(t){var i=we[t.type];return we[t.type]=!1,i}function Ct(t,i){var e=i.relatedTarget;if(!e)return!0;try{for(;e&&e!==t;)e=e.parentNode}catch(t){return!1}return e!==t}function St(t,i){var e=t.timeStamp||t.originalEvent&&t.originalEvent.timeStamp,n=ge&&e-ge;n&&n>100&&n<500||t.target._simulatedClick&&!t._simulated?Lt(t):(ge=e,i(t))}function Zt(t,i){if(!i||!t.length)return t.slice();var e=i*i;return t=Bt(t,e),t=kt(t,e)}function Et(t,i,e){return Math.sqrt(Dt(t,i,e,!0))}function kt(t,i){var e=t.length,n=new(typeof Uint8Array!=void 0+""?Uint8Array:Array)(e);n[0]=n[e-1]=1,At(t,n,i,0,e-1);var o,s=[];for(o=0;o<e;o++)n[o]&&s.push(t[o]);return s}function At(t,i,e,n,o){var s,r,a,h=0;for(r=n+1;r<=o-1;r++)(a=Dt(t[r],t[n],t[o],!0))>h&&(s=r,h=a);h>e&&(i[s]=1,At(t,i,e,n,s),At(t,i,e,s,o))}function Bt(t,i){for(var e=[t[0]],n=1,o=0,s=t.length;n<s;n++)Nt(t[n],t[o])>i&&(e.push(t[n]),o=n);return o<s-1&&e.push(t[s-1]),e}function It(t,i,e,n,o){var s,r,a,h=n?ke:Rt(t,e),u=Rt(i,e);for(ke=u;;){if(!(h|u))return[t,i];if(h&u)return!1;a=Rt(r=Ot(t,i,s=h||u,e,o),e),s===h?(t=r,h=a):(i=r,u=a)}}function Ot(t,i,e,n,o){var s,r,a=i.x-t.x,h=i.y-t.y,u=n.min,l=n.max;return 8&e?(s=t.x+a*(l.y-t.y)/h,r=l.y):4&e?(s=t.x+a*(u.y-t.y)/h,r=u.y):2&e?(s=l.x,r=t.y+h*(l.x-t.x)/a):1&e&&(s=u.x,r=t.y+h*(u.x-t.x)/a),new x(s,r,o)}function Rt(t,i){var e=0;return t.x<i.min.x?e|=1:t.x>i.max.x&&(e|=2),t.y<i.min.y?e|=4:t.y>i.max.y&&(e|=8),e}function Nt(t,i){var e=i.x-t.x,n=i.y-t.y;return e*e+n*n}function Dt(t,i,e,n){var o,s=i.x,r=i.y,a=e.x-s,h=e.y-r,u=a*a+h*h;return u>0&&((o=((t.x-s)*a+(t.y-r)*h)/u)>1?(s=e.x,r=e.y):o>0&&(s+=a*o,r+=h*o)),a=t.x-s,h=t.y-r,n?a*a+h*h:new x(s,r)}function jt(t){return!oi(t[0])||"object"!=typeof t[0][0]&&void 0!==t[0][0]}function Wt(t){return console.warn("Deprecated use of _flat, please use L.LineUtil.isFlat instead."),jt(t)}function Ht(t,i,e){var n,o,s,r,a,h,u,l,c,_=[1,4,2,8];for(o=0,u=t.length;o<u;o++)t[o]._code=Rt(t[o],i);for(r=0;r<4;r++){for(l=_[r],n=[],o=0,s=(u=t.length)-1;o<u;s=o++)a=t[o],h=t[s],a._code&l?h._code&l||((c=Ot(h,a,l,i,e))._code=Rt(c,i),n.push(c)):(h._code&l&&((c=Ot(h,a,l,i,e))._code=Rt(c,i),n.push(c)),n.push(a));t=n}return t}function Ft(t,i){var e,n,o,s,r="Feature"===t.type?t.geometry:t,a=r?r.coordinates:null,h=[],u=i&&i.pointToLayer,l=i&&i.coordsToLatLng||Ut;if(!a&&!r)return null;switch(r.type){case"Point":return e=l(a),u?u(t,e):new $e(e);case"MultiPoint":for(o=0,s=a.length;o<s;o++)e=l(a[o]),h.push(u?u(t,e):new $e(e));return new Ke(h);case"LineString":case"MultiLineString":return n=Vt(a,"LineString"===r.type?0:1,l),new nn(n,i);case"Polygon":case"MultiPolygon":return n=Vt(a,"Polygon"===r.type?1:2,l),new on(n,i);case"GeometryCollection":for(o=0,s=r.geometries.length;o<s;o++){var c=Ft({geometry:r.geometries[o],type:"Feature",properties:t.properties},i);c&&h.push(c)}return new Ke(h);default:throw new Error("Invalid GeoJSON object.")}}function Ut(t){return new M(t[1],t[0],t[2])}function Vt(t,i,e){for(var n,o=[],s=0,r=t.length;s<r;s++)n=i?Vt(t[s],i-1,e):(e||Ut)(t[s]),o.push(n);return o}function qt(t,i){return i="number"==typeof i?i:6,void 0!==t.alt?[a(t.lng,i),a(t.lat,i),a(t.alt,i)]:[a(t.lng,i),a(t.lat,i)]}function Gt(t,i,e,n){for(var o=[],s=0,r=t.length;s<r;s++)o.push(i?Gt(t[s],i-1,e,n):qt(t[s],n));return!i&&e&&o.push(o[0]),o}function Kt(t,e){return t.feature?i({},t.feature,{geometry:e}):Yt(e)}function Yt(t){return"Feature"===t.type||"FeatureCollection"===t.type?t:{type:"Feature",properties:{},geometry:t}}function Xt(t,i){return new sn(t,i)}function Jt(t,i){return new mn(t,i)}function $t(t){return Xi?new vn(t):null}function Qt(t){return Ji||$i?new Pn(t):null}var ti=Object.freeze;Object.freeze=function(t){return t};var ii=Object.create||function(){function t(){}return function(i){return t.prototype=i,new t}}(),ei=0,ni=/\{ *([\w_-]+) *\}/g,oi=Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)},si="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=",ri=0,ai=window.requestAnimationFrame||p("RequestAnimationFrame")||m,hi=window.cancelAnimationFrame||p("CancelAnimationFrame")||p("CancelRequestAnimationFrame")||function(t){window.clearTimeout(t)},ui=(Object.freeze||Object)({freeze:ti,extend:i,create:ii,bind:e,lastId:ei,stamp:n,throttle:o,wrapNum:s,falseFn:r,formatNum:a,trim:h,splitWords:u,setOptions:l,getParamString:c,template:_,isArray:oi,indexOf:d,emptyImageUrl:si,requestFn:ai,cancelFn:hi,requestAnimFrame:f,cancelAnimFrame:g});v.extend=function(t){var e=function(){this.initialize&&this.initialize.apply(this,arguments),this.callInitHooks()},n=e.__super__=this.prototype,o=ii(n);o.constructor=e,e.prototype=o;for(var s in this)this.hasOwnProperty(s)&&"prototype"!==s&&"__super__"!==s&&(e[s]=this[s]);return t.statics&&(i(e,t.statics),delete t.statics),t.includes&&(y(t.includes),i.apply(null,[o].concat(t.includes)),delete t.includes),o.options&&(t.options=i(ii(o.options),t.options)),i(o,t),o._initHooks=[],o.callInitHooks=function(){if(!this._initHooksCalled){n.callInitHooks&&n.callInitHooks.call(this),this._initHooksCalled=!0;for(var t=0,i=o._initHooks.length;t<i;t++)o._initHooks[t].call(this)}},e},v.include=function(t){return i(this.prototype,t),this},v.mergeOptions=function(t){return i(this.prototype.options,t),this},v.addInitHook=function(t){var i=Array.prototype.slice.call(arguments,1),e="function"==typeof t?t:function(){this[t].apply(this,i)};return this.prototype._initHooks=this.prototype._initHooks||[],this.prototype._initHooks.push(e),this};var li={on:function(t,i,e){if("object"==typeof t)for(var n in t)this._on(n,t[n],i);else for(var o=0,s=(t=u(t)).length;o<s;o++)this._on(t[o],i,e);return this},off:function(t,i,e){if(t)if("object"==typeof t)for(var n in t)this._off(n,t[n],i);else for(var o=0,s=(t=u(t)).length;o<s;o++)this._off(t[o],i,e);else delete this._events;return this},_on:function(t,i,e){this._events=this._events||{};var n=this._events[t];n||(n=[],this._events[t]=n),e===this&&(e=void 0);for(var o={fn:i,ctx:e},s=n,r=0,a=s.length;r<a;r++)if(s[r].fn===i&&s[r].ctx===e)return;s.push(o)},_off:function(t,i,e){var n,o,s;if(this._events&&(n=this._events[t]))if(i){if(e===this&&(e=void 0),n)for(o=0,s=n.length;o<s;o++){var a=n[o];if(a.ctx===e&&a.fn===i)return a.fn=r,this._firingCount&&(this._events[t]=n=n.slice()),void n.splice(o,1)}}else{for(o=0,s=n.length;o<s;o++)n[o].fn=r;delete this._events[t]}},fire:function(t,e,n){if(!this.listens(t,n))return this;var o=i({},e,{type:t,target:this,sourceTarget:e&&e.sourceTarget||this});if(this._events){var s=this._events[t];if(s){this._firingCount=this._firingCount+1||1;for(var r=0,a=s.length;r<a;r++){var h=s[r];h.fn.call(h.ctx||this,o)}this._firingCount--}}return n&&this._propagateEvent(o),this},listens:function(t,i){var e=this._events&&this._events[t];if(e&&e.length)return!0;if(i)for(var n in this._eventParents)if(this._eventParents[n].listens(t,i))return!0;return!1},once:function(t,i,n){if("object"==typeof t){for(var o in t)this.once(o,t[o],i);return this}var s=e(function(){this.off(t,i,n).off(t,s,n)},this);return this.on(t,i,n).on(t,s,n)},addEventParent:function(t){return this._eventParents=this._eventParents||{},this._eventParents[n(t)]=t,this},removeEventParent:function(t){return this._eventParents&&delete this._eventParents[n(t)],this},_propagateEvent:function(t){for(var e in this._eventParents)this._eventParents[e].fire(t.type,i({layer:t.target,propagatedFrom:t.target},t),!0)}};li.addEventListener=li.on,li.removeEventListener=li.clearAllEventListeners=li.off,li.addOneTimeEventListener=li.once,li.fireEvent=li.fire,li.hasEventListeners=li.listens;var ci=v.extend(li),_i=Math.trunc||function(t){return t>0?Math.floor(t):Math.ceil(t)};x.prototype={clone:function(){return new x(this.x,this.y)},add:function(t){return this.clone()._add(w(t))},_add:function(t){return this.x+=t.x,this.y+=t.y,this},subtract:function(t){return this.clone()._subtract(w(t))},_subtract:function(t){return this.x-=t.x,this.y-=t.y,this},divideBy:function(t){return this.clone()._divideBy(t)},_divideBy:function(t){return this.x/=t,this.y/=t,this},multiplyBy:function(t){return this.clone()._multiplyBy(t)},_multiplyBy:function(t){return this.x*=t,this.y*=t,this},scaleBy:function(t){return new x(this.x*t.x,this.y*t.y)},unscaleBy:function(t){return new x(this.x/t.x,this.y/t.y)},round:function(){return this.clone()._round()},_round:function(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},floor:function(){return this.clone()._floor()},_floor:function(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this},ceil:function(){return this.clone()._ceil()},_ceil:function(){return this.x=Math.ceil(this.x),this.y=Math.ceil(this.y),this},trunc:function(){return this.clone()._trunc()},_trunc:function(){return this.x=_i(this.x),this.y=_i(this.y),this},distanceTo:function(t){var i=(t=w(t)).x-this.x,e=t.y-this.y;return Math.sqrt(i*i+e*e)},equals:function(t){return(t=w(t)).x===this.x&&t.y===this.y},contains:function(t){return t=w(t),Math.abs(t.x)<=Math.abs(this.x)&&Math.abs(t.y)<=Math.abs(this.y)},toString:function(){return"Point("+a(this.x)+", "+a(this.y)+")"}},P.prototype={extend:function(t){return t=w(t),this.min||this.max?(this.min.x=Math.min(t.x,this.min.x),this.max.x=Math.max(t.x,this.max.x),this.min.y=Math.min(t.y,this.min.y),this.max.y=Math.max(t.y,this.max.y)):(this.min=t.clone(),this.max=t.clone()),this},getCenter:function(t){return new x((this.min.x+this.max.x)/2,(this.min.y+this.max.y)/2,t)},getBottomLeft:function(){return new x(this.min.x,this.max.y)},getTopRight:function(){return new x(this.max.x,this.min.y)},getTopLeft:function(){return this.min},getBottomRight:function(){return this.max},getSize:function(){return this.max.subtract(this.min)},contains:function(t){var i,e;return(t="number"==typeof t[0]||t instanceof x?w(t):b(t))instanceof P?(i=t.min,e=t.max):i=e=t,i.x>=this.min.x&&e.x<=this.max.x&&i.y>=this.min.y&&e.y<=this.max.y},intersects:function(t){t=b(t);var i=this.min,e=this.max,n=t.min,o=t.max,s=o.x>=i.x&&n.x<=e.x,r=o.y>=i.y&&n.y<=e.y;return s&&r},overlaps:function(t){t=b(t);var i=this.min,e=this.max,n=t.min,o=t.max,s=o.x>i.x&&n.x<e.x,r=o.y>i.y&&n.y<e.y;return s&&r},isValid:function(){return!(!this.min||!this.max)}},T.prototype={extend:function(t){var i,e,n=this._southWest,o=this._northEast;if(t instanceof M)i=t,e=t;else{if(!(t instanceof T))return t?this.extend(C(t)||z(t)):this;if(i=t._southWest,e=t._northEast,!i||!e)return this}return n||o?(n.lat=Math.min(i.lat,n.lat),n.lng=Math.min(i.lng,n.lng),o.lat=Math.max(e.lat,o.lat),o.lng=Math.max(e.lng,o.lng)):(this._southWest=new M(i.lat,i.lng),this._northEast=new M(e.lat,e.lng)),this},pad:function(t){var i=this._southWest,e=this._northEast,n=Math.abs(i.lat-e.lat)*t,o=Math.abs(i.lng-e.lng)*t;return new T(new M(i.lat-n,i.lng-o),new M(e.lat+n,e.lng+o))},getCenter:function(){return new M((this._southWest.lat+this._northEast.lat)/2,(this._southWest.lng+this._northEast.lng)/2)},getSouthWest:function(){return this._southWest},getNorthEast:function(){return this._northEast},getNorthWest:function(){return new M(this.getNorth(),this.getWest())},getSouthEast:function(){return new M(this.getSouth(),this.getEast())},getWest:function(){return this._southWest.lng},getSouth:function(){return this._southWest.lat},getEast:function(){return this._northEast.lng},getNorth:function(){return this._northEast.lat},contains:function(t){t="number"==typeof t[0]||t instanceof M||"lat"in t?C(t):z(t);var i,e,n=this._southWest,o=this._northEast;return t instanceof T?(i=t.getSouthWest(),e=t.getNorthEast()):i=e=t,i.lat>=n.lat&&e.lat<=o.lat&&i.lng>=n.lng&&e.lng<=o.lng},intersects:function(t){t=z(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),s=o.lat>=i.lat&&n.lat<=e.lat,r=o.lng>=i.lng&&n.lng<=e.lng;return s&&r},overlaps:function(t){t=z(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),o=t.getNorthEast(),s=o.lat>i.lat&&n.lat<e.lat,r=o.lng>i.lng&&n.lng<e.lng;return s&&r},toBBoxString:function(){return[this.getWest(),this.getSouth(),this.getEast(),this.getNorth()].join(",")},equals:function(t,i){return!!t&&(t=z(t),this._southWest.equals(t.getSouthWest(),i)&&this._northEast.equals(t.getNorthEast(),i))},isValid:function(){return!(!this._southWest||!this._northEast)}},M.prototype={equals:function(t,i){return!!t&&(t=C(t),Math.max(Math.abs(this.lat-t.lat),Math.abs(this.lng-t.lng))<=(void 0===i?1e-9:i))},toString:function(t){return"LatLng("+a(this.lat,t)+", "+a(this.lng,t)+")"},distanceTo:function(t){return pi.distance(this,C(t))},wrap:function(){return pi.wrapLatLng(this)},toBounds:function(t){var i=180*t/40075017,e=i/Math.cos(Math.PI/180*this.lat);return z([this.lat-i,this.lng-e],[this.lat+i,this.lng+e])},clone:function(){return new M(this.lat,this.lng,this.alt)}};var di={latLngToPoint:function(t,i){var e=this.projection.project(t),n=this.scale(i);return this.transformation._transform(e,n)},pointToLatLng:function(t,i){var e=this.scale(i),n=this.transformation.untransform(t,e);return this.projection.unproject(n)},project:function(t){return this.projection.project(t)},unproject:function(t){return this.projection.unproject(t)},scale:function(t){return 256*Math.pow(2,t)},zoom:function(t){return Math.log(t/256)/Math.LN2},getProjectedBounds:function(t){if(this.infinite)return null;var i=this.projection.bounds,e=this.scale(t);return new P(this.transformation.transform(i.min,e),this.transformation.transform(i.max,e))},infinite:!1,wrapLatLng:function(t){var i=this.wrapLng?s(t.lng,this.wrapLng,!0):t.lng;return new M(this.wrapLat?s(t.lat,this.wrapLat,!0):t.lat,i,t.alt)},wrapLatLngBounds:function(t){var i=t.getCenter(),e=this.wrapLatLng(i),n=i.lat-e.lat,o=i.lng-e.lng;if(0===n&&0===o)return t;var s=t.getSouthWest(),r=t.getNorthEast();return new T(new M(s.lat-n,s.lng-o),new M(r.lat-n,r.lng-o))}},pi=i({},di,{wrapLng:[-180,180],R:6371e3,distance:function(t,i){var e=Math.PI/180,n=t.lat*e,o=i.lat*e,s=Math.sin((i.lat-t.lat)*e/2),r=Math.sin((i.lng-t.lng)*e/2),a=s*s+Math.cos(n)*Math.cos(o)*r*r,h=2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));return this.R*h}}),mi={R:6378137,MAX_LATITUDE:85.0511287798,project:function(t){var i=Math.PI/180,e=this.MAX_LATITUDE,n=Math.max(Math.min(e,t.lat),-e),o=Math.sin(n*i);return new x(this.R*t.lng*i,this.R*Math.log((1+o)/(1-o))/2)},unproject:function(t){var i=180/Math.PI;return new M((2*Math.atan(Math.exp(t.y/this.R))-Math.PI/2)*i,t.x*i/this.R)},bounds:function(){var t=6378137*Math.PI;return new P([-t,-t],[t,t])}()};S.prototype={transform:function(t,i){return this._transform(t.clone(),i)},_transform:function(t,i){return i=i||1,t.x=i*(this._a*t.x+this._b),t.y=i*(this._c*t.y+this._d),t},untransform:function(t,i){return i=i||1,new x((t.x/i-this._b)/this._a,(t.y/i-this._d)/this._c)}};var fi,gi,vi,yi=i({},pi,{code:"EPSG:3857",projection:mi,transformation:function(){var t=.5/(Math.PI*mi.R);return Z(t,.5,-t,.5)}()}),xi=i({},yi,{code:"EPSG:900913"}),wi=document.documentElement.style,Pi="ActiveXObject"in window,Li=Pi&&!document.addEventListener,bi="msLaunchUri"in navigator&&!("documentMode"in document),Ti=A("webkit"),zi=A("android"),Mi=A("android 2")||A("android 3"),Ci=parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1],10),Si=zi&&A("Google")&&Ci<537&&!("AudioNode"in window),Zi=!!window.opera,Ei=A("chrome"),ki=A("gecko")&&!Ti&&!Zi&&!Pi,Ai=!Ei&&A("safari"),Bi=A("phantom"),Ii="OTransition"in wi,Oi=0===navigator.platform.indexOf("Win"),Ri=Pi&&"transition"in wi,Ni="WebKitCSSMatrix"in window&&"m11"in new window.WebKitCSSMatrix&&!Mi,Di="MozPerspective"in wi,ji=!window.L_DISABLE_3D&&(Ri||Ni||Di)&&!Ii&&!Bi,Wi="undefined"!=typeof orientation||A("mobile"),Hi=Wi&&Ti,Fi=Wi&&Ni,Ui=!window.PointerEvent&&window.MSPointerEvent,Vi=!(!window.PointerEvent&&!Ui),qi=!window.L_NO_TOUCH&&(Vi||"ontouchstart"in window||window.DocumentTouch&&document instanceof window.DocumentTouch),Gi=Wi&&Zi,Ki=Wi&&ki,Yi=(window.devicePixelRatio||window.screen.deviceXDPI/window.screen.logicalXDPI)>1,Xi=!!document.createElement("canvas").getContext,Ji=!(!document.createElementNS||!E("svg").createSVGRect),$i=!Ji&&function(){try{var t=document.createElement("div");t.innerHTML='<v:shape adj="1"/>';var i=t.firstChild;return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(t){return!1}}(),Qi=(Object.freeze||Object)({ie:Pi,ielt9:Li,edge:bi,webkit:Ti,android:zi,android23:Mi,androidStock:Si,opera:Zi,chrome:Ei,gecko:ki,safari:Ai,phantom:Bi,opera12:Ii,win:Oi,ie3d:Ri,webkit3d:Ni,gecko3d:Di,any3d:ji,mobile:Wi,mobileWebkit:Hi,mobileWebkit3d:Fi,msPointer:Ui,pointer:Vi,touch:qi,mobileOpera:Gi,mobileGecko:Ki,retina:Yi,canvas:Xi,svg:Ji,vml:$i}),te=Ui?"MSPointerDown":"pointerdown",ie=Ui?"MSPointerMove":"pointermove",ee=Ui?"MSPointerUp":"pointerup",ne=Ui?"MSPointerCancel":"pointercancel",oe=["INPUT","SELECT","OPTION"],se={},re=!1,ae=0,he=Ui?"MSPointerDown":Vi?"pointerdown":"touchstart",ue=Ui?"MSPointerUp":Vi?"pointerup":"touchend",le="_leaflet_",ce=st(["transform","webkitTransform","OTransform","MozTransform","msTransform"]),_e=st(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),de="webkitTransition"===_e||"OTransition"===_e?_e+"End":"transitionend";if("onselectstart"in document)fi=function(){mt(window,"selectstart",Pt)},gi=function(){ft(window,"selectstart",Pt)};else{var pe=st(["userSelect","WebkitUserSelect","OUserSelect","MozUserSelect","msUserSelect"]);fi=function(){if(pe){var t=document.documentElement.style;vi=t[pe],t[pe]="none"}},gi=function(){pe&&(document.documentElement.style[pe]=vi,vi=void 0)}}var me,fe,ge,ve=(Object.freeze||Object)({TRANSFORM:ce,TRANSITION:_e,TRANSITION_END:de,get:V,getStyle:q,create:G,remove:K,empty:Y,toFront:X,toBack:J,hasClass:$,addClass:Q,removeClass:tt,setClass:it,getClass:et,setOpacity:nt,testProp:st,setTransform:rt,setPosition:at,getPosition:ht,disableTextSelection:fi,enableTextSelection:gi,disableImageDrag:ut,enableImageDrag:lt,preventOutline:ct,restoreOutline:_t,getSizedParentNode:dt,getScale:pt}),ye="_leaflet_events",xe=Oi&&Ei?2*window.devicePixelRatio:ki?window.devicePixelRatio:1,we={},Pe=(Object.freeze||Object)({on:mt,off:ft,stopPropagation:yt,disableScrollPropagation:xt,disableClickPropagation:wt,preventDefault:Pt,stop:Lt,getMousePosition:bt,getWheelDelta:Tt,fakeStop:zt,skipped:Mt,isExternalTarget:Ct,addListener:mt,removeListener:ft}),Le=ci.extend({run:function(t,i,e,n){this.stop(),this._el=t,this._inProgress=!0,this._duration=e||.25,this._easeOutPower=1/Math.max(n||.5,.2),this._startPos=ht(t),this._offset=i.subtract(this._startPos),this._startTime=+new Date,this.fire("start"),this._animate()},stop:function(){this._inProgress&&(this._step(!0),this._complete())},_animate:function(){this._animId=f(this._animate,this),this._step()},_step:function(t){var i=+new Date-this._startTime,e=1e3*this._duration;i<e?this._runFrame(this._easeOut(i/e),t):(this._runFrame(1),this._complete())},_runFrame:function(t,i){var e=this._startPos.add(this._offset.multiplyBy(t));i&&e._round(),at(this._el,e),this.fire("step")},_complete:function(){g(this._animId),this._inProgress=!1,this.fire("end")},_easeOut:function(t){return 1-Math.pow(1-t,this._easeOutPower)}}),be=ci.extend({options:{crs:yi,center:void 0,zoom:void 0,minZoom:void 0,maxZoom:void 0,layers:[],maxBounds:void 0,renderer:void 0,zoomAnimation:!0,zoomAnimationThreshold:4,fadeAnimation:!0,markerZoomAnimation:!0,transform3DLimit:8388608,zoomSnap:1,zoomDelta:1,trackResize:!0},initialize:function(t,i){i=l(this,i),this._initContainer(t),this._initLayout(),this._onResize=e(this._onResize,this),this._initEvents(),i.maxBounds&&this.setMaxBounds(i.maxBounds),void 0!==i.zoom&&(this._zoom=this._limitZoom(i.zoom)),i.center&&void 0!==i.zoom&&this.setView(C(i.center),i.zoom,{reset:!0}),this._handlers=[],this._layers={},this._zoomBoundLayers={},this._sizeChanged=!0,this.callInitHooks(),this._zoomAnimated=_e&&ji&&!Gi&&this.options.zoomAnimation,this._zoomAnimated&&(this._createAnimProxy(),mt(this._proxy,de,this._catchTransitionEnd,this)),this._addLayers(this.options.layers)},setView:function(t,e,n){return e=void 0===e?this._zoom:this._limitZoom(e),t=this._limitCenter(C(t),e,this.options.maxBounds),n=n||{},this._stop(),this._loaded&&!n.reset&&!0!==n&&(void 0!==n.animate&&(n.zoom=i({animate:n.animate},n.zoom),n.pan=i({animate:n.animate,duration:n.duration},n.pan)),this._zoom!==e?this._tryAnimatedZoom&&this._tryAnimatedZoom(t,e,n.zoom):this._tryAnimatedPan(t,n.pan))?(clearTimeout(this._sizeTimer),this):(this._resetView(t,e),this)},setZoom:function(t,i){return this._loaded?this.setView(this.getCenter(),t,{zoom:i}):(this._zoom=t,this)},zoomIn:function(t,i){return t=t||(ji?this.options.zoomDelta:1),this.setZoom(this._zoom+t,i)},zoomOut:function(t,i){return t=t||(ji?this.options.zoomDelta:1),this.setZoom(this._zoom-t,i)},setZoomAround:function(t,i,e){var n=this.getZoomScale(i),o=this.getSize().divideBy(2),s=(t instanceof x?t:this.latLngToContainerPoint(t)).subtract(o).multiplyBy(1-1/n),r=this.containerPointToLatLng(o.add(s));return this.setView(r,i,{zoom:e})},_getBoundsCenterZoom:function(t,i){i=i||{},t=t.getBounds?t.getBounds():z(t);var e=w(i.paddingTopLeft||i.padding||[0,0]),n=w(i.paddingBottomRight||i.padding||[0,0]),o=this.getBoundsZoom(t,!1,e.add(n));if((o="number"==typeof i.maxZoom?Math.min(i.maxZoom,o):o)===1/0)return{center:t.getCenter(),zoom:o};var s=n.subtract(e).divideBy(2),r=this.project(t.getSouthWest(),o),a=this.project(t.getNorthEast(),o);return{center:this.unproject(r.add(a).divideBy(2).add(s),o),zoom:o}},fitBounds:function(t,i){if(!(t=z(t)).isValid())throw new Error("Bounds are not valid.");var e=this._getBoundsCenterZoom(t,i);return this.setView(e.center,e.zoom,i)},fitWorld:function(t){return this.fitBounds([[-90,-180],[90,180]],t)},panTo:function(t,i){return this.setView(t,this._zoom,{pan:i})},panBy:function(t,i){if(t=w(t).round(),i=i||{},!t.x&&!t.y)return this.fire("moveend");if(!0!==i.animate&&!this.getSize().contains(t))return this._resetView(this.unproject(this.project(this.getCenter()).add(t)),this.getZoom()),this;if(this._panAnim||(this._panAnim=new Le,this._panAnim.on({step:this._onPanTransitionStep,end:this._onPanTransitionEnd},this)),i.noMoveStart||this.fire("movestart"),!1!==i.animate){Q(this._mapPane,"leaflet-pan-anim");var e=this._getMapPanePos().subtract(t).round();this._panAnim.run(this._mapPane,e,i.duration||.25,i.easeLinearity)}else this._rawPanBy(t),this.fire("move").fire("moveend");return this},flyTo:function(t,i,e){function n(t){var i=(g*g-m*m+(t?-1:1)*x*x*v*v)/(2*(t?g:m)*x*v),e=Math.sqrt(i*i+1)-i;return e<1e-9?-18:Math.log(e)}function o(t){return(Math.exp(t)-Math.exp(-t))/2}function s(t){return(Math.exp(t)+Math.exp(-t))/2}function r(t){return o(t)/s(t)}function a(t){return m*(s(w)/s(w+y*t))}function h(t){return m*(s(w)*r(w+y*t)-o(w))/x}function u(t){return 1-Math.pow(1-t,1.5)}function l(){var e=(Date.now()-P)/b,n=u(e)*L;e<=1?(this._flyToFrame=f(l,this),this._move(this.unproject(c.add(_.subtract(c).multiplyBy(h(n)/v)),p),this.getScaleZoom(m/a(n),p),{flyTo:!0})):this._move(t,i)._moveEnd(!0)}if(!1===(e=e||{}).animate||!ji)return this.setView(t,i,e);this._stop();var c=this.project(this.getCenter()),_=this.project(t),d=this.getSize(),p=this._zoom;t=C(t),i=void 0===i?p:i;var m=Math.max(d.x,d.y),g=m*this.getZoomScale(p,i),v=_.distanceTo(c)||1,y=1.42,x=y*y,w=n(0),P=Date.now(),L=(n(1)-w)/y,b=e.duration?1e3*e.duration:1e3*L*.8;return this._moveStart(!0,e.noMoveStart),l.call(this),this},flyToBounds:function(t,i){var e=this._getBoundsCenterZoom(t,i);return this.flyTo(e.center,e.zoom,i)},setMaxBounds:function(t){return(t=z(t)).isValid()?(this.options.maxBounds&&this.off("moveend",this._panInsideMaxBounds),this.options.maxBounds=t,this._loaded&&this._panInsideMaxBounds(),this.on("moveend",this._panInsideMaxBounds)):(this.options.maxBounds=null,this.off("moveend",this._panInsideMaxBounds))},setMinZoom:function(t){var i=this.options.minZoom;return this.options.minZoom=t,this._loaded&&i!==t&&(this.fire("zoomlevelschange"),this.getZoom()<this.options.minZoom)?this.setZoom(t):this},setMaxZoom:function(t){var i=this.options.maxZoom;return this.options.maxZoom=t,this._loaded&&i!==t&&(this.fire("zoomlevelschange"),this.getZoom()>this.options.maxZoom)?this.setZoom(t):this},panInsideBounds:function(t,i){this._enforcingBounds=!0;var e=this.getCenter(),n=this._limitCenter(e,this._zoom,z(t));return e.equals(n)||this.panTo(n,i),this._enforcingBounds=!1,this},invalidateSize:function(t){if(!this._loaded)return this;t=i({animate:!1,pan:!0},!0===t?{animate:!0}:t);var n=this.getSize();this._sizeChanged=!0,this._lastCenter=null;var o=this.getSize(),s=n.divideBy(2).round(),r=o.divideBy(2).round(),a=s.subtract(r);return a.x||a.y?(t.animate&&t.pan?this.panBy(a):(t.pan&&this._rawPanBy(a),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(e(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:n,newSize:o})):this},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire("viewreset"),this._stop()},locate:function(t){if(t=this._locateOptions=i({timeout:1e4,watch:!1},t),!("geolocation"in navigator))return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var n=e(this._handleGeolocationResponse,this),o=e(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(n,o,t):navigator.geolocation.getCurrentPosition(n,o,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var i=t.code,e=t.message||(1===i?"permission denied":2===i?"position unavailable":"timeout");this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:i,message:"Geolocation error: "+e+"."})},_handleGeolocationResponse:function(t){var i=new M(t.coords.latitude,t.coords.longitude),e=i.toBounds(2*t.coords.accuracy),n=this._locateOptions;if(n.setView){var o=this.getBoundsZoom(e);this.setView(i,n.maxZoom?Math.min(o,n.maxZoom):o)}var s={latlng:i,bounds:e,timestamp:t.timestamp};for(var r in t.coords)"number"==typeof t.coords[r]&&(s[r]=t.coords[r]);this.fire("locationfound",s)},addHandler:function(t,i){if(!i)return this;var e=this[t]=new i(this);return this._handlers.push(e),this.options[t]&&e.enable(),this},remove:function(){if(this._initEvents(!0),this._containerId!==this._container._leaflet_id)throw new Error("Map container is being reused by another instance");try{delete this._container._leaflet_id,delete this._containerId}catch(t){this._container._leaflet_id=void 0,this._containerId=void 0}void 0!==this._locationWatchId&&this.stopLocate(),this._stop(),K(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._resizeRequest&&(g(this._resizeRequest),this._resizeRequest=null),this._clearHandlers(),this._loaded&&this.fire("unload");var t;for(t in this._layers)this._layers[t].remove();for(t in this._panes)K(this._panes[t]);return this._layers=[],this._panes=[],delete this._mapPane,delete this._renderer,this},createPane:function(t,i){var e=G("div","leaflet-pane"+(t?" leaflet-"+t.replace("Pane","")+"-pane":""),i||this._mapPane);return t&&(this._panes[t]=e),e},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter:this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds();return new T(this.unproject(t.getBottomLeft()),this.unproject(t.getTopRight()))},getMinZoom:function(){return void 0===this.options.minZoom?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return void 0===this.options.maxZoom?void 0===this._layersMaxZoom?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,i,e){t=z(t),e=w(e||[0,0]);var n=this.getZoom()||0,o=this.getMinZoom(),s=this.getMaxZoom(),r=t.getNorthWest(),a=t.getSouthEast(),h=this.getSize().subtract(e),u=b(this.project(a,n),this.project(r,n)).getSize(),l=ji?this.options.zoomSnap:1,c=h.x/u.x,_=h.y/u.y,d=i?Math.max(c,_):Math.min(c,_);return n=this.getScaleZoom(d,n),l&&(n=Math.round(n/(l/100))*(l/100),n=i?Math.ceil(n/l)*l:Math.floor(n/l)*l),Math.max(o,Math.min(s,n))},getSize:function(){return this._size&&!this._sizeChanged||(this._size=new x(this._container.clientWidth||0,this._container.clientHeight||0),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(t,i){var e=this._getTopLeftPoint(t,i);return new P(e,e.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(t){return this.options.crs.getProjectedBounds(void 0===t?this.getZoom():t)},getPane:function(t){return"string"==typeof t?this._panes[t]:t},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t,i){var e=this.options.crs;return i=void 0===i?this._zoom:i,e.scale(t)/e.scale(i)},getScaleZoom:function(t,i){var e=this.options.crs;i=void 0===i?this._zoom:i;var n=e.zoom(t*e.scale(i));return isNaN(n)?1/0:n},project:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.latLngToPoint(C(t),i)},unproject:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.pointToLatLng(w(t),i)},layerPointToLatLng:function(t){var i=w(t).add(this.getPixelOrigin());return this.unproject(i)},latLngToLayerPoint:function(t){return this.project(C(t))._round()._subtract(this.getPixelOrigin())},wrapLatLng:function(t){return this.options.crs.wrapLatLng(C(t))},wrapLatLngBounds:function(t){return this.options.crs.wrapLatLngBounds(z(t))},distance:function(t,i){return this.options.crs.distance(C(t),C(i))},containerPointToLayerPoint:function(t){return w(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return w(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){var i=this.containerPointToLayerPoint(w(t));return this.layerPointToLatLng(i)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(C(t)))},mouseEventToContainerPoint:function(t){return bt(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){var i=this._container=V(t);if(!i)throw new Error("Map container not found.");if(i._leaflet_id)throw new Error("Map container is already initialized.");mt(i,"scroll",this._onScroll,this),this._containerId=n(i)},_initLayout:function(){var t=this._container;this._fadeAnimated=this.options.fadeAnimation&&ji,Q(t,"leaflet-container"+(qi?" leaflet-touch":"")+(Yi?" leaflet-retina":"")+(Li?" leaflet-oldie":"")+(Ai?" leaflet-safari":"")+(this._fadeAnimated?" leaflet-fade-anim":""));var i=q(t,"position");"absolute"!==i&&"relative"!==i&&"fixed"!==i&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._paneRenderers={},this._mapPane=this.createPane("mapPane",this._container),at(this._mapPane,new x(0,0)),this.createPane("tilePane"),this.createPane("shadowPane"),this.createPane("overlayPane"),this.createPane("markerPane"),this.createPane("tooltipPane"),this.createPane("popupPane"),this.options.markerZoomAnimation||(Q(t.markerPane,"leaflet-zoom-hide"),Q(t.shadowPane,"leaflet-zoom-hide"))},_resetView:function(t,i){at(this._mapPane,new x(0,0));var e=!this._loaded;this._loaded=!0,i=this._limitZoom(i),this.fire("viewprereset");var n=this._zoom!==i;this._moveStart(n,!1)._move(t,i)._moveEnd(n),this.fire("viewreset"),e&&this.fire("load")},_moveStart:function(t,i){return t&&this.fire("zoomstart"),i||this.fire("movestart"),this},_move:function(t,i,e){void 0===i&&(i=this._zoom);var n=this._zoom!==i;return this._zoom=i,this._lastCenter=t,this._pixelOrigin=this._getNewPixelOrigin(t),(n||e&&e.pinch)&&this.fire("zoom",e),this.fire("move",e)},_moveEnd:function(t){return t&&this.fire("zoomend"),this.fire("moveend")},_stop:function(){return g(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(t){at(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(t){this._targets={},this._targets[n(this._container)]=this;var i=t?ft:mt;i(this._container,"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress",this._handleDOMEvent,this),this.options.trackResize&&i(window,"resize",this._onResize,this),ji&&this.options.transform3DLimit&&(t?this.off:this.on).call(this,"moveend",this._onMoveEnd)},_onResize:function(){g(this._resizeRequest),this._resizeRequest=f(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var t=this._getMapPanePos();Math.max(Math.abs(t.x),Math.abs(t.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,i){for(var e,o=[],s="mouseout"===i||"mouseover"===i,r=t.target||t.srcElement,a=!1;r;){if((e=this._targets[n(r)])&&("click"===i||"preclick"===i)&&!t._simulated&&this._draggableMoved(e)){a=!0;break}if(e&&e.listens(i,!0)){if(s&&!Ct(r,t))break;if(o.push(e),s)break}if(r===this._container)break;r=r.parentNode}return o.length||a||s||!Ct(r,t)||(o=[this]),o},_handleDOMEvent:function(t){if(this._loaded&&!Mt(t)){var i=t.type;"mousedown"!==i&&"keypress"!==i||ct(t.target||t.srcElement),this._fireDOMEvent(t,i)}},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(t,e,n){if("click"===t.type){var o=i({},t);o.type="preclick",this._fireDOMEvent(o,o.type,n)}if(!t._stopped&&(n=(n||[]).concat(this._findEventTargets(t,e))).length){var s=n[0];"contextmenu"===e&&s.listens(e,!0)&&Pt(t);var r={originalEvent:t};if("keypress"!==t.type){var a=s.getLatLng&&(!s._radius||s._radius<=10);r.containerPoint=a?this.latLngToContainerPoint(s.getLatLng()):this.mouseEventToContainerPoint(t),r.layerPoint=this.containerPointToLayerPoint(r.containerPoint),r.latlng=a?s.getLatLng():this.layerPointToLatLng(r.layerPoint)}for(var h=0;h<n.length;h++)if(n[h].fire(e,r,!0),r.originalEvent._stopped||!1===n[h].options.bubblingMouseEvents&&-1!==d(this._mouseEvents,e))return}},_draggableMoved:function(t){return(t=t.dragging&&t.dragging.enabled()?t:this).dragging&&t.dragging.moved()||this.boxZoom&&this.boxZoom.moved()},_clearHandlers:function(){for(var t=0,i=this._handlers.length;t<i;t++)this._handlers[t].disable()},whenReady:function(t,i){return this._loaded?t.call(i||this,{target:this}):this.on("load",t,i),this},_getMapPanePos:function(){return ht(this._mapPane)||new x(0,0)},_moved:function(){var t=this._getMapPanePos();return t&&!t.equals([0,0])},_getTopLeftPoint:function(t,i){return(t&&void 0!==i?this._getNewPixelOrigin(t,i):this.getPixelOrigin()).subtract(this._getMapPanePos())},_getNewPixelOrigin:function(t,i){var e=this.getSize()._divideBy(2);return this.project(t,i)._subtract(e)._add(this._getMapPanePos())._round()},_latLngToNewLayerPoint:function(t,i,e){var n=this._getNewPixelOrigin(e,i);return this.project(t,i)._subtract(n)},_latLngBoundsToNewLayerBounds:function(t,i,e){var n=this._getNewPixelOrigin(e,i);return b([this.project(t.getSouthWest(),i)._subtract(n),this.project(t.getNorthWest(),i)._subtract(n),this.project(t.getSouthEast(),i)._subtract(n),this.project(t.getNorthEast(),i)._subtract(n)])},_getCenterLayerPoint:function(){return this.containerPointToLayerPoint(this.getSize()._divideBy(2))},_getCenterOffset:function(t){return this.latLngToLayerPoint(t).subtract(this._getCenterLayerPoint())},_limitCenter:function(t,i,e){if(!e)return t;var n=this.project(t,i),o=this.getSize().divideBy(2),s=new P(n.subtract(o),n.add(o)),r=this._getBoundsOffset(s,e,i);return r.round().equals([0,0])?t:this.unproject(n.add(r),i)},_limitOffset:function(t,i){if(!i)return t;var e=this.getPixelBounds(),n=new P(e.min.add(t),e.max.add(t));return t.add(this._getBoundsOffset(n,i))},_getBoundsOffset:function(t,i,e){var n=b(this.project(i.getNorthEast(),e),this.project(i.getSouthWest(),e)),o=n.min.subtract(t.min),s=n.max.subtract(t.max);return new x(this._rebound(o.x,-s.x),this._rebound(o.y,-s.y))},_rebound:function(t,i){return t+i>0?Math.round(t-i)/2:Math.max(0,Math.ceil(t))-Math.max(0,Math.floor(i))},_limitZoom:function(t){var i=this.getMinZoom(),e=this.getMaxZoom(),n=ji?this.options.zoomSnap:1;return n&&(t=Math.round(t/n)*n),Math.max(i,Math.min(e,t))},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){tt(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_tryAnimatedPan:function(t,i){var e=this._getCenterOffset(t)._trunc();return!(!0!==(i&&i.animate)&&!this.getSize().contains(e))&&(this.panBy(e,i),!0)},_createAnimProxy:function(){var t=this._proxy=G("div","leaflet-proxy leaflet-zoom-animated");this._panes.mapPane.appendChild(t),this.on("zoomanim",function(t){var i=ce,e=this._proxy.style[i];rt(this._proxy,this.project(t.center,t.zoom),this.getZoomScale(t.zoom,1)),e===this._proxy.style[i]&&this._animatingZoom&&this._onZoomTransitionEnd()},this),this.on("load moveend",function(){var t=this.getCenter(),i=this.getZoom();rt(this._proxy,this.project(t,i),this.getZoomScale(i,1))},this),this._on("unload",this._destroyAnimProxy,this)},_destroyAnimProxy:function(){K(this._proxy),delete this._proxy},_catchTransitionEnd:function(t){this._animatingZoom&&t.propertyName.indexOf("transform")>=0&&this._onZoomTransitionEnd()},_nothingToAnimate:function(){return!this._container.getElementsByClassName("leaflet-zoom-animated").length},_tryAnimatedZoom:function(t,i,e){if(this._animatingZoom)return!0;if(e=e||{},!this._zoomAnimated||!1===e.animate||this._nothingToAnimate()||Math.abs(i-this._zoom)>this.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(i),o=this._getCenterOffset(t)._divideBy(1-1/n);return!(!0!==e.animate&&!this.getSize().contains(o))&&(f(function(){this._moveStart(!0,!1)._animateZoom(t,i,!0)},this),!0)},_animateZoom:function(t,i,n,o){this._mapPane&&(n&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=i,Q(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:i,noUpdate:o}),setTimeout(e(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&tt(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom),f(function(){this._moveEnd(!0)},this))}}),Te=v.extend({options:{position:"topright"},initialize:function(t){l(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var i=this._map;return i&&i.removeControl(this),this.options.position=t,i&&i.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var i=this._container=this.onAdd(t),e=this.getPosition(),n=t._controlCorners[e];return Q(i,"leaflet-control"),-1!==e.indexOf("bottom")?n.insertBefore(i,n.firstChild):n.appendChild(i),this},remove:function(){return this._map?(K(this._container),this.onRemove&&this.onRemove(this._map),this._map=null,this):this},_refocusOnMap:function(t){this._map&&t&&t.screenX>0&&t.screenY>0&&this._map.getContainer().focus()}}),ze=function(t){return new Te(t)};be.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.remove(),this},_initControlPos:function(){function t(t,o){var s=e+t+" "+e+o;i[t+o]=G("div",s,n)}var i=this._controlCorners={},e="leaflet-",n=this._controlContainer=G("div",e+"control-container",this._container);t("top","left"),t("top","right"),t("bottom","left"),t("bottom","right")},_clearControlPos:function(){for(var t in this._controlCorners)K(this._controlCorners[t]);K(this._controlContainer),delete this._controlCorners,delete this._controlContainer}});var Me=Te.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0,hideSingleBase:!1,sortLayers:!1,sortFunction:function(t,i,e,n){return e<n?-1:n<e?1:0}},initialize:function(t,i,e){l(this,e),this._layerControlInputs=[],this._layers=[],this._lastZIndex=0,this._handlingClick=!1;for(var n in t)this._addLayer(t[n],n);for(n in i)this._addLayer(i[n],n,!0)},onAdd:function(t){this._initLayout(),this._update(),this._map=t,t.on("zoomend",this._checkDisabledLayers,this);for(var i=0;i<this._layers.length;i++)this._layers[i].layer.on("add remove",this._onLayerChange,this);return this._container},addTo:function(t){return Te.prototype.addTo.call(this,t),this._expandIfNotCollapsed()},onRemove:function(){this._map.off("zoomend",this._checkDisabledLayers,this);for(var t=0;t<this._layers.length;t++)this._layers[t].layer.off("add remove",this._onLayerChange,this)},addBaseLayer:function(t,i){return this._addLayer(t,i),this._map?this._update():this},addOverlay:function(t,i){return this._addLayer(t,i,!0),this._map?this._update():this},removeLayer:function(t){t.off("add remove",this._onLayerChange,this);var i=this._getLayer(n(t));return i&&this._layers.splice(this._layers.indexOf(i),1),this._map?this._update():this},expand:function(){Q(this._container,"leaflet-control-layers-expanded"),this._form.style.height=null;var t=this._map.getSize().y-(this._container.offsetTop+50);return t<this._form.clientHeight?(Q(this._form,"leaflet-control-layers-scrollbar"),this._form.style.height=t+"px"):tt(this._form,"leaflet-control-layers-scrollbar"),this._checkDisabledLayers(),this},collapse:function(){return tt(this._container,"leaflet-control-layers-expanded"),this},_initLayout:function(){var t="leaflet-control-layers",i=this._container=G("div",t),e=this.options.collapsed;i.setAttribute("aria-haspopup",!0),wt(i),xt(i);var n=this._form=G("form",t+"-list");e&&(this._map.on("click",this.collapse,this),zi||mt(i,{mouseenter:this.expand,mouseleave:this.collapse},this));var o=this._layersLink=G("a",t+"-toggle",i);o.href="#",o.title="Layers",qi?(mt(o,"click",Lt),mt(o,"click",this.expand,this)):mt(o,"focus",this.expand,this),e||this.expand(),this._baseLayersList=G("div",t+"-base",n),this._separator=G("div",t+"-separator",n),this._overlaysList=G("div",t+"-overlays",n),i.appendChild(n)},_getLayer:function(t){for(var i=0;i<this._layers.length;i++)if(this._layers[i]&&n(this._layers[i].layer)===t)return this._layers[i]},_addLayer:function(t,i,n){this._map&&t.on("add remove",this._onLayerChange,this),this._layers.push({layer:t,name:i,overlay:n}),this.options.sortLayers&&this._layers.sort(e(function(t,i){return this.options.sortFunction(t.layer,i.layer,t.name,i.name)},this)),this.options.autoZIndex&&t.setZIndex&&(this._lastZIndex++,t.setZIndex(this._lastZIndex)),this._expandIfNotCollapsed()},_update:function(){if(!this._container)return this;Y(this._baseLayersList),Y(this._overlaysList),this._layerControlInputs=[];var t,i,e,n,o=0;for(e=0;e<this._layers.length;e++)n=this._layers[e],this._addItem(n),i=i||n.overlay,t=t||!n.overlay,o+=n.overlay?0:1;return this.options.hideSingleBase&&(t=t&&o>1,this._baseLayersList.style.display=t?"":"none"),this._separator.style.display=i&&t?"":"none",this},_onLayerChange:function(t){this._handlingClick||this._update();var i=this._getLayer(n(t.target)),e=i.overlay?"add"===t.type?"overlayadd":"overlayremove":"add"===t.type?"baselayerchange":null;e&&this._map.fire(e,i)},_createRadioElement:function(t,i){var e='<input type="radio" class="leaflet-control-layers-selector" name="'+t+'"'+(i?' checked="checked"':"")+"/>",n=document.createElement("div");return n.innerHTML=e,n.firstChild},_addItem:function(t){var i,e=document.createElement("label"),o=this._map.hasLayer(t.layer);t.overlay?((i=document.createElement("input")).type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=o):i=this._createRadioElement("leaflet-base-layers",o),this._layerControlInputs.push(i),i.layerId=n(t.layer),mt(i,"click",this._onInputClick,this);var s=document.createElement("span");s.innerHTML=" "+t.name;var r=document.createElement("div");return e.appendChild(r),r.appendChild(i),r.appendChild(s),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(e),this._checkDisabledLayers(),e},_onInputClick:function(){var t,i,e=this._layerControlInputs,n=[],o=[];this._handlingClick=!0;for(var s=e.length-1;s>=0;s--)t=e[s],i=this._getLayer(t.layerId).layer,t.checked?n.push(i):t.checked||o.push(i);for(s=0;s<o.length;s++)this._map.hasLayer(o[s])&&this._map.removeLayer(o[s]);for(s=0;s<n.length;s++)this._map.hasLayer(n[s])||this._map.addLayer(n[s]);this._handlingClick=!1,this._refocusOnMap()},_checkDisabledLayers:function(){for(var t,i,e=this._layerControlInputs,n=this._map.getZoom(),o=e.length-1;o>=0;o--)t=e[o],i=this._getLayer(t.layerId).layer,t.disabled=void 0!==i.options.minZoom&&n<i.options.minZoom||void 0!==i.options.maxZoom&&n>i.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this},_expand:function(){return this.expand()},_collapse:function(){return this.collapse()}}),Ce=Te.extend({options:{position:"topleft",zoomInText:"+",zoomInTitle:"Zoom in",zoomOutText:"&#x2212;",zoomOutTitle:"Zoom out"},onAdd:function(t){var i="leaflet-control-zoom",e=G("div",i+" leaflet-bar"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,i+"-in",e,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,i+"-out",e,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),e},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoom<this._map.getMaxZoom()&&this._map.zoomIn(this._map.options.zoomDelta*(t.shiftKey?3:1))},_zoomOut:function(t){!this._disabled&&this._map._zoom>this._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,i,e,n,o){var s=G("a",e,n);return s.innerHTML=t,s.href="#",s.title=i,s.setAttribute("role","button"),s.setAttribute("aria-label",i),wt(s),mt(s,"click",Lt),mt(s,"click",o,this),mt(s,"click",this._refocusOnMap,this),s},_updateDisabled:function(){var t=this._map,i="leaflet-disabled";tt(this._zoomInButton,i),tt(this._zoomOutButton,i),(this._disabled||t._zoom===t.getMinZoom())&&Q(this._zoomOutButton,i),(this._disabled||t._zoom===t.getMaxZoom())&&Q(this._zoomInButton,i)}});be.mergeOptions({zoomControl:!0}),be.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new Ce,this.addControl(this.zoomControl))});var Se=Te.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var i=G("div","leaflet-control-scale"),e=this.options;return this._addScales(e,"leaflet-control-scale-line",i),t.on(e.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,i,e){t.metric&&(this._mScale=G("div",i,e)),t.imperial&&(this._iScale=G("div",i,e))},_update:function(){var t=this._map,i=t.getSize().y/2,e=t.distance(t.containerPointToLatLng([0,i]),t.containerPointToLatLng([this.options.maxWidth,i]));this._updateScales(e)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var i=this._getRoundNum(t),e=i<1e3?i+" m":i/1e3+" km";this._updateScale(this._mScale,e,i/t)},_updateImperial:function(t){var i,e,n,o=3.2808399*t;o>5280?(i=o/5280,e=this._getRoundNum(i),this._updateScale(this._iScale,e+" mi",e/i)):(n=this._getRoundNum(o),this._updateScale(this._iScale,n+" ft",n/o))},_updateScale:function(t,i,e){t.style.width=Math.round(this.options.maxWidth*e)+"px",t.innerHTML=i},_getRoundNum:function(t){var i=Math.pow(10,(Math.floor(t)+"").length-1),e=t/i;return e=e>=10?10:e>=5?5:e>=3?3:e>=2?2:1,i*e}}),Ze=Te.extend({options:{position:"bottomright",prefix:'<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'},initialize:function(t){l(this,t),this._attributions={}},onAdd:function(t){t.attributionControl=this,this._container=G("div","leaflet-control-attribution"),wt(this._container);for(var i in t._layers)t._layers[i].getAttribution&&this.addAttribution(t._layers[i].getAttribution());return this._update(),this._container},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t?(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update(),this):this},removeAttribution:function(t){return t?(this._attributions[t]&&(this._attributions[t]--,this._update()),this):this},_update:function(){if(this._map){var t=[];for(var i in this._attributions)this._attributions[i]&&t.push(i);var e=[];this.options.prefix&&e.push(this.options.prefix),t.length&&e.push(t.join(", ")),this._container.innerHTML=e.join(" | ")}}});be.mergeOptions({attributionControl:!0}),be.addInitHook(function(){this.options.attributionControl&&(new Ze).addTo(this)});Te.Layers=Me,Te.Zoom=Ce,Te.Scale=Se,Te.Attribution=Ze,ze.layers=function(t,i,e){return new Me(t,i,e)},ze.zoom=function(t){return new Ce(t)},ze.scale=function(t){return new Se(t)},ze.attribution=function(t){return new Ze(t)};var Ee=v.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled?this:(this._enabled=!0,this.addHooks(),this)},disable:function(){return this._enabled?(this._enabled=!1,this.removeHooks(),this):this},enabled:function(){return!!this._enabled}});Ee.addTo=function(t,i){return t.addHandler(i,this),this};var ke,Ae={Events:li},Be=qi?"touchstart mousedown":"mousedown",Ie={mousedown:"mouseup",touchstart:"touchend",pointerdown:"touchend",MSPointerDown:"touchend"},Oe={mousedown:"mousemove",touchstart:"touchmove",pointerdown:"touchmove",MSPointerDown:"touchmove"},Re=ci.extend({options:{clickTolerance:3},initialize:function(t,i,e,n){l(this,n),this._element=t,this._dragStartTarget=i||t,this._preventOutline=e},enable:function(){this._enabled||(mt(this._dragStartTarget,Be,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(Re._dragging===this&&this.finishDrag(),ft(this._dragStartTarget,Be,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){if(!t._simulated&&this._enabled&&(this._moved=!1,!$(this._element,"leaflet-zoom-anim")&&!(Re._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||(Re._dragging=this,this._preventOutline&&ct(this._element),ut(),fi(),this._moving)))){this.fire("down");var i=t.touches?t.touches[0]:t,e=dt(this._element);this._startPoint=new x(i.clientX,i.clientY),this._parentScale=pt(e),mt(document,Oe[t.type],this._onMove,this),mt(document,Ie[t.type],this._onUp,this)}},_onMove:function(t){if(!t._simulated&&this._enabled)if(t.touches&&t.touches.length>1)this._moved=!0;else{var i=t.touches&&1===t.touches.length?t.touches[0]:t,e=new x(i.clientX,i.clientY)._subtract(this._startPoint);(e.x||e.y)&&(Math.abs(e.x)+Math.abs(e.y)<this.options.clickTolerance||(e.x/=this._parentScale.x,e.y/=this._parentScale.y,Pt(t),this._moved||(this.fire("dragstart"),this._moved=!0,this._startPos=ht(this._element).subtract(e),Q(document.body,"leaflet-dragging"),this._lastTarget=t.target||t.srcElement,window.SVGElementInstance&&this._lastTarget instanceof SVGElementInstance&&(this._lastTarget=this._lastTarget.correspondingUseElement),Q(this._lastTarget,"leaflet-drag-target")),this._newPos=this._startPos.add(e),this._moving=!0,g(this._animRequest),this._lastEvent=t,this._animRequest=f(this._updatePosition,this,!0)))}},_updatePosition:function(){var t={originalEvent:this._lastEvent};this.fire("predrag",t),at(this._element,this._newPos),this.fire("drag",t)},_onUp:function(t){!t._simulated&&this._enabled&&this.finishDrag()},finishDrag:function(){tt(document.body,"leaflet-dragging"),this._lastTarget&&(tt(this._lastTarget,"leaflet-drag-target"),this._lastTarget=null);for(var t in Oe)ft(document,Oe[t],this._onMove,this),ft(document,Ie[t],this._onUp,this);lt(),gi(),this._moved&&this._moving&&(g(this._animRequest),this.fire("dragend",{distance:this._newPos.distanceTo(this._startPos)})),this._moving=!1,Re._dragging=!1}}),Ne=(Object.freeze||Object)({simplify:Zt,pointToSegmentDistance:Et,closestPointOnSegment:function(t,i,e){return Dt(t,i,e)},clipSegment:It,_getEdgeIntersection:Ot,_getBitCode:Rt,_sqClosestPointOnSegment:Dt,isFlat:jt,_flat:Wt}),De=(Object.freeze||Object)({clipPolygon:Ht}),je={project:function(t){return new x(t.lng,t.lat)},unproject:function(t){return new M(t.y,t.x)},bounds:new P([-180,-90],[180,90])},We={R:6378137,R_MINOR:6356752.314245179,bounds:new P([-20037508.34279,-15496570.73972],[20037508.34279,18764656.23138]),project:function(t){var i=Math.PI/180,e=this.R,n=t.lat*i,o=this.R_MINOR/e,s=Math.sqrt(1-o*o),r=s*Math.sin(n),a=Math.tan(Math.PI/4-n/2)/Math.pow((1-r)/(1+r),s/2);return n=-e*Math.log(Math.max(a,1e-10)),new x(t.lng*i*e,n)},unproject:function(t){for(var i,e=180/Math.PI,n=this.R,o=this.R_MINOR/n,s=Math.sqrt(1-o*o),r=Math.exp(-t.y/n),a=Math.PI/2-2*Math.atan(r),h=0,u=.1;h<15&&Math.abs(u)>1e-7;h++)i=s*Math.sin(a),i=Math.pow((1-i)/(1+i),s/2),a+=u=Math.PI/2-2*Math.atan(r*i)-a;return new M(a*e,t.x*e/n)}},He=(Object.freeze||Object)({LonLat:je,Mercator:We,SphericalMercator:mi}),Fe=i({},pi,{code:"EPSG:3395",projection:We,transformation:function(){var t=.5/(Math.PI*We.R);return Z(t,.5,-t,.5)}()}),Ue=i({},pi,{code:"EPSG:4326",projection:je,transformation:Z(1/180,1,-1/180,.5)}),Ve=i({},di,{projection:je,transformation:Z(1,0,-1,0),scale:function(t){return Math.pow(2,t)},zoom:function(t){return Math.log(t)/Math.LN2},distance:function(t,i){var e=i.lng-t.lng,n=i.lat-t.lat;return Math.sqrt(e*e+n*n)},infinite:!0});di.Earth=pi,di.EPSG3395=Fe,di.EPSG3857=yi,di.EPSG900913=xi,di.EPSG4326=Ue,di.Simple=Ve;var qe=ci.extend({options:{pane:"overlayPane",attribution:null,bubblingMouseEvents:!0},addTo:function(t){return t.addLayer(this),this},remove:function(){return this.removeFrom(this._map||this._mapToAdd)},removeFrom:function(t){return t&&t.removeLayer(this),this},getPane:function(t){return this._map.getPane(t?this.options[t]||t:this.options.pane)},addInteractiveTarget:function(t){return this._map._targets[n(t)]=this,this},removeInteractiveTarget:function(t){return delete this._map._targets[n(t)],this},getAttribution:function(){return this.options.attribution},_layerAdd:function(t){var i=t.target;if(i.hasLayer(this)){if(this._map=i,this._zoomAnimated=i._zoomAnimated,this.getEvents){var e=this.getEvents();i.on(e,this),this.once("remove",function(){i.off(e,this)},this)}this.onAdd(i),this.getAttribution&&i.attributionControl&&i.attributionControl.addAttribution(this.getAttribution()),this.fire("add"),i.fire("layeradd",{layer:this})}}});be.include({addLayer:function(t){if(!t._layerAdd)throw new Error("The provided object is not a Layer.");var i=n(t);return this._layers[i]?this:(this._layers[i]=t,t._mapToAdd=this,t.beforeAdd&&t.beforeAdd(this),this.whenReady(t._layerAdd,t),this)},removeLayer:function(t){var i=n(t);return this._layers[i]?(this._loaded&&t.onRemove(this),t.getAttribution&&this.attributionControl&&this.attributionControl.removeAttribution(t.getAttribution()),delete this._layers[i],this._loaded&&(this.fire("layerremove",{layer:t}),t.fire("remove")),t._map=t._mapToAdd=null,this):this},hasLayer:function(t){return!!t&&n(t)in this._layers},eachLayer:function(t,i){for(var e in this._layers)t.call(i,this._layers[e]);return this},_addLayers:function(t){for(var i=0,e=(t=t?oi(t)?t:[t]:[]).length;i<e;i++)this.addLayer(t[i])},_addZoomLimit:function(t){!isNaN(t.options.maxZoom)&&isNaN(t.options.minZoom)||(this._zoomBoundLayers[n(t)]=t,this._updateZoomLevels())},_removeZoomLimit:function(t){var i=n(t);this._zoomBoundLayers[i]&&(delete this._zoomBoundLayers[i],this._updateZoomLevels())},_updateZoomLevels:function(){var t=1/0,i=-1/0,e=this._getZoomSpan();for(var n in this._zoomBoundLayers){var o=this._zoomBoundLayers[n].options;t=void 0===o.minZoom?t:Math.min(t,o.minZoom),i=void 0===o.maxZoom?i:Math.max(i,o.maxZoom)}this._layersMaxZoom=i===-1/0?void 0:i,this._layersMinZoom=t===1/0?void 0:t,e!==this._getZoomSpan()&&this.fire("zoomlevelschange"),void 0===this.options.maxZoom&&this._layersMaxZoom&&this.getZoom()>this._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()<this._layersMinZoom&&this.setZoom(this._layersMinZoom)}});var Ge=qe.extend({initialize:function(t,i){l(this,i),this._layers={};var e,n;if(t)for(e=0,n=t.length;e<n;e++)this.addLayer(t[e])},addLayer:function(t){var i=this.getLayerId(t);return this._layers[i]=t,this._map&&this._map.addLayer(t),this},removeLayer:function(t){var i=t in this._layers?t:this.getLayerId(t);return this._map&&this._layers[i]&&this._map.removeLayer(this._layers[i]),delete this._layers[i],this},hasLayer:function(t){return!!t&&(t in this._layers||this.getLayerId(t)in this._layers)},clearLayers:function(){return this.eachLayer(this.removeLayer,this)},invoke:function(t){var i,e,n=Array.prototype.slice.call(arguments,1);for(i in this._layers)(e=this._layers[i])[t]&&e[t].apply(e,n);return this},onAdd:function(t){this.eachLayer(t.addLayer,t)},onRemove:function(t){this.eachLayer(t.removeLayer,t)},eachLayer:function(t,i){for(var e in this._layers)t.call(i,this._layers[e]);return this},getLayer:function(t){return this._layers[t]},getLayers:function(){var t=[];return this.eachLayer(t.push,t),t},setZIndex:function(t){return this.invoke("setZIndex",t)},getLayerId:function(t){return n(t)}}),Ke=Ge.extend({addLayer:function(t){return this.hasLayer(t)?this:(t.addEventParent(this),Ge.prototype.addLayer.call(this,t),this.fire("layeradd",{layer:t}))},removeLayer:function(t){return this.hasLayer(t)?(t in this._layers&&(t=this._layers[t]),t.removeEventParent(this),Ge.prototype.removeLayer.call(this,t),this.fire("layerremove",{layer:t})):this},setStyle:function(t){return this.invoke("setStyle",t)},bringToFront:function(){return this.invoke("bringToFront")},bringToBack:function(){return this.invoke("bringToBack")},getBounds:function(){var t=new T;for(var i in this._layers){var e=this._layers[i];t.extend(e.getBounds?e.getBounds():e.getLatLng())}return t}}),Ye=v.extend({options:{popupAnchor:[0,0],tooltipAnchor:[0,0]},initialize:function(t){l(this,t)},createIcon:function(t){return this._createIcon("icon",t)},createShadow:function(t){return this._createIcon("shadow",t)},_createIcon:function(t,i){var e=this._getIconUrl(t);if(!e){if("icon"===t)throw new Error("iconUrl not set in Icon options (see the docs).");return null}var n=this._createImg(e,i&&"IMG"===i.tagName?i:null);return this._setIconStyles(n,t),n},_setIconStyles:function(t,i){var e=this.options,n=e[i+"Size"];"number"==typeof n&&(n=[n,n]);var o=w(n),s=w("shadow"===i&&e.shadowAnchor||e.iconAnchor||o&&o.divideBy(2,!0));t.className="leaflet-marker-"+i+" "+(e.className||""),s&&(t.style.marginLeft=-s.x+"px",t.style.marginTop=-s.y+"px"),o&&(t.style.width=o.x+"px",t.style.height=o.y+"px")},_createImg:function(t,i){return i=i||document.createElement("img"),i.src=t,i},_getIconUrl:function(t){return Yi&&this.options[t+"RetinaUrl"]||this.options[t+"Url"]}}),Xe=Ye.extend({options:{iconUrl:"marker-icon.png",iconRetinaUrl:"marker-icon-2x.png",shadowUrl:"marker-shadow.png",iconSize:[25,41],iconAnchor:[12,41],popupAnchor:[1,-34],tooltipAnchor:[16,-28],shadowSize:[41,41]},_getIconUrl:function(t){return Xe.imagePath||(Xe.imagePath=this._detectIconPath()),(this.options.imagePath||Xe.imagePath)+Ye.prototype._getIconUrl.call(this,t)},_detectIconPath:function(){var t=G("div","leaflet-default-icon-path",document.body),i=q(t,"background-image")||q(t,"backgroundImage");return document.body.removeChild(t),i=null===i||0!==i.indexOf("url")?"":i.replace(/^url\(["']?/,"").replace(/marker-icon\.png["']?\)$/,"")}}),Je=Ee.extend({initialize:function(t){this._marker=t},addHooks:function(){var t=this._marker._icon;this._draggable||(this._draggable=new Re(t,t,!0)),this._draggable.on({dragstart:this._onDragStart,predrag:this._onPreDrag,drag:this._onDrag,dragend:this._onDragEnd},this).enable(),Q(t,"leaflet-marker-draggable")},removeHooks:function(){this._draggable.off({dragstart:this._onDragStart,predrag:this._onPreDrag,drag:this._onDrag,dragend:this._onDragEnd},this).disable(),this._marker._icon&&tt(this._marker._icon,"leaflet-marker-draggable")},moved:function(){return this._draggable&&this._draggable._moved},_adjustPan:function(t){var i=this._marker,e=i._map,n=this._marker.options.autoPanSpeed,o=this._marker.options.autoPanPadding,s=ht(i._icon),r=e.getPixelBounds(),a=e.getPixelOrigin(),h=b(r.min._subtract(a).add(o),r.max._subtract(a).subtract(o));if(!h.contains(s)){var u=w((Math.max(h.max.x,s.x)-h.max.x)/(r.max.x-h.max.x)-(Math.min(h.min.x,s.x)-h.min.x)/(r.min.x-h.min.x),(Math.max(h.max.y,s.y)-h.max.y)/(r.max.y-h.max.y)-(Math.min(h.min.y,s.y)-h.min.y)/(r.min.y-h.min.y)).multiplyBy(n);e.panBy(u,{animate:!1}),this._draggable._newPos._add(u),this._draggable._startPos._add(u),at(i._icon,this._draggable._newPos),this._onDrag(t),this._panRequest=f(this._adjustPan.bind(this,t))}},_onDragStart:function(){this._oldLatLng=this._marker.getLatLng(),this._marker.closePopup().fire("movestart").fire("dragstart")},_onPreDrag:function(t){this._marker.options.autoPan&&(g(this._panRequest),this._panRequest=f(this._adjustPan.bind(this,t)))},_onDrag:function(t){var i=this._marker,e=i._shadow,n=ht(i._icon),o=i._map.layerPointToLatLng(n);e&&at(e,n),i._latlng=o,t.latlng=o,t.oldLatLng=this._oldLatLng,i.fire("move",t).fire("drag",t)},_onDragEnd:function(t){g(this._panRequest),delete this._oldLatLng,this._marker.fire("moveend").fire("dragend",t)}}),$e=qe.extend({options:{icon:new Xe,interactive:!0,keyboard:!0,title:"",alt:"",zIndexOffset:0,opacity:1,riseOnHover:!1,riseOffset:250,pane:"markerPane",bubblingMouseEvents:!1,draggable:!1,autoPan:!1,autoPanPadding:[50,50],autoPanSpeed:10},initialize:function(t,i){l(this,i),this._latlng=C(t)},onAdd:function(t){this._zoomAnimated=this._zoomAnimated&&t.options.markerZoomAnimation,this._zoomAnimated&&t.on("zoomanim",this._animateZoom,this),this._initIcon(),this.update()},onRemove:function(t){this.dragging&&this.dragging.enabled()&&(this.options.draggable=!0,this.dragging.removeHooks()),delete this.dragging,this._zoomAnimated&&t.off("zoomanim",this._animateZoom,this),this._removeIcon(),this._removeShadow()},getEvents:function(){return{zoom:this.update,viewreset:this.update}},getLatLng:function(){return this._latlng},setLatLng:function(t){var i=this._latlng;return this._latlng=C(t),this.update(),this.fire("move",{oldLatLng:i,latlng:this._latlng})},setZIndexOffset:function(t){return this.options.zIndexOffset=t,this.update()},setIcon:function(t){return this.options.icon=t,this._map&&(this._initIcon(),this.update()),this._popup&&this.bindPopup(this._popup,this._popup.options),this},getElement:function(){return this._icon},update:function(){if(this._icon&&this._map){var t=this._map.latLngToLayerPoint(this._latlng).round();this._setPos(t)}return this},_initIcon:function(){var t=this.options,i="leaflet-zoom-"+(this._zoomAnimated?"animated":"hide"),e=t.icon.createIcon(this._icon),n=!1;e!==this._icon&&(this._icon&&this._removeIcon(),n=!0,t.title&&(e.title=t.title),"IMG"===e.tagName&&(e.alt=t.alt||"")),Q(e,i),t.keyboard&&(e.tabIndex="0"),this._icon=e,t.riseOnHover&&this.on({mouseover:this._bringToFront,mouseout:this._resetZIndex});var o=t.icon.createShadow(this._shadow),s=!1;o!==this._shadow&&(this._removeShadow(),s=!0),o&&(Q(o,i),o.alt=""),this._shadow=o,t.opacity<1&&this._updateOpacity(),n&&this.getPane().appendChild(this._icon),this._initInteraction(),o&&s&&this.getPane("shadowPane").appendChild(this._shadow)},_removeIcon:function(){this.options.riseOnHover&&this.off({mouseover:this._bringToFront,mouseout:this._resetZIndex}),K(this._icon),this.removeInteractiveTarget(this._icon),this._icon=null},_removeShadow:function(){this._shadow&&K(this._shadow),this._shadow=null},_setPos:function(t){at(this._icon,t),this._shadow&&at(this._shadow,t),this._zIndex=t.y+this.options.zIndexOffset,this._resetZIndex()},_updateZIndex:function(t){this._icon.style.zIndex=this._zIndex+t},_animateZoom:function(t){var i=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center).round();this._setPos(i)},_initInteraction:function(){if(this.options.interactive&&(Q(this._icon,"leaflet-interactive"),this.addInteractiveTarget(this._icon),Je)){var t=this.options.draggable;this.dragging&&(t=this.dragging.enabled(),this.dragging.disable()),this.dragging=new Je(this),t&&this.dragging.enable()}},setOpacity:function(t){return this.options.opacity=t,this._map&&this._updateOpacity(),this},_updateOpacity:function(){var t=this.options.opacity;nt(this._icon,t),this._shadow&&nt(this._shadow,t)},_bringToFront:function(){this._updateZIndex(this.options.riseOffset)},_resetZIndex:function(){this._updateZIndex(0)},_getPopupAnchor:function(){return this.options.icon.options.popupAnchor},_getTooltipAnchor:function(){return this.options.icon.options.tooltipAnchor}}),Qe=qe.extend({options:{stroke:!0,color:"#3388ff",weight:3,opacity:1,lineCap:"round",lineJoin:"round",dashArray:null,dashOffset:null,fill:!1,fillColor:null,fillOpacity:.2,fillRule:"evenodd",interactive:!0,bubblingMouseEvents:!0},beforeAdd:function(t){this._renderer=t.getRenderer(this)},onAdd:function(){this._renderer._initPath(this),this._reset(),this._renderer._addPath(this)},onRemove:function(){this._renderer._removePath(this)},redraw:function(){return this._map&&this._renderer._updatePath(this),this},setStyle:function(t){return l(this,t),this._renderer&&this._renderer._updateStyle(this),this},bringToFront:function(){return this._renderer&&this._renderer._bringToFront(this),this},bringToBack:function(){return this._renderer&&this._renderer._bringToBack(this),this},getElement:function(){return this._path},_reset:function(){this._project(),this._update()},_clickTolerance:function(){return(this.options.stroke?this.options.weight/2:0)+this._renderer.options.tolerance}}),tn=Qe.extend({options:{fill:!0,radius:10},initialize:function(t,i){l(this,i),this._latlng=C(t),this._radius=this.options.radius},setLatLng:function(t){return this._latlng=C(t),this.redraw(),this.fire("move",{latlng:this._latlng})},getLatLng:function(){return this._latlng},setRadius:function(t){return this.options.radius=this._radius=t,this.redraw()},getRadius:function(){return this._radius},setStyle:function(t){var i=t&&t.radius||this._radius;return Qe.prototype.setStyle.call(this,t),this.setRadius(i),this},_project:function(){this._point=this._map.latLngToLayerPoint(this._latlng),this._updateBounds()},_updateBounds:function(){var t=this._radius,i=this._radiusY||t,e=this._clickTolerance(),n=[t+e,i+e];this._pxBounds=new P(this._point.subtract(n),this._point.add(n))},_update:function(){this._map&&this._updatePath()},_updatePath:function(){this._renderer._updateCircle(this)},_empty:function(){return this._radius&&!this._renderer._bounds.intersects(this._pxBounds)},_containsPoint:function(t){return t.distanceTo(this._point)<=this._radius+this._clickTolerance()}}),en=tn.extend({initialize:function(t,e,n){if("number"==typeof e&&(e=i({},n,{radius:e})),l(this,e),this._latlng=C(t),isNaN(this.options.radius))throw new Error("Circle radius cannot be NaN");this._mRadius=this.options.radius},setRadius:function(t){return this._mRadius=t,this.redraw()},getRadius:function(){return this._mRadius},getBounds:function(){var t=[this._radius,this._radiusY||this._radius];return new T(this._map.layerPointToLatLng(this._point.subtract(t)),this._map.layerPointToLatLng(this._point.add(t)))},setStyle:Qe.prototype.setStyle,_project:function(){var t=this._latlng.lng,i=this._latlng.lat,e=this._map,n=e.options.crs;if(n.distance===pi.distance){var o=Math.PI/180,s=this._mRadius/pi.R/o,r=e.project([i+s,t]),a=e.project([i-s,t]),h=r.add(a).divideBy(2),u=e.unproject(h).lat,l=Math.acos((Math.cos(s*o)-Math.sin(i*o)*Math.sin(u*o))/(Math.cos(i*o)*Math.cos(u*o)))/o;(isNaN(l)||0===l)&&(l=s/Math.cos(Math.PI/180*i)),this._point=h.subtract(e.getPixelOrigin()),this._radius=isNaN(l)?0:h.x-e.project([u,t-l]).x,this._radiusY=h.y-r.y}else{var c=n.unproject(n.project(this._latlng).subtract([this._mRadius,0]));this._point=e.latLngToLayerPoint(this._latlng),this._radius=this._point.x-e.latLngToLayerPoint(c).x}this._updateBounds()}}),nn=Qe.extend({options:{smoothFactor:1,noClip:!1},initialize:function(t,i){l(this,i),this._setLatLngs(t)},getLatLngs:function(){return this._latlngs},setLatLngs:function(t){return this._setLatLngs(t),this.redraw()},isEmpty:function(){return!this._latlngs.length},closestLayerPoint:function(t){for(var i,e,n=1/0,o=null,s=Dt,r=0,a=this._parts.length;r<a;r++)for(var h=this._parts[r],u=1,l=h.length;u<l;u++){var c=s(t,i=h[u-1],e=h[u],!0);c<n&&(n=c,o=s(t,i,e))}return o&&(o.distance=Math.sqrt(n)),o},getCenter:function(){if(!this._map)throw new Error("Must add layer to map before using getCenter()");var t,i,e,n,o,s,r,a=this._rings[0],h=a.length;if(!h)return null;for(t=0,i=0;t<h-1;t++)i+=a[t].distanceTo(a[t+1])/2;if(0===i)return this._map.layerPointToLatLng(a[0]);for(t=0,n=0;t<h-1;t++)if(o=a[t],s=a[t+1],e=o.distanceTo(s),(n+=e)>i)return r=(n-i)/e,this._map.layerPointToLatLng([s.x-r*(s.x-o.x),s.y-r*(s.y-o.y)])},getBounds:function(){return this._bounds},addLatLng:function(t,i){return i=i||this._defaultShape(),t=C(t),i.push(t),this._bounds.extend(t),this.redraw()},_setLatLngs:function(t){this._bounds=new T,this._latlngs=this._convertLatLngs(t)},_defaultShape:function(){return jt(this._latlngs)?this._latlngs:this._latlngs[0]},_convertLatLngs:function(t){for(var i=[],e=jt(t),n=0,o=t.length;n<o;n++)e?(i[n]=C(t[n]),this._bounds.extend(i[n])):i[n]=this._convertLatLngs(t[n]);return i},_project:function(){var t=new P;this._rings=[],this._projectLatlngs(this._latlngs,this._rings,t);var i=this._clickTolerance(),e=new x(i,i);this._bounds.isValid()&&t.isValid()&&(t.min._subtract(e),t.max._add(e),this._pxBounds=t)},_projectLatlngs:function(t,i,e){var n,o,s=t[0]instanceof M,r=t.length;if(s){for(o=[],n=0;n<r;n++)o[n]=this._map.latLngToLayerPoint(t[n]),e.extend(o[n]);i.push(o)}else for(n=0;n<r;n++)this._projectLatlngs(t[n],i,e)},_clipPoints:function(){var t=this._renderer._bounds;if(this._parts=[],this._pxBounds&&this._pxBounds.intersects(t))if(this.options.noClip)this._parts=this._rings;else{var i,e,n,o,s,r,a,h=this._parts;for(i=0,n=0,o=this._rings.length;i<o;i++)for(e=0,s=(a=this._rings[i]).length;e<s-1;e++)(r=It(a[e],a[e+1],t,e,!0))&&(h[n]=h[n]||[],h[n].push(r[0]),r[1]===a[e+1]&&e!==s-2||(h[n].push(r[1]),n++))}},_simplifyPoints:function(){for(var t=this._parts,i=this.options.smoothFactor,e=0,n=t.length;e<n;e++)t[e]=Zt(t[e],i)},_update:function(){this._map&&(this._clipPoints(),this._simplifyPoints(),this._updatePath())},_updatePath:function(){this._renderer._updatePoly(this)},_containsPoint:function(t,i){var e,n,o,s,r,a,h=this._clickTolerance();if(!this._pxBounds||!this._pxBounds.contains(t))return!1;for(e=0,s=this._parts.length;e<s;e++)for(n=0,o=(r=(a=this._parts[e]).length)-1;n<r;o=n++)if((i||0!==n)&&Et(t,a[o],a[n])<=h)return!0;return!1}});nn._flat=Wt;var on=nn.extend({options:{fill:!0},isEmpty:function(){return!this._latlngs.length||!this._latlngs[0].length},getCenter:function(){if(!this._map)throw new Error("Must add layer to map before using getCenter()");var t,i,e,n,o,s,r,a,h,u=this._rings[0],l=u.length;if(!l)return null;for(s=r=a=0,t=0,i=l-1;t<l;i=t++)e=u[t],n=u[i],o=e.y*n.x-n.y*e.x,r+=(e.x+n.x)*o,a+=(e.y+n.y)*o,s+=3*o;return h=0===s?u[0]:[r/s,a/s],this._map.layerPointToLatLng(h)},_convertLatLngs:function(t){var i=nn.prototype._convertLatLngs.call(this,t),e=i.length;return e>=2&&i[0]instanceof M&&i[0].equals(i[e-1])&&i.pop(),i},_setLatLngs:function(t){nn.prototype._setLatLngs.call(this,t),jt(this._latlngs)&&(this._latlngs=[this._latlngs])},_defaultShape:function(){return jt(this._latlngs[0])?this._latlngs[0]:this._latlngs[0][0]},_clipPoints:function(){var t=this._renderer._bounds,i=this.options.weight,e=new x(i,i);if(t=new P(t.min.subtract(e),t.max.add(e)),this._parts=[],this._pxBounds&&this._pxBounds.intersects(t))if(this.options.noClip)this._parts=this._rings;else for(var n,o=0,s=this._rings.length;o<s;o++)(n=Ht(this._rings[o],t,!0)).length&&this._parts.push(n)},_updatePath:function(){this._renderer._updatePoly(this,!0)},_containsPoint:function(t){var i,e,n,o,s,r,a,h,u=!1;if(!this._pxBounds||!this._pxBounds.contains(t))return!1;for(o=0,a=this._parts.length;o<a;o++)for(s=0,r=(h=(i=this._parts[o]).length)-1;s<h;r=s++)e=i[s],n=i[r],e.y>t.y!=n.y>t.y&&t.x<(n.x-e.x)*(t.y-e.y)/(n.y-e.y)+e.x&&(u=!u);return u||nn.prototype._containsPoint.call(this,t,!0)}}),sn=Ke.extend({initialize:function(t,i){l(this,i),this._layers={},t&&this.addData(t)},addData:function(t){var i,e,n,o=oi(t)?t:t.features;if(o){for(i=0,e=o.length;i<e;i++)((n=o[i]).geometries||n.geometry||n.features||n.coordinates)&&this.addData(n);return this}var s=this.options;if(s.filter&&!s.filter(t))return this;var r=Ft(t,s);return r?(r.feature=Yt(t),r.defaultOptions=r.options,this.resetStyle(r),s.onEachFeature&&s.onEachFeature(t,r),this.addLayer(r)):this},resetStyle:function(t){return t.options=i({},t.defaultOptions),this._setLayerStyle(t,this.options.style),this},setStyle:function(t){return this.eachLayer(function(i){this._setLayerStyle(i,t)},this)},_setLayerStyle:function(t,i){"function"==typeof i&&(i=i(t.feature)),t.setStyle&&t.setStyle(i)}}),rn={toGeoJSON:function(t){return Kt(this,{type:"Point",coordinates:qt(this.getLatLng(),t)})}};$e.include(rn),en.include(rn),tn.include(rn),nn.include({toGeoJSON:function(t){var i=!jt(this._latlngs),e=Gt(this._latlngs,i?1:0,!1,t);return Kt(this,{type:(i?"Multi":"")+"LineString",coordinates:e})}}),on.include({toGeoJSON:function(t){var i=!jt(this._latlngs),e=i&&!jt(this._latlngs[0]),n=Gt(this._latlngs,e?2:i?1:0,!0,t);return i||(n=[n]),Kt(this,{type:(e?"Multi":"")+"Polygon",coordinates:n})}}),Ge.include({toMultiPoint:function(t){var i=[];return this.eachLayer(function(e){i.push(e.toGeoJSON(t).geometry.coordinates)}),Kt(this,{type:"MultiPoint",coordinates:i})},toGeoJSON:function(t){var i=this.feature&&this.feature.geometry&&this.feature.geometry.type;if("MultiPoint"===i)return this.toMultiPoint(t);var e="GeometryCollection"===i,n=[];return this.eachLayer(function(i){if(i.toGeoJSON){var o=i.toGeoJSON(t);if(e)n.push(o.geometry);else{var s=Yt(o);"FeatureCollection"===s.type?n.push.apply(n,s.features):n.push(s)}}}),e?Kt(this,{geometries:n,type:"GeometryCollection"}):{type:"FeatureCollection",features:n}}});var an=Xt,hn=qe.extend({options:{opacity:1,alt:"",interactive:!1,crossOrigin:!1,errorOverlayUrl:"",zIndex:1,className:""},initialize:function(t,i,e){this._url=t,this._bounds=z(i),l(this,e)},onAdd:function(){this._image||(this._initImage(),this.options.opacity<1&&this._updateOpacity()),this.options.interactive&&(Q(this._image,"leaflet-interactive"),this.addInteractiveTarget(this._image)),this.getPane().appendChild(this._image),this._reset()},onRemove:function(){K(this._image),this.options.interactive&&this.removeInteractiveTarget(this._image)},setOpacity:function(t){return this.options.opacity=t,this._image&&this._updateOpacity(),this},setStyle:function(t){return t.opacity&&this.setOpacity(t.opacity),this},bringToFront:function(){return this._map&&X(this._image),this},bringToBack:function(){return this._map&&J(this._image),this},setUrl:function(t){return this._url=t,this._image&&(this._image.src=t),this},setBounds:function(t){return this._bounds=z(t),this._map&&this._reset(),this},getEvents:function(){var t={zoom:this._reset,viewreset:this._reset};return this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},setZIndex:function(t){return this.options.zIndex=t,this._updateZIndex(),this},getBounds:function(){return this._bounds},getElement:function(){return this._image},_initImage:function(){var t="IMG"===this._url.tagName,i=this._image=t?this._url:G("img");Q(i,"leaflet-image-layer"),this._zoomAnimated&&Q(i,"leaflet-zoom-animated"),this.options.className&&Q(i,this.options.className),i.onselectstart=r,i.onmousemove=r,i.onload=e(this.fire,this,"load"),i.onerror=e(this._overlayOnError,this,"error"),(this.options.crossOrigin||""===this.options.crossOrigin)&&(i.crossOrigin=!0===this.options.crossOrigin?"":this.options.crossOrigin),this.options.zIndex&&this._updateZIndex(),t?this._url=i.src:(i.src=this._url,i.alt=this.options.alt)},_animateZoom:function(t){var i=this._map.getZoomScale(t.zoom),e=this._map._latLngBoundsToNewLayerBounds(this._bounds,t.zoom,t.center).min;rt(this._image,e,i)},_reset:function(){var t=this._image,i=new P(this._map.latLngToLayerPoint(this._bounds.getNorthWest()),this._map.latLngToLayerPoint(this._bounds.getSouthEast())),e=i.getSize();at(t,i.min),t.style.width=e.x+"px",t.style.height=e.y+"px"},_updateOpacity:function(){nt(this._image,this.options.opacity)},_updateZIndex:function(){this._image&&void 0!==this.options.zIndex&&null!==this.options.zIndex&&(this._image.style.zIndex=this.options.zIndex)},_overlayOnError:function(){this.fire("error");var t=this.options.errorOverlayUrl;t&&this._url!==t&&(this._url=t,this._image.src=t)}}),un=hn.extend({options:{autoplay:!0,loop:!0},_initImage:function(){var t="VIDEO"===this._url.tagName,i=this._image=t?this._url:G("video");if(Q(i,"leaflet-image-layer"),this._zoomAnimated&&Q(i,"leaflet-zoom-animated"),i.onselectstart=r,i.onmousemove=r,i.onloadeddata=e(this.fire,this,"load"),t){for(var n=i.getElementsByTagName("source"),o=[],s=0;s<n.length;s++)o.push(n[s].src);this._url=n.length>0?o:[i.src]}else{oi(this._url)||(this._url=[this._url]),i.autoplay=!!this.options.autoplay,i.loop=!!this.options.loop;for(var a=0;a<this._url.length;a++){var h=G("source");h.src=this._url[a],i.appendChild(h)}}}}),ln=qe.extend({options:{offset:[0,7],className:"",pane:"popupPane"},initialize:function(t,i){l(this,t),this._source=i},onAdd:function(t){this._zoomAnimated=t._zoomAnimated,this._container||this._initLayout(),t._fadeAnimated&&nt(this._container,0),clearTimeout(this._removeTimeout),this.getPane().appendChild(this._container),this.update(),t._fadeAnimated&&nt(this._container,1),this.bringToFront()},onRemove:function(t){t._fadeAnimated?(nt(this._container,0),this._removeTimeout=setTimeout(e(K,void 0,this._container),200)):K(this._container)},getLatLng:function(){return this._latlng},setLatLng:function(t){return this._latlng=C(t),this._map&&(this._updatePosition(),this._adjustPan()),this},getContent:function(){return this._content},setContent:function(t){return this._content=t,this.update(),this},getElement:function(){return this._container},update:function(){this._map&&(this._container.style.visibility="hidden",this._updateContent(),this._updateLayout(),this._updatePosition(),this._container.style.visibility="",this._adjustPan())},getEvents:function(){var t={zoom:this._updatePosition,viewreset:this._updatePosition};return this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},isOpen:function(){return!!this._map&&this._map.hasLayer(this)},bringToFront:function(){return this._map&&X(this._container),this},bringToBack:function(){return this._map&&J(this._container),this},_updateContent:function(){if(this._content){var t=this._contentNode,i="function"==typeof this._content?this._content(this._source||this):this._content;if("string"==typeof i)t.innerHTML=i;else{for(;t.hasChildNodes();)t.removeChild(t.firstChild);t.appendChild(i)}this.fire("contentupdate")}},_updatePosition:function(){if(this._map){var t=this._map.latLngToLayerPoint(this._latlng),i=w(this.options.offset),e=this._getAnchor();this._zoomAnimated?at(this._container,t.add(e)):i=i.add(t).add(e);var n=this._containerBottom=-i.y,o=this._containerLeft=-Math.round(this._containerWidth/2)+i.x;this._container.style.bottom=n+"px",this._container.style.left=o+"px"}},_getAnchor:function(){return[0,0]}}),cn=ln.extend({options:{maxWidth:300,minWidth:50,maxHeight:null,autoPan:!0,autoPanPaddingTopLeft:null,autoPanPaddingBottomRight:null,autoPanPadding:[5,5],keepInView:!1,closeButton:!0,autoClose:!0,closeOnEscapeKey:!0,className:""},openOn:function(t){return t.openPopup(this),this},onAdd:function(t){ln.prototype.onAdd.call(this,t),t.fire("popupopen",{popup:this}),this._source&&(this._source.fire("popupopen",{popup:this},!0),this._source instanceof Qe||this._source.on("preclick",yt))},onRemove:function(t){ln.prototype.onRemove.call(this,t),t.fire("popupclose",{popup:this}),this._source&&(this._source.fire("popupclose",{popup:this},!0),this._source instanceof Qe||this._source.off("preclick",yt))},getEvents:function(){var t=ln.prototype.getEvents.call(this);return(void 0!==this.options.closeOnClick?this.options.closeOnClick:this._map.options.closePopupOnClick)&&(t.preclick=this._close),this.options.keepInView&&(t.moveend=this._adjustPan),t},_close:function(){this._map&&this._map.closePopup(this)},_initLayout:function(){var t="leaflet-popup",i=this._container=G("div",t+" "+(this.options.className||"")+" leaflet-zoom-animated"),e=this._wrapper=G("div",t+"-content-wrapper",i);if(this._contentNode=G("div",t+"-content",e),wt(e),xt(this._contentNode),mt(e,"contextmenu",yt),this._tipContainer=G("div",t+"-tip-container",i),this._tip=G("div",t+"-tip",this._tipContainer),this.options.closeButton){var n=this._closeButton=G("a",t+"-close-button",i);n.href="#close",n.innerHTML="&#215;",mt(n,"click",this._onCloseButtonClick,this)}},_updateLayout:function(){var t=this._contentNode,i=t.style;i.width="",i.whiteSpace="nowrap";var e=t.offsetWidth;e=Math.min(e,this.options.maxWidth),e=Math.max(e,this.options.minWidth),i.width=e+1+"px",i.whiteSpace="",i.height="";var n=t.offsetHeight,o=this.options.maxHeight;o&&n>o?(i.height=o+"px",Q(t,"leaflet-popup-scrolled")):tt(t,"leaflet-popup-scrolled"),this._containerWidth=this._container.offsetWidth},_animateZoom:function(t){var i=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center),e=this._getAnchor();at(this._container,i.add(e))},_adjustPan:function(){if(!(!this.options.autoPan||this._map._panAnim&&this._map._panAnim._inProgress)){var t=this._map,i=parseInt(q(this._container,"marginBottom"),10)||0,e=this._container.offsetHeight+i,n=this._containerWidth,o=new x(this._containerLeft,-e-this._containerBottom);o._add(ht(this._container));var s=t.layerPointToContainerPoint(o),r=w(this.options.autoPanPadding),a=w(this.options.autoPanPaddingTopLeft||r),h=w(this.options.autoPanPaddingBottomRight||r),u=t.getSize(),l=0,c=0;s.x+n+h.x>u.x&&(l=s.x+n-u.x+h.x),s.x-l-a.x<0&&(l=s.x-a.x),s.y+e+h.y>u.y&&(c=s.y+e-u.y+h.y),s.y-c-a.y<0&&(c=s.y-a.y),(l||c)&&t.fire("autopanstart").panBy([l,c])}},_onCloseButtonClick:function(t){this._close(),Lt(t)},_getAnchor:function(){return w(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}});be.mergeOptions({closePopupOnClick:!0}),be.include({openPopup:function(t,i,e){return t instanceof cn||(t=new cn(e).setContent(t)),i&&t.setLatLng(i),this.hasLayer(t)?this:(this._popup&&this._popup.options.autoClose&&this.closePopup(),this._popup=t,this.addLayer(t))},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&this.removeLayer(t),this}}),qe.include({bindPopup:function(t,i){return t instanceof cn?(l(t,i),this._popup=t,t._source=this):(this._popup&&!i||(this._popup=new cn(i,this)),this._popup.setContent(t)),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t,i){if(t instanceof qe||(i=t,t=this),t instanceof Ke)for(var e in this._layers){t=this._layers[e];break}return i||(i=t.getCenter?t.getCenter():t.getLatLng()),this._popup&&this._map&&(this._popup._source=t,this._popup.update(),this._map.openPopup(this._popup,i)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(t){return this._popup&&(this._popup._map?this.closePopup():this.openPopup(t)),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var i=t.layer||t.target;this._popup&&this._map&&(Lt(t),i instanceof Qe?this.openPopup(t.layer||t.target,t.latlng):this._map.hasLayer(this._popup)&&this._popup._source===i?this.closePopup():this.openPopup(i,t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}});var _n=ln.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,interactive:!1,opacity:.9},onAdd:function(t){ln.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&this._source.fire("tooltipopen",{tooltip:this},!0)},onRemove:function(t){ln.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&this._source.fire("tooltipclose",{tooltip:this},!0)},getEvents:function(){var t=ln.prototype.getEvents.call(this);return qi&&!this.options.permanent&&(t.preclick=this._close),t},_close:function(){this._map&&this._map.closeTooltip(this)},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=G("div",t)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var i=this._map,e=this._container,n=i.latLngToContainerPoint(i.getCenter()),o=i.layerPointToContainerPoint(t),s=this.options.direction,r=e.offsetWidth,a=e.offsetHeight,h=w(this.options.offset),u=this._getAnchor();"top"===s?t=t.add(w(-r/2+h.x,-a+h.y+u.y,!0)):"bottom"===s?t=t.subtract(w(r/2-h.x,-h.y,!0)):"center"===s?t=t.subtract(w(r/2+h.x,a/2-u.y+h.y,!0)):"right"===s||"auto"===s&&o.x<n.x?(s="right",t=t.add(w(h.x+u.x,u.y-a/2+h.y,!0))):(s="left",t=t.subtract(w(r+u.x-h.x,a/2-u.y-h.y,!0))),tt(e,"leaflet-tooltip-right"),tt(e,"leaflet-tooltip-left"),tt(e,"leaflet-tooltip-top"),tt(e,"leaflet-tooltip-bottom"),Q(e,"leaflet-tooltip-"+s),at(e,t)},_updatePosition:function(){var t=this._map.latLngToLayerPoint(this._latlng);this._setPosition(t)},setOpacity:function(t){this.options.opacity=t,this._container&&nt(this._container,t)},_animateZoom:function(t){var i=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center);this._setPosition(i)},_getAnchor:function(){return w(this._source&&this._source._getTooltipAnchor&&!this.options.sticky?this._source._getTooltipAnchor():[0,0])}});be.include({openTooltip:function(t,i,e){return t instanceof _n||(t=new _n(e).setContent(t)),i&&t.setLatLng(i),this.hasLayer(t)?this:this.addLayer(t)},closeTooltip:function(t){return t&&this.removeLayer(t),this}}),qe.include({bindTooltip:function(t,i){return t instanceof _n?(l(t,i),this._tooltip=t,t._source=this):(this._tooltip&&!i||(this._tooltip=new _n(i,this)),this._tooltip.setContent(t)),this._initTooltipInteractions(),this._tooltip.options.permanent&&this._map&&this._map.hasLayer(this)&&this.openTooltip(),this},unbindTooltip:function(){return this._tooltip&&(this._initTooltipInteractions(!0),this.closeTooltip(),this._tooltip=null),this},_initTooltipInteractions:function(t){if(t||!this._tooltipHandlersAdded){var i=t?"off":"on",e={remove:this.closeTooltip,move:this._moveTooltip};this._tooltip.options.permanent?e.add=this._openTooltip:(e.mouseover=this._openTooltip,e.mouseout=this.closeTooltip,this._tooltip.options.sticky&&(e.mousemove=this._moveTooltip),qi&&(e.click=this._openTooltip)),this[i](e),this._tooltipHandlersAdded=!t}},openTooltip:function(t,i){if(t instanceof qe||(i=t,t=this),t instanceof Ke)for(var e in this._layers){t=this._layers[e];break}return i||(i=t.getCenter?t.getCenter():t.getLatLng()),this._tooltip&&this._map&&(this._tooltip._source=t,this._tooltip.update(),this._map.openTooltip(this._tooltip,i),this._tooltip.options.interactive&&this._tooltip._container&&(Q(this._tooltip._container,"leaflet-clickable"),this.addInteractiveTarget(this._tooltip._container))),this},closeTooltip:function(){return this._tooltip&&(this._tooltip._close(),this._tooltip.options.interactive&&this._tooltip._container&&(tt(this._tooltip._container,"leaflet-clickable"),this.removeInteractiveTarget(this._tooltip._container))),this},toggleTooltip:function(t){return this._tooltip&&(this._tooltip._map?this.closeTooltip():this.openTooltip(t)),this},isTooltipOpen:function(){return this._tooltip.isOpen()},setTooltipContent:function(t){return this._tooltip&&this._tooltip.setContent(t),this},getTooltip:function(){return this._tooltip},_openTooltip:function(t){var i=t.layer||t.target;this._tooltip&&this._map&&this.openTooltip(i,this._tooltip.options.sticky?t.latlng:void 0)},_moveTooltip:function(t){var i,e,n=t.latlng;this._tooltip.options.sticky&&t.originalEvent&&(i=this._map.mouseEventToContainerPoint(t.originalEvent),e=this._map.containerPointToLayerPoint(i),n=this._map.layerPointToLatLng(e)),this._tooltip.setLatLng(n)}});var dn=Ye.extend({options:{iconSize:[12,12],html:!1,bgPos:null,className:"leaflet-div-icon"},createIcon:function(t){var i=t&&"DIV"===t.tagName?t:document.createElement("div"),e=this.options;if(i.innerHTML=!1!==e.html?e.html:"",e.bgPos){var n=w(e.bgPos);i.style.backgroundPosition=-n.x+"px "+-n.y+"px"}return this._setIconStyles(i,"icon"),i},createShadow:function(){return null}});Ye.Default=Xe;var pn=qe.extend({options:{tileSize:256,opacity:1,updateWhenIdle:Wi,updateWhenZooming:!0,updateInterval:200,zIndex:1,bounds:null,minZoom:0,maxZoom:void 0,maxNativeZoom:void 0,minNativeZoom:void 0,noWrap:!1,pane:"tilePane",className:"",keepBuffer:2},initialize:function(t){l(this,t)},onAdd:function(){this._initContainer(),this._levels={},this._tiles={},this._resetView(),this._update()},beforeAdd:function(t){t._addZoomLimit(this)},onRemove:function(t){this._removeAllTiles(),K(this._container),t._removeZoomLimit(this),this._container=null,this._tileZoom=void 0},bringToFront:function(){return this._map&&(X(this._container),this._setAutoZIndex(Math.max)),this},bringToBack:function(){return this._map&&(J(this._container),this._setAutoZIndex(Math.min)),this},getContainer:function(){return this._container},setOpacity:function(t){return this.options.opacity=t,this._updateOpacity(),this},setZIndex:function(t){return this.options.zIndex=t,this._updateZIndex(),this},isLoading:function(){return this._loading},redraw:function(){return this._map&&(this._removeAllTiles(),this._update()),this},getEvents:function(){var t={viewprereset:this._invalidateAll,viewreset:this._resetView,zoom:this._resetView,moveend:this._onMoveEnd};return this.options.updateWhenIdle||(this._onMove||(this._onMove=o(this._onMoveEnd,this.options.updateInterval,this)),t.move=this._onMove),this._zoomAnimated&&(t.zoomanim=this._animateZoom),t},createTile:function(){return document.createElement("div")},getTileSize:function(){var t=this.options.tileSize;return t instanceof x?t:new x(t,t)},_updateZIndex:function(){this._container&&void 0!==this.options.zIndex&&null!==this.options.zIndex&&(this._container.style.zIndex=this.options.zIndex)},_setAutoZIndex:function(t){for(var i,e=this.getPane().children,n=-t(-1/0,1/0),o=0,s=e.length;o<s;o++)i=e[o].style.zIndex,e[o]!==this._container&&i&&(n=t(n,+i));isFinite(n)&&(this.options.zIndex=n+t(-1,1),this._updateZIndex())},_updateOpacity:function(){if(this._map&&!Li){nt(this._container,this.options.opacity);var t=+new Date,i=!1,e=!1;for(var n in this._tiles){var o=this._tiles[n];if(o.current&&o.loaded){var s=Math.min(1,(t-o.loaded)/200);nt(o.el,s),s<1?i=!0:(o.active?e=!0:this._onOpaqueTile(o),o.active=!0)}}e&&!this._noPrune&&this._pruneTiles(),i&&(g(this._fadeFrame),this._fadeFrame=f(this._updateOpacity,this))}},_onOpaqueTile:r,_initContainer:function(){this._container||(this._container=G("div","leaflet-layer "+(this.options.className||"")),this._updateZIndex(),this.options.opacity<1&&this._updateOpacity(),this.getPane().appendChild(this._container))},_updateLevels:function(){var t=this._tileZoom,i=this.options.maxZoom;if(void 0!==t){for(var e in this._levels)this._levels[e].el.children.length||e===t?(this._levels[e].el.style.zIndex=i-Math.abs(t-e),this._onUpdateLevel(e)):(K(this._levels[e].el),this._removeTilesAtZoom(e),this._onRemoveLevel(e),delete this._levels[e]);var n=this._levels[t],o=this._map;return n||((n=this._levels[t]={}).el=G("div","leaflet-tile-container leaflet-zoom-animated",this._container),n.el.style.zIndex=i,n.origin=o.project(o.unproject(o.getPixelOrigin()),t).round(),n.zoom=t,this._setZoomTransform(n,o.getCenter(),o.getZoom()),n.el.offsetWidth,this._onCreateLevel(n)),this._level=n,n}},_onUpdateLevel:r,_onRemoveLevel:r,_onCreateLevel:r,_pruneTiles:function(){if(this._map){var t,i,e=this._map.getZoom();if(e>this.options.maxZoom||e<this.options.minZoom)this._removeAllTiles();else{for(t in this._tiles)(i=this._tiles[t]).retain=i.current;for(t in this._tiles)if((i=this._tiles[t]).current&&!i.active){var n=i.coords;this._retainParent(n.x,n.y,n.z,n.z-5)||this._retainChildren(n.x,n.y,n.z,n.z+2)}for(t in this._tiles)this._tiles[t].retain||this._removeTile(t)}}},_removeTilesAtZoom:function(t){for(var i in this._tiles)this._tiles[i].coords.z===t&&this._removeTile(i)},_removeAllTiles:function(){for(var t in this._tiles)this._removeTile(t)},_invalidateAll:function(){for(var t in this._levels)K(this._levels[t].el),this._onRemoveLevel(t),delete this._levels[t];this._removeAllTiles(),this._tileZoom=void 0},_retainParent:function(t,i,e,n){var o=Math.floor(t/2),s=Math.floor(i/2),r=e-1,a=new x(+o,+s);a.z=+r;var h=this._tileCoordsToKey(a),u=this._tiles[h];return u&&u.active?(u.retain=!0,!0):(u&&u.loaded&&(u.retain=!0),r>n&&this._retainParent(o,s,r,n))},_retainChildren:function(t,i,e,n){for(var o=2*t;o<2*t+2;o++)for(var s=2*i;s<2*i+2;s++){var r=new x(o,s);r.z=e+1;var a=this._tileCoordsToKey(r),h=this._tiles[a];h&&h.active?h.retain=!0:(h&&h.loaded&&(h.retain=!0),e+1<n&&this._retainChildren(o,s,e+1,n))}},_resetView:function(t){var i=t&&(t.pinch||t.flyTo);this._setView(this._map.getCenter(),this._map.getZoom(),i,i)},_animateZoom:function(t){this._setView(t.center,t.zoom,!0,t.noUpdate)},_clampZoom:function(t){var i=this.options;return void 0!==i.minNativeZoom&&t<i.minNativeZoom?i.minNativeZoom:void 0!==i.maxNativeZoom&&i.maxNativeZoom<t?i.maxNativeZoom:t},_setView:function(t,i,e,n){var o=this._clampZoom(Math.round(i));(void 0!==this.options.maxZoom&&o>this.options.maxZoom||void 0!==this.options.minZoom&&o<this.options.minZoom)&&(o=void 0);var s=this.options.updateWhenZooming&&o!==this._tileZoom;n&&!s||(this._tileZoom=o,this._abortLoading&&this._abortLoading(),this._updateLevels(),this._resetGrid(),void 0!==o&&this._update(t),e||this._pruneTiles(),this._noPrune=!!e),this._setZoomTransforms(t,i)},_setZoomTransforms:function(t,i){for(var e in this._levels)this._setZoomTransform(this._levels[e],t,i)},_setZoomTransform:function(t,i,e){var n=this._map.getZoomScale(e,t.zoom),o=t.origin.multiplyBy(n).subtract(this._map._getNewPixelOrigin(i,e)).round();ji?rt(t.el,o,n):at(t.el,o)},_resetGrid:function(){var t=this._map,i=t.options.crs,e=this._tileSize=this.getTileSize(),n=this._tileZoom,o=this._map.getPixelWorldBounds(this._tileZoom);o&&(this._globalTileRange=this._pxBoundsToTileRange(o)),this._wrapX=i.wrapLng&&!this.options.noWrap&&[Math.floor(t.project([0,i.wrapLng[0]],n).x/e.x),Math.ceil(t.project([0,i.wrapLng[1]],n).x/e.y)],this._wrapY=i.wrapLat&&!this.options.noWrap&&[Math.floor(t.project([i.wrapLat[0],0],n).y/e.x),Math.ceil(t.project([i.wrapLat[1],0],n).y/e.y)]},_onMoveEnd:function(){this._map&&!this._map._animatingZoom&&this._update()},_getTiledPixelBounds:function(t){var i=this._map,e=i._animatingZoom?Math.max(i._animateToZoom,i.getZoom()):i.getZoom(),n=i.getZoomScale(e,this._tileZoom),o=i.project(t,this._tileZoom).floor(),s=i.getSize().divideBy(2*n);return new P(o.subtract(s),o.add(s))},_update:function(t){var i=this._map;if(i){var e=this._clampZoom(i.getZoom());if(void 0===t&&(t=i.getCenter()),void 0!==this._tileZoom){var n=this._getTiledPixelBounds(t),o=this._pxBoundsToTileRange(n),s=o.getCenter(),r=[],a=this.options.keepBuffer,h=new P(o.getBottomLeft().subtract([a,-a]),o.getTopRight().add([a,-a]));if(!(isFinite(o.min.x)&&isFinite(o.min.y)&&isFinite(o.max.x)&&isFinite(o.max.y)))throw new Error("Attempted to load an infinite number of tiles");for(var u in this._tiles){var l=this._tiles[u].coords;l.z===this._tileZoom&&h.contains(new x(l.x,l.y))||(this._tiles[u].current=!1)}if(Math.abs(e-this._tileZoom)>1)this._setView(t,e);else{for(var c=o.min.y;c<=o.max.y;c++)for(var _=o.min.x;_<=o.max.x;_++){var d=new x(_,c);if(d.z=this._tileZoom,this._isValidTile(d)){var p=this._tiles[this._tileCoordsToKey(d)];p?p.current=!0:r.push(d)}}if(r.sort(function(t,i){return t.distanceTo(s)-i.distanceTo(s)}),0!==r.length){this._loading||(this._loading=!0,this.fire("loading"));var m=document.createDocumentFragment();for(_=0;_<r.length;_++)this._addTile(r[_],m);this._level.el.appendChild(m)}}}}},_isValidTile:function(t){var i=this._map.options.crs;if(!i.infinite){var e=this._globalTileRange;if(!i.wrapLng&&(t.x<e.min.x||t.x>e.max.x)||!i.wrapLat&&(t.y<e.min.y||t.y>e.max.y))return!1}if(!this.options.bounds)return!0;var n=this._tileCoordsToBounds(t);return z(this.options.bounds).overlaps(n)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var i=this._map,e=this.getTileSize(),n=t.scaleBy(e),o=n.add(e);return[i.unproject(n,t.z),i.unproject(o,t.z)]},_tileCoordsToBounds:function(t){var i=this._tileCoordsToNwSe(t),e=new T(i[0],i[1]);return this.options.noWrap||(e=this._map.wrapLatLngBounds(e)),e},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var i=t.split(":"),e=new x(+i[0],+i[1]);return e.z=+i[2],e},_removeTile:function(t){var i=this._tiles[t];i&&(K(i.el),delete this._tiles[t],this.fire("tileunload",{tile:i.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){Q(t,"leaflet-tile");var i=this.getTileSize();t.style.width=i.x+"px",t.style.height=i.y+"px",t.onselectstart=r,t.onmousemove=r,Li&&this.options.opacity<1&&nt(t,this.options.opacity),zi&&!Mi&&(t.style.WebkitBackfaceVisibility="hidden")},_addTile:function(t,i){var n=this._getTilePos(t),o=this._tileCoordsToKey(t),s=this.createTile(this._wrapCoords(t),e(this._tileReady,this,t));this._initTile(s),this.createTile.length<2&&f(e(this._tileReady,this,t,null,s)),at(s,n),this._tiles[o]={el:s,coords:t,current:!0},i.appendChild(s),this.fire("tileloadstart",{tile:s,coords:t})},_tileReady:function(t,i,n){i&&this.fire("tileerror",{error:i,tile:n,coords:t});var o=this._tileCoordsToKey(t);(n=this._tiles[o])&&(n.loaded=+new Date,this._map._fadeAnimated?(nt(n.el,0),g(this._fadeFrame),this._fadeFrame=f(this._updateOpacity,this)):(n.active=!0,this._pruneTiles()),i||(Q(n.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:n.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),Li||!this._map._fadeAnimated?f(this._pruneTiles,this):setTimeout(e(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var i=new x(this._wrapX?s(t.x,this._wrapX):t.x,this._wrapY?s(t.y,this._wrapY):t.y);return i.z=t.z,i},_pxBoundsToTileRange:function(t){var i=this.getTileSize();return new P(t.min.unscaleBy(i).floor(),t.max.unscaleBy(i).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}}),mn=pn.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1},initialize:function(t,i){this._url=t,(i=l(this,i)).detectRetina&&Yi&&i.maxZoom>0&&(i.tileSize=Math.floor(i.tileSize/2),i.zoomReverse?(i.zoomOffset--,i.minZoom++):(i.zoomOffset++,i.maxZoom--),i.minZoom=Math.max(0,i.minZoom)),"string"==typeof i.subdomains&&(i.subdomains=i.subdomains.split("")),zi||this.on("tileunload",this._onTileRemove)},setUrl:function(t,i){return this._url=t,i||this.redraw(),this},createTile:function(t,i){var n=document.createElement("img");return mt(n,"load",e(this._tileOnLoad,this,i,n)),mt(n,"error",e(this._tileOnError,this,i,n)),(this.options.crossOrigin||""===this.options.crossOrigin)&&(n.crossOrigin=!0===this.options.crossOrigin?"":this.options.crossOrigin),n.alt="",n.setAttribute("role","presentation"),n.src=this.getTileUrl(t),n},getTileUrl:function(t){var e={r:Yi?"@2x":"",s:this._getSubdomain(t),x:t.x,y:t.y,z:this._getZoomForUrl()};if(this._map&&!this._map.options.crs.infinite){var n=this._globalTileRange.max.y-t.y;this.options.tms&&(e.y=n),e["-y"]=n}return _(this._url,i(e,this.options))},_tileOnLoad:function(t,i){Li?setTimeout(e(t,this,null,i),0):t(null,i)},_tileOnError:function(t,i,e){var n=this.options.errorTileUrl;n&&i.getAttribute("src")!==n&&(i.src=n),t(e,i)},_onTileRemove:function(t){t.tile.onload=null},_getZoomForUrl:function(){var t=this._tileZoom,i=this.options.maxZoom,e=this.options.zoomReverse,n=this.options.zoomOffset;return e&&(t=i-t),t+n},_getSubdomain:function(t){var i=Math.abs(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[i]},_abortLoading:function(){var t,i;for(t in this._tiles)this._tiles[t].coords.z!==this._tileZoom&&((i=this._tiles[t].el).onload=r,i.onerror=r,i.complete||(i.src=si,K(i),delete this._tiles[t]))},_removeTile:function(t){var i=this._tiles[t];if(i)return Si||i.el.setAttribute("src",si),pn.prototype._removeTile.call(this,t)},_tileReady:function(t,i,e){if(this._map&&(!e||e.getAttribute("src")!==si))return pn.prototype._tileReady.call(this,t,i,e)}}),fn=mn.extend({defaultWmsParams:{service:"WMS",request:"GetMap",layers:"",styles:"",format:"image/jpeg",transparent:!1,version:"1.1.1"},options:{crs:null,uppercase:!1},initialize:function(t,e){this._url=t;var n=i({},this.defaultWmsParams);for(var o in e)o in this.options||(n[o]=e[o]);var s=(e=l(this,e)).detectRetina&&Yi?2:1,r=this.getTileSize();n.width=r.x*s,n.height=r.y*s,this.wmsParams=n},onAdd:function(t){this._crs=this.options.crs||t.options.crs,this._wmsVersion=parseFloat(this.wmsParams.version);var i=this._wmsVersion>=1.3?"crs":"srs";this.wmsParams[i]=this._crs.code,mn.prototype.onAdd.call(this,t)},getTileUrl:function(t){var i=this._tileCoordsToNwSe(t),e=this._crs,n=b(e.project(i[0]),e.project(i[1])),o=n.min,s=n.max,r=(this._wmsVersion>=1.3&&this._crs===Ue?[o.y,o.x,s.y,s.x]:[o.x,o.y,s.x,s.y]).join(","),a=mn.prototype.getTileUrl.call(this,t);return a+c(this.wmsParams,a,this.options.uppercase)+(this.options.uppercase?"&BBOX=":"&bbox=")+r},setParams:function(t,e){return i(this.wmsParams,t),e||this.redraw(),this}});mn.WMS=fn,Jt.wms=function(t,i){return new fn(t,i)};var gn=qe.extend({options:{padding:.1,tolerance:0},initialize:function(t){l(this,t),n(this),this._layers=this._layers||{}},onAdd:function(){this._container||(this._initContainer(),this._zoomAnimated&&Q(this._container,"leaflet-zoom-animated")),this.getPane().appendChild(this._container),this._update(),this.on("update",this._updatePaths,this)},onRemove:function(){this.off("update",this._updatePaths,this),this._destroyContainer()},getEvents:function(){var t={viewreset:this._reset,zoom:this._onZoom,moveend:this._update,zoomend:this._onZoomEnd};return this._zoomAnimated&&(t.zoomanim=this._onAnimZoom),t},_onAnimZoom:function(t){this._updateTransform(t.center,t.zoom)},_onZoom:function(){this._updateTransform(this._map.getCenter(),this._map.getZoom())},_updateTransform:function(t,i){var e=this._map.getZoomScale(i,this._zoom),n=ht(this._container),o=this._map.getSize().multiplyBy(.5+this.options.padding),s=this._map.project(this._center,i),r=this._map.project(t,i).subtract(s),a=o.multiplyBy(-e).add(n).add(o).subtract(r);ji?rt(this._container,a,e):at(this._container,a)},_reset:function(){this._update(),this._updateTransform(this._center,this._zoom);for(var t in this._layers)this._layers[t]._reset()},_onZoomEnd:function(){for(var t in this._layers)this._layers[t]._project()},_updatePaths:function(){for(var t in this._layers)this._layers[t]._update()},_update:function(){var t=this.options.padding,i=this._map.getSize(),e=this._map.containerPointToLayerPoint(i.multiplyBy(-t)).round();this._bounds=new P(e,e.add(i.multiplyBy(1+2*t)).round()),this._center=this._map.getCenter(),this._zoom=this._map.getZoom()}}),vn=gn.extend({getEvents:function(){var t=gn.prototype.getEvents.call(this);return t.viewprereset=this._onViewPreReset,t},_onViewPreReset:function(){this._postponeUpdatePaths=!0},onAdd:function(){gn.prototype.onAdd.call(this),this._draw()},_initContainer:function(){var t=this._container=document.createElement("canvas");mt(t,"mousemove",o(this._onMouseMove,32,this),this),mt(t,"click dblclick mousedown mouseup contextmenu",this._onClick,this),mt(t,"mouseout",this._handleMouseOut,this),this._ctx=t.getContext("2d")},_destroyContainer:function(){g(this._redrawRequest),delete this._ctx,K(this._container),ft(this._container),delete this._container},_updatePaths:function(){if(!this._postponeUpdatePaths){this._redrawBounds=null;for(var t in this._layers)this._layers[t]._update();this._redraw()}},_update:function(){if(!this._map._animatingZoom||!this._bounds){this._drawnLayers={},gn.prototype._update.call(this);var t=this._bounds,i=this._container,e=t.getSize(),n=Yi?2:1;at(i,t.min),i.width=n*e.x,i.height=n*e.y,i.style.width=e.x+"px",i.style.height=e.y+"px",Yi&&this._ctx.scale(2,2),this._ctx.translate(-t.min.x,-t.min.y),this.fire("update")}},_reset:function(){gn.prototype._reset.call(this),this._postponeUpdatePaths&&(this._postponeUpdatePaths=!1,this._updatePaths())},_initPath:function(t){this._updateDashArray(t),this._layers[n(t)]=t;var i=t._order={layer:t,prev:this._drawLast,next:null};this._drawLast&&(this._drawLast.next=i),this._drawLast=i,this._drawFirst=this._drawFirst||this._drawLast},_addPath:function(t){this._requestRedraw(t)},_removePath:function(t){var i=t._order,e=i.next,o=i.prev;e?e.prev=o:this._drawLast=o,o?o.next=e:this._drawFirst=e,delete this._drawnLayers[t._leaflet_id],delete t._order,delete this._layers[n(t)],this._requestRedraw(t)},_updatePath:function(t){this._extendRedrawBounds(t),t._project(),t._update(),this._requestRedraw(t)},_updateStyle:function(t){this._updateDashArray(t),this._requestRedraw(t)},_updateDashArray:function(t){if("string"==typeof t.options.dashArray){var i,e=t.options.dashArray.split(/[, ]+/),n=[];for(i=0;i<e.length;i++)n.push(Number(e[i]));t.options._dashArray=n}else t.options._dashArray=t.options.dashArray},_requestRedraw:function(t){this._map&&(this._extendRedrawBounds(t),this._redrawRequest=this._redrawRequest||f(this._redraw,this))},_extendRedrawBounds:function(t){if(t._pxBounds){var i=(t.options.weight||0)+1;this._redrawBounds=this._redrawBounds||new P,this._redrawBounds.extend(t._pxBounds.min.subtract([i,i])),this._redrawBounds.extend(t._pxBounds.max.add([i,i]))}},_redraw:function(){this._redrawRequest=null,this._redrawBounds&&(this._redrawBounds.min._floor(),this._redrawBounds.max._ceil()),this._clear(),this._draw(),this._redrawBounds=null},_clear:function(){var t=this._redrawBounds;if(t){var i=t.getSize();this._ctx.clearRect(t.min.x,t.min.y,i.x,i.y)}else this._ctx.clearRect(0,0,this._container.width,this._container.height)},_draw:function(){var t,i=this._redrawBounds;if(this._ctx.save(),i){var e=i.getSize();this._ctx.beginPath(),this._ctx.rect(i.min.x,i.min.y,e.x,e.y),this._ctx.clip()}this._drawing=!0;for(var n=this._drawFirst;n;n=n.next)t=n.layer,(!i||t._pxBounds&&t._pxBounds.intersects(i))&&t._updatePath();this._drawing=!1,this._ctx.restore()},_updatePoly:function(t,i){if(this._drawing){var e,n,o,s,r=t._parts,a=r.length,h=this._ctx;if(a){for(this._drawnLayers[t._leaflet_id]=t,h.beginPath(),e=0;e<a;e++){for(n=0,o=r[e].length;n<o;n++)s=r[e][n],h[n?"lineTo":"moveTo"](s.x,s.y);i&&h.closePath()}this._fillStroke(h,t)}}},_updateCircle:function(t){if(this._drawing&&!t._empty()){var i=t._point,e=this._ctx,n=Math.max(Math.round(t._radius),1),o=(Math.max(Math.round(t._radiusY),1)||n)/n;this._drawnLayers[t._leaflet_id]=t,1!==o&&(e.save(),e.scale(1,o)),e.beginPath(),e.arc(i.x,i.y/o,n,0,2*Math.PI,!1),1!==o&&e.restore(),this._fillStroke(e,t)}},_fillStroke:function(t,i){var e=i.options;e.fill&&(t.globalAlpha=e.fillOpacity,t.fillStyle=e.fillColor||e.color,t.fill(e.fillRule||"evenodd")),e.stroke&&0!==e.weight&&(t.setLineDash&&t.setLineDash(i.options&&i.options._dashArray||[]),t.globalAlpha=e.opacity,t.lineWidth=e.weight,t.strokeStyle=e.color,t.lineCap=e.lineCap,t.lineJoin=e.lineJoin,t.stroke())},_onClick:function(t){for(var i,e,n=this._map.mouseEventToLayerPoint(t),o=this._drawFirst;o;o=o.next)(i=o.layer).options.interactive&&i._containsPoint(n)&&!this._map._draggableMoved(i)&&(e=i);e&&(zt(t),this._fireEvent([e],t))},_onMouseMove:function(t){if(this._map&&!this._map.dragging.moving()&&!this._map._animatingZoom){var i=this._map.mouseEventToLayerPoint(t);this._handleMouseHover(t,i)}},_handleMouseOut:function(t){var i=this._hoveredLayer;i&&(tt(this._container,"leaflet-interactive"),this._fireEvent([i],t,"mouseout"),this._hoveredLayer=null)},_handleMouseHover:function(t,i){for(var e,n,o=this._drawFirst;o;o=o.next)(e=o.layer).options.interactive&&e._containsPoint(i)&&(n=e);n!==this._hoveredLayer&&(this._handleMouseOut(t),n&&(Q(this._container,"leaflet-interactive"),this._fireEvent([n],t,"mouseover"),this._hoveredLayer=n)),this._hoveredLayer&&this._fireEvent([this._hoveredLayer],t)},_fireEvent:function(t,i,e){this._map._fireDOMEvent(i,e||i.type,t)},_bringToFront:function(t){var i=t._order,e=i.next,n=i.prev;e&&(e.prev=n,n?n.next=e:e&&(this._drawFirst=e),i.prev=this._drawLast,this._drawLast.next=i,i.next=null,this._drawLast=i,this._requestRedraw(t))},_bringToBack:function(t){var i=t._order,e=i.next,n=i.prev;n&&(n.next=e,e?e.prev=n:n&&(this._drawLast=n),i.prev=null,i.next=this._drawFirst,this._drawFirst.prev=i,this._drawFirst=i,this._requestRedraw(t))}}),yn=function(){try{return document.namespaces.add("lvml","urn:schemas-microsoft-com:vml"),function(t){return document.createElement("<lvml:"+t+' class="lvml">')}}catch(t){return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),xn={_initContainer:function(){this._container=G("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(gn.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var i=t._container=yn("shape");Q(i,"leaflet-vml-shape "+(this.options.className||"")),i.coordsize="1 1",t._path=yn("path"),i.appendChild(t._path),this._updateStyle(t),this._layers[n(t)]=t},_addPath:function(t){var i=t._container;this._container.appendChild(i),t.options.interactive&&t.addInteractiveTarget(i)},_removePath:function(t){var i=t._container;K(i),t.removeInteractiveTarget(i),delete this._layers[n(t)]},_updateStyle:function(t){var i=t._stroke,e=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(i||(i=t._stroke=yn("stroke")),o.appendChild(i),i.weight=n.weight+"px",i.color=n.color,i.opacity=n.opacity,n.dashArray?i.dashStyle=oi(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):i.dashStyle="",i.endcap=n.lineCap.replace("butt","flat"),i.joinstyle=n.lineJoin):i&&(o.removeChild(i),t._stroke=null),n.fill?(e||(e=t._fill=yn("fill")),o.appendChild(e),e.color=n.fillColor||n.color,e.opacity=n.fillOpacity):e&&(o.removeChild(e),t._fill=null)},_updateCircle:function(t){var i=t._point.round(),e=Math.round(t._radius),n=Math.round(t._radiusY||e);this._setPath(t,t._empty()?"M0 0":"AL "+i.x+","+i.y+" "+e+","+n+" 0,23592600")},_setPath:function(t,i){t._path.v=i},_bringToFront:function(t){X(t._container)},_bringToBack:function(t){J(t._container)}},wn=$i?yn:E,Pn=gn.extend({getEvents:function(){var t=gn.prototype.getEvents.call(this);return t.zoomstart=this._onZoomStart,t},_initContainer:function(){this._container=wn("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=wn("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){K(this._container),ft(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_onZoomStart:function(){this._update()},_update:function(){if(!this._map._animatingZoom||!this._bounds){gn.prototype._update.call(this);var t=this._bounds,i=t.getSize(),e=this._container;this._svgSize&&this._svgSize.equals(i)||(this._svgSize=i,e.setAttribute("width",i.x),e.setAttribute("height",i.y)),at(e,t.min),e.setAttribute("viewBox",[t.min.x,t.min.y,i.x,i.y].join(" ")),this.fire("update")}},_initPath:function(t){var i=t._path=wn("path");t.options.className&&Q(i,t.options.className),t.options.interactive&&Q(i,"leaflet-interactive"),this._updateStyle(t),this._layers[n(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){K(t._path),t.removeInteractiveTarget(t._path),delete this._layers[n(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var i=t._path,e=t.options;i&&(e.stroke?(i.setAttribute("stroke",e.color),i.setAttribute("stroke-opacity",e.opacity),i.setAttribute("stroke-width",e.weight),i.setAttribute("stroke-linecap",e.lineCap),i.setAttribute("stroke-linejoin",e.lineJoin),e.dashArray?i.setAttribute("stroke-dasharray",e.dashArray):i.removeAttribute("stroke-dasharray"),e.dashOffset?i.setAttribute("stroke-dashoffset",e.dashOffset):i.removeAttribute("stroke-dashoffset")):i.setAttribute("stroke","none"),e.fill?(i.setAttribute("fill",e.fillColor||e.color),i.setAttribute("fill-opacity",e.fillOpacity),i.setAttribute("fill-rule",e.fillRule||"evenodd")):i.setAttribute("fill","none"))},_updatePoly:function(t,i){this._setPath(t,k(t._parts,i))},_updateCircle:function(t){var i=t._point,e=Math.max(Math.round(t._radius),1),n="a"+e+","+(Math.max(Math.round(t._radiusY),1)||e)+" 0 1,0 ",o=t._empty()?"M0 0":"M"+(i.x-e)+","+i.y+n+2*e+",0 "+n+2*-e+",0 ";this._setPath(t,o)},_setPath:function(t,i){t._path.setAttribute("d",i)},_bringToFront:function(t){X(t._path)},_bringToBack:function(t){J(t._path)}});$i&&Pn.include(xn),be.include({getRenderer:function(t){var i=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer;return i||(i=this._renderer=this._createRenderer()),this.hasLayer(i)||this.addLayer(i),i},_getPaneRenderer:function(t){if("overlayPane"===t||void 0===t)return!1;var i=this._paneRenderers[t];return void 0===i&&(i=this._createRenderer({pane:t}),this._paneRenderers[t]=i),i},_createRenderer:function(t){return this.options.preferCanvas&&$t(t)||Qt(t)}});var Ln=on.extend({initialize:function(t,i){on.prototype.initialize.call(this,this._boundsToLatLngs(t),i)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return t=z(t),[t.getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});Pn.create=wn,Pn.pointsToPath=k,sn.geometryToLayer=Ft,sn.coordsToLatLng=Ut,sn.coordsToLatLngs=Vt,sn.latLngToCoords=qt,sn.latLngsToCoords=Gt,sn.getFeature=Kt,sn.asFeature=Yt,be.mergeOptions({boxZoom:!0});var bn=Ee.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){mt(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){ft(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){K(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),fi(),ut(),this._startPoint=this._map.mouseEventToContainerPoint(t),mt(document,{contextmenu:Lt,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=G("div","leaflet-zoom-box",this._container),Q(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var i=new P(this._point,this._startPoint),e=i.getSize();at(this._box,i.min),this._box.style.width=e.x+"px",this._box.style.height=e.y+"px"},_finish:function(){this._moved&&(K(this._box),tt(this._container,"leaflet-crosshair")),gi(),lt(),ft(document,{contextmenu:Lt,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){if((1===t.which||1===t.button)&&(this._finish(),this._moved)){this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(e(this._resetState,this),0);var i=new T(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point));this._map.fitBounds(i).fire("boxzoomend",{boxZoomBounds:i})}},_onKeyDown:function(t){27===t.keyCode&&this._finish()}});be.addInitHook("addHandler","boxZoom",bn),be.mergeOptions({doubleClickZoom:!0});var Tn=Ee.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var i=this._map,e=i.getZoom(),n=i.options.zoomDelta,o=t.originalEvent.shiftKey?e-n:e+n;"center"===i.options.doubleClickZoom?i.setZoom(o):i.setZoomAround(t.containerPoint,o)}});be.addInitHook("addHandler","doubleClickZoom",Tn),be.mergeOptions({dragging:!0,inertia:!Mi,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0});var zn=Ee.extend({addHooks:function(){if(!this._draggable){var t=this._map;this._draggable=new Re(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))}Q(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){tt(this._map._container,"leaflet-grab"),tt(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t=this._map;if(t._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity){var i=z(this._map.options.maxBounds);this._offsetLimit=b(this._map.latLngToContainerPoint(i.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(i.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))}else this._offsetLimit=null;t.fire("movestart").fire("dragstart"),t.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){if(this._map.options.inertia){var i=this._lastTime=+new Date,e=this._lastPos=this._draggable._absPos||this._draggable._newPos;this._positions.push(e),this._times.push(i),this._prunePositions(i)}this._map.fire("move",t).fire("drag",t)},_prunePositions:function(t){for(;this._positions.length>1&&t-this._times[0]>50;)this._positions.shift(),this._times.shift()},_onZoomEnd:function(){var t=this._map.getSize().divideBy(2),i=this._map.latLngToLayerPoint([0,0]);this._initialWorldOffset=i.subtract(t).x,this._worldWidth=this._map.getPixelWorldBounds().getSize().x},_viscousLimit:function(t,i){return t-(t-i)*this._viscosity},_onPreDragLimit:function(){if(this._viscosity&&this._offsetLimit){var t=this._draggable._newPos.subtract(this._draggable._startPos),i=this._offsetLimit;t.x<i.min.x&&(t.x=this._viscousLimit(t.x,i.min.x)),t.y<i.min.y&&(t.y=this._viscousLimit(t.y,i.min.y)),t.x>i.max.x&&(t.x=this._viscousLimit(t.x,i.max.x)),t.y>i.max.y&&(t.y=this._viscousLimit(t.y,i.max.y)),this._draggable._newPos=this._draggable._startPos.add(t)}},_onPreDragWrap:function(){var t=this._worldWidth,i=Math.round(t/2),e=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-i+e)%t+i-e,s=(n+i+e)%t-i-e,r=Math.abs(o+e)<Math.abs(s+e)?o:s;this._draggable._absPos=this._draggable._newPos.clone(),this._draggable._newPos.x=r},_onDragEnd:function(t){var i=this._map,e=i.options,n=!e.inertia||this._times.length<2;if(i.fire("dragend",t),n)i.fire("moveend");else{this._prunePositions(+new Date);var o=this._lastPos.subtract(this._positions[0]),s=(this._lastTime-this._times[0])/1e3,r=e.easeLinearity,a=o.multiplyBy(r/s),h=a.distanceTo([0,0]),u=Math.min(e.inertiaMaxSpeed,h),l=a.multiplyBy(u/h),c=u/(e.inertiaDeceleration*r),_=l.multiplyBy(-c/2).round();_.x||_.y?(_=i._limitOffset(_,i.options.maxBounds),f(function(){i.panBy(_,{duration:c,easeLinearity:r,noMoveStart:!0,animate:!0})})):i.fire("moveend")}}});be.addInitHook("addHandler","dragging",zn),be.mergeOptions({keyboard:!0,keyboardPanDelta:80});var Mn=Ee.extend({keyCodes:{left:[37],right:[39],down:[40],up:[38],zoomIn:[187,107,61,171],zoomOut:[189,109,54,173]},initialize:function(t){this._map=t,this._setPanDelta(t.options.keyboardPanDelta),this._setZoomDelta(t.options.zoomDelta)},addHooks:function(){var t=this._map._container;t.tabIndex<=0&&(t.tabIndex="0"),mt(t,{focus:this._onFocus,blur:this._onBlur,mousedown:this._onMouseDown},this),this._map.on({focus:this._addHooks,blur:this._removeHooks},this)},removeHooks:function(){this._removeHooks(),ft(this._map._container,{focus:this._onFocus,blur:this._onBlur,mousedown:this._onMouseDown},this),this._map.off({focus:this._addHooks,blur:this._removeHooks},this)},_onMouseDown:function(){if(!this._focused){var t=document.body,i=document.documentElement,e=t.scrollTop||i.scrollTop,n=t.scrollLeft||i.scrollLeft;this._map._container.focus(),window.scrollTo(n,e)}},_onFocus:function(){this._focused=!0,this._map.fire("focus")},_onBlur:function(){this._focused=!1,this._map.fire("blur")},_setPanDelta:function(t){var i,e,n=this._panKeys={},o=this.keyCodes;for(i=0,e=o.left.length;i<e;i++)n[o.left[i]]=[-1*t,0];for(i=0,e=o.right.length;i<e;i++)n[o.right[i]]=[t,0];for(i=0,e=o.down.length;i<e;i++)n[o.down[i]]=[0,t];for(i=0,e=o.up.length;i<e;i++)n[o.up[i]]=[0,-1*t]},_setZoomDelta:function(t){var i,e,n=this._zoomKeys={},o=this.keyCodes;for(i=0,e=o.zoomIn.length;i<e;i++)n[o.zoomIn[i]]=t;for(i=0,e=o.zoomOut.length;i<e;i++)n[o.zoomOut[i]]=-t},_addHooks:function(){mt(document,"keydown",this._onKeyDown,this)},_removeHooks:function(){ft(document,"keydown",this._onKeyDown,this)},_onKeyDown:function(t){if(!(t.altKey||t.ctrlKey||t.metaKey)){var i,e=t.keyCode,n=this._map;if(e in this._panKeys)n._panAnim&&n._panAnim._inProgress||(i=this._panKeys[e],t.shiftKey&&(i=w(i).multiplyBy(3)),n.panBy(i),n.options.maxBounds&&n.panInsideBounds(n.options.maxBounds));else if(e in this._zoomKeys)n.setZoom(n.getZoom()+(t.shiftKey?3:1)*this._zoomKeys[e]);else{if(27!==e||!n._popup||!n._popup.options.closeOnEscapeKey)return;n.closePopup()}Lt(t)}}});be.addInitHook("addHandler","keyboard",Mn),be.mergeOptions({scrollWheelZoom:!0,wheelDebounceTime:40,wheelPxPerZoomLevel:60});var Cn=Ee.extend({addHooks:function(){mt(this._map._container,"mousewheel",this._onWheelScroll,this),this._delta=0},removeHooks:function(){ft(this._map._container,"mousewheel",this._onWheelScroll,this)},_onWheelScroll:function(t){var i=Tt(t),n=this._map.options.wheelDebounceTime;this._delta+=i,this._lastMousePos=this._map.mouseEventToContainerPoint(t),this._startTime||(this._startTime=+new Date);var o=Math.max(n-(+new Date-this._startTime),0);clearTimeout(this._timer),this._timer=setTimeout(e(this._performZoom,this),o),Lt(t)},_performZoom:function(){var t=this._map,i=t.getZoom(),e=this._map.options.zoomSnap||0;t._stop();var n=this._delta/(4*this._map.options.wheelPxPerZoomLevel),o=4*Math.log(2/(1+Math.exp(-Math.abs(n))))/Math.LN2,s=e?Math.ceil(o/e)*e:o,r=t._limitZoom(i+(this._delta>0?s:-s))-i;this._delta=0,this._startTime=null,r&&("center"===t.options.scrollWheelZoom?t.setZoom(i+r):t.setZoomAround(this._lastMousePos,i+r))}});be.addInitHook("addHandler","scrollWheelZoom",Cn),be.mergeOptions({tap:!0,tapTolerance:15});var Sn=Ee.extend({addHooks:function(){mt(this._map._container,"touchstart",this._onDown,this)},removeHooks:function(){ft(this._map._container,"touchstart",this._onDown,this)},_onDown:function(t){if(t.touches){if(Pt(t),this._fireClick=!0,t.touches.length>1)return this._fireClick=!1,void clearTimeout(this._holdTimeout);var i=t.touches[0],n=i.target;this._startPos=this._newPos=new x(i.clientX,i.clientY),n.tagName&&"a"===n.tagName.toLowerCase()&&Q(n,"leaflet-active"),this._holdTimeout=setTimeout(e(function(){this._isTapValid()&&(this._fireClick=!1,this._onUp(),this._simulateEvent("contextmenu",i))},this),1e3),this._simulateEvent("mousedown",i),mt(document,{touchmove:this._onMove,touchend:this._onUp},this)}},_onUp:function(t){if(clearTimeout(this._holdTimeout),ft(document,{touchmove:this._onMove,touchend:this._onUp},this),this._fireClick&&t&&t.changedTouches){var i=t.changedTouches[0],e=i.target;e&&e.tagName&&"a"===e.tagName.toLowerCase()&&tt(e,"leaflet-active"),this._simulateEvent("mouseup",i),this._isTapValid()&&this._simulateEvent("click",i)}},_isTapValid:function(){return this._newPos.distanceTo(this._startPos)<=this._map.options.tapTolerance},_onMove:function(t){var i=t.touches[0];this._newPos=new x(i.clientX,i.clientY),this._simulateEvent("mousemove",i)},_simulateEvent:function(t,i){var e=document.createEvent("MouseEvents");e._simulated=!0,i.target._simulatedClick=!0,e.initMouseEvent(t,!0,!0,window,1,i.screenX,i.screenY,i.clientX,i.clientY,!1,!1,!1,!1,0,null),i.target.dispatchEvent(e)}});qi&&!Vi&&be.addInitHook("addHandler","tap",Sn),be.mergeOptions({touchZoom:qi&&!Mi,bounceAtZoomLimits:!0});var Zn=Ee.extend({addHooks:function(){Q(this._map._container,"leaflet-touch-zoom"),mt(this._map._container,"touchstart",this._onTouchStart,this)},removeHooks:function(){tt(this._map._container,"leaflet-touch-zoom"),ft(this._map._container,"touchstart",this._onTouchStart,this)},_onTouchStart:function(t){var i=this._map;if(t.touches&&2===t.touches.length&&!i._animatingZoom&&!this._zooming){var e=i.mouseEventToContainerPoint(t.touches[0]),n=i.mouseEventToContainerPoint(t.touches[1]);this._centerPoint=i.getSize()._divideBy(2),this._startLatLng=i.containerPointToLatLng(this._centerPoint),"center"!==i.options.touchZoom&&(this._pinchStartLatLng=i.containerPointToLatLng(e.add(n)._divideBy(2))),this._startDist=e.distanceTo(n),this._startZoom=i.getZoom(),this._moved=!1,this._zooming=!0,i._stop(),mt(document,"touchmove",this._onTouchMove,this),mt(document,"touchend",this._onTouchEnd,this),Pt(t)}},_onTouchMove:function(t){if(t.touches&&2===t.touches.length&&this._zooming){var i=this._map,n=i.mouseEventToContainerPoint(t.touches[0]),o=i.mouseEventToContainerPoint(t.touches[1]),s=n.distanceTo(o)/this._startDist;if(this._zoom=i.getScaleZoom(s,this._startZoom),!i.options.bounceAtZoomLimits&&(this._zoom<i.getMinZoom()&&s<1||this._zoom>i.getMaxZoom()&&s>1)&&(this._zoom=i._limitZoom(this._zoom)),"center"===i.options.touchZoom){if(this._center=this._startLatLng,1===s)return}else{var r=n._add(o)._divideBy(2)._subtract(this._centerPoint);if(1===s&&0===r.x&&0===r.y)return;this._center=i.unproject(i.project(this._pinchStartLatLng,this._zoom).subtract(r),this._zoom)}this._moved||(i._moveStart(!0,!1),this._moved=!0),g(this._animRequest);var a=e(i._move,i,this._center,this._zoom,{pinch:!0,round:!1});this._animRequest=f(a,this,!0),Pt(t)}},_onTouchEnd:function(){this._moved&&this._zooming?(this._zooming=!1,g(this._animRequest),ft(document,"touchmove",this._onTouchMove),ft(document,"touchend",this._onTouchEnd),this._map.options.zoomAnimation?this._map._animateZoom(this._center,this._map._limitZoom(this._zoom),!0,this._map.options.zoomSnap):this._map._resetView(this._center,this._map._limitZoom(this._zoom))):this._zooming=!1}});be.addInitHook("addHandler","touchZoom",Zn),be.BoxZoom=bn,be.DoubleClickZoom=Tn,be.Drag=zn,be.Keyboard=Mn,be.ScrollWheelZoom=Cn,be.Tap=Sn,be.TouchZoom=Zn,Object.freeze=ti,t.version="1.3.4+HEAD.0e566b2",t.Control=Te,t.control=ze,t.Browser=Qi,t.Evented=ci,t.Mixin=Ae,t.Util=ui,t.Class=v,t.Handler=Ee,t.extend=i,t.bind=e,t.stamp=n,t.setOptions=l,t.DomEvent=Pe,t.DomUtil=ve,t.PosAnimation=Le,t.Draggable=Re,t.LineUtil=Ne,t.PolyUtil=De,t.Point=x,t.point=w,t.Bounds=P,t.bounds=b,t.Transformation=S,t.transformation=Z,t.Projection=He,t.LatLng=M,t.latLng=C,t.LatLngBounds=T,t.latLngBounds=z,t.CRS=di,t.GeoJSON=sn,t.geoJSON=Xt,t.geoJson=an,t.Layer=qe,t.LayerGroup=Ge,t.layerGroup=function(t,i){return new Ge(t,i)},t.FeatureGroup=Ke,t.featureGroup=function(t){return new Ke(t)},t.ImageOverlay=hn,t.imageOverlay=function(t,i,e){return new hn(t,i,e)},t.VideoOverlay=un,t.videoOverlay=function(t,i,e){return new un(t,i,e)},t.DivOverlay=ln,t.Popup=cn,t.popup=function(t,i){return new cn(t,i)},t.Tooltip=_n,t.tooltip=function(t,i){return new _n(t,i)},t.Icon=Ye,t.icon=function(t){return new Ye(t)},t.DivIcon=dn,t.divIcon=function(t){return new dn(t)},t.Marker=$e,t.marker=function(t,i){return new $e(t,i)},t.TileLayer=mn,t.tileLayer=Jt,t.GridLayer=pn,t.gridLayer=function(t){return new pn(t)},t.SVG=Pn,t.svg=Qt,t.Renderer=gn,t.Canvas=vn,t.canvas=$t,t.Path=Qe,t.CircleMarker=tn,t.circleMarker=function(t,i){return new tn(t,i)},t.Circle=en,t.circle=function(t,i,e){return new en(t,i,e)},t.Polyline=nn,t.polyline=function(t,i){return new nn(t,i)},t.Polygon=on,t.polygon=function(t,i){return new on(t,i)},t.Rectangle=Ln,t.rectangle=function(t,i){return new Ln(t,i)},t.Map=be,t.map=function(t,i){return new be(t,i)};var En=window.L;t.noConflict=function(){return window.L=En,this},window.L=t}); \ No newline at end of file
diff --git a/www/wiki/extensions/Maps/resources/maps.common.js b/www/wiki/extensions/Maps/resources/maps.common.js
new file mode 100644
index 00000000..8e0180b4
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/maps.common.js
@@ -0,0 +1,4 @@
+window.maps = new ( function() {
+ this.googlemapsList = [];
+ this.leafletList = [];
+} )();
diff --git a/www/wiki/extensions/Maps/resources/maps.services.js b/www/wiki/extensions/Maps/resources/maps.services.js
new file mode 100644
index 00000000..a23ff0c8
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/maps.services.js
@@ -0,0 +1,86 @@
+/*global jQuery, mediaWiki, maps */
+/*global confirm */
+( function ( $, mw, maps ) {
+ 'use strict';
+
+ /**
+ * @since 3.5
+ *
+ * @param {object} container
+ * @return {this}
+ */
+ var services = function ( container ) {
+
+ if ( $.type( container ) !== 'object' ) {
+ throw new Error( 'The container is not of the correct type ' + $.type( container ) );
+ }
+
+ this.container = container;
+
+ return this;
+ };
+
+ /* Public methods */
+
+ services.prototype = {
+
+ constructor: services,
+
+ /**
+ * @since 3.5
+ *
+ * @param {string} service
+ */
+ render: function( service ) {
+ if ( service === 'googlemaps' || service === 'maps' || service === 'googlemaps3' ) {
+ this.google();
+ }
+
+ if ( service === 'leaflet' || service === 'leafletmaps' ) {
+ this.leaflet();
+ }
+ },
+
+ /**
+ * Google service
+ *
+ * @since 3.5
+ */
+ google: function() {
+
+ var self = this;
+
+ // https://www.mediawiki.org/wiki/ResourceLoader/Modules#mw.loader.using
+ mw.loader.using( 'ext.maps.googlemaps3' ).done( function () {
+
+ if ( typeof google === 'undefined' ) {
+ throw new Error( 'The google map service is unknown, please ensure that the API or module is loaded correctly.' );
+ }
+
+ self.container.find( '.maps-googlemaps3' ).each( function() {
+ var $this = $( this );
+ $this.googlemaps( $.parseJSON( $this.find( 'div').text() ) );
+ } );
+ } );
+ },
+
+ /**
+ * Leaflet service
+ *
+ * @since 3.5
+ */
+ leaflet: function() {
+ mw.loader.using( 'ext.maps.leaflet' ).done( function () {
+ $( '.maps-leaflet' ).each( function() {
+ var $this = $( this );
+ maps.leafletList.push(
+ $this.leafletmaps( $.parseJSON( $this.find( 'div').text() ) )
+ );
+ } );
+ } );
+ }
+ };
+
+ maps.services = services;
+
+}( jQuery, mediaWiki, maps ) );
diff --git a/www/wiki/extensions/Maps/resources/sm.common.js b/www/wiki/extensions/Maps/resources/sm.common.js
new file mode 100644
index 00000000..373c1dd9
--- /dev/null
+++ b/www/wiki/extensions/Maps/resources/sm.common.js
@@ -0,0 +1,76 @@
+/**
+ * JavaScript the Semantic Maps extension.
+ * @see https://www.mediawiki.org/wiki/Extension:Semantic_Maps
+ *
+ * @licence GNU GPL v2++
+ * @author Peter Grassberger < petertheone@gmail.com >
+ */
+window.sm = new ( function( $, mw ) {
+
+ this.buildQueryString = function( query, ajaxcoordproperty, top, right, bottom, left ) {
+ var isCompoundQuery = query.indexOf( '|' ) > -1;
+ var query = query.split( '|' );
+ $.each( query, function( index ) {
+ query[index] += ' [[' + ajaxcoordproperty + '::+]] ';
+ query[index] += '[[' + ajaxcoordproperty + '::>' + bottom + '°, ' + left + '°]] ';
+ query[index] += '[[' + ajaxcoordproperty + '::<' + top + '°, ' + right + '°]]';
+ if( !isCompoundQuery ) {
+ query[index] += '|?' + ajaxcoordproperty;
+ } else {
+ query[index] += ';?' + ajaxcoordproperty;
+ }
+ } );
+ return query.join( ' | ' );
+ };
+
+ /**
+ * Detects semicolons `;` not in square brackets `[]`.
+ *
+ * @param string
+ * @returns {boolean}
+ */
+ this.hasCompoundQuerySemicolon = function( string ) {
+ return /;(?![^[]*])/g.test( string );
+ };
+
+ this.sendQuery = function( query ) {
+ var action = this.hasCompoundQuerySemicolon( query ) ? 'compoundquery' : 'ask';
+ return $.ajax( {
+ method: 'GET',
+ url: mw.util.wikiScript( 'api' ),
+ data: {
+ 'action': action,
+ 'query': query,
+ 'format': 'json'
+ },
+ dataType: 'json'
+ } );
+ };
+
+ this.ajaxUpdateMarker = function( map, query, icon ) {
+ return this.sendQuery( query ).done( function( data ) {
+ if( !data.hasOwnProperty( 'query' ) ||
+ !data.query.hasOwnProperty( 'results' ) ) {
+ return;
+ }
+ // todo: don't remove and recreate all markers..
+ // only add new ones.
+ map.removeMarkers();
+ for( var property in data.query.results ) {
+ if( data.query.results.hasOwnProperty( property ) ) {
+ var location = data.query.results[property];
+ var coordinates = location.printouts[map.options.ajaxcoordproperty][0];
+ var markerOptions = {
+ lat: coordinates.lat,
+ lon: coordinates.lon,
+ title: location.fulltext,
+ text: '<b><a href="' + location.fullurl + '">' + location.fulltext + '</a></b>',
+ icon: icon
+ };
+ map.addMarker( markerOptions );
+ }
+ }
+ } );
+ };
+
+} )( jQuery, mediaWiki );