diff --git a/bower.json b/bower.json
index b5ada5d1cd660f76453210b84748dfa178bd713c..3dd6aebb391f62a08b0075308e0014124d2539f8 100644
--- a/bower.json
+++ b/bower.json
@@ -20,7 +20,8 @@
     "leaflet-search": "^2.7.2",
     "angular-leaflet-directive": "angular-leaflet#^0.10.0",
     "Leaflet.EasyButton": "~1.3.2",
-    "leaflet.loading": "Leaflet.loading#^0.1.24"
+    "leaflet.loading": "Leaflet.loading#^0.1.24",
+    "ui-leaflet": "^2.0.0"
   },
   "resolutions": {
     "angular-sanitize": "1.5.3",
diff --git a/www/index.html b/www/index.html
index e04b68967a5f389a5f2f4dc31f934eaacb0686dd..2f61aada146f0f95a7e721ef59a1bb99e7253daf 100644
--- a/www/index.html
+++ b/www/index.html
@@ -55,7 +55,7 @@
   <script src="lib/ionic/js/angular/angular-file-saver.bundle.js"></script>
   <script src="lib/ionic/js/angular/angular-idle.js"></script>
   <script src="lib/ionic/js/angular/angular-simple-logger.light.js"></script>
-  <script src="lib/ionic/js/angular/angular-leaflet-directive.min.js"></script>
+  <script src="lib/ionic/js/angular/ui-leaflet.js"></script>
 
   <!--removeIf(ubuntu)--> <!-- FIXME: issue #463 -->
   <script src="lib/ionic/js/angular/angular-chart.min.js"></script>
diff --git a/www/js/app.js b/www/js/app.js
index c8906bcf1085e27c95dbfb3edaf13f47ef315a63..5a074e5f323999e5b874046f832f366a97465ee3 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -85,6 +85,17 @@ angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'pascalprecht
     IdleProvider.timeout(csConfig.logoutTimeout||15); // display warning during 15s
   })
 
+
+  // removeIf(device)
+  // Override the automatic sync between location URL and state
+  // (see watch event $locationChangeSuccess in the run() function bellow)
+  .config(function ($urlRouterProvider) {
+    'ngInject';
+
+    $urlRouterProvider.deferIntercept();
+  })
+  // endRemoveIf(device)
+
   .factory('$exceptionHandler', function() {
     'ngInject';
 
@@ -95,8 +106,7 @@ angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'pascalprecht
   })
 
 
-
-.run(function($rootScope, $translate, $state, $window, ionicReady, localStorage,
+.run(function($rootScope, $translate, $state, $window, $urlRouter, ionicReady, localStorage,
               filterTranslations, Device, BMA, UIUtils, csHttp, $ionicConfig, PluginService, csPlatform, csWallet, csSettings, csConfig, csCurrency) {
   'ngInject';
 
@@ -188,12 +198,28 @@ angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'pascalprecht
             });
         }
       }
+
     }
   });
   // endRemoveIf(firefoxos)
   // endRemoveIf(ios)
   // endRemoveIf(android)
 
+  // removeIf(device)
+  $rootScope.$on('$locationChangeSuccess', function(e, newUrl, oldUrl) {
+    if ($state.current.data && $state.current.data.silentLocationChange === true) {
+      // Prevent $urlRouter's default handler from firing (don't sync ui router)
+      e.preventDefault();
+      console.debug('[app] Skipping state sync for location change');
+    }
+    else {
+      $urlRouter.sync();
+    }
+  });
+  // Configures $urlRouter's listener *after* the previous listener
+  $urlRouter.listen();
+  // endRemoveIf(device)
+
   // Start plugins eager services
   PluginService.start();
 
diff --git a/www/js/config.js b/www/js/config.js
index 698f15d19fce84f141b4e5eb7c71f89643a9b856..4ce3354a20d2cbc411f7075cee86cba0a28dff16 100644
--- a/www/js/config.js
+++ b/www/js/config.js
@@ -59,7 +59,7 @@ angular.module("cesium.config", [])
 		}
 	},
 	"version": "0.15.0",
-	"build": "2017-07-28T14:45:48.593Z",
+	"build": "2017-07-31T07:51:20.763Z",
 	"newIssueUrl": "https://github.com/duniter/cesium/issues/new?labels=bug"
 })
 
diff --git a/www/js/controllers/wot-controllers.js b/www/js/controllers/wot-controllers.js
index 87abfc28d393eeb8d3298401efba594172050c05..172181ff9881294b365d1f40a021837c75f53ee2 100644
--- a/www/js/controllers/wot-controllers.js
+++ b/www/js/controllers/wot-controllers.js
@@ -11,6 +11,9 @@ angular.module('cesium.wot.controllers', ['cesium.services'])
             templateUrl: "templates/wot/lookup.html",
             controller: 'WotLookupCtrl'
           }
+        },
+        data: {
+          silentLocationChange: true
         }
       })
 
@@ -121,7 +124,7 @@ angular.module('cesium.wot.controllers', ['cesium.services'])
 
 ;
 
-function WotLookupController($scope, $state, $timeout, $focus, $ionicPopover, $ionicHistory,
+function WotLookupController($scope, $state, $timeout, $focus, $ionicPopover, $location,
                              UIUtils, csConfig, csCurrency, csSettings, Device, BMA, csWallet, csWot) {
   'ngInject';
 
@@ -182,6 +185,13 @@ function WotLookupController($scope, $state, $timeout, $focus, $ionicPopover, $i
 
       $scope.showHelpTip();
     }
+    else {
+
+      $scope.doRefreshLocationHref();
+      if ($scope.search.results && $scope.search.results.length) {
+        $scope.motion.show({selector: '.lookupForm .list .item', ink: true});
+      }
+    }
   });
 
   $scope.resetWotSearch = function() {
@@ -210,22 +220,12 @@ function WotLookupController($scope, $state, $timeout, $focus, $ionicPopover, $i
         stateParams.q = text;
       }
     }
-    else {
+    else if ($scope.search.type != 'last') {
       stateParams.type = $scope.search.type;
     }
 
     // Update location href
-    $ionicHistory.nextViewOptions({
-      disableAnimate: true,
-      disableBack: true,
-      historyRoot: true
-    });
-    $state.go('app.wot_lookup', stateParams,
-      {
-        reload: false,
-        inherit: true,
-        notify: false
-      });
+    $location.search(stateParams).replace();
   };
 
   $scope.doSearchText = function() {
diff --git a/www/js/vendor/leaflet-src.js b/www/js/vendor/leaflet-src.js
new file mode 100644
index 0000000000000000000000000000000000000000..d3d5635741c2dc68a16e0a3b72ed000ede34e3a4
--- /dev/null
+++ b/www/js/vendor/leaflet-src.js
@@ -0,0 +1,9168 @@
+/*
+ Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com
+ (c) 2010-2013, Vladimir Agafonkin
+ (c) 2010-2011, CloudMade
+*/
+(function (window, document, undefined) {
+var oldL = window.L,
+    L = {};
+
+L.version = '0.7.7';
+
+// define Leaflet for Node module pattern loaders, including Browserify
+if (typeof module === 'object' && typeof module.exports === 'object') {
+	module.exports = L;
+
+// define Leaflet as an AMD module
+} else if (typeof define === 'function' && define.amd) {
+	define(L);
+}
+
+// define Leaflet as a global L variable, saving the original L to restore later if needed
+
+L.noConflict = function () {
+	window.L = oldL;
+	return this;
+};
+
+window.L = L;
+
+
+/*
+ * L.Util contains various utility functions used throughout Leaflet code.
+ */
+
+L.Util = {
+	extend: function (dest) { // (Object[, Object, ...]) ->
+		var sources = Array.prototype.slice.call(arguments, 1),
+		    i, j, len, src;
+
+		for (j = 0, len = sources.length; j < len; j++) {
+			src = sources[j] || {};
+			for (i in src) {
+				if (src.hasOwnProperty(i)) {
+					dest[i] = src[i];
+				}
+			}
+		}
+		return dest;
+	},
+
+	bind: function (fn, obj) { // (Function, Object) -> Function
+		var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;
+		return function () {
+			return fn.apply(obj, args || arguments);
+		};
+	},
+
+	stamp: (function () {
+		var lastId = 0,
+		    key = '_leaflet_id';
+		return function (obj) {
+			obj[key] = obj[key] || ++lastId;
+			return obj[key];
+		};
+	}()),
+
+	invokeEach: function (obj, method, context) {
+		var i, args;
+
+		if (typeof obj === 'object') {
+			args = Array.prototype.slice.call(arguments, 3);
+
+			for (i in obj) {
+				method.apply(context, [i, obj[i]].concat(args));
+			}
+			return true;
+		}
+
+		return false;
+	},
+
+	limitExecByInterval: function (fn, time, context) {
+		var lock, execOnUnlock;
+
+		return function wrapperFn() {
+			var args = arguments;
+
+			if (lock) {
+				execOnUnlock = true;
+				return;
+			}
+
+			lock = true;
+
+			setTimeout(function () {
+				lock = false;
+
+				if (execOnUnlock) {
+					wrapperFn.apply(context, args);
+					execOnUnlock = false;
+				}
+			}, time);
+
+			fn.apply(context, args);
+		};
+	},
+
+	falseFn: function () {
+		return false;
+	},
+
+	formatNum: function (num, digits) {
+		var pow = Math.pow(10, digits || 5);
+		return Math.round(num * pow) / pow;
+	},
+
+	trim: function (str) {
+		return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
+	},
+
+	splitWords: function (str) {
+		return L.Util.trim(str).split(/\s+/);
+	},
+
+	setOptions: function (obj, options) {
+		obj.options = L.extend({}, obj.options, options);
+		return obj.options;
+	},
+
+	getParamString: function (obj, existingUrl, uppercase) {
+		var params = [];
+		for (var i in obj) {
+			params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
+		}
+		return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
+	},
+	template: function (str, data) {
+		return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
+			var value = data[key];
+			if (value === undefined) {
+				throw new Error('No value provided for variable ' + str);
+			} else if (typeof value === 'function') {
+				value = value(data);
+			}
+			return value;
+		});
+	},
+
+	isArray: Array.isArray || function (obj) {
+		return (Object.prototype.toString.call(obj) === '[object Array]');
+	},
+
+	emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
+};
+
+(function () {
+
+	// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+
+	function getPrefixed(name) {
+		var i, fn,
+		    prefixes = ['webkit', 'moz', 'o', 'ms'];
+
+		for (i = 0; i < prefixes.length && !fn; i++) {
+			fn = window[prefixes[i] + name];
+		}
+
+		return fn;
+	}
+
+	var lastTime = 0;
+
+	function timeoutDefer(fn) {
+		var time = +new Date(),
+		    timeToCall = Math.max(0, 16 - (time - lastTime));
+
+		lastTime = time + timeToCall;
+		return window.setTimeout(fn, timeToCall);
+	}
+
+	var requestFn = window.requestAnimationFrame ||
+	        getPrefixed('RequestAnimationFrame') || timeoutDefer;
+
+	var cancelFn = window.cancelAnimationFrame ||
+	        getPrefixed('CancelAnimationFrame') ||
+	        getPrefixed('CancelRequestAnimationFrame') ||
+	        function (id) { window.clearTimeout(id); };
+
+
+	L.Util.requestAnimFrame = function (fn, context, immediate, element) {
+		fn = L.bind(fn, context);
+
+		if (immediate && requestFn === timeoutDefer) {
+			fn();
+		} else {
+			return requestFn.call(window, fn, element);
+		}
+	};
+
+	L.Util.cancelAnimFrame = function (id) {
+		if (id) {
+			cancelFn.call(window, id);
+		}
+	};
+
+}());
+
+// shortcuts for most used utility functions
+L.extend = L.Util.extend;
+L.bind = L.Util.bind;
+L.stamp = L.Util.stamp;
+L.setOptions = L.Util.setOptions;
+
+
+/*
+ * L.Class powers the OOP facilities of the library.
+ * Thanks to John Resig and Dean Edwards for inspiration!
+ */
+
+L.Class = function () {};
+
+L.Class.extend = function (props) {
+
+	// extended class with the new prototype
+	var NewClass = function () {
+
+		// call the constructor
+		if (this.initialize) {
+			this.initialize.apply(this, arguments);
+		}
+
+		// call all constructor hooks
+		if (this._initHooks) {
+			this.callInitHooks();
+		}
+	};
+
+	// instantiate class without calling constructor
+	var F = function () {};
+	F.prototype = this.prototype;
+
+	var proto = new F();
+	proto.constructor = NewClass;
+
+	NewClass.prototype = proto;
+
+	//inherit parent's statics
+	for (var i in this) {
+		if (this.hasOwnProperty(i) && i !== 'prototype') {
+			NewClass[i] = this[i];
+		}
+	}
+
+	// mix static properties into the class
+	if (props.statics) {
+		L.extend(NewClass, props.statics);
+		delete props.statics;
+	}
+
+	// mix includes into the prototype
+	if (props.includes) {
+		L.Util.extend.apply(null, [proto].concat(props.includes));
+		delete props.includes;
+	}
+
+	// merge options
+	if (props.options && proto.options) {
+		props.options = L.extend({}, proto.options, props.options);
+	}
+
+	// mix given properties into the prototype
+	L.extend(proto, props);
+
+	proto._initHooks = [];
+
+	var parent = this;
+	// jshint camelcase: false
+	NewClass.__super__ = parent.prototype;
+
+	// add method for calling all hooks
+	proto.callInitHooks = function () {
+
+		if (this._initHooksCalled) { return; }
+
+		if (parent.prototype.callInitHooks) {
+			parent.prototype.callInitHooks.call(this);
+		}
+
+		this._initHooksCalled = true;
+
+		for (var i = 0, len = proto._initHooks.length; i < len; i++) {
+			proto._initHooks[i].call(this);
+		}
+	};
+
+	return NewClass;
+};
+
+
+// method for adding properties to prototype
+L.Class.include = function (props) {
+	L.extend(this.prototype, props);
+};
+
+// merge new default options to the Class
+L.Class.mergeOptions = function (options) {
+	L.extend(this.prototype.options, options);
+};
+
+// add a constructor hook
+L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
+	var args = Array.prototype.slice.call(arguments, 1);
+
+	var init = typeof fn === 'function' ? fn : function () {
+		this[fn].apply(this, args);
+	};
+
+	this.prototype._initHooks = this.prototype._initHooks || [];
+	this.prototype._initHooks.push(init);
+};
+
+
+/*
+ * L.Mixin.Events is used to add custom events functionality to Leaflet classes.
+ */
+
+var eventsKey = '_leaflet_events';
+
+L.Mixin = {};
+
+L.Mixin.Events = {
+
+	addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])
+
+		// types can be a map of types/handlers
+		if (L.Util.invokeEach(types, this.addEventListener, this, fn, context)) { return this; }
+
+		var events = this[eventsKey] = this[eventsKey] || {},
+		    contextId = context && context !== this && L.stamp(context),
+		    i, len, event, type, indexKey, indexLenKey, typeIndex;
+
+		// types can be a string of space-separated words
+		types = L.Util.splitWords(types);
+
+		for (i = 0, len = types.length; i < len; i++) {
+			event = {
+				action: fn,
+				context: context || this
+			};
+			type = types[i];
+
+			if (contextId) {
+				// store listeners of a particular context in a separate hash (if it has an id)
+				// gives a major performance boost when removing thousands of map layers
+
+				indexKey = type + '_idx';
+				indexLenKey = indexKey + '_len';
+
+				typeIndex = events[indexKey] = events[indexKey] || {};
+
+				if (!typeIndex[contextId]) {
+					typeIndex[contextId] = [];
+
+					// keep track of the number of keys in the index to quickly check if it's empty
+					events[indexLenKey] = (events[indexLenKey] || 0) + 1;
+				}
+
+				typeIndex[contextId].push(event);
+
+
+			} else {
+				events[type] = events[type] || [];
+				events[type].push(event);
+			}
+		}
+
+		return this;
+	},
+
+	hasEventListeners: function (type) { // (String) -> Boolean
+		var events = this[eventsKey];
+		return !!events && ((type in events && events[type].length > 0) ||
+		                    (type + '_idx' in events && events[type + '_idx_len'] > 0));
+	},
+
+	removeEventListener: function (types, fn, context) { // ([String, Function, Object]) or (Object[, Object])
+
+		if (!this[eventsKey]) {
+			return this;
+		}
+
+		if (!types) {
+			return this.clearAllEventListeners();
+		}
+
+		if (L.Util.invokeEach(types, this.removeEventListener, this, fn, context)) { return this; }
+
+		var events = this[eventsKey],
+		    contextId = context && context !== this && L.stamp(context),
+		    i, len, type, listeners, j, indexKey, indexLenKey, typeIndex, removed;
+
+		types = L.Util.splitWords(types);
+
+		for (i = 0, len = types.length; i < len; i++) {
+			type = types[i];
+			indexKey = type + '_idx';
+			indexLenKey = indexKey + '_len';
+
+			typeIndex = events[indexKey];
+
+			if (!fn) {
+				// clear all listeners for a type if function isn't specified
+				delete events[type];
+				delete events[indexKey];
+				delete events[indexLenKey];
+
+			} else {
+				listeners = contextId && typeIndex ? typeIndex[contextId] : events[type];
+
+				if (listeners) {
+					for (j = listeners.length - 1; j >= 0; j--) {
+						if ((listeners[j].action === fn) && (!context || (listeners[j].context === context))) {
+							removed = listeners.splice(j, 1);
+							// set the old action to a no-op, because it is possible
+							// that the listener is being iterated over as part of a dispatch
+							removed[0].action = L.Util.falseFn;
+						}
+					}
+
+					if (context && typeIndex && (listeners.length === 0)) {
+						delete typeIndex[contextId];
+						events[indexLenKey]--;
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	clearAllEventListeners: function () {
+		delete this[eventsKey];
+		return this;
+	},
+
+	fireEvent: function (type, data) { // (String[, Object])
+		if (!this.hasEventListeners(type)) {
+			return this;
+		}
+
+		var event = L.Util.extend({}, data, { type: type, target: this });
+
+		var events = this[eventsKey],
+		    listeners, i, len, typeIndex, contextId;
+
+		if (events[type]) {
+			// make sure adding/removing listeners inside other listeners won't cause infinite loop
+			listeners = events[type].slice();
+
+			for (i = 0, len = listeners.length; i < len; i++) {
+				listeners[i].action.call(listeners[i].context, event);
+			}
+		}
+
+		// fire event for the context-indexed listeners as well
+		typeIndex = events[type + '_idx'];
+
+		for (contextId in typeIndex) {
+			listeners = typeIndex[contextId].slice();
+
+			if (listeners) {
+				for (i = 0, len = listeners.length; i < len; i++) {
+					listeners[i].action.call(listeners[i].context, event);
+				}
+			}
+		}
+
+		return this;
+	},
+
+	addOneTimeEventListener: function (types, fn, context) {
+
+		if (L.Util.invokeEach(types, this.addOneTimeEventListener, this, fn, context)) { return this; }
+
+		var handler = L.bind(function () {
+			this
+			    .removeEventListener(types, fn, context)
+			    .removeEventListener(types, handler, context);
+		}, this);
+
+		return this
+		    .addEventListener(types, fn, context)
+		    .addEventListener(types, handler, context);
+	}
+};
+
+L.Mixin.Events.on = L.Mixin.Events.addEventListener;
+L.Mixin.Events.off = L.Mixin.Events.removeEventListener;
+L.Mixin.Events.once = L.Mixin.Events.addOneTimeEventListener;
+L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
+
+
+/*
+ * L.Browser handles different browser and feature detections for internal Leaflet use.
+ */
+
+(function () {
+
+	var ie = 'ActiveXObject' in window,
+		ielt9 = ie && !document.addEventListener,
+
+	    // terrible browser detection to work around Safari / iOS / Android browser bugs
+	    ua = navigator.userAgent.toLowerCase(),
+	    webkit = ua.indexOf('webkit') !== -1,
+	    chrome = ua.indexOf('chrome') !== -1,
+	    phantomjs = ua.indexOf('phantom') !== -1,
+	    android = ua.indexOf('android') !== -1,
+	    android23 = ua.search('android [23]') !== -1,
+		gecko = ua.indexOf('gecko') !== -1,
+
+	    mobile = typeof orientation !== undefined + '',
+	    msPointer = !window.PointerEvent && window.MSPointerEvent,
+		pointer = (window.PointerEvent && window.navigator.pointerEnabled) ||
+				  msPointer,
+	    retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) ||
+	             ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&
+	              window.matchMedia('(min-resolution:144dpi)').matches),
+
+	    doc = document.documentElement,
+	    ie3d = ie && ('transition' in doc.style),
+	    webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
+	    gecko3d = 'MozPerspective' in doc.style,
+	    opera3d = 'OTransition' in doc.style,
+	    any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs;
+
+	var touch = !window.L_NO_TOUCH && !phantomjs && (pointer || 'ontouchstart' in window ||
+		(window.DocumentTouch && document instanceof window.DocumentTouch));
+
+	L.Browser = {
+		ie: ie,
+		ielt9: ielt9,
+		webkit: webkit,
+		gecko: gecko && !webkit && !window.opera && !ie,
+
+		android: android,
+		android23: android23,
+
+		chrome: chrome,
+
+		ie3d: ie3d,
+		webkit3d: webkit3d,
+		gecko3d: gecko3d,
+		opera3d: opera3d,
+		any3d: any3d,
+
+		mobile: mobile,
+		mobileWebkit: mobile && webkit,
+		mobileWebkit3d: mobile && webkit3d,
+		mobileOpera: mobile && window.opera,
+
+		touch: touch,
+		msPointer: msPointer,
+		pointer: pointer,
+
+		retina: retina
+	};
+
+}());
+
+
+/*
+ * L.Point represents a point with x and y coordinates.
+ */
+
+L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {
+	this.x = (round ? Math.round(x) : x);
+	this.y = (round ? Math.round(y) : y);
+};
+
+L.Point.prototype = {
+
+	clone: function () {
+		return new L.Point(this.x, this.y);
+	},
+
+	// non-destructive, returns a new point
+	add: function (point) {
+		return this.clone()._add(L.point(point));
+	},
+
+	// destructive, used directly for performance in situations where it's safe to modify existing point
+	_add: function (point) {
+		this.x += point.x;
+		this.y += point.y;
+		return this;
+	},
+
+	subtract: function (point) {
+		return this.clone()._subtract(L.point(point));
+	},
+
+	_subtract: function (point) {
+		this.x -= point.x;
+		this.y -= point.y;
+		return this;
+	},
+
+	divideBy: function (num) {
+		return this.clone()._divideBy(num);
+	},
+
+	_divideBy: function (num) {
+		this.x /= num;
+		this.y /= num;
+		return this;
+	},
+
+	multiplyBy: function (num) {
+		return this.clone()._multiplyBy(num);
+	},
+
+	_multiplyBy: function (num) {
+		this.x *= num;
+		this.y *= num;
+		return this;
+	},
+
+	round: function () {
+		return this.clone()._round();
+	},
+
+	_round: function () {
+		this.x = Math.round(this.x);
+		this.y = Math.round(this.y);
+		return this;
+	},
+
+	floor: function () {
+		return this.clone()._floor();
+	},
+
+	_floor: function () {
+		this.x = Math.floor(this.x);
+		this.y = Math.floor(this.y);
+		return this;
+	},
+
+	distanceTo: function (point) {
+		point = L.point(point);
+
+		var x = point.x - this.x,
+		    y = point.y - this.y;
+
+		return Math.sqrt(x * x + y * y);
+	},
+
+	equals: function (point) {
+		point = L.point(point);
+
+		return point.x === this.x &&
+		       point.y === this.y;
+	},
+
+	contains: function (point) {
+		point = L.point(point);
+
+		return Math.abs(point.x) <= Math.abs(this.x) &&
+		       Math.abs(point.y) <= Math.abs(this.y);
+	},
+
+	toString: function () {
+		return 'Point(' +
+		        L.Util.formatNum(this.x) + ', ' +
+		        L.Util.formatNum(this.y) + ')';
+	}
+};
+
+L.point = function (x, y, round) {
+	if (x instanceof L.Point) {
+		return x;
+	}
+	if (L.Util.isArray(x)) {
+		return new L.Point(x[0], x[1]);
+	}
+	if (x === undefined || x === null) {
+		return x;
+	}
+	return new L.Point(x, y, round);
+};
+
+
+/*
+ * L.Bounds represents a rectangular area on the screen in pixel coordinates.
+ */
+
+L.Bounds = function (a, b) { //(Point, Point) or Point[]
+	if (!a) { return; }
+
+	var points = b ? [a, b] : a;
+
+	for (var i = 0, len = points.length; i < len; i++) {
+		this.extend(points[i]);
+	}
+};
+
+L.Bounds.prototype = {
+	// extend the bounds to contain the given point
+	extend: function (point) { // (Point)
+		point = L.point(point);
+
+		if (!this.min && !this.max) {
+			this.min = point.clone();
+			this.max = point.clone();
+		} else {
+			this.min.x = Math.min(point.x, this.min.x);
+			this.max.x = Math.max(point.x, this.max.x);
+			this.min.y = Math.min(point.y, this.min.y);
+			this.max.y = Math.max(point.y, this.max.y);
+		}
+		return this;
+	},
+
+	getCenter: function (round) { // (Boolean) -> Point
+		return new L.Point(
+		        (this.min.x + this.max.x) / 2,
+		        (this.min.y + this.max.y) / 2, round);
+	},
+
+	getBottomLeft: function () { // -> Point
+		return new L.Point(this.min.x, this.max.y);
+	},
+
+	getTopRight: function () { // -> Point
+		return new L.Point(this.max.x, this.min.y);
+	},
+
+	getSize: function () {
+		return this.max.subtract(this.min);
+	},
+
+	contains: function (obj) { // (Bounds) or (Point) -> Boolean
+		var min, max;
+
+		if (typeof obj[0] === 'number' || obj instanceof L.Point) {
+			obj = L.point(obj);
+		} else {
+			obj = L.bounds(obj);
+		}
+
+		if (obj instanceof L.Bounds) {
+			min = obj.min;
+			max = obj.max;
+		} else {
+			min = max = obj;
+		}
+
+		return (min.x >= this.min.x) &&
+		       (max.x <= this.max.x) &&
+		       (min.y >= this.min.y) &&
+		       (max.y <= this.max.y);
+	},
+
+	intersects: function (bounds) { // (Bounds) -> Boolean
+		bounds = L.bounds(bounds);
+
+		var min = this.min,
+		    max = this.max,
+		    min2 = bounds.min,
+		    max2 = bounds.max,
+		    xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
+		    yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
+
+		return xIntersects && yIntersects;
+	},
+
+	isValid: function () {
+		return !!(this.min && this.max);
+	}
+};
+
+L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
+	if (!a || a instanceof L.Bounds) {
+		return a;
+	}
+	return new L.Bounds(a, b);
+};
+
+
+/*
+ * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.
+ */
+
+L.Transformation = function (a, b, c, d) {
+	this._a = a;
+	this._b = b;
+	this._c = c;
+	this._d = d;
+};
+
+L.Transformation.prototype = {
+	transform: function (point, scale) { // (Point, Number) -> Point
+		return this._transform(point.clone(), scale);
+	},
+
+	// destructive transform (faster)
+	_transform: function (point, scale) {
+		scale = scale || 1;
+		point.x = scale * (this._a * point.x + this._b);
+		point.y = scale * (this._c * point.y + this._d);
+		return point;
+	},
+
+	untransform: function (point, scale) {
+		scale = scale || 1;
+		return new L.Point(
+		        (point.x / scale - this._b) / this._a,
+		        (point.y / scale - this._d) / this._c);
+	}
+};
+
+
+/*
+ * L.DomUtil contains various utility functions for working with DOM.
+ */
+
+L.DomUtil = {
+	get: function (id) {
+		return (typeof id === 'string' ? document.getElementById(id) : id);
+	},
+
+	getStyle: function (el, style) {
+
+		var value = el.style[style];
+
+		if (!value && el.currentStyle) {
+			value = el.currentStyle[style];
+		}
+
+		if ((!value || value === 'auto') && document.defaultView) {
+			var css = document.defaultView.getComputedStyle(el, null);
+			value = css ? css[style] : null;
+		}
+
+		return value === 'auto' ? null : value;
+	},
+
+	getViewportOffset: function (element) {
+
+		var top = 0,
+		    left = 0,
+		    el = element,
+		    docBody = document.body,
+		    docEl = document.documentElement,
+		    pos;
+
+		do {
+			top  += el.offsetTop  || 0;
+			left += el.offsetLeft || 0;
+
+			//add borders
+			top += parseInt(L.DomUtil.getStyle(el, 'borderTopWidth'), 10) || 0;
+			left += parseInt(L.DomUtil.getStyle(el, 'borderLeftWidth'), 10) || 0;
+
+			pos = L.DomUtil.getStyle(el, 'position');
+
+			if (el.offsetParent === docBody && pos === 'absolute') { break; }
+
+			if (pos === 'fixed') {
+				top  += docBody.scrollTop  || docEl.scrollTop  || 0;
+				left += docBody.scrollLeft || docEl.scrollLeft || 0;
+				break;
+			}
+
+			if (pos === 'relative' && !el.offsetLeft) {
+				var width = L.DomUtil.getStyle(el, 'width'),
+				    maxWidth = L.DomUtil.getStyle(el, 'max-width'),
+				    r = el.getBoundingClientRect();
+
+				if (width !== 'none' || maxWidth !== 'none') {
+					left += r.left + el.clientLeft;
+				}
+
+				//calculate full y offset since we're breaking out of the loop
+				top += r.top + (docBody.scrollTop  || docEl.scrollTop  || 0);
+
+				break;
+			}
+
+			el = el.offsetParent;
+
+		} while (el);
+
+		el = element;
+
+		do {
+			if (el === docBody) { break; }
+
+			top  -= el.scrollTop  || 0;
+			left -= el.scrollLeft || 0;
+
+			el = el.parentNode;
+		} while (el);
+
+		return new L.Point(left, top);
+	},
+
+	documentIsLtr: function () {
+		if (!L.DomUtil._docIsLtrCached) {
+			L.DomUtil._docIsLtrCached = true;
+			L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === 'ltr';
+		}
+		return L.DomUtil._docIsLtr;
+	},
+
+	create: function (tagName, className, container) {
+
+		var el = document.createElement(tagName);
+		el.className = className;
+
+		if (container) {
+			container.appendChild(el);
+		}
+
+		return el;
+	},
+
+	hasClass: function (el, name) {
+		if (el.classList !== undefined) {
+			return el.classList.contains(name);
+		}
+		var className = L.DomUtil._getClass(el);
+		return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
+	},
+
+	addClass: function (el, name) {
+		if (el.classList !== undefined) {
+			var classes = L.Util.splitWords(name);
+			for (var i = 0, len = classes.length; i < len; i++) {
+				el.classList.add(classes[i]);
+			}
+		} else if (!L.DomUtil.hasClass(el, name)) {
+			var className = L.DomUtil._getClass(el);
+			L.DomUtil._setClass(el, (className ? className + ' ' : '') + name);
+		}
+	},
+
+	removeClass: function (el, name) {
+		if (el.classList !== undefined) {
+			el.classList.remove(name);
+		} else {
+			L.DomUtil._setClass(el, L.Util.trim((' ' + L.DomUtil._getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
+		}
+	},
+
+	_setClass: function (el, name) {
+		if (el.className.baseVal === undefined) {
+			el.className = name;
+		} else {
+			// in case of SVG element
+			el.className.baseVal = name;
+		}
+	},
+
+	_getClass: function (el) {
+		return el.className.baseVal === undefined ? el.className : el.className.baseVal;
+	},
+
+	setOpacity: function (el, value) {
+
+		if ('opacity' in el.style) {
+			el.style.opacity = value;
+
+		} else if ('filter' in el.style) {
+
+			var filter = false,
+			    filterName = 'DXImageTransform.Microsoft.Alpha';
+
+			// filters collection throws an error if we try to retrieve a filter that doesn't exist
+			try {
+				filter = el.filters.item(filterName);
+			} catch (e) {
+				// don't set opacity to 1 if we haven't already set an opacity,
+				// it isn't needed and breaks transparent pngs.
+				if (value === 1) { return; }
+			}
+
+			value = Math.round(value * 100);
+
+			if (filter) {
+				filter.Enabled = (value !== 100);
+				filter.Opacity = value;
+			} else {
+				el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
+			}
+		}
+	},
+
+	testProp: function (props) {
+
+		var style = document.documentElement.style;
+
+		for (var i = 0; i < props.length; i++) {
+			if (props[i] in style) {
+				return props[i];
+			}
+		}
+		return false;
+	},
+
+	getTranslateString: function (point) {
+		// on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate
+		// makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care
+		// (same speed either way), Opera 12 doesn't support translate3d
+
+		var is3d = L.Browser.webkit3d,
+		    open = 'translate' + (is3d ? '3d' : '') + '(',
+		    close = (is3d ? ',0' : '') + ')';
+
+		return open + point.x + 'px,' + point.y + 'px' + close;
+	},
+
+	getScaleString: function (scale, origin) {
+
+		var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))),
+		    scaleStr = ' scale(' + scale + ') ';
+
+		return preTranslateStr + scaleStr;
+	},
+
+	setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean])
+
+		// jshint camelcase: false
+		el._leaflet_pos = point;
+
+		if (!disable3D && L.Browser.any3d) {
+			el.style[L.DomUtil.TRANSFORM] =  L.DomUtil.getTranslateString(point);
+		} else {
+			el.style.left = point.x + 'px';
+			el.style.top = point.y + 'px';
+		}
+	},
+
+	getPosition: function (el) {
+		// this method is only used for elements previously positioned using setPosition,
+		// so it's safe to cache the position for performance
+
+		// jshint camelcase: false
+		return el._leaflet_pos;
+	}
+};
+
+
+// prefix style property names
+
+L.DomUtil.TRANSFORM = L.DomUtil.testProp(
+        ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
+
+// webkitTransition comes first because some browser versions that drop vendor prefix don't do
+// the same for the transitionend event, in particular the Android 4.1 stock browser
+
+L.DomUtil.TRANSITION = L.DomUtil.testProp(
+        ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
+
+L.DomUtil.TRANSITION_END =
+        L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?
+        L.DomUtil.TRANSITION + 'End' : 'transitionend';
+
+(function () {
+    if ('onselectstart' in document) {
+        L.extend(L.DomUtil, {
+            disableTextSelection: function () {
+                L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
+            },
+
+            enableTextSelection: function () {
+                L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
+            }
+        });
+    } else {
+        var userSelectProperty = L.DomUtil.testProp(
+            ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
+
+        L.extend(L.DomUtil, {
+            disableTextSelection: function () {
+                if (userSelectProperty) {
+                    var style = document.documentElement.style;
+                    this._userSelect = style[userSelectProperty];
+                    style[userSelectProperty] = 'none';
+                }
+            },
+
+            enableTextSelection: function () {
+                if (userSelectProperty) {
+                    document.documentElement.style[userSelectProperty] = this._userSelect;
+                    delete this._userSelect;
+                }
+            }
+        });
+    }
+
+	L.extend(L.DomUtil, {
+		disableImageDrag: function () {
+			L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
+		},
+
+		enableImageDrag: function () {
+			L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
+		}
+	});
+})();
+
+
+/*
+ * L.LatLng represents a geographical point with latitude and longitude coordinates.
+ */
+
+L.LatLng = function (lat, lng, alt) { // (Number, Number, Number)
+	lat = parseFloat(lat);
+	lng = parseFloat(lng);
+
+	if (isNaN(lat) || isNaN(lng)) {
+		throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
+	}
+
+	this.lat = lat;
+	this.lng = lng;
+
+	if (alt !== undefined) {
+		this.alt = parseFloat(alt);
+	}
+};
+
+L.extend(L.LatLng, {
+	DEG_TO_RAD: Math.PI / 180,
+	RAD_TO_DEG: 180 / Math.PI,
+	MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check
+});
+
+L.LatLng.prototype = {
+	equals: function (obj) { // (LatLng) -> Boolean
+		if (!obj) { return false; }
+
+		obj = L.latLng(obj);
+
+		var margin = Math.max(
+		        Math.abs(this.lat - obj.lat),
+		        Math.abs(this.lng - obj.lng));
+
+		return margin <= L.LatLng.MAX_MARGIN;
+	},
+
+	toString: function (precision) { // (Number) -> String
+		return 'LatLng(' +
+		        L.Util.formatNum(this.lat, precision) + ', ' +
+		        L.Util.formatNum(this.lng, precision) + ')';
+	},
+
+	// Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
+	// TODO move to projection code, LatLng shouldn't know about Earth
+	distanceTo: function (other) { // (LatLng) -> Number
+		other = L.latLng(other);
+
+		var R = 6378137, // earth radius in meters
+		    d2r = L.LatLng.DEG_TO_RAD,
+		    dLat = (other.lat - this.lat) * d2r,
+		    dLon = (other.lng - this.lng) * d2r,
+		    lat1 = this.lat * d2r,
+		    lat2 = other.lat * d2r,
+		    sin1 = Math.sin(dLat / 2),
+		    sin2 = Math.sin(dLon / 2);
+
+		var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
+
+		return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
+	},
+
+	wrap: function (a, b) { // (Number, Number) -> LatLng
+		var lng = this.lng;
+
+		a = a || -180;
+		b = b ||  180;
+
+		lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a);
+
+		return new L.LatLng(this.lat, lng);
+	}
+};
+
+L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number)
+	if (a instanceof L.LatLng) {
+		return a;
+	}
+	if (L.Util.isArray(a)) {
+		if (typeof a[0] === 'number' || typeof a[0] === 'string') {
+			return new L.LatLng(a[0], a[1], a[2]);
+		} else {
+			return null;
+		}
+	}
+	if (a === undefined || a === null) {
+		return a;
+	}
+	if (typeof a === 'object' && 'lat' in a) {
+		return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon);
+	}
+	if (b === undefined) {
+		return null;
+	}
+	return new L.LatLng(a, b);
+};
+
+
+
+/*
+ * L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
+ */
+
+L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
+	if (!southWest) { return; }
+
+	var latlngs = northEast ? [southWest, northEast] : southWest;
+
+	for (var i = 0, len = latlngs.length; i < len; i++) {
+		this.extend(latlngs[i]);
+	}
+};
+
+L.LatLngBounds.prototype = {
+	// extend the bounds to contain the given point or bounds
+	extend: function (obj) { // (LatLng) or (LatLngBounds)
+		if (!obj) { return this; }
+
+		var latLng = L.latLng(obj);
+		if (latLng !== null) {
+			obj = latLng;
+		} else {
+			obj = L.latLngBounds(obj);
+		}
+
+		if (obj instanceof L.LatLng) {
+			if (!this._southWest && !this._northEast) {
+				this._southWest = new L.LatLng(obj.lat, obj.lng);
+				this._northEast = new L.LatLng(obj.lat, obj.lng);
+			} else {
+				this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
+				this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
+
+				this._northEast.lat = Math.max(obj.lat, this._northEast.lat);
+				this._northEast.lng = Math.max(obj.lng, this._northEast.lng);
+			}
+		} else if (obj instanceof L.LatLngBounds) {
+			this.extend(obj._southWest);
+			this.extend(obj._northEast);
+		}
+		return this;
+	},
+
+	// extend the bounds by a percentage
+	pad: function (bufferRatio) { // (Number) -> LatLngBounds
+		var sw = this._southWest,
+		    ne = this._northEast,
+		    heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
+		    widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
+
+		return new L.LatLngBounds(
+		        new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
+		        new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
+	},
+
+	getCenter: function () { // -> LatLng
+		return new L.LatLng(
+		        (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 L.LatLng(this.getNorth(), this.getWest());
+	},
+
+	getSouthEast: function () {
+		return new L.LatLng(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 (obj) { // (LatLngBounds) or (LatLng) -> Boolean
+		if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
+			obj = L.latLng(obj);
+		} else {
+			obj = L.latLngBounds(obj);
+		}
+
+		var sw = this._southWest,
+		    ne = this._northEast,
+		    sw2, ne2;
+
+		if (obj instanceof L.LatLngBounds) {
+			sw2 = obj.getSouthWest();
+			ne2 = obj.getNorthEast();
+		} else {
+			sw2 = ne2 = obj;
+		}
+
+		return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
+		       (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
+	},
+
+	intersects: function (bounds) { // (LatLngBounds)
+		bounds = L.latLngBounds(bounds);
+
+		var sw = this._southWest,
+		    ne = this._northEast,
+		    sw2 = bounds.getSouthWest(),
+		    ne2 = bounds.getNorthEast(),
+
+		    latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
+		    lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
+
+		return latIntersects && lngIntersects;
+	},
+
+	toBBoxString: function () {
+		return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
+	},
+
+	equals: function (bounds) { // (LatLngBounds)
+		if (!bounds) { return false; }
+
+		bounds = L.latLngBounds(bounds);
+
+		return this._southWest.equals(bounds.getSouthWest()) &&
+		       this._northEast.equals(bounds.getNorthEast());
+	},
+
+	isValid: function () {
+		return !!(this._southWest && this._northEast);
+	}
+};
+
+//TODO International date line?
+
+L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)
+	if (!a || a instanceof L.LatLngBounds) {
+		return a;
+	}
+	return new L.LatLngBounds(a, b);
+};
+
+
+/*
+ * L.Projection contains various geographical projections used by CRS classes.
+ */
+
+L.Projection = {};
+
+
+/*
+ * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default.
+ */
+
+L.Projection.SphericalMercator = {
+	MAX_LATITUDE: 85.0511287798,
+
+	project: function (latlng) { // (LatLng) -> Point
+		var d = L.LatLng.DEG_TO_RAD,
+		    max = this.MAX_LATITUDE,
+		    lat = Math.max(Math.min(max, latlng.lat), -max),
+		    x = latlng.lng * d,
+		    y = lat * d;
+
+		y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));
+
+		return new L.Point(x, y);
+	},
+
+	unproject: function (point) { // (Point, Boolean) -> LatLng
+		var d = L.LatLng.RAD_TO_DEG,
+		    lng = point.x * d,
+		    lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
+
+		return new L.LatLng(lat, lng);
+	}
+};
+
+
+/*
+ * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple.
+ */
+
+L.Projection.LonLat = {
+	project: function (latlng) {
+		return new L.Point(latlng.lng, latlng.lat);
+	},
+
+	unproject: function (point) {
+		return new L.LatLng(point.y, point.x);
+	}
+};
+
+
+/*
+ * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet.
+ */
+
+L.CRS = {
+	latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point
+		var projectedPoint = this.projection.project(latlng),
+		    scale = this.scale(zoom);
+
+		return this.transformation._transform(projectedPoint, scale);
+	},
+
+	pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng
+		var scale = this.scale(zoom),
+		    untransformedPoint = this.transformation.untransform(point, scale);
+
+		return this.projection.unproject(untransformedPoint);
+	},
+
+	project: function (latlng) {
+		return this.projection.project(latlng);
+	},
+
+	scale: function (zoom) {
+		return 256 * Math.pow(2, zoom);
+	},
+
+	getSize: function (zoom) {
+		var s = this.scale(zoom);
+		return L.point(s, s);
+	}
+};
+
+
+/*
+ * A simple CRS that can be used for flat non-Earth maps like panoramas or game maps.
+ */
+
+L.CRS.Simple = L.extend({}, L.CRS, {
+	projection: L.Projection.LonLat,
+	transformation: new L.Transformation(1, 0, -1, 0),
+
+	scale: function (zoom) {
+		return Math.pow(2, zoom);
+	}
+});
+
+
+/*
+ * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping
+ * and is used by Leaflet by default.
+ */
+
+L.CRS.EPSG3857 = L.extend({}, L.CRS, {
+	code: 'EPSG:3857',
+
+	projection: L.Projection.SphericalMercator,
+	transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),
+
+	project: function (latlng) { // (LatLng) -> Point
+		var projectedPoint = this.projection.project(latlng),
+		    earthRadius = 6378137;
+		return projectedPoint.multiplyBy(earthRadius);
+	}
+});
+
+L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
+	code: 'EPSG:900913'
+});
+
+
+/*
+ * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists.
+ */
+
+L.CRS.EPSG4326 = L.extend({}, L.CRS, {
+	code: 'EPSG:4326',
+
+	projection: L.Projection.LonLat,
+	transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5)
+});
+
+
+/*
+ * L.Map is the central class of the API - it is used to create a map.
+ */
+
+L.Map = L.Class.extend({
+
+	includes: L.Mixin.Events,
+
+	options: {
+		crs: L.CRS.EPSG3857,
+
+		/*
+		center: LatLng,
+		zoom: Number,
+		layers: Array,
+		*/
+
+		fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
+		trackResize: true,
+		markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
+	},
+
+	initialize: function (id, options) { // (HTMLElement or String, Object)
+		options = L.setOptions(this, options);
+
+
+		this._initContainer(id);
+		this._initLayout();
+
+		// hack for https://github.com/Leaflet/Leaflet/issues/1980
+		this._onResize = L.bind(this._onResize, this);
+
+		this._initEvents();
+
+		if (options.maxBounds) {
+			this.setMaxBounds(options.maxBounds);
+		}
+
+		if (options.center && options.zoom !== undefined) {
+			this.setView(L.latLng(options.center), options.zoom, {reset: true});
+		}
+
+		this._handlers = [];
+
+		this._layers = {};
+		this._zoomBoundLayers = {};
+		this._tileLayersNum = 0;
+
+		this.callInitHooks();
+
+		this._addLayers(options.layers);
+	},
+
+
+	// public methods that modify map state
+
+	// replaced by animation-powered implementation in Map.PanAnimation.js
+	setView: function (center, zoom) {
+		zoom = zoom === undefined ? this.getZoom() : zoom;
+		this._resetView(L.latLng(center), this._limitZoom(zoom));
+		return this;
+	},
+
+	setZoom: function (zoom, options) {
+		if (!this._loaded) {
+			this._zoom = this._limitZoom(zoom);
+			return this;
+		}
+		return this.setView(this.getCenter(), zoom, {zoom: options});
+	},
+
+	zoomIn: function (delta, options) {
+		return this.setZoom(this._zoom + (delta || 1), options);
+	},
+
+	zoomOut: function (delta, options) {
+		return this.setZoom(this._zoom - (delta || 1), options);
+	},
+
+	setZoomAround: function (latlng, zoom, options) {
+		var scale = this.getZoomScale(zoom),
+		    viewHalf = this.getSize().divideBy(2),
+		    containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
+
+		    centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
+		    newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
+
+		return this.setView(newCenter, zoom, {zoom: options});
+	},
+
+	fitBounds: function (bounds, options) {
+
+		options = options || {};
+		bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
+
+		var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
+		    paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
+
+		    zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
+
+		zoom = (options.maxZoom) ? Math.min(options.maxZoom, zoom) : zoom;
+
+		var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
+
+		    swPoint = this.project(bounds.getSouthWest(), zoom),
+		    nePoint = this.project(bounds.getNorthEast(), zoom),
+		    center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
+
+		return this.setView(center, zoom, options);
+	},
+
+	fitWorld: function (options) {
+		return this.fitBounds([[-90, -180], [90, 180]], options);
+	},
+
+	panTo: function (center, options) { // (LatLng)
+		return this.setView(center, this._zoom, {pan: options});
+	},
+
+	panBy: function (offset) { // (Point)
+		// replaced with animated panBy in Map.PanAnimation.js
+		this.fire('movestart');
+
+		this._rawPanBy(L.point(offset));
+
+		this.fire('move');
+		return this.fire('moveend');
+	},
+
+	setMaxBounds: function (bounds) {
+		bounds = L.latLngBounds(bounds);
+
+		this.options.maxBounds = bounds;
+
+		if (!bounds) {
+			return this.off('moveend', this._panInsideMaxBounds, this);
+		}
+
+		if (this._loaded) {
+			this._panInsideMaxBounds();
+		}
+
+		return this.on('moveend', this._panInsideMaxBounds, this);
+	},
+
+	panInsideBounds: function (bounds, options) {
+		var center = this.getCenter(),
+			newCenter = this._limitCenter(center, this._zoom, bounds);
+
+		if (center.equals(newCenter)) { return this; }
+
+		return this.panTo(newCenter, options);
+	},
+
+	addLayer: function (layer) {
+		// TODO method is too big, refactor
+
+		var id = L.stamp(layer);
+
+		if (this._layers[id]) { return this; }
+
+		this._layers[id] = layer;
+
+		// TODO getMaxZoom, getMinZoom in ILayer (instead of options)
+		if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) {
+			this._zoomBoundLayers[id] = layer;
+			this._updateZoomLevels();
+		}
+
+		// TODO looks ugly, refactor!!!
+		if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
+			this._tileLayersNum++;
+			this._tileLayersToLoad++;
+			layer.on('load', this._onTileLayerLoad, this);
+		}
+
+		if (this._loaded) {
+			this._layerAdd(layer);
+		}
+
+		return this;
+	},
+
+	removeLayer: function (layer) {
+		var id = L.stamp(layer);
+
+		if (!this._layers[id]) { return this; }
+
+		if (this._loaded) {
+			layer.onRemove(this);
+		}
+
+		delete this._layers[id];
+
+		if (this._loaded) {
+			this.fire('layerremove', {layer: layer});
+		}
+
+		if (this._zoomBoundLayers[id]) {
+			delete this._zoomBoundLayers[id];
+			this._updateZoomLevels();
+		}
+
+		// TODO looks ugly, refactor
+		if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
+			this._tileLayersNum--;
+			this._tileLayersToLoad--;
+			layer.off('load', this._onTileLayerLoad, this);
+		}
+
+		return this;
+	},
+
+	hasLayer: function (layer) {
+		if (!layer) { return false; }
+
+		return (L.stamp(layer) in this._layers);
+	},
+
+	eachLayer: function (method, context) {
+		for (var i in this._layers) {
+			method.call(context, this._layers[i]);
+		}
+		return this;
+	},
+
+	invalidateSize: function (options) {
+		if (!this._loaded) { return this; }
+
+		options = L.extend({
+			animate: false,
+			pan: true
+		}, options === true ? {animate: true} : options);
+
+		var oldSize = this.getSize();
+		this._sizeChanged = true;
+		this._initialCenter = null;
+
+		var newSize = this.getSize(),
+		    oldCenter = oldSize.divideBy(2).round(),
+		    newCenter = newSize.divideBy(2).round(),
+		    offset = oldCenter.subtract(newCenter);
+
+		if (!offset.x && !offset.y) { return this; }
+
+		if (options.animate && options.pan) {
+			this.panBy(offset);
+
+		} else {
+			if (options.pan) {
+				this._rawPanBy(offset);
+			}
+
+			this.fire('move');
+
+			if (options.debounceMoveend) {
+				clearTimeout(this._sizeTimer);
+				this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
+			} else {
+				this.fire('moveend');
+			}
+		}
+
+		return this.fire('resize', {
+			oldSize: oldSize,
+			newSize: newSize
+		});
+	},
+
+	// TODO handler.addTo
+	addHandler: function (name, HandlerClass) {
+		if (!HandlerClass) { return this; }
+
+		var handler = this[name] = new HandlerClass(this);
+
+		this._handlers.push(handler);
+
+		if (this.options[name]) {
+			handler.enable();
+		}
+
+		return this;
+	},
+
+	remove: function () {
+		if (this._loaded) {
+			this.fire('unload');
+		}
+
+		this._initEvents('off');
+
+		try {
+			// throws error in IE6-8
+			delete this._container._leaflet;
+		} catch (e) {
+			this._container._leaflet = undefined;
+		}
+
+		this._clearPanes();
+		if (this._clearControlPos) {
+			this._clearControlPos();
+		}
+
+		this._clearHandlers();
+
+		return this;
+	},
+
+
+	// public methods for getting map state
+
+	getCenter: function () { // (Boolean) -> LatLng
+		this._checkIfLoaded();
+
+		if (this._initialCenter && !this._moved()) {
+			return this._initialCenter;
+		}
+		return this.layerPointToLatLng(this._getCenterLayerPoint());
+	},
+
+	getZoom: function () {
+		return this._zoom;
+	},
+
+	getBounds: function () {
+		var bounds = this.getPixelBounds(),
+		    sw = this.unproject(bounds.getBottomLeft()),
+		    ne = this.unproject(bounds.getTopRight());
+
+		return new L.LatLngBounds(sw, ne);
+	},
+
+	getMinZoom: function () {
+		return this.options.minZoom === undefined ?
+			(this._layersMinZoom === undefined ? 0 : this._layersMinZoom) :
+			this.options.minZoom;
+	},
+
+	getMaxZoom: function () {
+		return this.options.maxZoom === undefined ?
+			(this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
+			this.options.maxZoom;
+	},
+
+	getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
+		bounds = L.latLngBounds(bounds);
+
+		var zoom = this.getMinZoom() - (inside ? 1 : 0),
+		    maxZoom = this.getMaxZoom(),
+		    size = this.getSize(),
+
+		    nw = bounds.getNorthWest(),
+		    se = bounds.getSouthEast(),
+
+		    zoomNotFound = true,
+		    boundsSize;
+
+		padding = L.point(padding || [0, 0]);
+
+		do {
+			zoom++;
+			boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding);
+			zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y;
+
+		} while (zoomNotFound && zoom <= maxZoom);
+
+		if (zoomNotFound && inside) {
+			return null;
+		}
+
+		return inside ? zoom : zoom - 1;
+	},
+
+	getSize: function () {
+		if (!this._size || this._sizeChanged) {
+			this._size = new L.Point(
+				this._container.clientWidth,
+				this._container.clientHeight);
+
+			this._sizeChanged = false;
+		}
+		return this._size.clone();
+	},
+
+	getPixelBounds: function () {
+		var topLeftPoint = this._getTopLeftPoint();
+		return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
+	},
+
+	getPixelOrigin: function () {
+		this._checkIfLoaded();
+		return this._initialTopLeftPoint;
+	},
+
+	getPanes: function () {
+		return this._panes;
+	},
+
+	getContainer: function () {
+		return this._container;
+	},
+
+
+	// TODO replace with universal implementation after refactoring projections
+
+	getZoomScale: function (toZoom) {
+		var crs = this.options.crs;
+		return crs.scale(toZoom) / crs.scale(this._zoom);
+	},
+
+	getScaleZoom: function (scale) {
+		return this._zoom + (Math.log(scale) / Math.LN2);
+	},
+
+
+	// conversion methods
+
+	project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
+		zoom = zoom === undefined ? this._zoom : zoom;
+		return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
+	},
+
+	unproject: function (point, zoom) { // (Point[, Number]) -> LatLng
+		zoom = zoom === undefined ? this._zoom : zoom;
+		return this.options.crs.pointToLatLng(L.point(point), zoom);
+	},
+
+	layerPointToLatLng: function (point) { // (Point)
+		var projectedPoint = L.point(point).add(this.getPixelOrigin());
+		return this.unproject(projectedPoint);
+	},
+
+	latLngToLayerPoint: function (latlng) { // (LatLng)
+		var projectedPoint = this.project(L.latLng(latlng))._round();
+		return projectedPoint._subtract(this.getPixelOrigin());
+	},
+
+	containerPointToLayerPoint: function (point) { // (Point)
+		return L.point(point).subtract(this._getMapPanePos());
+	},
+
+	layerPointToContainerPoint: function (point) { // (Point)
+		return L.point(point).add(this._getMapPanePos());
+	},
+
+	containerPointToLatLng: function (point) {
+		var layerPoint = this.containerPointToLayerPoint(L.point(point));
+		return this.layerPointToLatLng(layerPoint);
+	},
+
+	latLngToContainerPoint: function (latlng) {
+		return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
+	},
+
+	mouseEventToContainerPoint: function (e) { // (MouseEvent)
+		return L.DomEvent.getMousePosition(e, this._container);
+	},
+
+	mouseEventToLayerPoint: function (e) { // (MouseEvent)
+		return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
+	},
+
+	mouseEventToLatLng: function (e) { // (MouseEvent)
+		return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
+	},
+
+
+	// map initialization methods
+
+	_initContainer: function (id) {
+		var container = this._container = L.DomUtil.get(id);
+
+		if (!container) {
+			throw new Error('Map container not found.');
+		} else if (container._leaflet) {
+			throw new Error('Map container is already initialized.');
+		}
+
+		container._leaflet = true;
+	},
+
+	_initLayout: function () {
+		var container = this._container;
+
+		L.DomUtil.addClass(container, 'leaflet-container' +
+			(L.Browser.touch ? ' leaflet-touch' : '') +
+			(L.Browser.retina ? ' leaflet-retina' : '') +
+			(L.Browser.ielt9 ? ' leaflet-oldie' : '') +
+			(this.options.fadeAnimation ? ' leaflet-fade-anim' : ''));
+
+		var position = L.DomUtil.getStyle(container, 'position');
+
+		if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
+			container.style.position = 'relative';
+		}
+
+		this._initPanes();
+
+		if (this._initControlPos) {
+			this._initControlPos();
+		}
+	},
+
+	_initPanes: function () {
+		var panes = this._panes = {};
+
+		this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);
+
+		this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);
+		panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);
+		panes.shadowPane = this._createPane('leaflet-shadow-pane');
+		panes.overlayPane = this._createPane('leaflet-overlay-pane');
+		panes.markerPane = this._createPane('leaflet-marker-pane');
+		panes.popupPane = this._createPane('leaflet-popup-pane');
+
+		var zoomHide = ' leaflet-zoom-hide';
+
+		if (!this.options.markerZoomAnimation) {
+			L.DomUtil.addClass(panes.markerPane, zoomHide);
+			L.DomUtil.addClass(panes.shadowPane, zoomHide);
+			L.DomUtil.addClass(panes.popupPane, zoomHide);
+		}
+	},
+
+	_createPane: function (className, container) {
+		return L.DomUtil.create('div', className, container || this._panes.objectsPane);
+	},
+
+	_clearPanes: function () {
+		this._container.removeChild(this._mapPane);
+	},
+
+	_addLayers: function (layers) {
+		layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
+
+		for (var i = 0, len = layers.length; i < len; i++) {
+			this.addLayer(layers[i]);
+		}
+	},
+
+
+	// private methods that modify map state
+
+	_resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
+
+		var zoomChanged = (this._zoom !== zoom);
+
+		if (!afterZoomAnim) {
+			this.fire('movestart');
+
+			if (zoomChanged) {
+				this.fire('zoomstart');
+			}
+		}
+
+		this._zoom = zoom;
+		this._initialCenter = center;
+
+		this._initialTopLeftPoint = this._getNewTopLeftPoint(center);
+
+		if (!preserveMapOffset) {
+			L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
+		} else {
+			this._initialTopLeftPoint._add(this._getMapPanePos());
+		}
+
+		this._tileLayersToLoad = this._tileLayersNum;
+
+		var loading = !this._loaded;
+		this._loaded = true;
+
+		this.fire('viewreset', {hard: !preserveMapOffset});
+
+		if (loading) {
+			this.fire('load');
+			this.eachLayer(this._layerAdd, this);
+		}
+
+		this.fire('move');
+
+		if (zoomChanged || afterZoomAnim) {
+			this.fire('zoomend');
+		}
+
+		this.fire('moveend', {hard: !preserveMapOffset});
+	},
+
+	_rawPanBy: function (offset) {
+		L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
+	},
+
+	_getZoomSpan: function () {
+		return this.getMaxZoom() - this.getMinZoom();
+	},
+
+	_updateZoomLevels: function () {
+		var i,
+			minZoom = Infinity,
+			maxZoom = -Infinity,
+			oldZoomSpan = this._getZoomSpan();
+
+		for (i in this._zoomBoundLayers) {
+			var layer = this._zoomBoundLayers[i];
+			if (!isNaN(layer.options.minZoom)) {
+				minZoom = Math.min(minZoom, layer.options.minZoom);
+			}
+			if (!isNaN(layer.options.maxZoom)) {
+				maxZoom = Math.max(maxZoom, layer.options.maxZoom);
+			}
+		}
+
+		if (i === undefined) { // we have no tilelayers
+			this._layersMaxZoom = this._layersMinZoom = undefined;
+		} else {
+			this._layersMaxZoom = maxZoom;
+			this._layersMinZoom = minZoom;
+		}
+
+		if (oldZoomSpan !== this._getZoomSpan()) {
+			this.fire('zoomlevelschange');
+		}
+	},
+
+	_panInsideMaxBounds: function () {
+		this.panInsideBounds(this.options.maxBounds);
+	},
+
+	_checkIfLoaded: function () {
+		if (!this._loaded) {
+			throw new Error('Set map center and zoom first.');
+		}
+	},
+
+	// map events
+
+	_initEvents: function (onOff) {
+		if (!L.DomEvent) { return; }
+
+		onOff = onOff || 'on';
+
+		L.DomEvent[onOff](this._container, 'click', this._onMouseClick, this);
+
+		var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter',
+		              'mouseleave', 'mousemove', 'contextmenu'],
+		    i, len;
+
+		for (i = 0, len = events.length; i < len; i++) {
+			L.DomEvent[onOff](this._container, events[i], this._fireMouseEvent, this);
+		}
+
+		if (this.options.trackResize) {
+			L.DomEvent[onOff](window, 'resize', this._onResize, this);
+		}
+	},
+
+	_onResize: function () {
+		L.Util.cancelAnimFrame(this._resizeRequest);
+		this._resizeRequest = L.Util.requestAnimFrame(
+		        function () { this.invalidateSize({debounceMoveend: true}); }, this, false, this._container);
+	},
+
+	_onMouseClick: function (e) {
+		if (!this._loaded || (!e._simulated &&
+		        ((this.dragging && this.dragging.moved()) ||
+		         (this.boxZoom  && this.boxZoom.moved()))) ||
+		            L.DomEvent._skipped(e)) { return; }
+
+		this.fire('preclick');
+		this._fireMouseEvent(e);
+	},
+
+	_fireMouseEvent: function (e) {
+		if (!this._loaded || L.DomEvent._skipped(e)) { return; }
+
+		var type = e.type;
+
+		type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
+
+		if (!this.hasEventListeners(type)) { return; }
+
+		if (type === 'contextmenu') {
+			L.DomEvent.preventDefault(e);
+		}
+
+		var containerPoint = this.mouseEventToContainerPoint(e),
+		    layerPoint = this.containerPointToLayerPoint(containerPoint),
+		    latlng = this.layerPointToLatLng(layerPoint);
+
+		this.fire(type, {
+			latlng: latlng,
+			layerPoint: layerPoint,
+			containerPoint: containerPoint,
+			originalEvent: e
+		});
+	},
+
+	_onTileLayerLoad: function () {
+		this._tileLayersToLoad--;
+		if (this._tileLayersNum && !this._tileLayersToLoad) {
+			this.fire('tilelayersload');
+		}
+	},
+
+	_clearHandlers: function () {
+		for (var i = 0, len = this._handlers.length; i < len; i++) {
+			this._handlers[i].disable();
+		}
+	},
+
+	whenReady: function (callback, context) {
+		if (this._loaded) {
+			callback.call(context || this, this);
+		} else {
+			this.on('load', callback, context);
+		}
+		return this;
+	},
+
+	_layerAdd: function (layer) {
+		layer.onAdd(this);
+		this.fire('layeradd', {layer: layer});
+	},
+
+
+	// private methods for getting map state
+
+	_getMapPanePos: function () {
+		return L.DomUtil.getPosition(this._mapPane);
+	},
+
+	_moved: function () {
+		var pos = this._getMapPanePos();
+		return pos && !pos.equals([0, 0]);
+	},
+
+	_getTopLeftPoint: function () {
+		return this.getPixelOrigin().subtract(this._getMapPanePos());
+	},
+
+	_getNewTopLeftPoint: function (center, zoom) {
+		var viewHalf = this.getSize()._divideBy(2);
+		// TODO round on display, not calculation to increase precision?
+		return this.project(center, zoom)._subtract(viewHalf)._round();
+	},
+
+	_latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
+		var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());
+		return this.project(latlng, newZoom)._subtract(topLeft);
+	},
+
+	// layer point of the current center
+	_getCenterLayerPoint: function () {
+		return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
+	},
+
+	// offset of the specified place to the current center in pixels
+	_getCenterOffset: function (latlng) {
+		return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
+	},
+
+	// adjust center for view to get inside bounds
+	_limitCenter: function (center, zoom, bounds) {
+
+		if (!bounds) { return center; }
+
+		var centerPoint = this.project(center, zoom),
+		    viewHalf = this.getSize().divideBy(2),
+		    viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
+		    offset = this._getBoundsOffset(viewBounds, bounds, zoom);
+
+		return this.unproject(centerPoint.add(offset), zoom);
+	},
+
+	// adjust offset for view to get inside bounds
+	_limitOffset: function (offset, bounds) {
+		if (!bounds) { return offset; }
+
+		var viewBounds = this.getPixelBounds(),
+		    newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
+
+		return offset.add(this._getBoundsOffset(newBounds, bounds));
+	},
+
+	// returns offset needed for pxBounds to get inside maxBounds at a specified zoom
+	_getBoundsOffset: function (pxBounds, maxBounds, zoom) {
+		var nwOffset = this.project(maxBounds.getNorthWest(), zoom).subtract(pxBounds.min),
+		    seOffset = this.project(maxBounds.getSouthEast(), zoom).subtract(pxBounds.max),
+
+		    dx = this._rebound(nwOffset.x, -seOffset.x),
+		    dy = this._rebound(nwOffset.y, -seOffset.y);
+
+		return new L.Point(dx, dy);
+	},
+
+	_rebound: function (left, right) {
+		return left + right > 0 ?
+			Math.round(left - right) / 2 :
+			Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
+	},
+
+	_limitZoom: function (zoom) {
+		var min = this.getMinZoom(),
+		    max = this.getMaxZoom();
+
+		return Math.max(min, Math.min(max, zoom));
+	}
+});
+
+L.map = function (id, options) {
+	return new L.Map(id, options);
+};
+
+
+/*
+ * Mercator projection that takes into account that the Earth is not a perfect sphere.
+ * Less popular than spherical mercator; used by projections like EPSG:3395.
+ */
+
+L.Projection.Mercator = {
+	MAX_LATITUDE: 85.0840591556,
+
+	R_MINOR: 6356752.314245179,
+	R_MAJOR: 6378137,
+
+	project: function (latlng) { // (LatLng) -> Point
+		var d = L.LatLng.DEG_TO_RAD,
+		    max = this.MAX_LATITUDE,
+		    lat = Math.max(Math.min(max, latlng.lat), -max),
+		    r = this.R_MAJOR,
+		    r2 = this.R_MINOR,
+		    x = latlng.lng * d * r,
+		    y = lat * d,
+		    tmp = r2 / r,
+		    eccent = Math.sqrt(1.0 - tmp * tmp),
+		    con = eccent * Math.sin(y);
+
+		con = Math.pow((1 - con) / (1 + con), eccent * 0.5);
+
+		var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;
+		y = -r * Math.log(ts);
+
+		return new L.Point(x, y);
+	},
+
+	unproject: function (point) { // (Point, Boolean) -> LatLng
+		var d = L.LatLng.RAD_TO_DEG,
+		    r = this.R_MAJOR,
+		    r2 = this.R_MINOR,
+		    lng = point.x * d / r,
+		    tmp = r2 / r,
+		    eccent = Math.sqrt(1 - (tmp * tmp)),
+		    ts = Math.exp(- point.y / r),
+		    phi = (Math.PI / 2) - 2 * Math.atan(ts),
+		    numIter = 15,
+		    tol = 1e-7,
+		    i = numIter,
+		    dphi = 0.1,
+		    con;
+
+		while ((Math.abs(dphi) > tol) && (--i > 0)) {
+			con = eccent * Math.sin(phi);
+			dphi = (Math.PI / 2) - 2 * Math.atan(ts *
+			            Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;
+			phi += dphi;
+		}
+
+		return new L.LatLng(phi * d, lng);
+	}
+};
+
+
+
+L.CRS.EPSG3395 = L.extend({}, L.CRS, {
+	code: 'EPSG:3395',
+
+	projection: L.Projection.Mercator,
+
+	transformation: (function () {
+		var m = L.Projection.Mercator,
+		    r = m.R_MAJOR,
+		    scale = 0.5 / (Math.PI * r);
+
+		return new L.Transformation(scale, 0.5, -scale, 0.5);
+	}())
+});
+
+
+/*
+ * L.TileLayer is used for standard xyz-numbered tile layers.
+ */
+
+L.TileLayer = L.Class.extend({
+	includes: L.Mixin.Events,
+
+	options: {
+		minZoom: 0,
+		maxZoom: 18,
+		tileSize: 256,
+		subdomains: 'abc',
+		errorTileUrl: '',
+		attribution: '',
+		zoomOffset: 0,
+		opacity: 1,
+		/*
+		maxNativeZoom: null,
+		zIndex: null,
+		tms: false,
+		continuousWorld: false,
+		noWrap: false,
+		zoomReverse: false,
+		detectRetina: false,
+		reuseTiles: false,
+		bounds: false,
+		*/
+		unloadInvisibleTiles: L.Browser.mobile,
+		updateWhenIdle: L.Browser.mobile
+	},
+
+	initialize: function (url, options) {
+		options = L.setOptions(this, options);
+
+		// detecting retina displays, adjusting tileSize and zoom levels
+		if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
+
+			options.tileSize = Math.floor(options.tileSize / 2);
+			options.zoomOffset++;
+
+			if (options.minZoom > 0) {
+				options.minZoom--;
+			}
+			this.options.maxZoom--;
+		}
+
+		if (options.bounds) {
+			options.bounds = L.latLngBounds(options.bounds);
+		}
+
+		this._url = url;
+
+		var subdomains = this.options.subdomains;
+
+		if (typeof subdomains === 'string') {
+			this.options.subdomains = subdomains.split('');
+		}
+	},
+
+	onAdd: function (map) {
+		this._map = map;
+		this._animated = map._zoomAnimated;
+
+		// create a container div for tiles
+		this._initContainer();
+
+		// set up events
+		map.on({
+			'viewreset': this._reset,
+			'moveend': this._update
+		}, this);
+
+		if (this._animated) {
+			map.on({
+				'zoomanim': this._animateZoom,
+				'zoomend': this._endZoomAnim
+			}, this);
+		}
+
+		if (!this.options.updateWhenIdle) {
+			this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
+			map.on('move', this._limitedUpdate, this);
+		}
+
+		this._reset();
+		this._update();
+	},
+
+	addTo: function (map) {
+		map.addLayer(this);
+		return this;
+	},
+
+	onRemove: function (map) {
+		this._container.parentNode.removeChild(this._container);
+
+		map.off({
+			'viewreset': this._reset,
+			'moveend': this._update
+		}, this);
+
+		if (this._animated) {
+			map.off({
+				'zoomanim': this._animateZoom,
+				'zoomend': this._endZoomAnim
+			}, this);
+		}
+
+		if (!this.options.updateWhenIdle) {
+			map.off('move', this._limitedUpdate, this);
+		}
+
+		this._container = null;
+		this._map = null;
+	},
+
+	bringToFront: function () {
+		var pane = this._map._panes.tilePane;
+
+		if (this._container) {
+			pane.appendChild(this._container);
+			this._setAutoZIndex(pane, Math.max);
+		}
+
+		return this;
+	},
+
+	bringToBack: function () {
+		var pane = this._map._panes.tilePane;
+
+		if (this._container) {
+			pane.insertBefore(this._container, pane.firstChild);
+			this._setAutoZIndex(pane, Math.min);
+		}
+
+		return this;
+	},
+
+	getAttribution: function () {
+		return this.options.attribution;
+	},
+
+	getContainer: function () {
+		return this._container;
+	},
+
+	setOpacity: function (opacity) {
+		this.options.opacity = opacity;
+
+		if (this._map) {
+			this._updateOpacity();
+		}
+
+		return this;
+	},
+
+	setZIndex: function (zIndex) {
+		this.options.zIndex = zIndex;
+		this._updateZIndex();
+
+		return this;
+	},
+
+	setUrl: function (url, noRedraw) {
+		this._url = url;
+
+		if (!noRedraw) {
+			this.redraw();
+		}
+
+		return this;
+	},
+
+	redraw: function () {
+		if (this._map) {
+			this._reset({hard: true});
+			this._update();
+		}
+		return this;
+	},
+
+	_updateZIndex: function () {
+		if (this._container && this.options.zIndex !== undefined) {
+			this._container.style.zIndex = this.options.zIndex;
+		}
+	},
+
+	_setAutoZIndex: function (pane, compare) {
+
+		var layers = pane.children,
+		    edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min
+		    zIndex, i, len;
+
+		for (i = 0, len = layers.length; i < len; i++) {
+
+			if (layers[i] !== this._container) {
+				zIndex = parseInt(layers[i].style.zIndex, 10);
+
+				if (!isNaN(zIndex)) {
+					edgeZIndex = compare(edgeZIndex, zIndex);
+				}
+			}
+		}
+
+		this.options.zIndex = this._container.style.zIndex =
+		        (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);
+	},
+
+	_updateOpacity: function () {
+		var i,
+		    tiles = this._tiles;
+
+		if (L.Browser.ielt9) {
+			for (i in tiles) {
+				L.DomUtil.setOpacity(tiles[i], this.options.opacity);
+			}
+		} else {
+			L.DomUtil.setOpacity(this._container, this.options.opacity);
+		}
+	},
+
+	_initContainer: function () {
+		var tilePane = this._map._panes.tilePane;
+
+		if (!this._container) {
+			this._container = L.DomUtil.create('div', 'leaflet-layer');
+
+			this._updateZIndex();
+
+			if (this._animated) {
+				var className = 'leaflet-tile-container';
+
+				this._bgBuffer = L.DomUtil.create('div', className, this._container);
+				this._tileContainer = L.DomUtil.create('div', className, this._container);
+
+			} else {
+				this._tileContainer = this._container;
+			}
+
+			tilePane.appendChild(this._container);
+
+			if (this.options.opacity < 1) {
+				this._updateOpacity();
+			}
+		}
+	},
+
+	_reset: function (e) {
+		for (var key in this._tiles) {
+			this.fire('tileunload', {tile: this._tiles[key]});
+		}
+
+		this._tiles = {};
+		this._tilesToLoad = 0;
+
+		if (this.options.reuseTiles) {
+			this._unusedTiles = [];
+		}
+
+		this._tileContainer.innerHTML = '';
+
+		if (this._animated && e && e.hard) {
+			this._clearBgBuffer();
+		}
+
+		this._initContainer();
+	},
+
+	_getTileSize: function () {
+		var map = this._map,
+		    zoom = map.getZoom() + this.options.zoomOffset,
+		    zoomN = this.options.maxNativeZoom,
+		    tileSize = this.options.tileSize;
+
+		if (zoomN && zoom > zoomN) {
+			tileSize = Math.round(map.getZoomScale(zoom) / map.getZoomScale(zoomN) * tileSize);
+		}
+
+		return tileSize;
+	},
+
+	_update: function () {
+
+		if (!this._map) { return; }
+
+		var map = this._map,
+		    bounds = map.getPixelBounds(),
+		    zoom = map.getZoom(),
+		    tileSize = this._getTileSize();
+
+		if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
+			return;
+		}
+
+		var tileBounds = L.bounds(
+		        bounds.min.divideBy(tileSize)._floor(),
+		        bounds.max.divideBy(tileSize)._floor());
+
+		this._addTilesFromCenterOut(tileBounds);
+
+		if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {
+			this._removeOtherTiles(tileBounds);
+		}
+	},
+
+	_addTilesFromCenterOut: function (bounds) {
+		var queue = [],
+		    center = bounds.getCenter();
+
+		var j, i, point;
+
+		for (j = bounds.min.y; j <= bounds.max.y; j++) {
+			for (i = bounds.min.x; i <= bounds.max.x; i++) {
+				point = new L.Point(i, j);
+
+				if (this._tileShouldBeLoaded(point)) {
+					queue.push(point);
+				}
+			}
+		}
+
+		var tilesToLoad = queue.length;
+
+		if (tilesToLoad === 0) { return; }
+
+		// load tiles in order of their distance to center
+		queue.sort(function (a, b) {
+			return a.distanceTo(center) - b.distanceTo(center);
+		});
+
+		var fragment = document.createDocumentFragment();
+
+		// if its the first batch of tiles to load
+		if (!this._tilesToLoad) {
+			this.fire('loading');
+		}
+
+		this._tilesToLoad += tilesToLoad;
+
+		for (i = 0; i < tilesToLoad; i++) {
+			this._addTile(queue[i], fragment);
+		}
+
+		this._tileContainer.appendChild(fragment);
+	},
+
+	_tileShouldBeLoaded: function (tilePoint) {
+		if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {
+			return false; // already loaded
+		}
+
+		var options = this.options;
+
+		if (!options.continuousWorld) {
+			var limit = this._getWrapTileNum();
+
+			// don't load if exceeds world bounds
+			if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit.x)) ||
+				tilePoint.y < 0 || tilePoint.y >= limit.y) { return false; }
+		}
+
+		if (options.bounds) {
+			var tileSize = this._getTileSize(),
+			    nwPoint = tilePoint.multiplyBy(tileSize),
+			    sePoint = nwPoint.add([tileSize, tileSize]),
+			    nw = this._map.unproject(nwPoint),
+			    se = this._map.unproject(sePoint);
+
+			// TODO temporary hack, will be removed after refactoring projections
+			// https://github.com/Leaflet/Leaflet/issues/1618
+			if (!options.continuousWorld && !options.noWrap) {
+				nw = nw.wrap();
+				se = se.wrap();
+			}
+
+			if (!options.bounds.intersects([nw, se])) { return false; }
+		}
+
+		return true;
+	},
+
+	_removeOtherTiles: function (bounds) {
+		var kArr, x, y, key;
+
+		for (key in this._tiles) {
+			kArr = key.split(':');
+			x = parseInt(kArr[0], 10);
+			y = parseInt(kArr[1], 10);
+
+			// remove tile if it's out of bounds
+			if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
+				this._removeTile(key);
+			}
+		}
+	},
+
+	_removeTile: function (key) {
+		var tile = this._tiles[key];
+
+		this.fire('tileunload', {tile: tile, url: tile.src});
+
+		if (this.options.reuseTiles) {
+			L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
+			this._unusedTiles.push(tile);
+
+		} else if (tile.parentNode === this._tileContainer) {
+			this._tileContainer.removeChild(tile);
+		}
+
+		// for https://github.com/CloudMade/Leaflet/issues/137
+		if (!L.Browser.android) {
+			tile.onload = null;
+			tile.src = L.Util.emptyImageUrl;
+		}
+
+		delete this._tiles[key];
+	},
+
+	_addTile: function (tilePoint, container) {
+		var tilePos = this._getTilePos(tilePoint);
+
+		// get unused tile - or create a new tile
+		var tile = this._getTile();
+
+		/*
+		Chrome 20 layouts much faster with top/left (verify with timeline, frames)
+		Android 4 browser has display issues with top/left and requires transform instead
+		(other browsers don't currently care) - see debug/hacks/jitter.html for an example
+		*/
+		L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome);
+
+		this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
+
+		this._loadTile(tile, tilePoint);
+
+		if (tile.parentNode !== this._tileContainer) {
+			container.appendChild(tile);
+		}
+	},
+
+	_getZoomForUrl: function () {
+
+		var options = this.options,
+		    zoom = this._map.getZoom();
+
+		if (options.zoomReverse) {
+			zoom = options.maxZoom - zoom;
+		}
+
+		zoom += options.zoomOffset;
+
+		return options.maxNativeZoom ? Math.min(zoom, options.maxNativeZoom) : zoom;
+	},
+
+	_getTilePos: function (tilePoint) {
+		var origin = this._map.getPixelOrigin(),
+		    tileSize = this._getTileSize();
+
+		return tilePoint.multiplyBy(tileSize).subtract(origin);
+	},
+
+	// image-specific code (override to implement e.g. Canvas or SVG tile layer)
+
+	getTileUrl: function (tilePoint) {
+		return L.Util.template(this._url, L.extend({
+			s: this._getSubdomain(tilePoint),
+			z: tilePoint.z,
+			x: tilePoint.x,
+			y: tilePoint.y
+		}, this.options));
+	},
+
+	_getWrapTileNum: function () {
+		var crs = this._map.options.crs,
+		    size = crs.getSize(this._map.getZoom());
+		return size.divideBy(this._getTileSize())._floor();
+	},
+
+	_adjustTilePoint: function (tilePoint) {
+
+		var limit = this._getWrapTileNum();
+
+		// wrap tile coordinates
+		if (!this.options.continuousWorld && !this.options.noWrap) {
+			tilePoint.x = ((tilePoint.x % limit.x) + limit.x) % limit.x;
+		}
+
+		if (this.options.tms) {
+			tilePoint.y = limit.y - tilePoint.y - 1;
+		}
+
+		tilePoint.z = this._getZoomForUrl();
+	},
+
+	_getSubdomain: function (tilePoint) {
+		var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
+		return this.options.subdomains[index];
+	},
+
+	_getTile: function () {
+		if (this.options.reuseTiles && this._unusedTiles.length > 0) {
+			var tile = this._unusedTiles.pop();
+			this._resetTile(tile);
+			return tile;
+		}
+		return this._createTile();
+	},
+
+	// Override if data stored on a tile needs to be cleaned up before reuse
+	_resetTile: function (/*tile*/) {},
+
+	_createTile: function () {
+		var tile = L.DomUtil.create('img', 'leaflet-tile');
+		tile.style.width = tile.style.height = this._getTileSize() + 'px';
+		tile.galleryimg = 'no';
+
+		tile.onselectstart = tile.onmousemove = L.Util.falseFn;
+
+		if (L.Browser.ielt9 && this.options.opacity !== undefined) {
+			L.DomUtil.setOpacity(tile, this.options.opacity);
+		}
+		// without this hack, tiles disappear after zoom on Chrome for Android
+		// https://github.com/Leaflet/Leaflet/issues/2078
+		if (L.Browser.mobileWebkit3d) {
+			tile.style.WebkitBackfaceVisibility = 'hidden';
+		}
+		return tile;
+	},
+
+	_loadTile: function (tile, tilePoint) {
+		tile._layer  = this;
+		tile.onload  = this._tileOnLoad;
+		tile.onerror = this._tileOnError;
+
+		this._adjustTilePoint(tilePoint);
+		tile.src     = this.getTileUrl(tilePoint);
+
+		this.fire('tileloadstart', {
+			tile: tile,
+			url: tile.src
+		});
+	},
+
+	_tileLoaded: function () {
+		this._tilesToLoad--;
+
+		if (this._animated) {
+			L.DomUtil.addClass(this._tileContainer, 'leaflet-zoom-animated');
+		}
+
+		if (!this._tilesToLoad) {
+			this.fire('load');
+
+			if (this._animated) {
+				// clear scaled tiles after all new tiles are loaded (for performance)
+				clearTimeout(this._clearBgBufferTimer);
+				this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 500);
+			}
+		}
+	},
+
+	_tileOnLoad: function () {
+		var layer = this._layer;
+
+		//Only if we are loading an actual image
+		if (this.src !== L.Util.emptyImageUrl) {
+			L.DomUtil.addClass(this, 'leaflet-tile-loaded');
+
+			layer.fire('tileload', {
+				tile: this,
+				url: this.src
+			});
+		}
+
+		layer._tileLoaded();
+	},
+
+	_tileOnError: function () {
+		var layer = this._layer;
+
+		layer.fire('tileerror', {
+			tile: this,
+			url: this.src
+		});
+
+		var newUrl = layer.options.errorTileUrl;
+		if (newUrl) {
+			this.src = newUrl;
+		}
+
+		layer._tileLoaded();
+	}
+});
+
+L.tileLayer = function (url, options) {
+	return new L.TileLayer(url, options);
+};
+
+
+/*
+ * L.TileLayer.WMS is used for putting WMS tile layers on the map.
+ */
+
+L.TileLayer.WMS = L.TileLayer.extend({
+
+	defaultWmsParams: {
+		service: 'WMS',
+		request: 'GetMap',
+		version: '1.1.1',
+		layers: '',
+		styles: '',
+		format: 'image/jpeg',
+		transparent: false
+	},
+
+	initialize: function (url, options) { // (String, Object)
+
+		this._url = url;
+
+		var wmsParams = L.extend({}, this.defaultWmsParams),
+		    tileSize = options.tileSize || this.options.tileSize;
+
+		if (options.detectRetina && L.Browser.retina) {
+			wmsParams.width = wmsParams.height = tileSize * 2;
+		} else {
+			wmsParams.width = wmsParams.height = tileSize;
+		}
+
+		for (var i in options) {
+			// all keys that are not TileLayer options go to WMS params
+			if (!this.options.hasOwnProperty(i) && i !== 'crs') {
+				wmsParams[i] = options[i];
+			}
+		}
+
+		this.wmsParams = wmsParams;
+
+		L.setOptions(this, options);
+	},
+
+	onAdd: function (map) {
+
+		this._crs = this.options.crs || map.options.crs;
+
+		this._wmsVersion = parseFloat(this.wmsParams.version);
+
+		var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
+		this.wmsParams[projectionKey] = this._crs.code;
+
+		L.TileLayer.prototype.onAdd.call(this, map);
+	},
+
+	getTileUrl: function (tilePoint) { // (Point, Number) -> String
+
+		var map = this._map,
+		    tileSize = this.options.tileSize,
+
+		    nwPoint = tilePoint.multiplyBy(tileSize),
+		    sePoint = nwPoint.add([tileSize, tileSize]),
+
+		    nw = this._crs.project(map.unproject(nwPoint, tilePoint.z)),
+		    se = this._crs.project(map.unproject(sePoint, tilePoint.z)),
+		    bbox = this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
+		        [se.y, nw.x, nw.y, se.x].join(',') :
+		        [nw.x, se.y, se.x, nw.y].join(','),
+
+		    url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
+
+		return url + L.Util.getParamString(this.wmsParams, url, true) + '&BBOX=' + bbox;
+	},
+
+	setParams: function (params, noRedraw) {
+
+		L.extend(this.wmsParams, params);
+
+		if (!noRedraw) {
+			this.redraw();
+		}
+
+		return this;
+	}
+});
+
+L.tileLayer.wms = function (url, options) {
+	return new L.TileLayer.WMS(url, options);
+};
+
+
+/*
+ * L.TileLayer.Canvas is a class that you can use as a base for creating
+ * dynamically drawn Canvas-based tile layers.
+ */
+
+L.TileLayer.Canvas = L.TileLayer.extend({
+	options: {
+		async: false
+	},
+
+	initialize: function (options) {
+		L.setOptions(this, options);
+	},
+
+	redraw: function () {
+		if (this._map) {
+			this._reset({hard: true});
+			this._update();
+		}
+
+		for (var i in this._tiles) {
+			this._redrawTile(this._tiles[i]);
+		}
+		return this;
+	},
+
+	_redrawTile: function (tile) {
+		this.drawTile(tile, tile._tilePoint, this._map._zoom);
+	},
+
+	_createTile: function () {
+		var tile = L.DomUtil.create('canvas', 'leaflet-tile');
+		tile.width = tile.height = this.options.tileSize;
+		tile.onselectstart = tile.onmousemove = L.Util.falseFn;
+		return tile;
+	},
+
+	_loadTile: function (tile, tilePoint) {
+		tile._layer = this;
+		tile._tilePoint = tilePoint;
+
+		this._redrawTile(tile);
+
+		if (!this.options.async) {
+			this.tileDrawn(tile);
+		}
+	},
+
+	drawTile: function (/*tile, tilePoint*/) {
+		// override with rendering code
+	},
+
+	tileDrawn: function (tile) {
+		this._tileOnLoad.call(tile);
+	}
+});
+
+
+L.tileLayer.canvas = function (options) {
+	return new L.TileLayer.Canvas(options);
+};
+
+
+/*
+ * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds).
+ */
+
+L.ImageOverlay = L.Class.extend({
+	includes: L.Mixin.Events,
+
+	options: {
+		opacity: 1
+	},
+
+	initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
+		this._url = url;
+		this._bounds = L.latLngBounds(bounds);
+
+		L.setOptions(this, options);
+	},
+
+	onAdd: function (map) {
+		this._map = map;
+
+		if (!this._image) {
+			this._initImage();
+		}
+
+		map._panes.overlayPane.appendChild(this._image);
+
+		map.on('viewreset', this._reset, this);
+
+		if (map.options.zoomAnimation && L.Browser.any3d) {
+			map.on('zoomanim', this._animateZoom, this);
+		}
+
+		this._reset();
+	},
+
+	onRemove: function (map) {
+		map.getPanes().overlayPane.removeChild(this._image);
+
+		map.off('viewreset', this._reset, this);
+
+		if (map.options.zoomAnimation) {
+			map.off('zoomanim', this._animateZoom, this);
+		}
+	},
+
+	addTo: function (map) {
+		map.addLayer(this);
+		return this;
+	},
+
+	setOpacity: function (opacity) {
+		this.options.opacity = opacity;
+		this._updateOpacity();
+		return this;
+	},
+
+	// TODO remove bringToFront/bringToBack duplication from TileLayer/Path
+	bringToFront: function () {
+		if (this._image) {
+			this._map._panes.overlayPane.appendChild(this._image);
+		}
+		return this;
+	},
+
+	bringToBack: function () {
+		var pane = this._map._panes.overlayPane;
+		if (this._image) {
+			pane.insertBefore(this._image, pane.firstChild);
+		}
+		return this;
+	},
+
+	setUrl: function (url) {
+		this._url = url;
+		this._image.src = this._url;
+	},
+
+	getAttribution: function () {
+		return this.options.attribution;
+	},
+
+	_initImage: function () {
+		this._image = L.DomUtil.create('img', 'leaflet-image-layer');
+
+		if (this._map.options.zoomAnimation && L.Browser.any3d) {
+			L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
+		} else {
+			L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
+		}
+
+		this._updateOpacity();
+
+		//TODO createImage util method to remove duplication
+		L.extend(this._image, {
+			galleryimg: 'no',
+			onselectstart: L.Util.falseFn,
+			onmousemove: L.Util.falseFn,
+			onload: L.bind(this._onImageLoad, this),
+			src: this._url
+		});
+	},
+
+	_animateZoom: function (e) {
+		var map = this._map,
+		    image = this._image,
+		    scale = map.getZoomScale(e.zoom),
+		    nw = this._bounds.getNorthWest(),
+		    se = this._bounds.getSouthEast(),
+
+		    topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
+		    size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
+		    origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));
+
+		image.style[L.DomUtil.TRANSFORM] =
+		        L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
+	},
+
+	_reset: function () {
+		var image   = this._image,
+		    topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
+		    size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);
+
+		L.DomUtil.setPosition(image, topLeft);
+
+		image.style.width  = size.x + 'px';
+		image.style.height = size.y + 'px';
+	},
+
+	_onImageLoad: function () {
+		this.fire('load');
+	},
+
+	_updateOpacity: function () {
+		L.DomUtil.setOpacity(this._image, this.options.opacity);
+	}
+});
+
+L.imageOverlay = function (url, bounds, options) {
+	return new L.ImageOverlay(url, bounds, options);
+};
+
+
+/*
+ * L.Icon is an image-based icon class that you can use with L.Marker for custom markers.
+ */
+
+L.Icon = L.Class.extend({
+	options: {
+		/*
+		iconUrl: (String) (required)
+		iconRetinaUrl: (String) (optional, used for retina devices if detected)
+		iconSize: (Point) (can be set through CSS)
+		iconAnchor: (Point) (centered by default, can be set in CSS with negative margins)
+		popupAnchor: (Point) (if not specified, popup opens in the anchor point)
+		shadowUrl: (String) (no shadow by default)
+		shadowRetinaUrl: (String) (optional, used for retina devices if detected)
+		shadowSize: (Point)
+		shadowAnchor: (Point)
+		*/
+		className: ''
+	},
+
+	initialize: function (options) {
+		L.setOptions(this, options);
+	},
+
+	createIcon: function (oldIcon) {
+		return this._createIcon('icon', oldIcon);
+	},
+
+	createShadow: function (oldIcon) {
+		return this._createIcon('shadow', oldIcon);
+	},
+
+	_createIcon: function (name, oldIcon) {
+		var src = this._getIconUrl(name);
+
+		if (!src) {
+			if (name === 'icon') {
+				throw new Error('iconUrl not set in Icon options (see the docs).');
+			}
+			return null;
+		}
+
+		var img;
+		if (!oldIcon || oldIcon.tagName !== 'IMG') {
+			img = this._createImg(src);
+		} else {
+			img = this._createImg(src, oldIcon);
+		}
+		this._setIconStyles(img, name);
+
+		return img;
+	},
+
+	_setIconStyles: function (img, name) {
+		var options = this.options,
+		    size = L.point(options[name + 'Size']),
+		    anchor;
+
+		if (name === 'shadow') {
+			anchor = L.point(options.shadowAnchor || options.iconAnchor);
+		} else {
+			anchor = L.point(options.iconAnchor);
+		}
+
+		if (!anchor && size) {
+			anchor = size.divideBy(2, true);
+		}
+
+		img.className = 'leaflet-marker-' + name + ' ' + options.className;
+
+		if (anchor) {
+			img.style.marginLeft = (-anchor.x) + 'px';
+			img.style.marginTop  = (-anchor.y) + 'px';
+		}
+
+		if (size) {
+			img.style.width  = size.x + 'px';
+			img.style.height = size.y + 'px';
+		}
+	},
+
+	_createImg: function (src, el) {
+		el = el || document.createElement('img');
+		el.src = src;
+		return el;
+	},
+
+	_getIconUrl: function (name) {
+		if (L.Browser.retina && this.options[name + 'RetinaUrl']) {
+			return this.options[name + 'RetinaUrl'];
+		}
+		return this.options[name + 'Url'];
+	}
+});
+
+L.icon = function (options) {
+	return new L.Icon(options);
+};
+
+
+/*
+ * L.Icon.Default is the blue marker icon used by default in Leaflet.
+ */
+
+L.Icon.Default = L.Icon.extend({
+
+	options: {
+		iconSize: [25, 41],
+		iconAnchor: [12, 41],
+		popupAnchor: [1, -34],
+
+		shadowSize: [41, 41]
+	},
+
+	_getIconUrl: function (name) {
+		var key = name + 'Url';
+
+		if (this.options[key]) {
+			return this.options[key];
+		}
+
+		if (L.Browser.retina && name === 'icon') {
+			name += '-2x';
+		}
+
+		var path = L.Icon.Default.imagePath;
+
+		if (!path) {
+			throw new Error('Couldn\'t autodetect L.Icon.Default.imagePath, set it manually.');
+		}
+
+		return path + '/marker-' + name + '.png';
+	}
+});
+
+L.Icon.Default.imagePath = (function () {
+	var scripts = document.getElementsByTagName('script'),
+	    leafletRe = /[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/;
+
+	var i, len, src, matches, path;
+
+	for (i = 0, len = scripts.length; i < len; i++) {
+		src = scripts[i].src;
+		matches = src.match(leafletRe);
+
+		if (matches) {
+			path = src.split(leafletRe)[0];
+			return (path ? path + '/' : '') + 'images';
+		}
+	}
+}());
+
+
+/*
+ * L.Marker is used to display clickable/draggable icons on the map.
+ */
+
+L.Marker = L.Class.extend({
+
+	includes: L.Mixin.Events,
+
+	options: {
+		icon: new L.Icon.Default(),
+		title: '',
+		alt: '',
+		clickable: true,
+		draggable: false,
+		keyboard: true,
+		zIndexOffset: 0,
+		opacity: 1,
+		riseOnHover: false,
+		riseOffset: 250
+	},
+
+	initialize: function (latlng, options) {
+		L.setOptions(this, options);
+		this._latlng = L.latLng(latlng);
+	},
+
+	onAdd: function (map) {
+		this._map = map;
+
+		map.on('viewreset', this.update, this);
+
+		this._initIcon();
+		this.update();
+		this.fire('add');
+
+		if (map.options.zoomAnimation && map.options.markerZoomAnimation) {
+			map.on('zoomanim', this._animateZoom, this);
+		}
+	},
+
+	addTo: function (map) {
+		map.addLayer(this);
+		return this;
+	},
+
+	onRemove: function (map) {
+		if (this.dragging) {
+			this.dragging.disable();
+		}
+
+		this._removeIcon();
+		this._removeShadow();
+
+		this.fire('remove');
+
+		map.off({
+			'viewreset': this.update,
+			'zoomanim': this._animateZoom
+		}, this);
+
+		this._map = null;
+	},
+
+	getLatLng: function () {
+		return this._latlng;
+	},
+
+	setLatLng: function (latlng) {
+		this._latlng = L.latLng(latlng);
+
+		this.update();
+
+		return this.fire('move', { latlng: this._latlng });
+	},
+
+	setZIndexOffset: function (offset) {
+		this.options.zIndexOffset = offset;
+		this.update();
+
+		return this;
+	},
+
+	setIcon: function (icon) {
+
+		this.options.icon = icon;
+
+		if (this._map) {
+			this._initIcon();
+			this.update();
+		}
+
+		if (this._popup) {
+			this.bindPopup(this._popup);
+		}
+
+		return this;
+	},
+
+	update: function () {
+		if (this._icon) {
+			this._setPos(this._map.latLngToLayerPoint(this._latlng).round());
+		}
+		return this;
+	},
+
+	_initIcon: function () {
+		var options = this.options,
+		    map = this._map,
+		    animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),
+		    classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide';
+
+		var icon = options.icon.createIcon(this._icon),
+			addIcon = false;
+
+		// if we're not reusing the icon, remove the old one and init new one
+		if (icon !== this._icon) {
+			if (this._icon) {
+				this._removeIcon();
+			}
+			addIcon = true;
+
+			if (options.title) {
+				icon.title = options.title;
+			}
+
+			if (options.alt) {
+				icon.alt = options.alt;
+			}
+		}
+
+		L.DomUtil.addClass(icon, classToAdd);
+
+		if (options.keyboard) {
+			icon.tabIndex = '0';
+		}
+
+		this._icon = icon;
+
+		this._initInteraction();
+
+		if (options.riseOnHover) {
+			L.DomEvent
+				.on(icon, 'mouseover', this._bringToFront, this)
+				.on(icon, 'mouseout', this._resetZIndex, this);
+		}
+
+		var newShadow = options.icon.createShadow(this._shadow),
+			addShadow = false;
+
+		if (newShadow !== this._shadow) {
+			this._removeShadow();
+			addShadow = true;
+		}
+
+		if (newShadow) {
+			L.DomUtil.addClass(newShadow, classToAdd);
+		}
+		this._shadow = newShadow;
+
+
+		if (options.opacity < 1) {
+			this._updateOpacity();
+		}
+
+
+		var panes = this._map._panes;
+
+		if (addIcon) {
+			panes.markerPane.appendChild(this._icon);
+		}
+
+		if (newShadow && addShadow) {
+			panes.shadowPane.appendChild(this._shadow);
+		}
+	},
+
+	_removeIcon: function () {
+		if (this.options.riseOnHover) {
+			L.DomEvent
+			    .off(this._icon, 'mouseover', this._bringToFront)
+			    .off(this._icon, 'mouseout', this._resetZIndex);
+		}
+
+		this._map._panes.markerPane.removeChild(this._icon);
+
+		this._icon = null;
+	},
+
+	_removeShadow: function () {
+		if (this._shadow) {
+			this._map._panes.shadowPane.removeChild(this._shadow);
+		}
+		this._shadow = null;
+	},
+
+	_setPos: function (pos) {
+		L.DomUtil.setPosition(this._icon, pos);
+
+		if (this._shadow) {
+			L.DomUtil.setPosition(this._shadow, pos);
+		}
+
+		this._zIndex = pos.y + this.options.zIndexOffset;
+
+		this._resetZIndex();
+	},
+
+	_updateZIndex: function (offset) {
+		this._icon.style.zIndex = this._zIndex + offset;
+	},
+
+	_animateZoom: function (opt) {
+		var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
+
+		this._setPos(pos);
+	},
+
+	_initInteraction: function () {
+
+		if (!this.options.clickable) { return; }
+
+		// TODO refactor into something shared with Map/Path/etc. to DRY it up
+
+		var icon = this._icon,
+		    events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu'];
+
+		L.DomUtil.addClass(icon, 'leaflet-clickable');
+		L.DomEvent.on(icon, 'click', this._onMouseClick, this);
+		L.DomEvent.on(icon, 'keypress', this._onKeyPress, this);
+
+		for (var i = 0; i < events.length; i++) {
+			L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
+		}
+
+		if (L.Handler.MarkerDrag) {
+			this.dragging = new L.Handler.MarkerDrag(this);
+
+			if (this.options.draggable) {
+				this.dragging.enable();
+			}
+		}
+	},
+
+	_onMouseClick: function (e) {
+		var wasDragged = this.dragging && this.dragging.moved();
+
+		if (this.hasEventListeners(e.type) || wasDragged) {
+			L.DomEvent.stopPropagation(e);
+		}
+
+		if (wasDragged) { return; }
+
+		if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; }
+
+		this.fire(e.type, {
+			originalEvent: e,
+			latlng: this._latlng
+		});
+	},
+
+	_onKeyPress: function (e) {
+		if (e.keyCode === 13) {
+			this.fire('click', {
+				originalEvent: e,
+				latlng: this._latlng
+			});
+		}
+	},
+
+	_fireMouseEvent: function (e) {
+
+		this.fire(e.type, {
+			originalEvent: e,
+			latlng: this._latlng
+		});
+
+		// TODO proper custom event propagation
+		// this line will always be called if marker is in a FeatureGroup
+		if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) {
+			L.DomEvent.preventDefault(e);
+		}
+		if (e.type !== 'mousedown') {
+			L.DomEvent.stopPropagation(e);
+		} else {
+			L.DomEvent.preventDefault(e);
+		}
+	},
+
+	setOpacity: function (opacity) {
+		this.options.opacity = opacity;
+		if (this._map) {
+			this._updateOpacity();
+		}
+
+		return this;
+	},
+
+	_updateOpacity: function () {
+		L.DomUtil.setOpacity(this._icon, this.options.opacity);
+		if (this._shadow) {
+			L.DomUtil.setOpacity(this._shadow, this.options.opacity);
+		}
+	},
+
+	_bringToFront: function () {
+		this._updateZIndex(this.options.riseOffset);
+	},
+
+	_resetZIndex: function () {
+		this._updateZIndex(0);
+	}
+});
+
+L.marker = function (latlng, options) {
+	return new L.Marker(latlng, options);
+};
+
+
+/*
+ * L.DivIcon is a lightweight HTML-based icon class (as opposed to the image-based L.Icon)
+ * to use with L.Marker.
+ */
+
+L.DivIcon = L.Icon.extend({
+	options: {
+		iconSize: [12, 12], // also can be set through CSS
+		/*
+		iconAnchor: (Point)
+		popupAnchor: (Point)
+		html: (String)
+		bgPos: (Point)
+		*/
+		className: 'leaflet-div-icon',
+		html: false
+	},
+
+	createIcon: function (oldIcon) {
+		var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
+		    options = this.options;
+
+		if (options.html !== false) {
+			div.innerHTML = options.html;
+		} else {
+			div.innerHTML = '';
+		}
+
+		if (options.bgPos) {
+			div.style.backgroundPosition =
+			        (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
+		}
+
+		this._setIconStyles(div, 'icon');
+		return div;
+	},
+
+	createShadow: function () {
+		return null;
+	}
+});
+
+L.divIcon = function (options) {
+	return new L.DivIcon(options);
+};
+
+
+/*
+ * L.Popup is used for displaying popups on the map.
+ */
+
+L.Map.mergeOptions({
+	closePopupOnClick: true
+});
+
+L.Popup = L.Class.extend({
+	includes: L.Mixin.Events,
+
+	options: {
+		minWidth: 50,
+		maxWidth: 300,
+		// maxHeight: null,
+		autoPan: true,
+		closeButton: true,
+		offset: [0, 7],
+		autoPanPadding: [5, 5],
+		// autoPanPaddingTopLeft: null,
+		// autoPanPaddingBottomRight: null,
+		keepInView: false,
+		className: '',
+		zoomAnimation: true
+	},
+
+	initialize: function (options, source) {
+		L.setOptions(this, options);
+
+		this._source = source;
+		this._animated = L.Browser.any3d && this.options.zoomAnimation;
+		this._isOpen = false;
+	},
+
+	onAdd: function (map) {
+		this._map = map;
+
+		if (!this._container) {
+			this._initLayout();
+		}
+
+		var animFade = map.options.fadeAnimation;
+
+		if (animFade) {
+			L.DomUtil.setOpacity(this._container, 0);
+		}
+		map._panes.popupPane.appendChild(this._container);
+
+		map.on(this._getEvents(), this);
+
+		this.update();
+
+		if (animFade) {
+			L.DomUtil.setOpacity(this._container, 1);
+		}
+
+		this.fire('open');
+
+		map.fire('popupopen', {popup: this});
+
+		if (this._source) {
+			this._source.fire('popupopen', {popup: this});
+		}
+	},
+
+	addTo: function (map) {
+		map.addLayer(this);
+		return this;
+	},
+
+	openOn: function (map) {
+		map.openPopup(this);
+		return this;
+	},
+
+	onRemove: function (map) {
+		map._panes.popupPane.removeChild(this._container);
+
+		L.Util.falseFn(this._container.offsetWidth); // force reflow
+
+		map.off(this._getEvents(), this);
+
+		if (map.options.fadeAnimation) {
+			L.DomUtil.setOpacity(this._container, 0);
+		}
+
+		this._map = null;
+
+		this.fire('close');
+
+		map.fire('popupclose', {popup: this});
+
+		if (this._source) {
+			this._source.fire('popupclose', {popup: this});
+		}
+	},
+
+	getLatLng: function () {
+		return this._latlng;
+	},
+
+	setLatLng: function (latlng) {
+		this._latlng = L.latLng(latlng);
+		if (this._map) {
+			this._updatePosition();
+			this._adjustPan();
+		}
+		return this;
+	},
+
+	getContent: function () {
+		return this._content;
+	},
+
+	setContent: function (content) {
+		this._content = content;
+		this.update();
+		return this;
+	},
+
+	update: function () {
+		if (!this._map) { return; }
+
+		this._container.style.visibility = 'hidden';
+
+		this._updateContent();
+		this._updateLayout();
+		this._updatePosition();
+
+		this._container.style.visibility = '';
+
+		this._adjustPan();
+	},
+
+	_getEvents: function () {
+		var events = {
+			viewreset: this._updatePosition
+		};
+
+		if (this._animated) {
+			events.zoomanim = this._zoomAnimation;
+		}
+		if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
+			events.preclick = this._close;
+		}
+		if (this.options.keepInView) {
+			events.moveend = this._adjustPan;
+		}
+
+		return events;
+	},
+
+	_close: function () {
+		if (this._map) {
+			this._map.closePopup(this);
+		}
+	},
+
+	_initLayout: function () {
+		var prefix = 'leaflet-popup',
+			containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' +
+			        (this._animated ? 'animated' : 'hide'),
+			container = this._container = L.DomUtil.create('div', containerClass),
+			closeButton;
+
+		if (this.options.closeButton) {
+			closeButton = this._closeButton =
+			        L.DomUtil.create('a', prefix + '-close-button', container);
+			closeButton.href = '#close';
+			closeButton.innerHTML = '&#215;';
+			L.DomEvent.disableClickPropagation(closeButton);
+
+			L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
+		}
+
+		var wrapper = this._wrapper =
+		        L.DomUtil.create('div', prefix + '-content-wrapper', container);
+		L.DomEvent.disableClickPropagation(wrapper);
+
+		this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
+
+		L.DomEvent.disableScrollPropagation(this._contentNode);
+		L.DomEvent.on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
+
+		this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
+		this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
+	},
+
+	_updateContent: function () {
+		if (!this._content) { return; }
+
+		if (typeof this._content === 'string') {
+			this._contentNode.innerHTML = this._content;
+		} else {
+			while (this._contentNode.hasChildNodes()) {
+				this._contentNode.removeChild(this._contentNode.firstChild);
+			}
+			this._contentNode.appendChild(this._content);
+		}
+		this.fire('contentupdate');
+	},
+
+	_updateLayout: function () {
+		var container = this._contentNode,
+		    style = container.style;
+
+		style.width = '';
+		style.whiteSpace = 'nowrap';
+
+		var width = container.offsetWidth;
+		width = Math.min(width, this.options.maxWidth);
+		width = Math.max(width, this.options.minWidth);
+
+		style.width = (width + 1) + 'px';
+		style.whiteSpace = '';
+
+		style.height = '';
+
+		var height = container.offsetHeight,
+		    maxHeight = this.options.maxHeight,
+		    scrolledClass = 'leaflet-popup-scrolled';
+
+		if (maxHeight && height > maxHeight) {
+			style.height = maxHeight + 'px';
+			L.DomUtil.addClass(container, scrolledClass);
+		} else {
+			L.DomUtil.removeClass(container, scrolledClass);
+		}
+
+		this._containerWidth = this._container.offsetWidth;
+	},
+
+	_updatePosition: function () {
+		if (!this._map) { return; }
+
+		var pos = this._map.latLngToLayerPoint(this._latlng),
+		    animated = this._animated,
+		    offset = L.point(this.options.offset);
+
+		if (animated) {
+			L.DomUtil.setPosition(this._container, pos);
+		}
+
+		this._containerBottom = -offset.y - (animated ? 0 : pos.y);
+		this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x);
+
+		// bottom position the popup in case the height of the popup changes (images loading etc)
+		this._container.style.bottom = this._containerBottom + 'px';
+		this._container.style.left = this._containerLeft + 'px';
+	},
+
+	_zoomAnimation: function (opt) {
+		var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
+
+		L.DomUtil.setPosition(this._container, pos);
+	},
+
+	_adjustPan: function () {
+		if (!this.options.autoPan) { return; }
+
+		var map = this._map,
+		    containerHeight = this._container.offsetHeight,
+		    containerWidth = this._containerWidth,
+
+		    layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
+
+		if (this._animated) {
+			layerPos._add(L.DomUtil.getPosition(this._container));
+		}
+
+		var containerPos = map.layerPointToContainerPoint(layerPos),
+		    padding = L.point(this.options.autoPanPadding),
+		    paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),
+		    paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),
+		    size = map.getSize(),
+		    dx = 0,
+		    dy = 0;
+
+		if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
+			dx = containerPos.x + containerWidth - size.x + paddingBR.x;
+		}
+		if (containerPos.x - dx - paddingTL.x < 0) { // left
+			dx = containerPos.x - paddingTL.x;
+		}
+		if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
+			dy = containerPos.y + containerHeight - size.y + paddingBR.y;
+		}
+		if (containerPos.y - dy - paddingTL.y < 0) { // top
+			dy = containerPos.y - paddingTL.y;
+		}
+
+		if (dx || dy) {
+			map
+			    .fire('autopanstart')
+			    .panBy([dx, dy]);
+		}
+	},
+
+	_onCloseButtonClick: function (e) {
+		this._close();
+		L.DomEvent.stop(e);
+	}
+});
+
+L.popup = function (options, source) {
+	return new L.Popup(options, source);
+};
+
+
+L.Map.include({
+	openPopup: function (popup, latlng, options) { // (Popup) or (String || HTMLElement, LatLng[, Object])
+		this.closePopup();
+
+		if (!(popup instanceof L.Popup)) {
+			var content = popup;
+
+			popup = new L.Popup(options)
+			    .setLatLng(latlng)
+			    .setContent(content);
+		}
+		popup._isOpen = true;
+
+		this._popup = popup;
+		return this.addLayer(popup);
+	},
+
+	closePopup: function (popup) {
+		if (!popup || popup === this._popup) {
+			popup = this._popup;
+			this._popup = null;
+		}
+		if (popup) {
+			this.removeLayer(popup);
+			popup._isOpen = false;
+		}
+		return this;
+	}
+});
+
+
+/*
+ * Popup extension to L.Marker, adding popup-related methods.
+ */
+
+L.Marker.include({
+	openPopup: function () {
+		if (this._popup && this._map && !this._map.hasLayer(this._popup)) {
+			this._popup.setLatLng(this._latlng);
+			this._map.openPopup(this._popup);
+		}
+
+		return this;
+	},
+
+	closePopup: function () {
+		if (this._popup) {
+			this._popup._close();
+		}
+		return this;
+	},
+
+	togglePopup: function () {
+		if (this._popup) {
+			if (this._popup._isOpen) {
+				this.closePopup();
+			} else {
+				this.openPopup();
+			}
+		}
+		return this;
+	},
+
+	bindPopup: function (content, options) {
+		var anchor = L.point(this.options.icon.options.popupAnchor || [0, 0]);
+
+		anchor = anchor.add(L.Popup.prototype.options.offset);
+
+		if (options && options.offset) {
+			anchor = anchor.add(options.offset);
+		}
+
+		options = L.extend({offset: anchor}, options);
+
+		if (!this._popupHandlersAdded) {
+			this
+			    .on('click', this.togglePopup, this)
+			    .on('remove', this.closePopup, this)
+			    .on('move', this._movePopup, this);
+			this._popupHandlersAdded = true;
+		}
+
+		if (content instanceof L.Popup) {
+			L.setOptions(content, options);
+			this._popup = content;
+			content._source = this;
+		} else {
+			this._popup = new L.Popup(options, this)
+				.setContent(content);
+		}
+
+		return this;
+	},
+
+	setPopupContent: function (content) {
+		if (this._popup) {
+			this._popup.setContent(content);
+		}
+		return this;
+	},
+
+	unbindPopup: function () {
+		if (this._popup) {
+			this._popup = null;
+			this
+			    .off('click', this.togglePopup, this)
+			    .off('remove', this.closePopup, this)
+			    .off('move', this._movePopup, this);
+			this._popupHandlersAdded = false;
+		}
+		return this;
+	},
+
+	getPopup: function () {
+		return this._popup;
+	},
+
+	_movePopup: function (e) {
+		this._popup.setLatLng(e.latlng);
+	}
+});
+
+
+/*
+ * L.LayerGroup is a class to combine several layers into one so that
+ * you can manipulate the group (e.g. add/remove it) as one layer.
+ */
+
+L.LayerGroup = L.Class.extend({
+	initialize: function (layers) {
+		this._layers = {};
+
+		var i, len;
+
+		if (layers) {
+			for (i = 0, len = layers.length; i < len; i++) {
+				this.addLayer(layers[i]);
+			}
+		}
+	},
+
+	addLayer: function (layer) {
+		var id = this.getLayerId(layer);
+
+		this._layers[id] = layer;
+
+		if (this._map) {
+			this._map.addLayer(layer);
+		}
+
+		return this;
+	},
+
+	removeLayer: function (layer) {
+		var id = layer in this._layers ? layer : this.getLayerId(layer);
+
+		if (this._map && this._layers[id]) {
+			this._map.removeLayer(this._layers[id]);
+		}
+
+		delete this._layers[id];
+
+		return this;
+	},
+
+	hasLayer: function (layer) {
+		if (!layer) { return false; }
+
+		return (layer in this._layers || this.getLayerId(layer) in this._layers);
+	},
+
+	clearLayers: function () {
+		this.eachLayer(this.removeLayer, this);
+		return this;
+	},
+
+	invoke: function (methodName) {
+		var args = Array.prototype.slice.call(arguments, 1),
+		    i, layer;
+
+		for (i in this._layers) {
+			layer = this._layers[i];
+
+			if (layer[methodName]) {
+				layer[methodName].apply(layer, args);
+			}
+		}
+
+		return this;
+	},
+
+	onAdd: function (map) {
+		this._map = map;
+		this.eachLayer(map.addLayer, map);
+	},
+
+	onRemove: function (map) {
+		this.eachLayer(map.removeLayer, map);
+		this._map = null;
+	},
+
+	addTo: function (map) {
+		map.addLayer(this);
+		return this;
+	},
+
+	eachLayer: function (method, context) {
+		for (var i in this._layers) {
+			method.call(context, this._layers[i]);
+		}
+		return this;
+	},
+
+	getLayer: function (id) {
+		return this._layers[id];
+	},
+
+	getLayers: function () {
+		var layers = [];
+
+		for (var i in this._layers) {
+			layers.push(this._layers[i]);
+		}
+		return layers;
+	},
+
+	setZIndex: function (zIndex) {
+		return this.invoke('setZIndex', zIndex);
+	},
+
+	getLayerId: function (layer) {
+		return L.stamp(layer);
+	}
+});
+
+L.layerGroup = function (layers) {
+	return new L.LayerGroup(layers);
+};
+
+
+/*
+ * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods
+ * shared between a group of interactive layers (like vectors or markers).
+ */
+
+L.FeatureGroup = L.LayerGroup.extend({
+	includes: L.Mixin.Events,
+
+	statics: {
+		EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose'
+	},
+
+	addLayer: function (layer) {
+		if (this.hasLayer(layer)) {
+			return this;
+		}
+
+		if ('on' in layer) {
+			layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
+		}
+
+		L.LayerGroup.prototype.addLayer.call(this, layer);
+
+		if (this._popupContent && layer.bindPopup) {
+			layer.bindPopup(this._popupContent, this._popupOptions);
+		}
+
+		return this.fire('layeradd', {layer: layer});
+	},
+
+	removeLayer: function (layer) {
+		if (!this.hasLayer(layer)) {
+			return this;
+		}
+		if (layer in this._layers) {
+			layer = this._layers[layer];
+		}
+
+		if ('off' in layer) {
+			layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this);
+		}
+
+		L.LayerGroup.prototype.removeLayer.call(this, layer);
+
+		if (this._popupContent) {
+			this.invoke('unbindPopup');
+		}
+
+		return this.fire('layerremove', {layer: layer});
+	},
+
+	bindPopup: function (content, options) {
+		this._popupContent = content;
+		this._popupOptions = options;
+		return this.invoke('bindPopup', content, options);
+	},
+
+	openPopup: function (latlng) {
+		// open popup on the first layer
+		for (var id in this._layers) {
+			this._layers[id].openPopup(latlng);
+			break;
+		}
+		return this;
+	},
+
+	setStyle: function (style) {
+		return this.invoke('setStyle', style);
+	},
+
+	bringToFront: function () {
+		return this.invoke('bringToFront');
+	},
+
+	bringToBack: function () {
+		return this.invoke('bringToBack');
+	},
+
+	getBounds: function () {
+		var bounds = new L.LatLngBounds();
+
+		this.eachLayer(function (layer) {
+			bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
+		});
+
+		return bounds;
+	},
+
+	_propagateEvent: function (e) {
+		e = L.extend({
+			layer: e.target,
+			target: this
+		}, e);
+		this.fire(e.type, e);
+	}
+});
+
+L.featureGroup = function (layers) {
+	return new L.FeatureGroup(layers);
+};
+
+
+/*
+ * L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc.
+ */
+
+L.Path = L.Class.extend({
+	includes: [L.Mixin.Events],
+
+	statics: {
+		// how much to extend the clip area around the map view
+		// (relative to its size, e.g. 0.5 is half the screen in each direction)
+		// set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is)
+		CLIP_PADDING: (function () {
+			var max = L.Browser.mobile ? 1280 : 2000,
+			    target = (max / Math.max(window.outerWidth, window.outerHeight) - 1) / 2;
+			return Math.max(0, Math.min(0.5, target));
+		})()
+	},
+
+	options: {
+		stroke: true,
+		color: '#0033ff',
+		dashArray: null,
+		lineCap: null,
+		lineJoin: null,
+		weight: 5,
+		opacity: 0.5,
+
+		fill: false,
+		fillColor: null, //same as color by default
+		fillOpacity: 0.2,
+
+		clickable: true
+	},
+
+	initialize: function (options) {
+		L.setOptions(this, options);
+	},
+
+	onAdd: function (map) {
+		this._map = map;
+
+		if (!this._container) {
+			this._initElements();
+			this._initEvents();
+		}
+
+		this.projectLatlngs();
+		this._updatePath();
+
+		if (this._container) {
+			this._map._pathRoot.appendChild(this._container);
+		}
+
+		this.fire('add');
+
+		map.on({
+			'viewreset': this.projectLatlngs,
+			'moveend': this._updatePath
+		}, this);
+	},
+
+	addTo: function (map) {
+		map.addLayer(this);
+		return this;
+	},
+
+	onRemove: function (map) {
+		map._pathRoot.removeChild(this._container);
+
+		// Need to fire remove event before we set _map to null as the event hooks might need the object
+		this.fire('remove');
+		this._map = null;
+
+		if (L.Browser.vml) {
+			this._container = null;
+			this._stroke = null;
+			this._fill = null;
+		}
+
+		map.off({
+			'viewreset': this.projectLatlngs,
+			'moveend': this._updatePath
+		}, this);
+	},
+
+	projectLatlngs: function () {
+		// do all projection stuff here
+	},
+
+	setStyle: function (style) {
+		L.setOptions(this, style);
+
+		if (this._container) {
+			this._updateStyle();
+		}
+
+		return this;
+	},
+
+	redraw: function () {
+		if (this._map) {
+			this.projectLatlngs();
+			this._updatePath();
+		}
+		return this;
+	}
+});
+
+L.Map.include({
+	_updatePathViewport: function () {
+		var p = L.Path.CLIP_PADDING,
+		    size = this.getSize(),
+		    panePos = L.DomUtil.getPosition(this._mapPane),
+		    min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()),
+		    max = min.add(size.multiplyBy(1 + p * 2)._round());
+
+		this._pathViewport = new L.Bounds(min, max);
+	}
+});
+
+
+/*
+ * Extends L.Path with SVG-specific rendering code.
+ */
+
+L.Path.SVG_NS = 'http://www.w3.org/2000/svg';
+
+L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);
+
+L.Path = L.Path.extend({
+	statics: {
+		SVG: L.Browser.svg
+	},
+
+	bringToFront: function () {
+		var root = this._map._pathRoot,
+		    path = this._container;
+
+		if (path && root.lastChild !== path) {
+			root.appendChild(path);
+		}
+		return this;
+	},
+
+	bringToBack: function () {
+		var root = this._map._pathRoot,
+		    path = this._container,
+		    first = root.firstChild;
+
+		if (path && first !== path) {
+			root.insertBefore(path, first);
+		}
+		return this;
+	},
+
+	getPathString: function () {
+		// form path string here
+	},
+
+	_createElement: function (name) {
+		return document.createElementNS(L.Path.SVG_NS, name);
+	},
+
+	_initElements: function () {
+		this._map._initPathRoot();
+		this._initPath();
+		this._initStyle();
+	},
+
+	_initPath: function () {
+		this._container = this._createElement('g');
+
+		this._path = this._createElement('path');
+
+		if (this.options.className) {
+			L.DomUtil.addClass(this._path, this.options.className);
+		}
+
+		this._container.appendChild(this._path);
+	},
+
+	_initStyle: function () {
+		if (this.options.stroke) {
+			this._path.setAttribute('stroke-linejoin', 'round');
+			this._path.setAttribute('stroke-linecap', 'round');
+		}
+		if (this.options.fill) {
+			this._path.setAttribute('fill-rule', 'evenodd');
+		}
+		if (this.options.pointerEvents) {
+			this._path.setAttribute('pointer-events', this.options.pointerEvents);
+		}
+		if (!this.options.clickable && !this.options.pointerEvents) {
+			this._path.setAttribute('pointer-events', 'none');
+		}
+		this._updateStyle();
+	},
+
+	_updateStyle: function () {
+		if (this.options.stroke) {
+			this._path.setAttribute('stroke', this.options.color);
+			this._path.setAttribute('stroke-opacity', this.options.opacity);
+			this._path.setAttribute('stroke-width', this.options.weight);
+			if (this.options.dashArray) {
+				this._path.setAttribute('stroke-dasharray', this.options.dashArray);
+			} else {
+				this._path.removeAttribute('stroke-dasharray');
+			}
+			if (this.options.lineCap) {
+				this._path.setAttribute('stroke-linecap', this.options.lineCap);
+			}
+			if (this.options.lineJoin) {
+				this._path.setAttribute('stroke-linejoin', this.options.lineJoin);
+			}
+		} else {
+			this._path.setAttribute('stroke', 'none');
+		}
+		if (this.options.fill) {
+			this._path.setAttribute('fill', this.options.fillColor || this.options.color);
+			this._path.setAttribute('fill-opacity', this.options.fillOpacity);
+		} else {
+			this._path.setAttribute('fill', 'none');
+		}
+	},
+
+	_updatePath: function () {
+		var str = this.getPathString();
+		if (!str) {
+			// fix webkit empty string parsing bug
+			str = 'M0 0';
+		}
+		this._path.setAttribute('d', str);
+	},
+
+	// TODO remove duplication with L.Map
+	_initEvents: function () {
+		if (this.options.clickable) {
+			if (L.Browser.svg || !L.Browser.vml) {
+				L.DomUtil.addClass(this._path, 'leaflet-clickable');
+			}
+
+			L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
+
+			var events = ['dblclick', 'mousedown', 'mouseover',
+			              'mouseout', 'mousemove', 'contextmenu'];
+			for (var i = 0; i < events.length; i++) {
+				L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
+			}
+		}
+	},
+
+	_onMouseClick: function (e) {
+		if (this._map.dragging && this._map.dragging.moved()) { return; }
+
+		this._fireMouseEvent(e);
+	},
+
+	_fireMouseEvent: function (e) {
+		if (!this._map || !this.hasEventListeners(e.type)) { return; }
+
+		var map = this._map,
+		    containerPoint = map.mouseEventToContainerPoint(e),
+		    layerPoint = map.containerPointToLayerPoint(containerPoint),
+		    latlng = map.layerPointToLatLng(layerPoint);
+
+		this.fire(e.type, {
+			latlng: latlng,
+			layerPoint: layerPoint,
+			containerPoint: containerPoint,
+			originalEvent: e
+		});
+
+		if (e.type === 'contextmenu') {
+			L.DomEvent.preventDefault(e);
+		}
+		if (e.type !== 'mousemove') {
+			L.DomEvent.stopPropagation(e);
+		}
+	}
+});
+
+L.Map.include({
+	_initPathRoot: function () {
+		if (!this._pathRoot) {
+			this._pathRoot = L.Path.prototype._createElement('svg');
+			this._panes.overlayPane.appendChild(this._pathRoot);
+
+			if (this.options.zoomAnimation && L.Browser.any3d) {
+				L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-animated');
+
+				this.on({
+					'zoomanim': this._animatePathZoom,
+					'zoomend': this._endPathZoom
+				});
+			} else {
+				L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-hide');
+			}
+
+			this.on('moveend', this._updateSvgViewport);
+			this._updateSvgViewport();
+		}
+	},
+
+	_animatePathZoom: function (e) {
+		var scale = this.getZoomScale(e.zoom),
+		    offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min);
+
+		this._pathRoot.style[L.DomUtil.TRANSFORM] =
+		        L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') ';
+
+		this._pathZooming = true;
+	},
+
+	_endPathZoom: function () {
+		this._pathZooming = false;
+	},
+
+	_updateSvgViewport: function () {
+
+		if (this._pathZooming) {
+			// Do not update SVGs while a zoom animation is going on otherwise the animation will break.
+			// When the zoom animation ends we will be updated again anyway
+			// This fixes the case where you do a momentum move and zoom while the move is still ongoing.
+			return;
+		}
+
+		this._updatePathViewport();
+
+		var vp = this._pathViewport,
+		    min = vp.min,
+		    max = vp.max,
+		    width = max.x - min.x,
+		    height = max.y - min.y,
+		    root = this._pathRoot,
+		    pane = this._panes.overlayPane;
+
+		// Hack to make flicker on drag end on mobile webkit less irritating
+		if (L.Browser.mobileWebkit) {
+			pane.removeChild(root);
+		}
+
+		L.DomUtil.setPosition(root, min);
+		root.setAttribute('width', width);
+		root.setAttribute('height', height);
+		root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));
+
+		if (L.Browser.mobileWebkit) {
+			pane.appendChild(root);
+		}
+	}
+});
+
+
+/*
+ * Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods.
+ */
+
+L.Path.include({
+
+	bindPopup: function (content, options) {
+
+		if (content instanceof L.Popup) {
+			this._popup = content;
+		} else {
+			if (!this._popup || options) {
+				this._popup = new L.Popup(options, this);
+			}
+			this._popup.setContent(content);
+		}
+
+		if (!this._popupHandlersAdded) {
+			this
+			    .on('click', this._openPopup, this)
+			    .on('remove', this.closePopup, this);
+
+			this._popupHandlersAdded = true;
+		}
+
+		return this;
+	},
+
+	unbindPopup: function () {
+		if (this._popup) {
+			this._popup = null;
+			this
+			    .off('click', this._openPopup)
+			    .off('remove', this.closePopup);
+
+			this._popupHandlersAdded = false;
+		}
+		return this;
+	},
+
+	openPopup: function (latlng) {
+
+		if (this._popup) {
+			// open the popup from one of the path's points if not specified
+			latlng = latlng || this._latlng ||
+			         this._latlngs[Math.floor(this._latlngs.length / 2)];
+
+			this._openPopup({latlng: latlng});
+		}
+
+		return this;
+	},
+
+	closePopup: function () {
+		if (this._popup) {
+			this._popup._close();
+		}
+		return this;
+	},
+
+	_openPopup: function (e) {
+		this._popup.setLatLng(e.latlng);
+		this._map.openPopup(this._popup);
+	}
+});
+
+
+/*
+ * Vector rendering for IE6-8 through VML.
+ * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
+ */
+
+L.Browser.vml = !L.Browser.svg && (function () {
+	try {
+		var div = document.createElement('div');
+		div.innerHTML = '<v:shape adj="1"/>';
+
+		var shape = div.firstChild;
+		shape.style.behavior = 'url(#default#VML)';
+
+		return shape && (typeof shape.adj === 'object');
+
+	} catch (e) {
+		return false;
+	}
+}());
+
+L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
+	statics: {
+		VML: true,
+		CLIP_PADDING: 0.02
+	},
+
+	_createElement: (function () {
+		try {
+			document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
+			return function (name) {
+				return document.createElement('<lvml:' + name + ' class="lvml">');
+			};
+		} catch (e) {
+			return function (name) {
+				return document.createElement(
+				        '<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
+			};
+		}
+	}()),
+
+	_initPath: function () {
+		var container = this._container = this._createElement('shape');
+
+		L.DomUtil.addClass(container, 'leaflet-vml-shape' +
+			(this.options.className ? ' ' + this.options.className : ''));
+
+		if (this.options.clickable) {
+			L.DomUtil.addClass(container, 'leaflet-clickable');
+		}
+
+		container.coordsize = '1 1';
+
+		this._path = this._createElement('path');
+		container.appendChild(this._path);
+
+		this._map._pathRoot.appendChild(container);
+	},
+
+	_initStyle: function () {
+		this._updateStyle();
+	},
+
+	_updateStyle: function () {
+		var stroke = this._stroke,
+		    fill = this._fill,
+		    options = this.options,
+		    container = this._container;
+
+		container.stroked = options.stroke;
+		container.filled = options.fill;
+
+		if (options.stroke) {
+			if (!stroke) {
+				stroke = this._stroke = this._createElement('stroke');
+				stroke.endcap = 'round';
+				container.appendChild(stroke);
+			}
+			stroke.weight = options.weight + 'px';
+			stroke.color = options.color;
+			stroke.opacity = options.opacity;
+
+			if (options.dashArray) {
+				stroke.dashStyle = L.Util.isArray(options.dashArray) ?
+				    options.dashArray.join(' ') :
+				    options.dashArray.replace(/( *, *)/g, ' ');
+			} else {
+				stroke.dashStyle = '';
+			}
+			if (options.lineCap) {
+				stroke.endcap = options.lineCap.replace('butt', 'flat');
+			}
+			if (options.lineJoin) {
+				stroke.joinstyle = options.lineJoin;
+			}
+
+		} else if (stroke) {
+			container.removeChild(stroke);
+			this._stroke = null;
+		}
+
+		if (options.fill) {
+			if (!fill) {
+				fill = this._fill = this._createElement('fill');
+				container.appendChild(fill);
+			}
+			fill.color = options.fillColor || options.color;
+			fill.opacity = options.fillOpacity;
+
+		} else if (fill) {
+			container.removeChild(fill);
+			this._fill = null;
+		}
+	},
+
+	_updatePath: function () {
+		var style = this._container.style;
+
+		style.display = 'none';
+		this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug
+		style.display = '';
+	}
+});
+
+L.Map.include(L.Browser.svg || !L.Browser.vml ? {} : {
+	_initPathRoot: function () {
+		if (this._pathRoot) { return; }
+
+		var root = this._pathRoot = document.createElement('div');
+		root.className = 'leaflet-vml-container';
+		this._panes.overlayPane.appendChild(root);
+
+		this.on('moveend', this._updatePathViewport);
+		this._updatePathViewport();
+	}
+});
+
+
+/*
+ * Vector rendering for all browsers that support canvas.
+ */
+
+L.Browser.canvas = (function () {
+	return !!document.createElement('canvas').getContext;
+}());
+
+L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({
+	statics: {
+		//CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value
+		CANVAS: true,
+		SVG: false
+	},
+
+	redraw: function () {
+		if (this._map) {
+			this.projectLatlngs();
+			this._requestUpdate();
+		}
+		return this;
+	},
+
+	setStyle: function (style) {
+		L.setOptions(this, style);
+
+		if (this._map) {
+			this._updateStyle();
+			this._requestUpdate();
+		}
+		return this;
+	},
+
+	onRemove: function (map) {
+		map
+		    .off('viewreset', this.projectLatlngs, this)
+		    .off('moveend', this._updatePath, this);
+
+		if (this.options.clickable) {
+			this._map.off('click', this._onClick, this);
+			this._map.off('mousemove', this._onMouseMove, this);
+		}
+
+		this._requestUpdate();
+		
+		this.fire('remove');
+		this._map = null;
+	},
+
+	_requestUpdate: function () {
+		if (this._map && !L.Path._updateRequest) {
+			L.Path._updateRequest = L.Util.requestAnimFrame(this._fireMapMoveEnd, this._map);
+		}
+	},
+
+	_fireMapMoveEnd: function () {
+		L.Path._updateRequest = null;
+		this.fire('moveend');
+	},
+
+	_initElements: function () {
+		this._map._initPathRoot();
+		this._ctx = this._map._canvasCtx;
+	},
+
+	_updateStyle: function () {
+		var options = this.options;
+
+		if (options.stroke) {
+			this._ctx.lineWidth = options.weight;
+			this._ctx.strokeStyle = options.color;
+		}
+		if (options.fill) {
+			this._ctx.fillStyle = options.fillColor || options.color;
+		}
+
+		if (options.lineCap) {
+			this._ctx.lineCap = options.lineCap;
+		}
+		if (options.lineJoin) {
+			this._ctx.lineJoin = options.lineJoin;
+		}
+	},
+
+	_drawPath: function () {
+		var i, j, len, len2, point, drawMethod;
+
+		this._ctx.beginPath();
+
+		for (i = 0, len = this._parts.length; i < len; i++) {
+			for (j = 0, len2 = this._parts[i].length; j < len2; j++) {
+				point = this._parts[i][j];
+				drawMethod = (j === 0 ? 'move' : 'line') + 'To';
+
+				this._ctx[drawMethod](point.x, point.y);
+			}
+			// TODO refactor ugly hack
+			if (this instanceof L.Polygon) {
+				this._ctx.closePath();
+			}
+		}
+	},
+
+	_checkIfEmpty: function () {
+		return !this._parts.length;
+	},
+
+	_updatePath: function () {
+		if (this._checkIfEmpty()) { return; }
+
+		var ctx = this._ctx,
+		    options = this.options;
+
+		this._drawPath();
+		ctx.save();
+		this._updateStyle();
+
+		if (options.fill) {
+			ctx.globalAlpha = options.fillOpacity;
+			ctx.fill(options.fillRule || 'evenodd');
+		}
+
+		if (options.stroke) {
+			ctx.globalAlpha = options.opacity;
+			ctx.stroke();
+		}
+
+		ctx.restore();
+
+		// TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
+	},
+
+	_initEvents: function () {
+		if (this.options.clickable) {
+			this._map.on('mousemove', this._onMouseMove, this);
+			this._map.on('click dblclick contextmenu', this._fireMouseEvent, this);
+		}
+	},
+
+	_fireMouseEvent: function (e) {
+		if (this._containsPoint(e.layerPoint)) {
+			this.fire(e.type, e);
+		}
+	},
+
+	_onMouseMove: function (e) {
+		if (!this._map || this._map._animatingZoom) { return; }
+
+		// TODO don't do on each move
+		if (this._containsPoint(e.layerPoint)) {
+			this._ctx.canvas.style.cursor = 'pointer';
+			this._mouseInside = true;
+			this.fire('mouseover', e);
+
+		} else if (this._mouseInside) {
+			this._ctx.canvas.style.cursor = '';
+			this._mouseInside = false;
+			this.fire('mouseout', e);
+		}
+	}
+});
+
+L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} : {
+	_initPathRoot: function () {
+		var root = this._pathRoot,
+		    ctx;
+
+		if (!root) {
+			root = this._pathRoot = document.createElement('canvas');
+			root.style.position = 'absolute';
+			ctx = this._canvasCtx = root.getContext('2d');
+
+			ctx.lineCap = 'round';
+			ctx.lineJoin = 'round';
+
+			this._panes.overlayPane.appendChild(root);
+
+			if (this.options.zoomAnimation) {
+				this._pathRoot.className = 'leaflet-zoom-animated';
+				this.on('zoomanim', this._animatePathZoom);
+				this.on('zoomend', this._endPathZoom);
+			}
+			this.on('moveend', this._updateCanvasViewport);
+			this._updateCanvasViewport();
+		}
+	},
+
+	_updateCanvasViewport: function () {
+		// don't redraw while zooming. See _updateSvgViewport for more details
+		if (this._pathZooming) { return; }
+		this._updatePathViewport();
+
+		var vp = this._pathViewport,
+		    min = vp.min,
+		    size = vp.max.subtract(min),
+		    root = this._pathRoot;
+
+		//TODO check if this works properly on mobile webkit
+		L.DomUtil.setPosition(root, min);
+		root.width = size.x;
+		root.height = size.y;
+		root.getContext('2d').translate(-min.x, -min.y);
+	}
+});
+
+
+/*
+ * L.LineUtil contains different utility functions for line segments
+ * and polylines (clipping, simplification, distances, etc.)
+ */
+
+/*jshint bitwise:false */ // allow bitwise operations for this file
+
+L.LineUtil = {
+
+	// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
+	// Improves rendering performance dramatically by lessening the number of points to draw.
+
+	simplify: function (/*Point[]*/ points, /*Number*/ tolerance) {
+		if (!tolerance || !points.length) {
+			return points.slice();
+		}
+
+		var sqTolerance = tolerance * tolerance;
+
+		// stage 1: vertex reduction
+		points = this._reducePoints(points, sqTolerance);
+
+		// stage 2: Douglas-Peucker simplification
+		points = this._simplifyDP(points, sqTolerance);
+
+		return points;
+	},
+
+	// distance from a point to a segment between two points
+	pointToSegmentDistance:  function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
+		return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
+	},
+
+	closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
+		return this._sqClosestPointOnSegment(p, p1, p2);
+	},
+
+	// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
+	_simplifyDP: function (points, sqTolerance) {
+
+		var len = points.length,
+		    ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
+		    markers = new ArrayConstructor(len);
+
+		markers[0] = markers[len - 1] = 1;
+
+		this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
+
+		var i,
+		    newPoints = [];
+
+		for (i = 0; i < len; i++) {
+			if (markers[i]) {
+				newPoints.push(points[i]);
+			}
+		}
+
+		return newPoints;
+	},
+
+	_simplifyDPStep: function (points, markers, sqTolerance, first, last) {
+
+		var maxSqDist = 0,
+		    index, i, sqDist;
+
+		for (i = first + 1; i <= last - 1; i++) {
+			sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
+
+			if (sqDist > maxSqDist) {
+				index = i;
+				maxSqDist = sqDist;
+			}
+		}
+
+		if (maxSqDist > sqTolerance) {
+			markers[index] = 1;
+
+			this._simplifyDPStep(points, markers, sqTolerance, first, index);
+			this._simplifyDPStep(points, markers, sqTolerance, index, last);
+		}
+	},
+
+	// reduce points that are too close to each other to a single point
+	_reducePoints: function (points, sqTolerance) {
+		var reducedPoints = [points[0]];
+
+		for (var i = 1, prev = 0, len = points.length; i < len; i++) {
+			if (this._sqDist(points[i], points[prev]) > sqTolerance) {
+				reducedPoints.push(points[i]);
+				prev = i;
+			}
+		}
+		if (prev < len - 1) {
+			reducedPoints.push(points[len - 1]);
+		}
+		return reducedPoints;
+	},
+
+	// Cohen-Sutherland line clipping algorithm.
+	// Used to avoid rendering parts of a polyline that are not currently visible.
+
+	clipSegment: function (a, b, bounds, useLastCode) {
+		var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
+		    codeB = this._getBitCode(b, bounds),
+
+		    codeOut, p, newCode;
+
+		// save 2nd code to avoid calculating it on the next segment
+		this._lastCode = codeB;
+
+		while (true) {
+			// if a,b is inside the clip window (trivial accept)
+			if (!(codeA | codeB)) {
+				return [a, b];
+			// if a,b is outside the clip window (trivial reject)
+			} else if (codeA & codeB) {
+				return false;
+			// other cases
+			} else {
+				codeOut = codeA || codeB;
+				p = this._getEdgeIntersection(a, b, codeOut, bounds);
+				newCode = this._getBitCode(p, bounds);
+
+				if (codeOut === codeA) {
+					a = p;
+					codeA = newCode;
+				} else {
+					b = p;
+					codeB = newCode;
+				}
+			}
+		}
+	},
+
+	_getEdgeIntersection: function (a, b, code, bounds) {
+		var dx = b.x - a.x,
+		    dy = b.y - a.y,
+		    min = bounds.min,
+		    max = bounds.max;
+
+		if (code & 8) { // top
+			return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);
+		} else if (code & 4) { // bottom
+			return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y);
+		} else if (code & 2) { // right
+			return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx);
+		} else if (code & 1) { // left
+			return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx);
+		}
+	},
+
+	_getBitCode: function (/*Point*/ p, bounds) {
+		var code = 0;
+
+		if (p.x < bounds.min.x) { // left
+			code |= 1;
+		} else if (p.x > bounds.max.x) { // right
+			code |= 2;
+		}
+		if (p.y < bounds.min.y) { // bottom
+			code |= 4;
+		} else if (p.y > bounds.max.y) { // top
+			code |= 8;
+		}
+
+		return code;
+	},
+
+	// square distance (to avoid unnecessary Math.sqrt calls)
+	_sqDist: function (p1, p2) {
+		var dx = p2.x - p1.x,
+		    dy = p2.y - p1.y;
+		return dx * dx + dy * dy;
+	},
+
+	// return closest point on segment or distance to that point
+	_sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
+		var x = p1.x,
+		    y = p1.y,
+		    dx = p2.x - x,
+		    dy = p2.y - y,
+		    dot = dx * dx + dy * dy,
+		    t;
+
+		if (dot > 0) {
+			t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
+
+			if (t > 1) {
+				x = p2.x;
+				y = p2.y;
+			} else if (t > 0) {
+				x += dx * t;
+				y += dy * t;
+			}
+		}
+
+		dx = p.x - x;
+		dy = p.y - y;
+
+		return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
+	}
+};
+
+
+/*
+ * L.Polyline is used to display polylines on a map.
+ */
+
+L.Polyline = L.Path.extend({
+	initialize: function (latlngs, options) {
+		L.Path.prototype.initialize.call(this, options);
+
+		this._latlngs = this._convertLatLngs(latlngs);
+	},
+
+	options: {
+		// how much to simplify the polyline on each zoom level
+		// more = better performance and smoother look, less = more accurate
+		smoothFactor: 1.0,
+		noClip: false
+	},
+
+	projectLatlngs: function () {
+		this._originalPoints = [];
+
+		for (var i = 0, len = this._latlngs.length; i < len; i++) {
+			this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]);
+		}
+	},
+
+	getPathString: function () {
+		for (var i = 0, len = this._parts.length, str = ''; i < len; i++) {
+			str += this._getPathPartStr(this._parts[i]);
+		}
+		return str;
+	},
+
+	getLatLngs: function () {
+		return this._latlngs;
+	},
+
+	setLatLngs: function (latlngs) {
+		this._latlngs = this._convertLatLngs(latlngs);
+		return this.redraw();
+	},
+
+	addLatLng: function (latlng) {
+		this._latlngs.push(L.latLng(latlng));
+		return this.redraw();
+	},
+
+	spliceLatLngs: function () { // (Number index, Number howMany)
+		var removed = [].splice.apply(this._latlngs, arguments);
+		this._convertLatLngs(this._latlngs, true);
+		this.redraw();
+		return removed;
+	},
+
+	closestLayerPoint: function (p) {
+		var minDistance = Infinity, parts = this._parts, p1, p2, minPoint = null;
+
+		for (var j = 0, jLen = parts.length; j < jLen; j++) {
+			var points = parts[j];
+			for (var i = 1, len = points.length; i < len; i++) {
+				p1 = points[i - 1];
+				p2 = points[i];
+				var sqDist = L.LineUtil._sqClosestPointOnSegment(p, p1, p2, true);
+				if (sqDist < minDistance) {
+					minDistance = sqDist;
+					minPoint = L.LineUtil._sqClosestPointOnSegment(p, p1, p2);
+				}
+			}
+		}
+		if (minPoint) {
+			minPoint.distance = Math.sqrt(minDistance);
+		}
+		return minPoint;
+	},
+
+	getBounds: function () {
+		return new L.LatLngBounds(this.getLatLngs());
+	},
+
+	_convertLatLngs: function (latlngs, overwrite) {
+		var i, len, target = overwrite ? latlngs : [];
+
+		for (i = 0, len = latlngs.length; i < len; i++) {
+			if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') {
+				return;
+			}
+			target[i] = L.latLng(latlngs[i]);
+		}
+		return target;
+	},
+
+	_initEvents: function () {
+		L.Path.prototype._initEvents.call(this);
+	},
+
+	_getPathPartStr: function (points) {
+		var round = L.Path.VML;
+
+		for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) {
+			p = points[j];
+			if (round) {
+				p._round();
+			}
+			str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
+		}
+		return str;
+	},
+
+	_clipPoints: function () {
+		var points = this._originalPoints,
+		    len = points.length,
+		    i, k, segment;
+
+		if (this.options.noClip) {
+			this._parts = [points];
+			return;
+		}
+
+		this._parts = [];
+
+		var parts = this._parts,
+		    vp = this._map._pathViewport,
+		    lu = L.LineUtil;
+
+		for (i = 0, k = 0; i < len - 1; i++) {
+			segment = lu.clipSegment(points[i], points[i + 1], vp, i);
+			if (!segment) {
+				continue;
+			}
+
+			parts[k] = parts[k] || [];
+			parts[k].push(segment[0]);
+
+			// if segment goes out of screen, or it's the last one, it's the end of the line part
+			if ((segment[1] !== points[i + 1]) || (i === len - 2)) {
+				parts[k].push(segment[1]);
+				k++;
+			}
+		}
+	},
+
+	// simplify each clipped part of the polyline
+	_simplifyPoints: function () {
+		var parts = this._parts,
+		    lu = L.LineUtil;
+
+		for (var i = 0, len = parts.length; i < len; i++) {
+			parts[i] = lu.simplify(parts[i], this.options.smoothFactor);
+		}
+	},
+
+	_updatePath: function () {
+		if (!this._map) { return; }
+
+		this._clipPoints();
+		this._simplifyPoints();
+
+		L.Path.prototype._updatePath.call(this);
+	}
+});
+
+L.polyline = function (latlngs, options) {
+	return new L.Polyline(latlngs, options);
+};
+
+
+/*
+ * L.PolyUtil contains utility functions for polygons (clipping, etc.).
+ */
+
+/*jshint bitwise:false */ // allow bitwise operations here
+
+L.PolyUtil = {};
+
+/*
+ * Sutherland-Hodgeman polygon clipping algorithm.
+ * Used to avoid rendering parts of a polygon that are not currently visible.
+ */
+L.PolyUtil.clipPolygon = function (points, bounds) {
+	var clippedPoints,
+	    edges = [1, 4, 2, 8],
+	    i, j, k,
+	    a, b,
+	    len, edge, p,
+	    lu = L.LineUtil;
+
+	for (i = 0, len = points.length; i < len; i++) {
+		points[i]._code = lu._getBitCode(points[i], bounds);
+	}
+
+	// for each edge (left, bottom, right, top)
+	for (k = 0; k < 4; k++) {
+		edge = edges[k];
+		clippedPoints = [];
+
+		for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
+			a = points[i];
+			b = points[j];
+
+			// if a is inside the clip window
+			if (!(a._code & edge)) {
+				// if b is outside the clip window (a->b goes out of screen)
+				if (b._code & edge) {
+					p = lu._getEdgeIntersection(b, a, edge, bounds);
+					p._code = lu._getBitCode(p, bounds);
+					clippedPoints.push(p);
+				}
+				clippedPoints.push(a);
+
+			// else if b is inside the clip window (a->b enters the screen)
+			} else if (!(b._code & edge)) {
+				p = lu._getEdgeIntersection(b, a, edge, bounds);
+				p._code = lu._getBitCode(p, bounds);
+				clippedPoints.push(p);
+			}
+		}
+		points = clippedPoints;
+	}
+
+	return points;
+};
+
+
+/*
+ * L.Polygon is used to display polygons on a map.
+ */
+
+L.Polygon = L.Polyline.extend({
+	options: {
+		fill: true
+	},
+
+	initialize: function (latlngs, options) {
+		L.Polyline.prototype.initialize.call(this, latlngs, options);
+		this._initWithHoles(latlngs);
+	},
+
+	_initWithHoles: function (latlngs) {
+		var i, len, hole;
+		if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
+			this._latlngs = this._convertLatLngs(latlngs[0]);
+			this._holes = latlngs.slice(1);
+
+			for (i = 0, len = this._holes.length; i < len; i++) {
+				hole = this._holes[i] = this._convertLatLngs(this._holes[i]);
+				if (hole[0].equals(hole[hole.length - 1])) {
+					hole.pop();
+				}
+			}
+		}
+
+		// filter out last point if its equal to the first one
+		latlngs = this._latlngs;
+
+		if (latlngs.length >= 2 && latlngs[0].equals(latlngs[latlngs.length - 1])) {
+			latlngs.pop();
+		}
+	},
+
+	projectLatlngs: function () {
+		L.Polyline.prototype.projectLatlngs.call(this);
+
+		// project polygon holes points
+		// TODO move this logic to Polyline to get rid of duplication
+		this._holePoints = [];
+
+		if (!this._holes) { return; }
+
+		var i, j, len, len2;
+
+		for (i = 0, len = this._holes.length; i < len; i++) {
+			this._holePoints[i] = [];
+
+			for (j = 0, len2 = this._holes[i].length; j < len2; j++) {
+				this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]);
+			}
+		}
+	},
+
+	setLatLngs: function (latlngs) {
+		if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) {
+			this._initWithHoles(latlngs);
+			return this.redraw();
+		} else {
+			return L.Polyline.prototype.setLatLngs.call(this, latlngs);
+		}
+	},
+
+	_clipPoints: function () {
+		var points = this._originalPoints,
+		    newParts = [];
+
+		this._parts = [points].concat(this._holePoints);
+
+		if (this.options.noClip) { return; }
+
+		for (var i = 0, len = this._parts.length; i < len; i++) {
+			var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport);
+			if (clipped.length) {
+				newParts.push(clipped);
+			}
+		}
+
+		this._parts = newParts;
+	},
+
+	_getPathPartStr: function (points) {
+		var str = L.Polyline.prototype._getPathPartStr.call(this, points);
+		return str + (L.Browser.svg ? 'z' : 'x');
+	}
+});
+
+L.polygon = function (latlngs, options) {
+	return new L.Polygon(latlngs, options);
+};
+
+
+/*
+ * Contains L.MultiPolyline and L.MultiPolygon layers.
+ */
+
+(function () {
+	function createMulti(Klass) {
+
+		return L.FeatureGroup.extend({
+
+			initialize: function (latlngs, options) {
+				this._layers = {};
+				this._options = options;
+				this.setLatLngs(latlngs);
+			},
+
+			setLatLngs: function (latlngs) {
+				var i = 0,
+				    len = latlngs.length;
+
+				this.eachLayer(function (layer) {
+					if (i < len) {
+						layer.setLatLngs(latlngs[i++]);
+					} else {
+						this.removeLayer(layer);
+					}
+				}, this);
+
+				while (i < len) {
+					this.addLayer(new Klass(latlngs[i++], this._options));
+				}
+
+				return this;
+			},
+
+			getLatLngs: function () {
+				var latlngs = [];
+
+				this.eachLayer(function (layer) {
+					latlngs.push(layer.getLatLngs());
+				});
+
+				return latlngs;
+			}
+		});
+	}
+
+	L.MultiPolyline = createMulti(L.Polyline);
+	L.MultiPolygon = createMulti(L.Polygon);
+
+	L.multiPolyline = function (latlngs, options) {
+		return new L.MultiPolyline(latlngs, options);
+	};
+
+	L.multiPolygon = function (latlngs, options) {
+		return new L.MultiPolygon(latlngs, options);
+	};
+}());
+
+
+/*
+ * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
+ */
+
+L.Rectangle = L.Polygon.extend({
+	initialize: function (latLngBounds, options) {
+		L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
+	},
+
+	setBounds: function (latLngBounds) {
+		this.setLatLngs(this._boundsToLatLngs(latLngBounds));
+	},
+
+	_boundsToLatLngs: function (latLngBounds) {
+		latLngBounds = L.latLngBounds(latLngBounds);
+		return [
+			latLngBounds.getSouthWest(),
+			latLngBounds.getNorthWest(),
+			latLngBounds.getNorthEast(),
+			latLngBounds.getSouthEast()
+		];
+	}
+});
+
+L.rectangle = function (latLngBounds, options) {
+	return new L.Rectangle(latLngBounds, options);
+};
+
+
+/*
+ * L.Circle is a circle overlay (with a certain radius in meters).
+ */
+
+L.Circle = L.Path.extend({
+	initialize: function (latlng, radius, options) {
+		L.Path.prototype.initialize.call(this, options);
+
+		this._latlng = L.latLng(latlng);
+		this._mRadius = radius;
+	},
+
+	options: {
+		fill: true
+	},
+
+	setLatLng: function (latlng) {
+		this._latlng = L.latLng(latlng);
+		return this.redraw();
+	},
+
+	setRadius: function (radius) {
+		this._mRadius = radius;
+		return this.redraw();
+	},
+
+	projectLatlngs: function () {
+		var lngRadius = this._getLngRadius(),
+		    latlng = this._latlng,
+		    pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]);
+
+		this._point = this._map.latLngToLayerPoint(latlng);
+		this._radius = Math.max(this._point.x - pointLeft.x, 1);
+	},
+
+	getBounds: function () {
+		var lngRadius = this._getLngRadius(),
+		    latRadius = (this._mRadius / 40075017) * 360,
+		    latlng = this._latlng;
+
+		return new L.LatLngBounds(
+		        [latlng.lat - latRadius, latlng.lng - lngRadius],
+		        [latlng.lat + latRadius, latlng.lng + lngRadius]);
+	},
+
+	getLatLng: function () {
+		return this._latlng;
+	},
+
+	getPathString: function () {
+		var p = this._point,
+		    r = this._radius;
+
+		if (this._checkIfEmpty()) {
+			return '';
+		}
+
+		if (L.Browser.svg) {
+			return 'M' + p.x + ',' + (p.y - r) +
+			       'A' + r + ',' + r + ',0,1,1,' +
+			       (p.x - 0.1) + ',' + (p.y - r) + ' z';
+		} else {
+			p._round();
+			r = Math.round(r);
+			return 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r + ' 0,' + (65535 * 360);
+		}
+	},
+
+	getRadius: function () {
+		return this._mRadius;
+	},
+
+	// TODO Earth hardcoded, move into projection code!
+
+	_getLatRadius: function () {
+		return (this._mRadius / 40075017) * 360;
+	},
+
+	_getLngRadius: function () {
+		return this._getLatRadius() / Math.cos(L.LatLng.DEG_TO_RAD * this._latlng.lat);
+	},
+
+	_checkIfEmpty: function () {
+		if (!this._map) {
+			return false;
+		}
+		var vp = this._map._pathViewport,
+		    r = this._radius,
+		    p = this._point;
+
+		return p.x - r > vp.max.x || p.y - r > vp.max.y ||
+		       p.x + r < vp.min.x || p.y + r < vp.min.y;
+	}
+});
+
+L.circle = function (latlng, radius, options) {
+	return new L.Circle(latlng, radius, options);
+};
+
+
+/*
+ * L.CircleMarker is a circle overlay with a permanent pixel radius.
+ */
+
+L.CircleMarker = L.Circle.extend({
+	options: {
+		radius: 10,
+		weight: 2
+	},
+
+	initialize: function (latlng, options) {
+		L.Circle.prototype.initialize.call(this, latlng, null, options);
+		this._radius = this.options.radius;
+	},
+
+	projectLatlngs: function () {
+		this._point = this._map.latLngToLayerPoint(this._latlng);
+	},
+
+	_updateStyle : function () {
+		L.Circle.prototype._updateStyle.call(this);
+		this.setRadius(this.options.radius);
+	},
+
+	setLatLng: function (latlng) {
+		L.Circle.prototype.setLatLng.call(this, latlng);
+		if (this._popup && this._popup._isOpen) {
+			this._popup.setLatLng(latlng);
+		}
+		return this;
+	},
+
+	setRadius: function (radius) {
+		this.options.radius = this._radius = radius;
+		return this.redraw();
+	},
+
+	getRadius: function () {
+		return this._radius;
+	}
+});
+
+L.circleMarker = function (latlng, options) {
+	return new L.CircleMarker(latlng, options);
+};
+
+
+/*
+ * Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines.
+ */
+
+L.Polyline.include(!L.Path.CANVAS ? {} : {
+	_containsPoint: function (p, closed) {
+		var i, j, k, len, len2, dist, part,
+		    w = this.options.weight / 2;
+
+		if (L.Browser.touch) {
+			w += 10; // polyline click tolerance on touch devices
+		}
+
+		for (i = 0, len = this._parts.length; i < len; i++) {
+			part = this._parts[i];
+			for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
+				if (!closed && (j === 0)) {
+					continue;
+				}
+
+				dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]);
+
+				if (dist <= w) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+});
+
+
+/*
+ * Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons.
+ */
+
+L.Polygon.include(!L.Path.CANVAS ? {} : {
+	_containsPoint: function (p) {
+		var inside = false,
+		    part, p1, p2,
+		    i, j, k,
+		    len, len2;
+
+		// TODO optimization: check if within bounds first
+
+		if (L.Polyline.prototype._containsPoint.call(this, p, true)) {
+			// click on polygon border
+			return true;
+		}
+
+		// ray casting algorithm for detecting if point is in polygon
+
+		for (i = 0, len = this._parts.length; i < len; i++) {
+			part = this._parts[i];
+
+			for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
+				p1 = part[j];
+				p2 = part[k];
+
+				if (((p1.y > p.y) !== (p2.y > p.y)) &&
+						(p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
+					inside = !inside;
+				}
+			}
+		}
+
+		return inside;
+	}
+});
+
+
+/*
+ * Extends L.Circle with Canvas-specific code.
+ */
+
+L.Circle.include(!L.Path.CANVAS ? {} : {
+	_drawPath: function () {
+		var p = this._point;
+		this._ctx.beginPath();
+		this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2, false);
+	},
+
+	_containsPoint: function (p) {
+		var center = this._point,
+		    w2 = this.options.stroke ? this.options.weight / 2 : 0;
+
+		return (p.distanceTo(center) <= this._radius + w2);
+	}
+});
+
+
+/*
+ * CircleMarker canvas specific drawing parts.
+ */
+
+L.CircleMarker.include(!L.Path.CANVAS ? {} : {
+	_updateStyle: function () {
+		L.Path.prototype._updateStyle.call(this);
+	}
+});
+
+
+/*
+ * L.GeoJSON turns any GeoJSON data into a Leaflet layer.
+ */
+
+L.GeoJSON = L.FeatureGroup.extend({
+
+	initialize: function (geojson, options) {
+		L.setOptions(this, options);
+
+		this._layers = {};
+
+		if (geojson) {
+			this.addData(geojson);
+		}
+	},
+
+	addData: function (geojson) {
+		var features = L.Util.isArray(geojson) ? geojson : geojson.features,
+		    i, len, feature;
+
+		if (features) {
+			for (i = 0, len = features.length; i < len; i++) {
+				// Only add this if geometry or geometries are set and not null
+				feature = features[i];
+				if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
+					this.addData(features[i]);
+				}
+			}
+			return this;
+		}
+
+		var options = this.options;
+
+		if (options.filter && !options.filter(geojson)) { return; }
+
+		var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng, options);
+		layer.feature = L.GeoJSON.asFeature(geojson);
+
+		layer.defaultOptions = layer.options;
+		this.resetStyle(layer);
+
+		if (options.onEachFeature) {
+			options.onEachFeature(geojson, layer);
+		}
+
+		return this.addLayer(layer);
+	},
+
+	resetStyle: function (layer) {
+		var style = this.options.style;
+		if (style) {
+			// reset any custom styles
+			L.Util.extend(layer.options, layer.defaultOptions);
+
+			this._setLayerStyle(layer, style);
+		}
+	},
+
+	setStyle: function (style) {
+		this.eachLayer(function (layer) {
+			this._setLayerStyle(layer, style);
+		}, this);
+	},
+
+	_setLayerStyle: function (layer, style) {
+		if (typeof style === 'function') {
+			style = style(layer.feature);
+		}
+		if (layer.setStyle) {
+			layer.setStyle(style);
+		}
+	}
+});
+
+L.extend(L.GeoJSON, {
+	geometryToLayer: function (geojson, pointToLayer, coordsToLatLng, vectorOptions) {
+		var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
+		    coords = geometry.coordinates,
+		    layers = [],
+		    latlng, latlngs, i, len;
+
+		coordsToLatLng = coordsToLatLng || this.coordsToLatLng;
+
+		switch (geometry.type) {
+		case 'Point':
+			latlng = coordsToLatLng(coords);
+			return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
+
+		case 'MultiPoint':
+			for (i = 0, len = coords.length; i < len; i++) {
+				latlng = coordsToLatLng(coords[i]);
+				layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));
+			}
+			return new L.FeatureGroup(layers);
+
+		case 'LineString':
+			latlngs = this.coordsToLatLngs(coords, 0, coordsToLatLng);
+			return new L.Polyline(latlngs, vectorOptions);
+
+		case 'Polygon':
+			if (coords.length === 2 && !coords[1].length) {
+				throw new Error('Invalid GeoJSON object.');
+			}
+			latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);
+			return new L.Polygon(latlngs, vectorOptions);
+
+		case 'MultiLineString':
+			latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng);
+			return new L.MultiPolyline(latlngs, vectorOptions);
+
+		case 'MultiPolygon':
+			latlngs = this.coordsToLatLngs(coords, 2, coordsToLatLng);
+			return new L.MultiPolygon(latlngs, vectorOptions);
+
+		case 'GeometryCollection':
+			for (i = 0, len = geometry.geometries.length; i < len; i++) {
+
+				layers.push(this.geometryToLayer({
+					geometry: geometry.geometries[i],
+					type: 'Feature',
+					properties: geojson.properties
+				}, pointToLayer, coordsToLatLng, vectorOptions));
+			}
+			return new L.FeatureGroup(layers);
+
+		default:
+			throw new Error('Invalid GeoJSON object.');
+		}
+	},
+
+	coordsToLatLng: function (coords) { // (Array[, Boolean]) -> LatLng
+		return new L.LatLng(coords[1], coords[0], coords[2]);
+	},
+
+	coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { // (Array[, Number, Function]) -> Array
+		var latlng, i, len,
+		    latlngs = [];
+
+		for (i = 0, len = coords.length; i < len; i++) {
+			latlng = levelsDeep ?
+			        this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
+			        (coordsToLatLng || this.coordsToLatLng)(coords[i]);
+
+			latlngs.push(latlng);
+		}
+
+		return latlngs;
+	},
+
+	latLngToCoords: function (latlng) {
+		var coords = [latlng.lng, latlng.lat];
+
+		if (latlng.alt !== undefined) {
+			coords.push(latlng.alt);
+		}
+		return coords;
+	},
+
+	latLngsToCoords: function (latLngs) {
+		var coords = [];
+
+		for (var i = 0, len = latLngs.length; i < len; i++) {
+			coords.push(L.GeoJSON.latLngToCoords(latLngs[i]));
+		}
+
+		return coords;
+	},
+
+	getFeature: function (layer, newGeometry) {
+		return layer.feature ? L.extend({}, layer.feature, {geometry: newGeometry}) : L.GeoJSON.asFeature(newGeometry);
+	},
+
+	asFeature: function (geoJSON) {
+		if (geoJSON.type === 'Feature') {
+			return geoJSON;
+		}
+
+		return {
+			type: 'Feature',
+			properties: {},
+			geometry: geoJSON
+		};
+	}
+});
+
+var PointToGeoJSON = {
+	toGeoJSON: function () {
+		return L.GeoJSON.getFeature(this, {
+			type: 'Point',
+			coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
+		});
+	}
+};
+
+L.Marker.include(PointToGeoJSON);
+L.Circle.include(PointToGeoJSON);
+L.CircleMarker.include(PointToGeoJSON);
+
+L.Polyline.include({
+	toGeoJSON: function () {
+		return L.GeoJSON.getFeature(this, {
+			type: 'LineString',
+			coordinates: L.GeoJSON.latLngsToCoords(this.getLatLngs())
+		});
+	}
+});
+
+L.Polygon.include({
+	toGeoJSON: function () {
+		var coords = [L.GeoJSON.latLngsToCoords(this.getLatLngs())],
+		    i, len, hole;
+
+		coords[0].push(coords[0][0]);
+
+		if (this._holes) {
+			for (i = 0, len = this._holes.length; i < len; i++) {
+				hole = L.GeoJSON.latLngsToCoords(this._holes[i]);
+				hole.push(hole[0]);
+				coords.push(hole);
+			}
+		}
+
+		return L.GeoJSON.getFeature(this, {
+			type: 'Polygon',
+			coordinates: coords
+		});
+	}
+});
+
+(function () {
+	function multiToGeoJSON(type) {
+		return function () {
+			var coords = [];
+
+			this.eachLayer(function (layer) {
+				coords.push(layer.toGeoJSON().geometry.coordinates);
+			});
+
+			return L.GeoJSON.getFeature(this, {
+				type: type,
+				coordinates: coords
+			});
+		};
+	}
+
+	L.MultiPolyline.include({toGeoJSON: multiToGeoJSON('MultiLineString')});
+	L.MultiPolygon.include({toGeoJSON: multiToGeoJSON('MultiPolygon')});
+
+	L.LayerGroup.include({
+		toGeoJSON: function () {
+
+			var geometry = this.feature && this.feature.geometry,
+				jsons = [],
+				json;
+
+			if (geometry && geometry.type === 'MultiPoint') {
+				return multiToGeoJSON('MultiPoint').call(this);
+			}
+
+			var isGeometryCollection = geometry && geometry.type === 'GeometryCollection';
+
+			this.eachLayer(function (layer) {
+				if (layer.toGeoJSON) {
+					json = layer.toGeoJSON();
+					jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));
+				}
+			});
+
+			if (isGeometryCollection) {
+				return L.GeoJSON.getFeature(this, {
+					geometries: jsons,
+					type: 'GeometryCollection'
+				});
+			}
+
+			return {
+				type: 'FeatureCollection',
+				features: jsons
+			};
+		}
+	});
+}());
+
+L.geoJson = function (geojson, options) {
+	return new L.GeoJSON(geojson, options);
+};
+
+
+/*
+ * L.DomEvent contains functions for working with DOM events.
+ */
+
+L.DomEvent = {
+	/* inspired by John Resig, Dean Edwards and YUI addEvent implementations */
+	addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object])
+
+		var id = L.stamp(fn),
+		    key = '_leaflet_' + type + id,
+		    handler, originalHandler, newType;
+
+		if (obj[key]) { return this; }
+
+		handler = function (e) {
+			return fn.call(context || obj, e || L.DomEvent._getEvent());
+		};
+
+		if (L.Browser.pointer && type.indexOf('touch') === 0) {
+			return this.addPointerListener(obj, type, handler, id);
+		}
+		if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
+			this.addDoubleTapListener(obj, handler, id);
+		}
+
+		if ('addEventListener' in obj) {
+
+			if (type === 'mousewheel') {
+				obj.addEventListener('DOMMouseScroll', handler, false);
+				obj.addEventListener(type, handler, false);
+
+			} else if ((type === 'mouseenter') || (type === 'mouseleave')) {
+
+				originalHandler = handler;
+				newType = (type === 'mouseenter' ? 'mouseover' : 'mouseout');
+
+				handler = function (e) {
+					if (!L.DomEvent._checkMouse(obj, e)) { return; }
+					return originalHandler(e);
+				};
+
+				obj.addEventListener(newType, handler, false);
+
+			} else if (type === 'click' && L.Browser.android) {
+				originalHandler = handler;
+				handler = function (e) {
+					return L.DomEvent._filterClick(e, originalHandler);
+				};
+
+				obj.addEventListener(type, handler, false);
+			} else {
+				obj.addEventListener(type, handler, false);
+			}
+
+		} else if ('attachEvent' in obj) {
+			obj.attachEvent('on' + type, handler);
+		}
+
+		obj[key] = handler;
+
+		return this;
+	},
+
+	removeListener: function (obj, type, fn) {  // (HTMLElement, String, Function)
+
+		var id = L.stamp(fn),
+		    key = '_leaflet_' + type + id,
+		    handler = obj[key];
+
+		if (!handler) { return this; }
+
+		if (L.Browser.pointer && type.indexOf('touch') === 0) {
+			this.removePointerListener(obj, type, id);
+		} else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
+			this.removeDoubleTapListener(obj, id);
+
+		} else if ('removeEventListener' in obj) {
+
+			if (type === 'mousewheel') {
+				obj.removeEventListener('DOMMouseScroll', handler, false);
+				obj.removeEventListener(type, handler, false);
+
+			} else if ((type === 'mouseenter') || (type === 'mouseleave')) {
+				obj.removeEventListener((type === 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);
+			} else {
+				obj.removeEventListener(type, handler, false);
+			}
+		} else if ('detachEvent' in obj) {
+			obj.detachEvent('on' + type, handler);
+		}
+
+		obj[key] = null;
+
+		return this;
+	},
+
+	stopPropagation: function (e) {
+
+		if (e.stopPropagation) {
+			e.stopPropagation();
+		} else {
+			e.cancelBubble = true;
+		}
+		L.DomEvent._skipped(e);
+
+		return this;
+	},
+
+	disableScrollPropagation: function (el) {
+		var stop = L.DomEvent.stopPropagation;
+
+		return L.DomEvent
+			.on(el, 'mousewheel', stop)
+			.on(el, 'MozMousePixelScroll', stop);
+	},
+
+	disableClickPropagation: function (el) {
+		var stop = L.DomEvent.stopPropagation;
+
+		for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
+			L.DomEvent.on(el, L.Draggable.START[i], stop);
+		}
+
+		return L.DomEvent
+			.on(el, 'click', L.DomEvent._fakeStop)
+			.on(el, 'dblclick', stop);
+	},
+
+	preventDefault: function (e) {
+
+		if (e.preventDefault) {
+			e.preventDefault();
+		} else {
+			e.returnValue = false;
+		}
+		return this;
+	},
+
+	stop: function (e) {
+		return L.DomEvent
+			.preventDefault(e)
+			.stopPropagation(e);
+	},
+
+	getMousePosition: function (e, container) {
+		if (!container) {
+			return new L.Point(e.clientX, e.clientY);
+		}
+
+		var rect = container.getBoundingClientRect();
+
+		return new L.Point(
+			e.clientX - rect.left - container.clientLeft,
+			e.clientY - rect.top - container.clientTop);
+	},
+
+	getWheelDelta: function (e) {
+
+		var delta = 0;
+
+		if (e.wheelDelta) {
+			delta = e.wheelDelta / 120;
+		}
+		if (e.detail) {
+			delta = -e.detail / 3;
+		}
+		return delta;
+	},
+
+	_skipEvents: {},
+
+	_fakeStop: function (e) {
+		// fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
+		L.DomEvent._skipEvents[e.type] = true;
+	},
+
+	_skipped: function (e) {
+		var skipped = this._skipEvents[e.type];
+		// reset when checking, as it's only used in map container and propagates outside of the map
+		this._skipEvents[e.type] = false;
+		return skipped;
+	},
+
+	// check if element really left/entered the event target (for mouseenter/mouseleave)
+	_checkMouse: function (el, e) {
+
+		var related = e.relatedTarget;
+
+		if (!related) { return true; }
+
+		try {
+			while (related && (related !== el)) {
+				related = related.parentNode;
+			}
+		} catch (err) {
+			return false;
+		}
+		return (related !== el);
+	},
+
+	_getEvent: function () { // evil magic for IE
+		/*jshint noarg:false */
+		var e = window.event;
+		if (!e) {
+			var caller = arguments.callee.caller;
+			while (caller) {
+				e = caller['arguments'][0];
+				if (e && window.Event === e.constructor) {
+					break;
+				}
+				caller = caller.caller;
+			}
+		}
+		return e;
+	},
+
+	// this is a horrible workaround for a bug in Android where a single touch triggers two click events
+	_filterClick: function (e, handler) {
+		var timeStamp = (e.timeStamp || e.originalEvent.timeStamp),
+			elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
+
+		// are they closer together than 500ms yet more than 100ms?
+		// Android typically triggers them ~300ms apart while multiple listeners
+		// on the same event should be triggered far faster;
+		// or check if click is simulated on the element, and if it is, reject any non-simulated events
+
+		if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
+			L.DomEvent.stop(e);
+			return;
+		}
+		L.DomEvent._lastClick = timeStamp;
+
+		return handler(e);
+	}
+};
+
+L.DomEvent.on = L.DomEvent.addListener;
+L.DomEvent.off = L.DomEvent.removeListener;
+
+
+/*
+ * L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
+ */
+
+L.Draggable = L.Class.extend({
+	includes: L.Mixin.Events,
+
+	statics: {
+		START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
+		END: {
+			mousedown: 'mouseup',
+			touchstart: 'touchend',
+			pointerdown: 'touchend',
+			MSPointerDown: 'touchend'
+		},
+		MOVE: {
+			mousedown: 'mousemove',
+			touchstart: 'touchmove',
+			pointerdown: 'touchmove',
+			MSPointerDown: 'touchmove'
+		}
+	},
+
+	initialize: function (element, dragStartTarget) {
+		this._element = element;
+		this._dragStartTarget = dragStartTarget || element;
+	},
+
+	enable: function () {
+		if (this._enabled) { return; }
+
+		for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
+			L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
+		}
+
+		this._enabled = true;
+	},
+
+	disable: function () {
+		if (!this._enabled) { return; }
+
+		for (var i = L.Draggable.START.length - 1; i >= 0; i--) {
+			L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this);
+		}
+
+		this._enabled = false;
+		this._moved = false;
+	},
+
+	_onDown: function (e) {
+		this._moved = false;
+
+		if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
+
+		L.DomEvent.stopPropagation(e);
+
+		if (L.Draggable._disabled) { return; }
+
+		L.DomUtil.disableImageDrag();
+		L.DomUtil.disableTextSelection();
+
+		if (this._moving) { return; }
+
+		var first = e.touches ? e.touches[0] : e;
+
+		this._startPoint = new L.Point(first.clientX, first.clientY);
+		this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
+
+		L.DomEvent
+		    .on(document, L.Draggable.MOVE[e.type], this._onMove, this)
+		    .on(document, L.Draggable.END[e.type], this._onUp, this);
+	},
+
+	_onMove: function (e) {
+		if (e.touches && e.touches.length > 1) {
+			this._moved = true;
+			return;
+		}
+
+		var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
+		    newPoint = new L.Point(first.clientX, first.clientY),
+		    offset = newPoint.subtract(this._startPoint);
+
+		if (!offset.x && !offset.y) { return; }
+		if (L.Browser.touch && Math.abs(offset.x) + Math.abs(offset.y) < 3) { return; }
+
+		L.DomEvent.preventDefault(e);
+
+		if (!this._moved) {
+			this.fire('dragstart');
+
+			this._moved = true;
+			this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
+
+			L.DomUtil.addClass(document.body, 'leaflet-dragging');
+			this._lastTarget = e.target || e.srcElement;
+			L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');
+		}
+
+		this._newPos = this._startPos.add(offset);
+		this._moving = true;
+
+		L.Util.cancelAnimFrame(this._animRequest);
+		this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true, this._dragStartTarget);
+	},
+
+	_updatePosition: function () {
+		this.fire('predrag');
+		L.DomUtil.setPosition(this._element, this._newPos);
+		this.fire('drag');
+	},
+
+	_onUp: function () {
+		L.DomUtil.removeClass(document.body, 'leaflet-dragging');
+
+		if (this._lastTarget) {
+			L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');
+			this._lastTarget = null;
+		}
+
+		for (var i in L.Draggable.MOVE) {
+			L.DomEvent
+			    .off(document, L.Draggable.MOVE[i], this._onMove)
+			    .off(document, L.Draggable.END[i], this._onUp);
+		}
+
+		L.DomUtil.enableImageDrag();
+		L.DomUtil.enableTextSelection();
+
+		if (this._moved && this._moving) {
+			// ensure drag is not fired after dragend
+			L.Util.cancelAnimFrame(this._animRequest);
+
+			this.fire('dragend', {
+				distance: this._newPos.distanceTo(this._startPos)
+			});
+		}
+
+		this._moving = false;
+	}
+});
+
+
+/*
+	L.Handler is a base class for handler classes that are used internally to inject
+	interaction features like dragging to classes like Map and Marker.
+*/
+
+L.Handler = L.Class.extend({
+	initialize: function (map) {
+		this._map = map;
+	},
+
+	enable: function () {
+		if (this._enabled) { return; }
+
+		this._enabled = true;
+		this.addHooks();
+	},
+
+	disable: function () {
+		if (!this._enabled) { return; }
+
+		this._enabled = false;
+		this.removeHooks();
+	},
+
+	enabled: function () {
+		return !!this._enabled;
+	}
+});
+
+
+/*
+ * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
+ */
+
+L.Map.mergeOptions({
+	dragging: true,
+
+	inertia: !L.Browser.android23,
+	inertiaDeceleration: 3400, // px/s^2
+	inertiaMaxSpeed: Infinity, // px/s
+	inertiaThreshold: L.Browser.touch ? 32 : 18, // ms
+	easeLinearity: 0.25,
+
+	// TODO refactor, move to CRS
+	worldCopyJump: false
+});
+
+L.Map.Drag = L.Handler.extend({
+	addHooks: function () {
+		if (!this._draggable) {
+			var map = this._map;
+
+			this._draggable = new L.Draggable(map._mapPane, map._container);
+
+			this._draggable.on({
+				'dragstart': this._onDragStart,
+				'drag': this._onDrag,
+				'dragend': this._onDragEnd
+			}, this);
+
+			if (map.options.worldCopyJump) {
+				this._draggable.on('predrag', this._onPreDrag, this);
+				map.on('viewreset', this._onViewReset, this);
+
+				map.whenReady(this._onViewReset, this);
+			}
+		}
+		this._draggable.enable();
+	},
+
+	removeHooks: function () {
+		this._draggable.disable();
+	},
+
+	moved: function () {
+		return this._draggable && this._draggable._moved;
+	},
+
+	_onDragStart: function () {
+		var map = this._map;
+
+		if (map._panAnim) {
+			map._panAnim.stop();
+		}
+
+		map
+		    .fire('movestart')
+		    .fire('dragstart');
+
+		if (map.options.inertia) {
+			this._positions = [];
+			this._times = [];
+		}
+	},
+
+	_onDrag: function () {
+		if (this._map.options.inertia) {
+			var time = this._lastTime = +new Date(),
+			    pos = this._lastPos = this._draggable._newPos;
+
+			this._positions.push(pos);
+			this._times.push(time);
+
+			if (time - this._times[0] > 200) {
+				this._positions.shift();
+				this._times.shift();
+			}
+		}
+
+		this._map
+		    .fire('move')
+		    .fire('drag');
+	},
+
+	_onViewReset: function () {
+		// TODO fix hardcoded Earth values
+		var pxCenter = this._map.getSize()._divideBy(2),
+		    pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
+
+		this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
+		this._worldWidth = this._map.project([0, 180]).x;
+	},
+
+	_onPreDrag: function () {
+		// TODO refactor to be able to adjust map pane position after zoom
+		var worldWidth = this._worldWidth,
+		    halfWidth = Math.round(worldWidth / 2),
+		    dx = this._initialWorldOffset,
+		    x = this._draggable._newPos.x,
+		    newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
+		    newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
+		    newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
+
+		this._draggable._newPos.x = newX;
+	},
+
+	_onDragEnd: function (e) {
+		var map = this._map,
+		    options = map.options,
+		    delay = +new Date() - this._lastTime,
+
+		    noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0];
+
+		map.fire('dragend', e);
+
+		if (noInertia) {
+			map.fire('moveend');
+
+		} else {
+
+			var direction = this._lastPos.subtract(this._positions[0]),
+			    duration = (this._lastTime + delay - this._times[0]) / 1000,
+			    ease = options.easeLinearity,
+
+			    speedVector = direction.multiplyBy(ease / duration),
+			    speed = speedVector.distanceTo([0, 0]),
+
+			    limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
+			    limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
+
+			    decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
+			    offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
+
+			if (!offset.x || !offset.y) {
+				map.fire('moveend');
+
+			} else {
+				offset = map._limitOffset(offset, map.options.maxBounds);
+
+				L.Util.requestAnimFrame(function () {
+					map.panBy(offset, {
+						duration: decelerationDuration,
+						easeLinearity: ease,
+						noMoveStart: true
+					});
+				});
+			}
+		}
+	}
+});
+
+L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
+
+
+/*
+ * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
+ */
+
+L.Map.mergeOptions({
+	doubleClickZoom: true
+});
+
+L.Map.DoubleClickZoom = L.Handler.extend({
+	addHooks: function () {
+		this._map.on('dblclick', this._onDoubleClick, this);
+	},
+
+	removeHooks: function () {
+		this._map.off('dblclick', this._onDoubleClick, this);
+	},
+
+	_onDoubleClick: function (e) {
+		var map = this._map,
+		    zoom = map.getZoom() + (e.originalEvent.shiftKey ? -1 : 1);
+
+		if (map.options.doubleClickZoom === 'center') {
+			map.setZoom(zoom);
+		} else {
+			map.setZoomAround(e.containerPoint, zoom);
+		}
+	}
+});
+
+L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
+
+
+/*
+ * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
+ */
+
+L.Map.mergeOptions({
+	scrollWheelZoom: true
+});
+
+L.Map.ScrollWheelZoom = L.Handler.extend({
+	addHooks: function () {
+		L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
+		L.DomEvent.on(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault);
+		this._delta = 0;
+	},
+
+	removeHooks: function () {
+		L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll);
+		L.DomEvent.off(this._map._container, 'MozMousePixelScroll', L.DomEvent.preventDefault);
+	},
+
+	_onWheelScroll: function (e) {
+		var delta = L.DomEvent.getWheelDelta(e);
+
+		this._delta += delta;
+		this._lastMousePos = this._map.mouseEventToContainerPoint(e);
+
+		if (!this._startTime) {
+			this._startTime = +new Date();
+		}
+
+		var left = Math.max(40 - (+new Date() - this._startTime), 0);
+
+		clearTimeout(this._timer);
+		this._timer = setTimeout(L.bind(this._performZoom, this), left);
+
+		L.DomEvent.preventDefault(e);
+		L.DomEvent.stopPropagation(e);
+	},
+
+	_performZoom: function () {
+		var map = this._map,
+		    delta = this._delta,
+		    zoom = map.getZoom();
+
+		delta = delta > 0 ? Math.ceil(delta) : Math.floor(delta);
+		delta = Math.max(Math.min(delta, 4), -4);
+		delta = map._limitZoom(zoom + delta) - zoom;
+
+		this._delta = 0;
+		this._startTime = null;
+
+		if (!delta) { return; }
+
+		if (map.options.scrollWheelZoom === 'center') {
+			map.setZoom(zoom + delta);
+		} else {
+			map.setZoomAround(this._lastMousePos, zoom + delta);
+		}
+	}
+});
+
+L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
+
+
+/*
+ * Extends the event handling code with double tap support for mobile browsers.
+ */
+
+L.extend(L.DomEvent, {
+
+	_touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
+	_touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',
+
+	// inspired by Zepto touch code by Thomas Fuchs
+	addDoubleTapListener: function (obj, handler, id) {
+		var last,
+		    doubleTap = false,
+		    delay = 250,
+		    touch,
+		    pre = '_leaflet_',
+		    touchstart = this._touchstart,
+		    touchend = this._touchend,
+		    trackedTouches = [];
+
+		function onTouchStart(e) {
+			var count;
+
+			if (L.Browser.pointer) {
+				trackedTouches.push(e.pointerId);
+				count = trackedTouches.length;
+			} else {
+				count = e.touches.length;
+			}
+			if (count > 1) {
+				return;
+			}
+
+			var now = Date.now(),
+				delta = now - (last || now);
+
+			touch = e.touches ? e.touches[0] : e;
+			doubleTap = (delta > 0 && delta <= delay);
+			last = now;
+		}
+
+		function onTouchEnd(e) {
+			if (L.Browser.pointer) {
+				var idx = trackedTouches.indexOf(e.pointerId);
+				if (idx === -1) {
+					return;
+				}
+				trackedTouches.splice(idx, 1);
+			}
+
+			if (doubleTap) {
+				if (L.Browser.pointer) {
+					// work around .type being readonly with MSPointer* events
+					var newTouch = { },
+						prop;
+
+					// jshint forin:false
+					for (var i in touch) {
+						prop = touch[i];
+						if (typeof prop === 'function') {
+							newTouch[i] = prop.bind(touch);
+						} else {
+							newTouch[i] = prop;
+						}
+					}
+					touch = newTouch;
+				}
+				touch.type = 'dblclick';
+				handler(touch);
+				last = null;
+			}
+		}
+		obj[pre + touchstart + id] = onTouchStart;
+		obj[pre + touchend + id] = onTouchEnd;
+
+		// on pointer we need to listen on the document, otherwise a drag starting on the map and moving off screen
+		// will not come through to us, so we will lose track of how many touches are ongoing
+		var endElement = L.Browser.pointer ? document.documentElement : obj;
+
+		obj.addEventListener(touchstart, onTouchStart, false);
+		endElement.addEventListener(touchend, onTouchEnd, false);
+
+		if (L.Browser.pointer) {
+			endElement.addEventListener(L.DomEvent.POINTER_CANCEL, onTouchEnd, false);
+		}
+
+		return this;
+	},
+
+	removeDoubleTapListener: function (obj, id) {
+		var pre = '_leaflet_';
+
+		obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false);
+		(L.Browser.pointer ? document.documentElement : obj).removeEventListener(
+		        this._touchend, obj[pre + this._touchend + id], false);
+
+		if (L.Browser.pointer) {
+			document.documentElement.removeEventListener(L.DomEvent.POINTER_CANCEL, obj[pre + this._touchend + id],
+				false);
+		}
+
+		return this;
+	}
+});
+
+
+/*
+ * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
+ */
+
+L.extend(L.DomEvent, {
+
+	//static
+	POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown',
+	POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove',
+	POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup',
+	POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
+
+	_pointers: [],
+	_pointerDocumentListener: false,
+
+	// Provides a touch events wrapper for (ms)pointer events.
+	// Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019
+	//ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
+
+	addPointerListener: function (obj, type, handler, id) {
+
+		switch (type) {
+		case 'touchstart':
+			return this.addPointerListenerStart(obj, type, handler, id);
+		case 'touchend':
+			return this.addPointerListenerEnd(obj, type, handler, id);
+		case 'touchmove':
+			return this.addPointerListenerMove(obj, type, handler, id);
+		default:
+			throw 'Unknown touch event type';
+		}
+	},
+
+	addPointerListenerStart: function (obj, type, handler, id) {
+		var pre = '_leaflet_',
+		    pointers = this._pointers;
+
+		var cb = function (e) {
+			if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
+				L.DomEvent.preventDefault(e);
+			}
+
+			var alreadyInArray = false;
+			for (var i = 0; i < pointers.length; i++) {
+				if (pointers[i].pointerId === e.pointerId) {
+					alreadyInArray = true;
+					break;
+				}
+			}
+			if (!alreadyInArray) {
+				pointers.push(e);
+			}
+
+			e.touches = pointers.slice();
+			e.changedTouches = [e];
+
+			handler(e);
+		};
+
+		obj[pre + 'touchstart' + id] = cb;
+		obj.addEventListener(this.POINTER_DOWN, cb, false);
+
+		// need to also listen for end events to keep the _pointers list accurate
+		// this needs to be on the body and never go away
+		if (!this._pointerDocumentListener) {
+			var internalCb = function (e) {
+				for (var i = 0; i < pointers.length; i++) {
+					if (pointers[i].pointerId === e.pointerId) {
+						pointers.splice(i, 1);
+						break;
+					}
+				}
+			};
+			//We listen on the documentElement as any drags that end by moving the touch off the screen get fired there
+			document.documentElement.addEventListener(this.POINTER_UP, internalCb, false);
+			document.documentElement.addEventListener(this.POINTER_CANCEL, internalCb, false);
+
+			this._pointerDocumentListener = true;
+		}
+
+		return this;
+	},
+
+	addPointerListenerMove: function (obj, type, handler, id) {
+		var pre = '_leaflet_',
+		    touches = this._pointers;
+
+		function cb(e) {
+
+			// don't fire touch moves when mouse isn't down
+			if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
+
+			for (var i = 0; i < touches.length; i++) {
+				if (touches[i].pointerId === e.pointerId) {
+					touches[i] = e;
+					break;
+				}
+			}
+
+			e.touches = touches.slice();
+			e.changedTouches = [e];
+
+			handler(e);
+		}
+
+		obj[pre + 'touchmove' + id] = cb;
+		obj.addEventListener(this.POINTER_MOVE, cb, false);
+
+		return this;
+	},
+
+	addPointerListenerEnd: function (obj, type, handler, id) {
+		var pre = '_leaflet_',
+		    touches = this._pointers;
+
+		var cb = function (e) {
+			for (var i = 0; i < touches.length; i++) {
+				if (touches[i].pointerId === e.pointerId) {
+					touches.splice(i, 1);
+					break;
+				}
+			}
+
+			e.touches = touches.slice();
+			e.changedTouches = [e];
+
+			handler(e);
+		};
+
+		obj[pre + 'touchend' + id] = cb;
+		obj.addEventListener(this.POINTER_UP, cb, false);
+		obj.addEventListener(this.POINTER_CANCEL, cb, false);
+
+		return this;
+	},
+
+	removePointerListener: function (obj, type, id) {
+		var pre = '_leaflet_',
+		    cb = obj[pre + type + id];
+
+		switch (type) {
+		case 'touchstart':
+			obj.removeEventListener(this.POINTER_DOWN, cb, false);
+			break;
+		case 'touchmove':
+			obj.removeEventListener(this.POINTER_MOVE, cb, false);
+			break;
+		case 'touchend':
+			obj.removeEventListener(this.POINTER_UP, cb, false);
+			obj.removeEventListener(this.POINTER_CANCEL, cb, false);
+			break;
+		}
+
+		return this;
+	}
+});
+
+
+/*
+ * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
+ */
+
+L.Map.mergeOptions({
+	touchZoom: L.Browser.touch && !L.Browser.android23,
+	bounceAtZoomLimits: true
+});
+
+L.Map.TouchZoom = L.Handler.extend({
+	addHooks: function () {
+		L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
+	},
+
+	removeHooks: function () {
+		L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
+	},
+
+	_onTouchStart: function (e) {
+		var map = this._map;
+
+		if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
+
+		var p1 = map.mouseEventToLayerPoint(e.touches[0]),
+		    p2 = map.mouseEventToLayerPoint(e.touches[1]),
+		    viewCenter = map._getCenterLayerPoint();
+
+		this._startCenter = p1.add(p2)._divideBy(2);
+		this._startDist = p1.distanceTo(p2);
+
+		this._moved = false;
+		this._zooming = true;
+
+		this._centerOffset = viewCenter.subtract(this._startCenter);
+
+		if (map._panAnim) {
+			map._panAnim.stop();
+		}
+
+		L.DomEvent
+		    .on(document, 'touchmove', this._onTouchMove, this)
+		    .on(document, 'touchend', this._onTouchEnd, this);
+
+		L.DomEvent.preventDefault(e);
+	},
+
+	_onTouchMove: function (e) {
+		var map = this._map;
+
+		if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
+
+		var p1 = map.mouseEventToLayerPoint(e.touches[0]),
+		    p2 = map.mouseEventToLayerPoint(e.touches[1]);
+
+		this._scale = p1.distanceTo(p2) / this._startDist;
+		this._delta = p1._add(p2)._divideBy(2)._subtract(this._startCenter);
+
+		if (this._scale === 1) { return; }
+
+		if (!map.options.bounceAtZoomLimits) {
+			if ((map.getZoom() === map.getMinZoom() && this._scale < 1) ||
+			    (map.getZoom() === map.getMaxZoom() && this._scale > 1)) { return; }
+		}
+
+		if (!this._moved) {
+			L.DomUtil.addClass(map._mapPane, 'leaflet-touching');
+
+			map
+			    .fire('movestart')
+			    .fire('zoomstart');
+
+			this._moved = true;
+		}
+
+		L.Util.cancelAnimFrame(this._animRequest);
+		this._animRequest = L.Util.requestAnimFrame(
+		        this._updateOnMove, this, true, this._map._container);
+
+		L.DomEvent.preventDefault(e);
+	},
+
+	_updateOnMove: function () {
+		var map = this._map,
+		    origin = this._getScaleOrigin(),
+		    center = map.layerPointToLatLng(origin),
+		    zoom = map.getScaleZoom(this._scale);
+
+		map._animateZoom(center, zoom, this._startCenter, this._scale, this._delta, false, true);
+	},
+
+	_onTouchEnd: function () {
+		if (!this._moved || !this._zooming) {
+			this._zooming = false;
+			return;
+		}
+
+		var map = this._map;
+
+		this._zooming = false;
+		L.DomUtil.removeClass(map._mapPane, 'leaflet-touching');
+		L.Util.cancelAnimFrame(this._animRequest);
+
+		L.DomEvent
+		    .off(document, 'touchmove', this._onTouchMove)
+		    .off(document, 'touchend', this._onTouchEnd);
+
+		var origin = this._getScaleOrigin(),
+		    center = map.layerPointToLatLng(origin),
+
+		    oldZoom = map.getZoom(),
+		    floatZoomDelta = map.getScaleZoom(this._scale) - oldZoom,
+		    roundZoomDelta = (floatZoomDelta > 0 ?
+		            Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
+
+		    zoom = map._limitZoom(oldZoom + roundZoomDelta),
+		    scale = map.getZoomScale(zoom) / this._scale;
+
+		map._animateZoom(center, zoom, origin, scale);
+	},
+
+	_getScaleOrigin: function () {
+		var centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale);
+		return this._startCenter.add(centerOffset);
+	}
+});
+
+L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
+
+
+/*
+ * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
+ */
+
+L.Map.mergeOptions({
+	tap: true,
+	tapTolerance: 15
+});
+
+L.Map.Tap = L.Handler.extend({
+	addHooks: function () {
+		L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
+	},
+
+	removeHooks: function () {
+		L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
+	},
+
+	_onDown: function (e) {
+		if (!e.touches) { return; }
+
+		L.DomEvent.preventDefault(e);
+
+		this._fireClick = true;
+
+		// don't simulate click or track longpress if more than 1 touch
+		if (e.touches.length > 1) {
+			this._fireClick = false;
+			clearTimeout(this._holdTimeout);
+			return;
+		}
+
+		var first = e.touches[0],
+		    el = first.target;
+
+		this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
+
+		// if touching a link, highlight it
+		if (el.tagName && el.tagName.toLowerCase() === 'a') {
+			L.DomUtil.addClass(el, 'leaflet-active');
+		}
+
+		// simulate long hold but setting a timeout
+		this._holdTimeout = setTimeout(L.bind(function () {
+			if (this._isTapValid()) {
+				this._fireClick = false;
+				this._onUp();
+				this._simulateEvent('contextmenu', first);
+			}
+		}, this), 1000);
+
+		L.DomEvent
+			.on(document, 'touchmove', this._onMove, this)
+			.on(document, 'touchend', this._onUp, this);
+	},
+
+	_onUp: function (e) {
+		clearTimeout(this._holdTimeout);
+
+		L.DomEvent
+			.off(document, 'touchmove', this._onMove, this)
+			.off(document, 'touchend', this._onUp, this);
+
+		if (this._fireClick && e && e.changedTouches) {
+
+			var first = e.changedTouches[0],
+			    el = first.target;
+
+			if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
+				L.DomUtil.removeClass(el, 'leaflet-active');
+			}
+
+			// simulate click if the touch didn't move too much
+			if (this._isTapValid()) {
+				this._simulateEvent('click', first);
+			}
+		}
+	},
+
+	_isTapValid: function () {
+		return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
+	},
+
+	_onMove: function (e) {
+		var first = e.touches[0];
+		this._newPos = new L.Point(first.clientX, first.clientY);
+	},
+
+	_simulateEvent: function (type, e) {
+		var simulatedEvent = document.createEvent('MouseEvents');
+
+		simulatedEvent._simulated = true;
+		e.target._simulatedClick = true;
+
+		simulatedEvent.initMouseEvent(
+		        type, true, true, window, 1,
+		        e.screenX, e.screenY,
+		        e.clientX, e.clientY,
+		        false, false, false, false, 0, null);
+
+		e.target.dispatchEvent(simulatedEvent);
+	}
+});
+
+if (L.Browser.touch && !L.Browser.pointer) {
+	L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
+}
+
+
+/*
+ * L.Handler.ShiftDragZoom is used to add shift-drag zoom interaction to the map
+  * (zoom to a selected bounding box), enabled by default.
+ */
+
+L.Map.mergeOptions({
+	boxZoom: true
+});
+
+L.Map.BoxZoom = L.Handler.extend({
+	initialize: function (map) {
+		this._map = map;
+		this._container = map._container;
+		this._pane = map._panes.overlayPane;
+		this._moved = false;
+	},
+
+	addHooks: function () {
+		L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
+	},
+
+	removeHooks: function () {
+		L.DomEvent.off(this._container, 'mousedown', this._onMouseDown);
+		this._moved = false;
+	},
+
+	moved: function () {
+		return this._moved;
+	},
+
+	_onMouseDown: function (e) {
+		this._moved = false;
+
+		if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
+
+		L.DomUtil.disableTextSelection();
+		L.DomUtil.disableImageDrag();
+
+		this._startLayerPoint = this._map.mouseEventToLayerPoint(e);
+
+		L.DomEvent
+		    .on(document, 'mousemove', this._onMouseMove, this)
+		    .on(document, 'mouseup', this._onMouseUp, this)
+		    .on(document, 'keydown', this._onKeyDown, this);
+	},
+
+	_onMouseMove: function (e) {
+		if (!this._moved) {
+			this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane);
+			L.DomUtil.setPosition(this._box, this._startLayerPoint);
+
+			//TODO refactor: move cursor to styles
+			this._container.style.cursor = 'crosshair';
+			this._map.fire('boxzoomstart');
+		}
+
+		var startPoint = this._startLayerPoint,
+		    box = this._box,
+
+		    layerPoint = this._map.mouseEventToLayerPoint(e),
+		    offset = layerPoint.subtract(startPoint),
+
+		    newPos = new L.Point(
+		        Math.min(layerPoint.x, startPoint.x),
+		        Math.min(layerPoint.y, startPoint.y));
+
+		L.DomUtil.setPosition(box, newPos);
+
+		this._moved = true;
+
+		// TODO refactor: remove hardcoded 4 pixels
+		box.style.width  = (Math.max(0, Math.abs(offset.x) - 4)) + 'px';
+		box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px';
+	},
+
+	_finish: function () {
+		if (this._moved) {
+			this._pane.removeChild(this._box);
+			this._container.style.cursor = '';
+		}
+
+		L.DomUtil.enableTextSelection();
+		L.DomUtil.enableImageDrag();
+
+		L.DomEvent
+		    .off(document, 'mousemove', this._onMouseMove)
+		    .off(document, 'mouseup', this._onMouseUp)
+		    .off(document, 'keydown', this._onKeyDown);
+	},
+
+	_onMouseUp: function (e) {
+
+		this._finish();
+
+		var map = this._map,
+		    layerPoint = map.mouseEventToLayerPoint(e);
+
+		if (this._startLayerPoint.equals(layerPoint)) { return; }
+
+		var bounds = new L.LatLngBounds(
+		        map.layerPointToLatLng(this._startLayerPoint),
+		        map.layerPointToLatLng(layerPoint));
+
+		map.fitBounds(bounds);
+
+		map.fire('boxzoomend', {
+			boxZoomBounds: bounds
+		});
+	},
+
+	_onKeyDown: function (e) {
+		if (e.keyCode === 27) {
+			this._finish();
+		}
+	}
+});
+
+L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
+
+
+/*
+ * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
+ */
+
+L.Map.mergeOptions({
+	keyboard: true,
+	keyboardPanOffset: 80,
+	keyboardZoomOffset: 1
+});
+
+L.Map.Keyboard = L.Handler.extend({
+
+	keyCodes: {
+		left:    [37],
+		right:   [39],
+		down:    [40],
+		up:      [38],
+		zoomIn:  [187, 107, 61, 171],
+		zoomOut: [189, 109, 173]
+	},
+
+	initialize: function (map) {
+		this._map = map;
+
+		this._setPanOffset(map.options.keyboardPanOffset);
+		this._setZoomOffset(map.options.keyboardZoomOffset);
+	},
+
+	addHooks: function () {
+		var container = this._map._container;
+
+		// make the container focusable by tabbing
+		if (container.tabIndex === -1) {
+			container.tabIndex = '0';
+		}
+
+		L.DomEvent
+		    .on(container, 'focus', this._onFocus, this)
+		    .on(container, 'blur', this._onBlur, this)
+		    .on(container, 'mousedown', this._onMouseDown, this);
+
+		this._map
+		    .on('focus', this._addHooks, this)
+		    .on('blur', this._removeHooks, this);
+	},
+
+	removeHooks: function () {
+		this._removeHooks();
+
+		var container = this._map._container;
+
+		L.DomEvent
+		    .off(container, 'focus', this._onFocus, this)
+		    .off(container, 'blur', this._onBlur, this)
+		    .off(container, 'mousedown', this._onMouseDown, this);
+
+		this._map
+		    .off('focus', this._addHooks, this)
+		    .off('blur', this._removeHooks, this);
+	},
+
+	_onMouseDown: function () {
+		if (this._focused) { return; }
+
+		var body = document.body,
+		    docEl = document.documentElement,
+		    top = body.scrollTop || docEl.scrollTop,
+		    left = body.scrollLeft || docEl.scrollLeft;
+
+		this._map._container.focus();
+
+		window.scrollTo(left, top);
+	},
+
+	_onFocus: function () {
+		this._focused = true;
+		this._map.fire('focus');
+	},
+
+	_onBlur: function () {
+		this._focused = false;
+		this._map.fire('blur');
+	},
+
+	_setPanOffset: function (pan) {
+		var keys = this._panKeys = {},
+		    codes = this.keyCodes,
+		    i, len;
+
+		for (i = 0, len = codes.left.length; i < len; i++) {
+			keys[codes.left[i]] = [-1 * pan, 0];
+		}
+		for (i = 0, len = codes.right.length; i < len; i++) {
+			keys[codes.right[i]] = [pan, 0];
+		}
+		for (i = 0, len = codes.down.length; i < len; i++) {
+			keys[codes.down[i]] = [0, pan];
+		}
+		for (i = 0, len = codes.up.length; i < len; i++) {
+			keys[codes.up[i]] = [0, -1 * pan];
+		}
+	},
+
+	_setZoomOffset: function (zoom) {
+		var keys = this._zoomKeys = {},
+		    codes = this.keyCodes,
+		    i, len;
+
+		for (i = 0, len = codes.zoomIn.length; i < len; i++) {
+			keys[codes.zoomIn[i]] = zoom;
+		}
+		for (i = 0, len = codes.zoomOut.length; i < len; i++) {
+			keys[codes.zoomOut[i]] = -zoom;
+		}
+	},
+
+	_addHooks: function () {
+		L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
+	},
+
+	_removeHooks: function () {
+		L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
+	},
+
+	_onKeyDown: function (e) {
+		var key = e.keyCode,
+		    map = this._map;
+
+		if (key in this._panKeys) {
+
+			if (map._panAnim && map._panAnim._inProgress) { return; }
+
+			map.panBy(this._panKeys[key]);
+
+			if (map.options.maxBounds) {
+				map.panInsideBounds(map.options.maxBounds);
+			}
+
+		} else if (key in this._zoomKeys) {
+			map.setZoom(map.getZoom() + this._zoomKeys[key]);
+
+		} else {
+			return;
+		}
+
+		L.DomEvent.stop(e);
+	}
+});
+
+L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
+
+
+/*
+ * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
+ */
+
+L.Handler.MarkerDrag = L.Handler.extend({
+	initialize: function (marker) {
+		this._marker = marker;
+	},
+
+	addHooks: function () {
+		var icon = this._marker._icon;
+		if (!this._draggable) {
+			this._draggable = new L.Draggable(icon, icon);
+		}
+
+		this._draggable
+			.on('dragstart', this._onDragStart, this)
+			.on('drag', this._onDrag, this)
+			.on('dragend', this._onDragEnd, this);
+		this._draggable.enable();
+		L.DomUtil.addClass(this._marker._icon, 'leaflet-marker-draggable');
+	},
+
+	removeHooks: function () {
+		this._draggable
+			.off('dragstart', this._onDragStart, this)
+			.off('drag', this._onDrag, this)
+			.off('dragend', this._onDragEnd, this);
+
+		this._draggable.disable();
+		L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
+	},
+
+	moved: function () {
+		return this._draggable && this._draggable._moved;
+	},
+
+	_onDragStart: function () {
+		this._marker
+		    .closePopup()
+		    .fire('movestart')
+		    .fire('dragstart');
+	},
+
+	_onDrag: function () {
+		var marker = this._marker,
+		    shadow = marker._shadow,
+		    iconPos = L.DomUtil.getPosition(marker._icon),
+		    latlng = marker._map.layerPointToLatLng(iconPos);
+
+		// update shadow position
+		if (shadow) {
+			L.DomUtil.setPosition(shadow, iconPos);
+		}
+
+		marker._latlng = latlng;
+
+		marker
+		    .fire('move', {latlng: latlng})
+		    .fire('drag');
+	},
+
+	_onDragEnd: function (e) {
+		this._marker
+		    .fire('moveend')
+		    .fire('dragend', e);
+	}
+});
+
+
+/*
+ * L.Control is a base class for implementing map controls. Handles positioning.
+ * All other controls extend from this class.
+ */
+
+L.Control = L.Class.extend({
+	options: {
+		position: 'topright'
+	},
+
+	initialize: function (options) {
+		L.setOptions(this, options);
+	},
+
+	getPosition: function () {
+		return this.options.position;
+	},
+
+	setPosition: function (position) {
+		var map = this._map;
+
+		if (map) {
+			map.removeControl(this);
+		}
+
+		this.options.position = position;
+
+		if (map) {
+			map.addControl(this);
+		}
+
+		return this;
+	},
+
+	getContainer: function () {
+		return this._container;
+	},
+
+	addTo: function (map) {
+		this._map = map;
+
+		var container = this._container = this.onAdd(map),
+		    pos = this.getPosition(),
+		    corner = map._controlCorners[pos];
+
+		L.DomUtil.addClass(container, 'leaflet-control');
+
+		if (pos.indexOf('bottom') !== -1) {
+			corner.insertBefore(container, corner.firstChild);
+		} else {
+			corner.appendChild(container);
+		}
+
+		return this;
+	},
+
+	removeFrom: function (map) {
+		var pos = this.getPosition(),
+		    corner = map._controlCorners[pos];
+
+		corner.removeChild(this._container);
+		this._map = null;
+
+		if (this.onRemove) {
+			this.onRemove(map);
+		}
+
+		return this;
+	},
+
+	_refocusOnMap: function () {
+		if (this._map) {
+			this._map.getContainer().focus();
+		}
+	}
+});
+
+L.control = function (options) {
+	return new L.Control(options);
+};
+
+
+// adds control-related methods to L.Map
+
+L.Map.include({
+	addControl: function (control) {
+		control.addTo(this);
+		return this;
+	},
+
+	removeControl: function (control) {
+		control.removeFrom(this);
+		return this;
+	},
+
+	_initControlPos: function () {
+		var corners = this._controlCorners = {},
+		    l = 'leaflet-',
+		    container = this._controlContainer =
+		            L.DomUtil.create('div', l + 'control-container', this._container);
+
+		function createCorner(vSide, hSide) {
+			var className = l + vSide + ' ' + l + hSide;
+
+			corners[vSide + hSide] = L.DomUtil.create('div', className, container);
+		}
+
+		createCorner('top', 'left');
+		createCorner('top', 'right');
+		createCorner('bottom', 'left');
+		createCorner('bottom', 'right');
+	},
+
+	_clearControlPos: function () {
+		this._container.removeChild(this._controlContainer);
+	}
+});
+
+
+/*
+ * L.Control.Zoom is used for the default zoom buttons on the map.
+ */
+
+L.Control.Zoom = L.Control.extend({
+	options: {
+		position: 'topleft',
+		zoomInText: '+',
+		zoomInTitle: 'Zoom in',
+		zoomOutText: '-',
+		zoomOutTitle: 'Zoom out'
+	},
+
+	onAdd: function (map) {
+		var zoomName = 'leaflet-control-zoom',
+		    container = L.DomUtil.create('div', zoomName + ' leaflet-bar');
+
+		this._map = map;
+
+		this._zoomInButton  = this._createButton(
+		        this.options.zoomInText, this.options.zoomInTitle,
+		        zoomName + '-in',  container, this._zoomIn,  this);
+		this._zoomOutButton = this._createButton(
+		        this.options.zoomOutText, this.options.zoomOutTitle,
+		        zoomName + '-out', container, this._zoomOut, this);
+
+		this._updateDisabled();
+		map.on('zoomend zoomlevelschange', this._updateDisabled, this);
+
+		return container;
+	},
+
+	onRemove: function (map) {
+		map.off('zoomend zoomlevelschange', this._updateDisabled, this);
+	},
+
+	_zoomIn: function (e) {
+		this._map.zoomIn(e.shiftKey ? 3 : 1);
+	},
+
+	_zoomOut: function (e) {
+		this._map.zoomOut(e.shiftKey ? 3 : 1);
+	},
+
+	_createButton: function (html, title, className, container, fn, context) {
+		var link = L.DomUtil.create('a', className, container);
+		link.innerHTML = html;
+		link.href = '#';
+		link.title = title;
+
+		var stop = L.DomEvent.stopPropagation;
+
+		L.DomEvent
+		    .on(link, 'click', stop)
+		    .on(link, 'mousedown', stop)
+		    .on(link, 'dblclick', stop)
+		    .on(link, 'click', L.DomEvent.preventDefault)
+		    .on(link, 'click', fn, context)
+		    .on(link, 'click', this._refocusOnMap, context);
+
+		return link;
+	},
+
+	_updateDisabled: function () {
+		var map = this._map,
+			className = 'leaflet-disabled';
+
+		L.DomUtil.removeClass(this._zoomInButton, className);
+		L.DomUtil.removeClass(this._zoomOutButton, className);
+
+		if (map._zoom === map.getMinZoom()) {
+			L.DomUtil.addClass(this._zoomOutButton, className);
+		}
+		if (map._zoom === map.getMaxZoom()) {
+			L.DomUtil.addClass(this._zoomInButton, className);
+		}
+	}
+});
+
+L.Map.mergeOptions({
+	zoomControl: true
+});
+
+L.Map.addInitHook(function () {
+	if (this.options.zoomControl) {
+		this.zoomControl = new L.Control.Zoom();
+		this.addControl(this.zoomControl);
+	}
+});
+
+L.control.zoom = function (options) {
+	return new L.Control.Zoom(options);
+};
+
+
+
+/*
+ * L.Control.Attribution is used for displaying attribution on the map (added by default).
+ */
+
+L.Control.Attribution = L.Control.extend({
+	options: {
+		position: 'bottomright',
+		prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
+	},
+
+	initialize: function (options) {
+		L.setOptions(this, options);
+
+		this._attributions = {};
+	},
+
+	onAdd: function (map) {
+		this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
+		L.DomEvent.disableClickPropagation(this._container);
+
+		for (var i in map._layers) {
+			if (map._layers[i].getAttribution) {
+				this.addAttribution(map._layers[i].getAttribution());
+			}
+		}
+		
+		map
+		    .on('layeradd', this._onLayerAdd, this)
+		    .on('layerremove', this._onLayerRemove, this);
+
+		this._update();
+
+		return this._container;
+	},
+
+	onRemove: function (map) {
+		map
+		    .off('layeradd', this._onLayerAdd)
+		    .off('layerremove', this._onLayerRemove);
+
+	},
+
+	setPrefix: function (prefix) {
+		this.options.prefix = prefix;
+		this._update();
+		return this;
+	},
+
+	addAttribution: function (text) {
+		if (!text) { return; }
+
+		if (!this._attributions[text]) {
+			this._attributions[text] = 0;
+		}
+		this._attributions[text]++;
+
+		this._update();
+
+		return this;
+	},
+
+	removeAttribution: function (text) {
+		if (!text) { return; }
+
+		if (this._attributions[text]) {
+			this._attributions[text]--;
+			this._update();
+		}
+
+		return this;
+	},
+
+	_update: function () {
+		if (!this._map) { return; }
+
+		var attribs = [];
+
+		for (var i in this._attributions) {
+			if (this._attributions[i]) {
+				attribs.push(i);
+			}
+		}
+
+		var prefixAndAttribs = [];
+
+		if (this.options.prefix) {
+			prefixAndAttribs.push(this.options.prefix);
+		}
+		if (attribs.length) {
+			prefixAndAttribs.push(attribs.join(', '));
+		}
+
+		this._container.innerHTML = prefixAndAttribs.join(' | ');
+	},
+
+	_onLayerAdd: function (e) {
+		if (e.layer.getAttribution) {
+			this.addAttribution(e.layer.getAttribution());
+		}
+	},
+
+	_onLayerRemove: function (e) {
+		if (e.layer.getAttribution) {
+			this.removeAttribution(e.layer.getAttribution());
+		}
+	}
+});
+
+L.Map.mergeOptions({
+	attributionControl: true
+});
+
+L.Map.addInitHook(function () {
+	if (this.options.attributionControl) {
+		this.attributionControl = (new L.Control.Attribution()).addTo(this);
+	}
+});
+
+L.control.attribution = function (options) {
+	return new L.Control.Attribution(options);
+};
+
+
+/*
+ * L.Control.Scale is used for displaying metric/imperial scale on the map.
+ */
+
+L.Control.Scale = L.Control.extend({
+	options: {
+		position: 'bottomleft',
+		maxWidth: 100,
+		metric: true,
+		imperial: true,
+		updateWhenIdle: false
+	},
+
+	onAdd: function (map) {
+		this._map = map;
+
+		var className = 'leaflet-control-scale',
+		    container = L.DomUtil.create('div', className),
+		    options = this.options;
+
+		this._addScales(options, className, container);
+
+		map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
+		map.whenReady(this._update, this);
+
+		return container;
+	},
+
+	onRemove: function (map) {
+		map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
+	},
+
+	_addScales: function (options, className, container) {
+		if (options.metric) {
+			this._mScale = L.DomUtil.create('div', className + '-line', container);
+		}
+		if (options.imperial) {
+			this._iScale = L.DomUtil.create('div', className + '-line', container);
+		}
+	},
+
+	_update: function () {
+		var bounds = this._map.getBounds(),
+		    centerLat = bounds.getCenter().lat,
+		    halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180),
+		    dist = halfWorldMeters * (bounds.getNorthEast().lng - bounds.getSouthWest().lng) / 180,
+
+		    size = this._map.getSize(),
+		    options = this.options,
+		    maxMeters = 0;
+
+		if (size.x > 0) {
+			maxMeters = dist * (options.maxWidth / size.x);
+		}
+
+		this._updateScales(options, maxMeters);
+	},
+
+	_updateScales: function (options, maxMeters) {
+		if (options.metric && maxMeters) {
+			this._updateMetric(maxMeters);
+		}
+
+		if (options.imperial && maxMeters) {
+			this._updateImperial(maxMeters);
+		}
+	},
+
+	_updateMetric: function (maxMeters) {
+		var meters = this._getRoundNum(maxMeters);
+
+		this._mScale.style.width = this._getScaleWidth(meters / maxMeters) + 'px';
+		this._mScale.innerHTML = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
+	},
+
+	_updateImperial: function (maxMeters) {
+		var maxFeet = maxMeters * 3.2808399,
+		    scale = this._iScale,
+		    maxMiles, miles, feet;
+
+		if (maxFeet > 5280) {
+			maxMiles = maxFeet / 5280;
+			miles = this._getRoundNum(maxMiles);
+
+			scale.style.width = this._getScaleWidth(miles / maxMiles) + 'px';
+			scale.innerHTML = miles + ' mi';
+
+		} else {
+			feet = this._getRoundNum(maxFeet);
+
+			scale.style.width = this._getScaleWidth(feet / maxFeet) + 'px';
+			scale.innerHTML = feet + ' ft';
+		}
+	},
+
+	_getScaleWidth: function (ratio) {
+		return Math.round(this.options.maxWidth * ratio) - 10;
+	},
+
+	_getRoundNum: function (num) {
+		var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
+		    d = num / pow10;
+
+		d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1;
+
+		return pow10 * d;
+	}
+});
+
+L.control.scale = function (options) {
+	return new L.Control.Scale(options);
+};
+
+
+/*
+ * L.Control.Layers is a control to allow users to switch between different layers on the map.
+ */
+
+L.Control.Layers = L.Control.extend({
+	options: {
+		collapsed: true,
+		position: 'topright',
+		autoZIndex: true
+	},
+
+	initialize: function (baseLayers, overlays, options) {
+		L.setOptions(this, options);
+
+		this._layers = {};
+		this._lastZIndex = 0;
+		this._handlingClick = false;
+
+		for (var i in baseLayers) {
+			this._addLayer(baseLayers[i], i);
+		}
+
+		for (i in overlays) {
+			this._addLayer(overlays[i], i, true);
+		}
+	},
+
+	onAdd: function (map) {
+		this._initLayout();
+		this._update();
+
+		map
+		    .on('layeradd', this._onLayerChange, this)
+		    .on('layerremove', this._onLayerChange, this);
+
+		return this._container;
+	},
+
+	onRemove: function (map) {
+		map
+		    .off('layeradd', this._onLayerChange, this)
+		    .off('layerremove', this._onLayerChange, this);
+	},
+
+	addBaseLayer: function (layer, name) {
+		this._addLayer(layer, name);
+		this._update();
+		return this;
+	},
+
+	addOverlay: function (layer, name) {
+		this._addLayer(layer, name, true);
+		this._update();
+		return this;
+	},
+
+	removeLayer: function (layer) {
+		var id = L.stamp(layer);
+		delete this._layers[id];
+		this._update();
+		return this;
+	},
+
+	_initLayout: function () {
+		var className = 'leaflet-control-layers',
+		    container = this._container = L.DomUtil.create('div', className);
+
+		//Makes this work on IE10 Touch devices by stopping it from firing a mouseout event when the touch is released
+		container.setAttribute('aria-haspopup', true);
+
+		if (!L.Browser.touch) {
+			L.DomEvent
+				.disableClickPropagation(container)
+				.disableScrollPropagation(container);
+		} else {
+			L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation);
+		}
+
+		var form = this._form = L.DomUtil.create('form', className + '-list');
+
+		if (this.options.collapsed) {
+			if (!L.Browser.android) {
+				L.DomEvent
+				    .on(container, 'mouseover', this._expand, this)
+				    .on(container, 'mouseout', this._collapse, this);
+			}
+			var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
+			link.href = '#';
+			link.title = 'Layers';
+
+			if (L.Browser.touch) {
+				L.DomEvent
+				    .on(link, 'click', L.DomEvent.stop)
+				    .on(link, 'click', this._expand, this);
+			}
+			else {
+				L.DomEvent.on(link, 'focus', this._expand, this);
+			}
+			//Work around for Firefox android issue https://github.com/Leaflet/Leaflet/issues/2033
+			L.DomEvent.on(form, 'click', function () {
+				setTimeout(L.bind(this._onInputClick, this), 0);
+			}, this);
+
+			this._map.on('click', this._collapse, this);
+			// TODO keyboard accessibility
+		} else {
+			this._expand();
+		}
+
+		this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
+		this._separator = L.DomUtil.create('div', className + '-separator', form);
+		this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
+
+		container.appendChild(form);
+	},
+
+	_addLayer: function (layer, name, overlay) {
+		var id = L.stamp(layer);
+
+		this._layers[id] = {
+			layer: layer,
+			name: name,
+			overlay: overlay
+		};
+
+		if (this.options.autoZIndex && layer.setZIndex) {
+			this._lastZIndex++;
+			layer.setZIndex(this._lastZIndex);
+		}
+	},
+
+	_update: function () {
+		if (!this._container) {
+			return;
+		}
+
+		this._baseLayersList.innerHTML = '';
+		this._overlaysList.innerHTML = '';
+
+		var baseLayersPresent = false,
+		    overlaysPresent = false,
+		    i, obj;
+
+		for (i in this._layers) {
+			obj = this._layers[i];
+			this._addItem(obj);
+			overlaysPresent = overlaysPresent || obj.overlay;
+			baseLayersPresent = baseLayersPresent || !obj.overlay;
+		}
+
+		this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
+	},
+
+	_onLayerChange: function (e) {
+		var obj = this._layers[L.stamp(e.layer)];
+
+		if (!obj) { return; }
+
+		if (!this._handlingClick) {
+			this._update();
+		}
+
+		var type = obj.overlay ?
+			(e.type === 'layeradd' ? 'overlayadd' : 'overlayremove') :
+			(e.type === 'layeradd' ? 'baselayerchange' : null);
+
+		if (type) {
+			this._map.fire(type, obj);
+		}
+	},
+
+	// IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
+	_createRadioElement: function (name, checked) {
+
+		var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + name + '"';
+		if (checked) {
+			radioHtml += ' checked="checked"';
+		}
+		radioHtml += '/>';
+
+		var radioFragment = document.createElement('div');
+		radioFragment.innerHTML = radioHtml;
+
+		return radioFragment.firstChild;
+	},
+
+	_addItem: function (obj) {
+		var label = document.createElement('label'),
+		    input,
+		    checked = this._map.hasLayer(obj.layer);
+
+		if (obj.overlay) {
+			input = document.createElement('input');
+			input.type = 'checkbox';
+			input.className = 'leaflet-control-layers-selector';
+			input.defaultChecked = checked;
+		} else {
+			input = this._createRadioElement('leaflet-base-layers', checked);
+		}
+
+		input.layerId = L.stamp(obj.layer);
+
+		L.DomEvent.on(input, 'click', this._onInputClick, this);
+
+		var name = document.createElement('span');
+		name.innerHTML = ' ' + obj.name;
+
+		label.appendChild(input);
+		label.appendChild(name);
+
+		var container = obj.overlay ? this._overlaysList : this._baseLayersList;
+		container.appendChild(label);
+
+		return label;
+	},
+
+	_onInputClick: function () {
+		var i, input, obj,
+		    inputs = this._form.getElementsByTagName('input'),
+		    inputsLen = inputs.length;
+
+		this._handlingClick = true;
+
+		for (i = 0; i < inputsLen; i++) {
+			input = inputs[i];
+			obj = this._layers[input.layerId];
+
+			if (input.checked && !this._map.hasLayer(obj.layer)) {
+				this._map.addLayer(obj.layer);
+
+			} else if (!input.checked && this._map.hasLayer(obj.layer)) {
+				this._map.removeLayer(obj.layer);
+			}
+		}
+
+		this._handlingClick = false;
+
+		this._refocusOnMap();
+	},
+
+	_expand: function () {
+		L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
+	},
+
+	_collapse: function () {
+		this._container.className = this._container.className.replace(' leaflet-control-layers-expanded', '');
+	}
+});
+
+L.control.layers = function (baseLayers, overlays, options) {
+	return new L.Control.Layers(baseLayers, overlays, options);
+};
+
+
+/*
+ * L.PosAnimation is used by Leaflet internally for pan animations.
+ */
+
+L.PosAnimation = L.Class.extend({
+	includes: L.Mixin.Events,
+
+	run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
+		this.stop();
+
+		this._el = el;
+		this._inProgress = true;
+		this._newPos = newPos;
+
+		this.fire('start');
+
+		el.style[L.DomUtil.TRANSITION] = 'all ' + (duration || 0.25) +
+		        's cubic-bezier(0,0,' + (easeLinearity || 0.5) + ',1)';
+
+		L.DomEvent.on(el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
+		L.DomUtil.setPosition(el, newPos);
+
+		// toggle reflow, Chrome flickers for some reason if you don't do this
+		L.Util.falseFn(el.offsetWidth);
+
+		// there's no native way to track value updates of transitioned properties, so we imitate this
+		this._stepTimer = setInterval(L.bind(this._onStep, this), 50);
+	},
+
+	stop: function () {
+		if (!this._inProgress) { return; }
+
+		// if we just removed the transition property, the element would jump to its final position,
+		// so we need to make it stay at the current position
+
+		L.DomUtil.setPosition(this._el, this._getPos());
+		this._onTransitionEnd();
+		L.Util.falseFn(this._el.offsetWidth); // force reflow in case we are about to start a new animation
+	},
+
+	_onStep: function () {
+		var stepPos = this._getPos();
+		if (!stepPos) {
+			this._onTransitionEnd();
+			return;
+		}
+		// jshint camelcase: false
+		// make L.DomUtil.getPosition return intermediate position value during animation
+		this._el._leaflet_pos = stepPos;
+
+		this.fire('step');
+	},
+
+	// you can't easily get intermediate values of properties animated with CSS3 Transitions,
+	// we need to parse computed style (in case of transform it returns matrix string)
+
+	_transformRe: /([-+]?(?:\d*\.)?\d+)\D*, ([-+]?(?:\d*\.)?\d+)\D*\)/,
+
+	_getPos: function () {
+		var left, top, matches,
+		    el = this._el,
+		    style = window.getComputedStyle(el);
+
+		if (L.Browser.any3d) {
+			matches = style[L.DomUtil.TRANSFORM].match(this._transformRe);
+			if (!matches) { return; }
+			left = parseFloat(matches[1]);
+			top  = parseFloat(matches[2]);
+		} else {
+			left = parseFloat(style.left);
+			top  = parseFloat(style.top);
+		}
+
+		return new L.Point(left, top, true);
+	},
+
+	_onTransitionEnd: function () {
+		L.DomEvent.off(this._el, L.DomUtil.TRANSITION_END, this._onTransitionEnd, this);
+
+		if (!this._inProgress) { return; }
+		this._inProgress = false;
+
+		this._el.style[L.DomUtil.TRANSITION] = '';
+
+		// jshint camelcase: false
+		// make sure L.DomUtil.getPosition returns the final position value after animation
+		this._el._leaflet_pos = this._newPos;
+
+		clearInterval(this._stepTimer);
+
+		this.fire('step').fire('end');
+	}
+
+});
+
+
+/*
+ * Extends L.Map to handle panning animations.
+ */
+
+L.Map.include({
+
+	setView: function (center, zoom, options) {
+
+		zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
+		center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
+		options = options || {};
+
+		if (this._panAnim) {
+			this._panAnim.stop();
+		}
+
+		if (this._loaded && !options.reset && options !== true) {
+
+			if (options.animate !== undefined) {
+				options.zoom = L.extend({animate: options.animate}, options.zoom);
+				options.pan = L.extend({animate: options.animate}, options.pan);
+			}
+
+			// try animating pan or zoom
+			var animated = (this._zoom !== zoom) ?
+				this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
+				this._tryAnimatedPan(center, options.pan);
+
+			if (animated) {
+				// prevent resize handler call, the view will refresh after animation anyway
+				clearTimeout(this._sizeTimer);
+				return this;
+			}
+		}
+
+		// animation didn't start, just reset the map view
+		this._resetView(center, zoom);
+
+		return this;
+	},
+
+	panBy: function (offset, options) {
+		offset = L.point(offset).round();
+		options = options || {};
+
+		if (!offset.x && !offset.y) {
+			return this;
+		}
+
+		if (!this._panAnim) {
+			this._panAnim = new L.PosAnimation();
+
+			this._panAnim.on({
+				'step': this._onPanTransitionStep,
+				'end': this._onPanTransitionEnd
+			}, this);
+		}
+
+		// don't fire movestart if animating inertia
+		if (!options.noMoveStart) {
+			this.fire('movestart');
+		}
+
+		// animate pan unless animate: false specified
+		if (options.animate !== false) {
+			L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
+
+			var newPos = this._getMapPanePos().subtract(offset);
+			this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
+		} else {
+			this._rawPanBy(offset);
+			this.fire('move').fire('moveend');
+		}
+
+		return this;
+	},
+
+	_onPanTransitionStep: function () {
+		this.fire('move');
+	},
+
+	_onPanTransitionEnd: function () {
+		L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
+		this.fire('moveend');
+	},
+
+	_tryAnimatedPan: function (center, options) {
+		// difference between the new and current centers in pixels
+		var offset = this._getCenterOffset(center)._floor();
+
+		// don't animate too far unless animate: true specified in options
+		if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
+
+		this.panBy(offset, options);
+
+		return true;
+	}
+});
+
+
+/*
+ * L.PosAnimation fallback implementation that powers Leaflet pan animations
+ * in browsers that don't support CSS3 Transitions.
+ */
+
+L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.PosAnimation.extend({
+
+	run: function (el, newPos, duration, easeLinearity) { // (HTMLElement, Point[, Number, Number])
+		this.stop();
+
+		this._el = el;
+		this._inProgress = true;
+		this._duration = duration || 0.25;
+		this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
+
+		this._startPos = L.DomUtil.getPosition(el);
+		this._offset = newPos.subtract(this._startPos);
+		this._startTime = +new Date();
+
+		this.fire('start');
+
+		this._animate();
+	},
+
+	stop: function () {
+		if (!this._inProgress) { return; }
+
+		this._step();
+		this._complete();
+	},
+
+	_animate: function () {
+		// animation loop
+		this._animId = L.Util.requestAnimFrame(this._animate, this);
+		this._step();
+	},
+
+	_step: function () {
+		var elapsed = (+new Date()) - this._startTime,
+		    duration = this._duration * 1000;
+
+		if (elapsed < duration) {
+			this._runFrame(this._easeOut(elapsed / duration));
+		} else {
+			this._runFrame(1);
+			this._complete();
+		}
+	},
+
+	_runFrame: function (progress) {
+		var pos = this._startPos.add(this._offset.multiplyBy(progress));
+		L.DomUtil.setPosition(this._el, pos);
+
+		this.fire('step');
+	},
+
+	_complete: function () {
+		L.Util.cancelAnimFrame(this._animId);
+
+		this._inProgress = false;
+		this.fire('end');
+	},
+
+	_easeOut: function (t) {
+		return 1 - Math.pow(1 - t, this._easeOutPower);
+	}
+});
+
+
+/*
+ * Extends L.Map to handle zoom animations.
+ */
+
+L.Map.mergeOptions({
+	zoomAnimation: true,
+	zoomAnimationThreshold: 4
+});
+
+if (L.DomUtil.TRANSITION) {
+
+	L.Map.addInitHook(function () {
+		// don't animate on browsers without hardware-accelerated transitions or old Android/Opera
+		this._zoomAnimated = this.options.zoomAnimation && L.DomUtil.TRANSITION &&
+				L.Browser.any3d && !L.Browser.android23 && !L.Browser.mobileOpera;
+
+		// zoom transitions run with the same duration for all layers, so if one of transitionend events
+		// happens after starting zoom animation (propagating to the map pane), we know that it ended globally
+		if (this._zoomAnimated) {
+			L.DomEvent.on(this._mapPane, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
+		}
+	});
+}
+
+L.Map.include(!L.DomUtil.TRANSITION ? {} : {
+
+	_catchTransitionEnd: function (e) {
+		if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
+			this._onZoomTransitionEnd();
+		}
+	},
+
+	_nothingToAnimate: function () {
+		return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
+	},
+
+	_tryAnimatedZoom: function (center, zoom, options) {
+
+		if (this._animatingZoom) { return true; }
+
+		options = options || {};
+
+		// don't animate if disabled, not supported or zoom difference is too large
+		if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
+		        Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
+
+		// offset is the pixel coords of the zoom origin relative to the current center
+		var scale = this.getZoomScale(zoom),
+		    offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale),
+			origin = this._getCenterLayerPoint()._add(offset);
+
+		// don't animate if the zoom origin isn't within one screen from the current center, unless forced
+		if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
+
+		this
+		    .fire('movestart')
+		    .fire('zoomstart');
+
+		this._animateZoom(center, zoom, origin, scale, null, true);
+
+		return true;
+	},
+
+	_animateZoom: function (center, zoom, origin, scale, delta, backwards, forTouchZoom) {
+
+		if (!forTouchZoom) {
+			this._animatingZoom = true;
+		}
+
+		// put transform transition on all layers with leaflet-zoom-animated class
+		L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
+
+		// remember what center/zoom to set after animation
+		this._animateToCenter = center;
+		this._animateToZoom = zoom;
+
+		// disable any dragging during animation
+		if (L.Draggable) {
+			L.Draggable._disabled = true;
+		}
+
+		L.Util.requestAnimFrame(function () {
+			this.fire('zoomanim', {
+				center: center,
+				zoom: zoom,
+				origin: origin,
+				scale: scale,
+				delta: delta,
+				backwards: backwards
+			});
+			// horrible hack to work around a Chrome bug https://github.com/Leaflet/Leaflet/issues/3689
+			setTimeout(L.bind(this._onZoomTransitionEnd, this), 250);
+		}, this);
+	},
+
+	_onZoomTransitionEnd: function () {
+		if (!this._animatingZoom) { return; }
+
+		this._animatingZoom = false;
+
+		L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
+
+		L.Util.requestAnimFrame(function () {
+			this._resetView(this._animateToCenter, this._animateToZoom, true, true);
+
+			if (L.Draggable) {
+				L.Draggable._disabled = false;
+			}
+		}, this);
+	}
+});
+
+
+/*
+	Zoom animation logic for L.TileLayer.
+*/
+
+L.TileLayer.include({
+	_animateZoom: function (e) {
+		if (!this._animating) {
+			this._animating = true;
+			this._prepareBgBuffer();
+		}
+
+		var bg = this._bgBuffer,
+		    transform = L.DomUtil.TRANSFORM,
+		    initialTransform = e.delta ? L.DomUtil.getTranslateString(e.delta) : bg.style[transform],
+		    scaleStr = L.DomUtil.getScaleString(e.scale, e.origin);
+
+		bg.style[transform] = e.backwards ?
+				scaleStr + ' ' + initialTransform :
+				initialTransform + ' ' + scaleStr;
+	},
+
+	_endZoomAnim: function () {
+		var front = this._tileContainer,
+		    bg = this._bgBuffer;
+
+		front.style.visibility = '';
+		front.parentNode.appendChild(front); // Bring to fore
+
+		// force reflow
+		L.Util.falseFn(bg.offsetWidth);
+
+		var zoom = this._map.getZoom();
+		if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
+			this._clearBgBuffer();
+		}
+
+		this._animating = false;
+	},
+
+	_clearBgBuffer: function () {
+		var map = this._map;
+
+		if (map && !map._animatingZoom && !map.touchZoom._zooming) {
+			this._bgBuffer.innerHTML = '';
+			this._bgBuffer.style[L.DomUtil.TRANSFORM] = '';
+		}
+	},
+
+	_prepareBgBuffer: function () {
+
+		var front = this._tileContainer,
+		    bg = this._bgBuffer;
+
+		// if foreground layer doesn't have many tiles but bg layer does,
+		// keep the existing bg layer and just zoom it some more
+
+		var bgLoaded = this._getLoadedTilesPercentage(bg),
+		    frontLoaded = this._getLoadedTilesPercentage(front);
+
+		if (bg && bgLoaded > 0.5 && frontLoaded < 0.5) {
+
+			front.style.visibility = 'hidden';
+			this._stopLoadingImages(front);
+			return;
+		}
+
+		// prepare the buffer to become the front tile pane
+		bg.style.visibility = 'hidden';
+		bg.style[L.DomUtil.TRANSFORM] = '';
+
+		// switch out the current layer to be the new bg layer (and vice-versa)
+		this._tileContainer = bg;
+		bg = this._bgBuffer = front;
+
+		this._stopLoadingImages(bg);
+
+		//prevent bg buffer from clearing right after zoom
+		clearTimeout(this._clearBgBufferTimer);
+	},
+
+	_getLoadedTilesPercentage: function (container) {
+		var tiles = container.getElementsByTagName('img'),
+		    i, len, count = 0;
+
+		for (i = 0, len = tiles.length; i < len; i++) {
+			if (tiles[i].complete) {
+				count++;
+			}
+		}
+		return count / len;
+	},
+
+	// stops loading all tiles in the background layer
+	_stopLoadingImages: function (container) {
+		var tiles = Array.prototype.slice.call(container.getElementsByTagName('img')),
+		    i, len, tile;
+
+		for (i = 0, len = tiles.length; i < len; i++) {
+			tile = tiles[i];
+
+			if (!tile.complete) {
+				tile.onload = L.Util.falseFn;
+				tile.onerror = L.Util.falseFn;
+				tile.src = L.Util.emptyImageUrl;
+
+				tile.parentNode.removeChild(tile);
+			}
+		}
+	}
+});
+
+
+/*
+ * Provides L.Map with convenient shortcuts for using browser geolocation features.
+ */
+
+L.Map.include({
+	_defaultLocateOptions: {
+		watch: false,
+		setView: false,
+		maxZoom: Infinity,
+		timeout: 10000,
+		maximumAge: 0,
+		enableHighAccuracy: false
+	},
+
+	locate: function (/*Object*/ options) {
+
+		options = this._locateOptions = L.extend(this._defaultLocateOptions, options);
+
+		if (!navigator.geolocation) {
+			this._handleGeolocationError({
+				code: 0,
+				message: 'Geolocation not supported.'
+			});
+			return this;
+		}
+
+		var onResponse = L.bind(this._handleGeolocationResponse, this),
+			onError = L.bind(this._handleGeolocationError, this);
+
+		if (options.watch) {
+			this._locationWatchId =
+			        navigator.geolocation.watchPosition(onResponse, onError, options);
+		} else {
+			navigator.geolocation.getCurrentPosition(onResponse, onError, options);
+		}
+		return this;
+	},
+
+	stopLocate: function () {
+		if (navigator.geolocation) {
+			navigator.geolocation.clearWatch(this._locationWatchId);
+		}
+		if (this._locateOptions) {
+			this._locateOptions.setView = false;
+		}
+		return this;
+	},
+
+	_handleGeolocationError: function (error) {
+		var c = error.code,
+		    message = error.message ||
+		            (c === 1 ? 'permission denied' :
+		            (c === 2 ? 'position unavailable' : 'timeout'));
+
+		if (this._locateOptions.setView && !this._loaded) {
+			this.fitWorld();
+		}
+
+		this.fire('locationerror', {
+			code: c,
+			message: 'Geolocation error: ' + message + '.'
+		});
+	},
+
+	_handleGeolocationResponse: function (pos) {
+		var lat = pos.coords.latitude,
+		    lng = pos.coords.longitude,
+		    latlng = new L.LatLng(lat, lng),
+
+		    latAccuracy = 180 * pos.coords.accuracy / 40075017,
+		    lngAccuracy = latAccuracy / Math.cos(L.LatLng.DEG_TO_RAD * lat),
+
+		    bounds = L.latLngBounds(
+		            [lat - latAccuracy, lng - lngAccuracy],
+		            [lat + latAccuracy, lng + lngAccuracy]),
+
+		    options = this._locateOptions;
+
+		if (options.setView) {
+			var zoom = Math.min(this.getBoundsZoom(bounds), options.maxZoom);
+			this.setView(latlng, zoom);
+		}
+
+		var data = {
+			latlng: latlng,
+			bounds: bounds,
+			timestamp: pos.timestamp
+		};
+
+		for (var i in pos.coords) {
+			if (typeof pos.coords[i] === 'number') {
+				data[i] = pos.coords[i];
+			}
+		}
+
+		this.fire('locationfound', data);
+	}
+});
+
+
+}(window, document));
\ No newline at end of file
diff --git a/www/lib/ionic/js/angular/angular-leaflet-directive.js b/www/lib/ionic/js/angular/angular-leaflet-directive.js
deleted file mode 100644
index b4e92c96c64a30f3ebaa225c5eef6858cf537df4..0000000000000000000000000000000000000000
--- a/www/lib/ionic/js/angular/angular-leaflet-directive.js
+++ /dev/null
@@ -1,5734 +0,0 @@
-/**!
- * The MIT License
- *
- * Copyright (c) 2013 the angular-leaflet-directive Team, http://tombatossals.github.io/angular-leaflet-directive
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * angular-leaflet-directive
- * https://github.com/tombatossals/angular-leaflet-directive
- *
- * @authors https://github.com/tombatossals/angular-leaflet-directive/graphs/contributors
- */
-
-/*!
-*  angular-leaflet-directive  2015-11-06
-*  angular-leaflet-directive - An AngularJS directive to easily interact with Leaflet maps
-*  git: https://github.com/tombatossals/angular-leaflet-directive
-*/
-(function(angular){
-'use strict';
-angular.module('leaflet-directive', []).directive('leaflet', ["$q", "leafletData", "leafletMapDefaults", "leafletHelpers", "leafletMapEvents", function($q, leafletData, leafletMapDefaults, leafletHelpers, leafletMapEvents) {
-  return {
-    restrict: 'EA',
-    replace: true,
-    scope: {
-      center: '=',
-      lfCenter: '=',
-      defaults: '=',
-      maxbounds: '=',
-      bounds: '=',
-      markers: '=',
-      legend: '=',
-      geojson: '=',
-      paths: '=',
-      tiles: '=',
-      layers: '=',
-      controls: '=',
-      decorations: '=',
-      eventBroadcast: '=',
-      markersWatchOptions: '=',
-      geojsonWatchOptions: '=',
-    },
-    transclude: true,
-    template: '<div class="angular-leaflet-map"><div ng-transclude></div></div>',
-    controller: ["$scope", function($scope) {
-      this._leafletMap = $q.defer();
-      this.getMap = function() {
-        return this._leafletMap.promise;
-      };
-
-      this.getLeafletScope = function() {
-        return $scope;
-      };
-    }],
-
-    link: function(scope, element, attrs, ctrl) {
-      var isDefined = leafletHelpers.isDefined;
-      var defaults  = leafletMapDefaults.setDefaults(scope.defaults, attrs.id);
-      var mapEvents = leafletMapEvents.getAvailableMapEvents();
-      var addEvents = leafletMapEvents.addEvents;
-
-      scope.mapId =  attrs.id;
-      leafletData.setDirectiveControls({}, attrs.id);
-
-      // Set width and height utility functions
-      function updateWidth() {
-        if (isNaN(attrs.width)) {
-          element.css('width', attrs.width);
-        } else {
-          element.css('width', attrs.width + 'px');
-        }
-      }
-
-      function updateHeight() {
-        if (isNaN(attrs.height)) {
-          element.css('height', attrs.height);
-        } else {
-          element.css('height', attrs.height + 'px');
-        }
-      }
-
-      // If the width attribute defined update css
-      // Then watch if bound property changes and update css
-      if (isDefined(attrs.width)) {
-        updateWidth();
-
-        scope.$watch(
-          function() {
-            return element[0].getAttribute('width');
-          },
-
-          function() {
-            updateWidth();
-            map.invalidateSize();
-          });
-      }
-
-      // If the height attribute defined update css
-      // Then watch if bound property changes and update css
-      if (isDefined(attrs.height)) {
-        updateHeight();
-
-        scope.$watch(
-          function() {
-            return element[0].getAttribute('height');
-          },
-
-          function() {
-            updateHeight();
-            map.invalidateSize();
-          });
-      }
-
-      // Create the Leaflet Map Object with the options
-      var map = new L.Map(element[0], leafletMapDefaults.getMapCreationDefaults(attrs.id));
-      ctrl._leafletMap.resolve(map);
-
-      if (!isDefined(attrs.center) && !isDefined(attrs.lfCenter)) {
-        map.setView([defaults.center.lat, defaults.center.lng], defaults.center.zoom);
-      }
-
-      // If no layers nor tiles defined, set the default tileLayer
-      if (!isDefined(attrs.tiles) && (!isDefined(attrs.layers))) {
-        var tileLayerObj = L.tileLayer(defaults.tileLayer, defaults.tileLayerOptions);
-        tileLayerObj.addTo(map);
-        leafletData.setTiles(tileLayerObj, attrs.id);
-      }
-
-      // Set zoom control configuration
-      if (isDefined(map.zoomControl) &&
-          isDefined(defaults.zoomControlPosition)) {
-        map.zoomControl.setPosition(defaults.zoomControlPosition);
-      }
-
-      if (isDefined(map.zoomControl) && defaults.zoomControl === false) {
-        map.zoomControl.removeFrom(map);
-      }
-
-      if (isDefined(map.zoomsliderControl) &&
-          isDefined(defaults.zoomsliderControl) &&
-          defaults.zoomsliderControl === false) {
-        map.zoomsliderControl.removeFrom(map);
-      }
-
-      // if no event-broadcast attribute, all events are broadcasted
-      if (!isDefined(attrs.eventBroadcast)) {
-        var logic = 'broadcast';
-        addEvents(map, mapEvents, 'eventName', scope, logic);
-      }
-
-      // Resolve the map object to the promises
-      map.whenReady(function() {
-        leafletData.setMap(map, attrs.id);
-      });
-
-      scope.$on('$destroy', function() {
-        leafletMapDefaults.reset();
-        map.remove();
-        leafletData.unresolveMap(attrs.id);
-      });
-
-      //Handle request to invalidate the map size
-      //Up scope using $scope.$emit('invalidateSize')
-      //Down scope using $scope.$broadcast('invalidateSize')
-      scope.$on('invalidateSize', function() {
-        map.invalidateSize();
-      });
-    },
-  };
-}]);
-
-angular.module('leaflet-directive').factory('leafletBoundsHelpers', ["$log", "leafletHelpers", function($log, leafletHelpers) {
-
-  var isArray = leafletHelpers.isArray;
-  var isNumber = leafletHelpers.isNumber;
-  var isFunction = leafletHelpers.isFunction;
-  var isDefined = leafletHelpers.isDefined;
-
-  function _isValidBounds(bounds) {
-    return angular.isDefined(bounds) && angular.isDefined(bounds.southWest) &&
-           angular.isDefined(bounds.northEast) && angular.isNumber(bounds.southWest.lat) &&
-           angular.isNumber(bounds.southWest.lng) && angular.isNumber(bounds.northEast.lat) &&
-           angular.isNumber(bounds.northEast.lng);
-  }
-
-  return {
-    createLeafletBounds: function(bounds) {
-      if (_isValidBounds(bounds)) {
-        return L.latLngBounds([bounds.southWest.lat, bounds.southWest.lng],
-                              [bounds.northEast.lat, bounds.northEast.lng]);
-      }
-    },
-
-    isValidBounds: _isValidBounds,
-
-    createBoundsFromArray: function(boundsArray) {
-      if (!(isArray(boundsArray) && boundsArray.length === 2 &&
-            isArray(boundsArray[0]) && isArray(boundsArray[1]) &&
-            boundsArray[0].length === 2 && boundsArray[1].length === 2 &&
-            isNumber(boundsArray[0][0]) && isNumber(boundsArray[0][1]) &&
-            isNumber(boundsArray[1][0]) && isNumber(boundsArray[1][1]))) {
-        $log.error('[AngularJS - Leaflet] The bounds array is not valid.');
-        return;
-      }
-
-      return {
-        northEast: {
-          lat: boundsArray[0][0],
-          lng: boundsArray[0][1],
-        },
-        southWest: {
-          lat: boundsArray[1][0],
-          lng: boundsArray[1][1],
-        },
-      };
-    },
-
-    createBoundsFromLeaflet: function(lfBounds) {
-      if (!(isDefined(lfBounds) && isFunction(lfBounds.getNorthEast) && isFunction(lfBounds.getSouthWest))) {
-        $log.error('[AngularJS - Leaflet] The leaflet bounds is not valid object.');
-        return;
-      }
-
-      var northEast = lfBounds.getNorthEast();
-      var southWest = lfBounds.getSouthWest();
-
-      return {
-        northEast: {
-          lat: northEast.lat,
-          lng: northEast.lng,
-        },
-        southWest: {
-          lat: southWest.lat,
-          lng: southWest.lng,
-        },
-      };
-    },
-  };
-}]);
-
-angular.module('leaflet-directive').factory('leafletControlHelpers', ["$rootScope", "$log", "leafletHelpers", "leafletLayerHelpers", "leafletMapDefaults", function($rootScope, $log, leafletHelpers, leafletLayerHelpers, leafletMapDefaults) {
-  var isDefined = leafletHelpers.isDefined;
-  var isObject = leafletHelpers.isObject;
-  var createLayer = leafletLayerHelpers.createLayer;
-  var _controls = {};
-  var errorHeader = leafletHelpers.errorHeader + ' [Controls] ';
-
-  var _controlLayersMustBeVisible = function(baselayers, overlays, mapId) {
-    var defaults = leafletMapDefaults.getDefaults(mapId);
-    if (!defaults.controls.layers.visible) {
-      return false;
-    }
-
-    var atLeastOneControlItemMustBeShown = false;
-
-    if (isObject(baselayers)) {
-      Object.keys(baselayers).forEach(function(key) {
-        var layer = baselayers[key];
-        if (!isDefined(layer.layerOptions) || layer.layerOptions.showOnSelector !== false) {
-          atLeastOneControlItemMustBeShown = true;
-        }
-      });
-    }
-
-    if (isObject(overlays)) {
-      Object.keys(overlays).forEach(function(key) {
-        var layer = overlays[key];
-        if (!isDefined(layer.layerParams) || layer.layerParams.showOnSelector !== false) {
-          atLeastOneControlItemMustBeShown = true;
-        }
-      });
-    }
-
-    return atLeastOneControlItemMustBeShown;
-  };
-
-  var _createLayersControl = function(mapId) {
-    var defaults = leafletMapDefaults.getDefaults(mapId);
-    var controlOptions = {
-      collapsed: defaults.controls.layers.collapsed,
-      position: defaults.controls.layers.position,
-      autoZIndex: false,
-    };
-
-    angular.extend(controlOptions, defaults.controls.layers.options);
-
-    var control;
-    if (defaults.controls.layers && isDefined(defaults.controls.layers.control)) {
-      control = defaults.controls.layers.control.apply(this, [[], [], controlOptions]);
-    } else {
-      control = new L.control.layers([], [], controlOptions);
-    }
-
-    return control;
-  };
-
-  var controlTypes = {
-    draw: {
-      isPluginLoaded: function() {
-        if (!angular.isDefined(L.Control.Draw)) {
-          $log.error(errorHeader + ' Draw plugin is not loaded.');
-          return false;
-        }
-
-        return true;
-      },
-
-      checkValidParams: function(/* params */) {
-        return true;
-      },
-
-      createControl: function(params) {
-        return new L.Control.Draw(params);
-      },
-    },
-    scale: {
-      isPluginLoaded: function() {
-        return true;
-      },
-
-      checkValidParams: function(/* params */) {
-        return true;
-      },
-
-      createControl: function(params) {
-        return new L.control.scale(params);
-      },
-    },
-    fullscreen: {
-      isPluginLoaded: function() {
-        if (!angular.isDefined(L.Control.Fullscreen)) {
-          $log.error(errorHeader + ' Fullscreen plugin is not loaded.');
-          return false;
-        }
-
-        return true;
-      },
-
-      checkValidParams: function(/* params */) {
-        return true;
-      },
-
-      createControl: function(params) {
-        return new L.Control.Fullscreen(params);
-      },
-    },
-    search: {
-      isPluginLoaded: function() {
-        if (!angular.isDefined(L.Control.Search)) {
-          $log.error(errorHeader + ' Search plugin is not loaded.');
-          return false;
-        }
-
-        return true;
-      },
-
-      checkValidParams: function(/* params */) {
-        return true;
-      },
-
-      createControl: function(params) {
-        return new L.Control.Search(params);
-      },
-    },
-    custom: {},
-    minimap: {
-      isPluginLoaded: function() {
-        if (!angular.isDefined(L.Control.MiniMap)) {
-          $log.error(errorHeader + ' Minimap plugin is not loaded.');
-          return false;
-        }
-
-        return true;
-      },
-
-      checkValidParams: function(params) {
-        if (!isDefined(params.layer)) {
-          $log.warn(errorHeader + ' minimap "layer" option should be defined.');
-          return false;
-        }
-
-        return true;
-      },
-
-      createControl: function(params) {
-        var layer = createLayer(params.layer);
-
-        if (!isDefined(layer)) {
-          $log.warn(errorHeader + ' minimap control "layer" could not be created.');
-          return;
-        }
-
-        return new L.Control.MiniMap(layer, params);
-      },
-    },
-  };
-
-  return {
-    layersControlMustBeVisible: _controlLayersMustBeVisible,
-
-    isValidControlType: function(type) {
-      return Object.keys(controlTypes).indexOf(type) !== -1;
-    },
-
-    createControl: function(type, params) {
-      if (!controlTypes[type].checkValidParams(params)) {
-        return;
-      }
-
-      return controlTypes[type].createControl(params);
-    },
-
-    updateLayersControl: function(map, mapId, loaded, baselayers, overlays, leafletLayers) {
-      var i;
-      var _layersControl = _controls[mapId];
-      var mustBeLoaded = _controlLayersMustBeVisible(baselayers, overlays, mapId);
-
-      if (isDefined(_layersControl) && loaded) {
-        for (i in leafletLayers.baselayers) {
-          _layersControl.removeLayer(leafletLayers.baselayers[i]);
-        }
-
-        for (i in leafletLayers.overlays) {
-          _layersControl.removeLayer(leafletLayers.overlays[i]);
-        }
-
-        map.removeControl(_layersControl);
-        delete _controls[mapId];
-      }
-
-      if (mustBeLoaded) {
-        _layersControl = _createLayersControl(mapId);
-        _controls[mapId] = _layersControl;
-        for (i in baselayers) {
-          var hideOnSelector = isDefined(baselayers[i].layerOptions) &&
-                               baselayers[i].layerOptions.showOnSelector === false;
-          if (!hideOnSelector && isDefined(leafletLayers.baselayers[i])) {
-            _layersControl.addBaseLayer(leafletLayers.baselayers[i], baselayers[i].name);
-          }
-        }
-
-        for (i in overlays) {
-          var hideOverlayOnSelector = isDefined(overlays[i].layerParams) &&
-                      overlays[i].layerParams.showOnSelector === false;
-          if (!hideOverlayOnSelector && isDefined(leafletLayers.overlays[i])) {
-            _layersControl.addOverlay(leafletLayers.overlays[i], overlays[i].name);
-          }
-        }
-
-        map.addControl(_layersControl);
-      }
-
-      return mustBeLoaded;
-    },
-  };
-}]);
-
-angular.module('leaflet-directive').service('leafletData', ["$log", "$q", "leafletHelpers", function($log, $q, leafletHelpers) {
-  var getDefer = leafletHelpers.getDefer,
-      getUnresolvedDefer = leafletHelpers.getUnresolvedDefer,
-      setResolvedDefer = leafletHelpers.setResolvedDefer;
-
-  var _private = {};
-  var self = this;
-
-  var upperFirst = function(string) {
-    return string.charAt(0).toUpperCase() + string.slice(1);
-  };
-
-  var _privateItems = [
-      'map',
-      'tiles',
-      'layers',
-      'paths',
-      'markers',
-      'geoJSON',
-      'UTFGrid', //odd ball on naming convention keeping to not break
-      'decorations',
-      'directiveControls',];
-
-  //init
-  _privateItems.forEach(function(itemName) {
-    _private[itemName] = {};
-  });
-
-  this.unresolveMap = function(scopeId) {
-    var id = leafletHelpers.obtainEffectiveMapId(_private.map, scopeId);
-    _privateItems.forEach(function(itemName) {
-      _private[itemName][id] = undefined;
-    });
-  };
-
-  //int repetitive stuff (get and sets)
-  _privateItems.forEach(function(itemName) {
-    var name = upperFirst(itemName);
-    self['set' + name] = function(lObject, scopeId) {
-      var defer = getUnresolvedDefer(_private[itemName], scopeId);
-      defer.resolve(lObject);
-      setResolvedDefer(_private[itemName], scopeId);
-    };
-
-    self['get' + name] = function(scopeId) {
-      var defer = getDefer(_private[itemName], scopeId);
-      return defer.promise;
-    };
-  });
-}]);
-
-angular.module('leaflet-directive')
-.service('leafletDirectiveControlsHelpers', ["$log", "leafletData", "leafletHelpers", function($log, leafletData, leafletHelpers) {
-  var _isDefined = leafletHelpers.isDefined;
-  var _isString = leafletHelpers.isString;
-  var _isObject = leafletHelpers.isObject;
-  var _mainErrorHeader = leafletHelpers.errorHeader;
-
-  var _errorHeader = _mainErrorHeader + '[leafletDirectiveControlsHelpers';
-
-  var _extend = function(id, thingToAddName, createFn, cleanFn) {
-    var _fnHeader = _errorHeader + '.extend] ';
-    var extender = {};
-    if (!_isDefined(thingToAddName)) {
-      $log.error(_fnHeader + 'thingToAddName cannot be undefined');
-      return;
-    }
-
-    if (_isString(thingToAddName) && _isDefined(createFn) && _isDefined(cleanFn)) {
-      extender[thingToAddName] = {
-        create: createFn,
-        clean: cleanFn,
-      };
-    }    else if (_isObject(thingToAddName) && !_isDefined(createFn) && !_isDefined(cleanFn)) {
-      extender = thingToAddName;
-    }    else {
-      $log.error(_fnHeader + 'incorrect arguments');
-      return;
-    }
-
-    //add external control to create / destroy markers without a watch
-    leafletData.getDirectiveControls().then(function(controls) {
-      angular.extend(controls, extender);
-      leafletData.setDirectiveControls(controls, id);
-    });
-  };
-
-  return {
-    extend: _extend,
-  };
-}]);
-
-angular.module('leaflet-directive')
-.service('leafletGeoJsonHelpers', ["leafletHelpers", "leafletIterators", function(leafletHelpers, leafletIterators) {
-  var lHlp = leafletHelpers;
-  var lIt = leafletIterators;
-  var Point = function(lat, lng) {
-    this.lat = lat;
-    this.lng = lng;
-    return this;
-  };
-
-  var _getLat = function(value) {
-    if (Array.isArray(value) && value.length === 2) {
-      return value[1];
-    } else if (lHlp.isDefined(value.type) && value.type === 'Point') {
-      return +value.coordinates[1];
-    } else {
-      return +value.lat;
-    }
-  };
-
-  var _getLng = function(value) {
-    if (Array.isArray(value) && value.length === 2) {
-      return value[0];
-    } else if (lHlp.isDefined(value.type) && value.type === 'Point') {
-      return +value.coordinates[0];
-    } else {
-      return +value.lng;
-    }
-  };
-
-  var _validateCoords = function(coords) {
-    if (lHlp.isUndefined(coords)) {
-      return false;
-    }
-
-    if (lHlp.isArray(coords)) {
-      if (coords.length === 2 && lHlp.isNumber(coords[0]) && lHlp.isNumber(coords[1])) {
-        return true;
-      }
-    } else if (lHlp.isDefined(coords.type)) {
-      if (
-          coords.type === 'Point' && lHlp.isArray(coords.coordinates) &&
-          coords.coordinates.length === 2  &&
-          lHlp.isNumber(coords.coordinates[0]) &&
-          lHlp.isNumber(coords.coordinates[1])) {
-        return true;
-      }
-    }
-
-    var ret = lIt.all(['lat', 'lng'], function(pos) {
-      return lHlp.isDefined(coords[pos]) && lHlp.isNumber(coords[pos]);
-    });
-
-    return ret;
-  };
-
-  var _getCoords = function(value) {
-    if (!value || !_validateCoords(value)) {
-      return;
-    }
-
-    var p =  null;
-    if (Array.isArray(value) && value.length === 2) {
-      p = new Point(value[1], value[0]);
-    } else if (lHlp.isDefined(value.type) && value.type === 'Point') {
-      p = new Point(value.coordinates[1], value.coordinates[0]);
-    } else {
-      return value;
-    }
-
-    //note angular.merge is avail in angular 1.4.X we might want to fill it here
-    return angular.extend(value, p);//tap on lat, lng if it doesnt exist
-  };
-
-  return {
-    getLat: _getLat,
-    getLng: _getLng,
-    validateCoords: _validateCoords,
-    getCoords: _getCoords,
-  };
-}]);
-
-angular.module('leaflet-directive').service('leafletHelpers', ["$q", "$log", function($q, $log) {
-  var _errorHeader = '[AngularJS - Leaflet] ';
-  var _copy = angular.copy;
-  var _clone = _copy;
-  /*
-  For parsing paths to a field in an object
-
-  Example:
-  var obj = {
-      bike:{
-       1: 'hi'
-       2: 'foo'
-      }
-  };
-  _getObjectValue(obj,"bike.1") returns 'hi'
-  this is getPath in ui-gmap
-   */
-  var _getObjectValue = function(object, pathStr) {
-    var obj;
-    if (!object || !angular.isObject(object))
-        return;
-
-    //if the key is not a sting then we already have the value
-    if ((pathStr === null) || !angular.isString(pathStr)) {
-      return pathStr;
-    }
-
-    obj = object;
-    pathStr.split('.').forEach(function(value) {
-      if (obj) {
-        obj = obj[value];
-      }
-    });
-
-    return obj;
-  };
-
-  /*
-   Object Array Notation
-   _getObjectArrayPath("bike.one.two")
-   returns:
-   'bike["one"]["two"]'
-   */
-  var _getObjectArrayPath = function(pathStr) {
-    return pathStr.split('.').reduce(function(previous, current) {
-      return previous + '["' + current + '"]';
-    });
-  };
-
-  /* Object Dot Notation
-   _getObjectPath(["bike","one","two"])
-   returns:
-   "bike.one.two"
-   */
-  var _getObjectDotPath = function(arrayOfStrings) {
-    return arrayOfStrings.reduce(function(previous, current) {
-      return previous + '.' + current;
-    });
-  };
-
-  function _obtainEffectiveMapId(d, mapId) {
-    var id;
-    var i;
-    if (!angular.isDefined(mapId)) {
-      if (Object.keys(d).length === 0) {
-        id = 'main';
-      } else if (Object.keys(d).length >= 1) {
-        for (i in d) {
-          if (d.hasOwnProperty(i)) {
-            id = i;
-          }
-        }
-      } else {
-        $log.error(_errorHeader + '- You have more than 1 map on the DOM, you must provide the map ID to the leafletData.getXXX call');
-      }
-    } else {
-      id = mapId;
-    }
-
-    return id;
-  }
-
-  function _getUnresolvedDefer(d, mapId) {
-    var id = _obtainEffectiveMapId(d, mapId);
-    var defer;
-
-    if (!angular.isDefined(d[id]) || d[id].resolvedDefer === true) {
-      defer = $q.defer();
-      d[id] = {
-        defer: defer,
-        resolvedDefer: false,
-      };
-    } else {
-      defer = d[id].defer;
-    }
-
-    return defer;
-  }
-
-  var _isDefined = function(value) {
-    return angular.isDefined(value) && value !== null;
-  };
-
-  var _isUndefined = function(value) {
-    return !_isDefined(value);
-  };
-
-  // BEGIN DIRECT PORT FROM AngularJS code base
-
-  var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
-
-  var MOZ_HACK_REGEXP = /^moz([A-Z])/;
-
-  var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
-
-  /**
-  Converts snake_case to camelCase.
-  Also there is special case for Moz prefix starting with upper case letter.
-  @param name Name to normalize
-   */
-
-  var camelCase = function(name) {
-      return name.replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
-        if (offset) {
-          return letter.toUpperCase();
-        } else {
-          return letter;
-        }
-      }).replace(MOZ_HACK_REGEXP, 'Moz$1');
-    };
-
-  /**
-  Converts all accepted directives format into proper directive name.
-  @param name Name to normalize
-   */
-
-  var directiveNormalize = function(name) {
-      return camelCase(name.replace(PREFIX_REGEXP, ''));
-    };
-
-  // END AngularJS port
-
-  return {
-    camelCase: camelCase,
-    directiveNormalize: directiveNormalize,
-    copy:_copy,
-    clone:_clone,
-    errorHeader: _errorHeader,
-    getObjectValue: _getObjectValue,
-    getObjectArrayPath:_getObjectArrayPath,
-    getObjectDotPath: _getObjectDotPath,
-    defaultTo: function(val, _default) {
-      return _isDefined(val) ? val : _default;
-    },
-
-    //mainly for checking attributes of directives lets keep this minimal (on what we accept)
-    isTruthy: function(val) {
-      return val === 'true' || val === true;
-    },
-
-    //Determine if a reference is {}
-    isEmpty: function(value) {
-      return Object.keys(value).length === 0;
-    },
-
-    //Determine if a reference is undefined or {}
-    isUndefinedOrEmpty: function(value) {
-      return (angular.isUndefined(value) || value === null) || Object.keys(value).length === 0;
-    },
-
-    // Determine if a reference is defined
-    isDefined: _isDefined,
-    isUndefined:_isUndefined,
-    isNumber: angular.isNumber,
-    isString: angular.isString,
-    isArray: angular.isArray,
-    isObject: angular.isObject,
-    isFunction: angular.isFunction,
-    equals: angular.equals,
-
-    isValidCenter: function(center) {
-      return angular.isDefined(center) && angular.isNumber(center.lat) &&
-             angular.isNumber(center.lng) && angular.isNumber(center.zoom);
-    },
-
-    isValidPoint: function(point) {
-      if (!angular.isDefined(point)) {
-        return false;
-      }
-
-      if (angular.isArray(point)) {
-        return point.length === 2 && angular.isNumber(point[0]) && angular.isNumber(point[1]);
-      }
-
-      return angular.isNumber(point.lat) && angular.isNumber(point.lng);
-    },
-
-    isSameCenterOnMap: function(centerModel, map) {
-      var mapCenter = map.getCenter();
-      var zoom = map.getZoom();
-      if (centerModel.lat && centerModel.lng &&
-          mapCenter.lat.toFixed(4) === centerModel.lat.toFixed(4) &&
-          mapCenter.lng.toFixed(4) === centerModel.lng.toFixed(4) &&
-          zoom === centerModel.zoom) {
-        return true;
-      }
-
-      return false;
-    },
-
-    safeApply: function($scope, fn) {
-      var phase = $scope.$root.$$phase;
-      if (phase === '$apply' || phase === '$digest') {
-        $scope.$eval(fn);
-      } else {
-        $scope.$evalAsync(fn);
-      }
-    },
-
-    obtainEffectiveMapId: _obtainEffectiveMapId,
-
-    getDefer: function(d, mapId) {
-      var id = _obtainEffectiveMapId(d, mapId);
-      var defer;
-      if (!angular.isDefined(d[id]) || d[id].resolvedDefer === false) {
-        defer = _getUnresolvedDefer(d, mapId);
-      } else {
-        defer = d[id].defer;
-      }
-
-      return defer;
-    },
-
-    getUnresolvedDefer: _getUnresolvedDefer,
-
-    setResolvedDefer: function(d, mapId) {
-      var id = _obtainEffectiveMapId(d, mapId);
-      d[id].resolvedDefer = true;
-    },
-
-    rangeIsSupported: function() {
-      var testrange = document.createElement('input');
-      testrange.setAttribute('type', 'range');
-      return testrange.type === 'range';
-    },
-
-    FullScreenControlPlugin: {
-      isLoaded: function() {
-        return angular.isDefined(L.Control.Fullscreen);
-      },
-    },
-
-    MiniMapControlPlugin: {
-      isLoaded: function() {
-        return angular.isDefined(L.Control.MiniMap);
-      },
-    },
-
-    AwesomeMarkersPlugin: {
-      isLoaded: function() {
-        return angular.isDefined(L.AwesomeMarkers) && angular.isDefined(L.AwesomeMarkers.Icon);
-      },
-
-      is: function(icon) {
-        if (this.isLoaded()) {
-          return icon instanceof L.AwesomeMarkers.Icon;
-        } else {
-          return false;
-        }
-      },
-
-      equal: function(iconA, iconB) {
-        if (!this.isLoaded()) {
-          return false;
-        }
-
-        if (this.is(iconA)) {
-          return angular.equals(iconA, iconB);
-        } else {
-          return false;
-        }
-      },
-    },
-
-    VectorMarkersPlugin: {
-      isLoaded: function() {
-        return angular.isDefined(L.VectorMarkers) && angular.isDefined(L.VectorMarkers.Icon);
-      },
-
-      is: function(icon) {
-        if (this.isLoaded()) {
-          return icon instanceof L.VectorMarkers.Icon;
-        } else {
-          return false;
-        }
-      },
-
-      equal: function(iconA, iconB) {
-        if (!this.isLoaded()) {
-          return false;
-        }
-
-        if (this.is(iconA)) {
-          return angular.equals(iconA, iconB);
-        } else {
-          return false;
-        }
-      },
-    },
-
-    DomMarkersPlugin: {
-      isLoaded: function() {
-        if (angular.isDefined(L.DomMarkers) && angular.isDefined(L.DomMarkers.Icon)) {
-          return true;
-        } else {
-          return false;
-        }
-      },
-
-      is: function(icon) {
-        if (this.isLoaded()) {
-          return icon instanceof L.DomMarkers.Icon;
-        } else {
-          return false;
-        }
-      },
-
-      equal: function(iconA, iconB) {
-        if (!this.isLoaded()) {
-          return false;
-        }
-
-        if (this.is(iconA)) {
-          return angular.equals(iconA, iconB);
-        } else {
-          return false;
-        }
-      },
-    },
-
-    PolylineDecoratorPlugin: {
-      isLoaded: function() {
-        if (angular.isDefined(L.PolylineDecorator)) {
-          return true;
-        } else {
-          return false;
-        }
-      },
-
-      is: function(decoration) {
-        if (this.isLoaded()) {
-          return decoration instanceof L.PolylineDecorator;
-        } else {
-          return false;
-        }
-      },
-
-      equal: function(decorationA, decorationB) {
-        if (!this.isLoaded()) {
-          return false;
-        }
-
-        if (this.is(decorationA)) {
-          return angular.equals(decorationA, decorationB);
-        } else {
-          return false;
-        }
-      },
-    },
-
-    MakiMarkersPlugin: {
-      isLoaded: function() {
-        if (angular.isDefined(L.MakiMarkers) && angular.isDefined(L.MakiMarkers.Icon)) {
-          return true;
-        } else {
-          return false;
-        }
-      },
-
-      is: function(icon) {
-        if (this.isLoaded()) {
-          return icon instanceof L.MakiMarkers.Icon;
-        } else {
-          return false;
-        }
-      },
-
-      equal: function(iconA, iconB) {
-        if (!this.isLoaded()) {
-          return false;
-        }
-
-        if (this.is(iconA)) {
-          return angular.equals(iconA, iconB);
-        } else {
-          return false;
-        }
-      },
-    },
-    ExtraMarkersPlugin: {
-      isLoaded: function() {
-        if (angular.isDefined(L.ExtraMarkers) && angular.isDefined(L.ExtraMarkers.Icon)) {
-          return true;
-        } else {
-          return false;
-        }
-      },
-
-      is: function(icon) {
-        if (this.isLoaded()) {
-          return icon instanceof L.ExtraMarkers.Icon;
-        } else {
-          return false;
-        }
-      },
-
-      equal: function(iconA, iconB) {
-        if (!this.isLoaded()) {
-          return false;
-        }
-
-        if (this.is(iconA)) {
-          return angular.equals(iconA, iconB);
-        } else {
-          return false;
-        }
-      },
-    },
-    LabelPlugin: {
-      isLoaded: function() {
-        return angular.isDefined(L.Label);
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof L.MarkerClusterGroup;
-        } else {
-          return false;
-        }
-      },
-    },
-    MarkerClusterPlugin: {
-      isLoaded: function() {
-        return angular.isDefined(L.MarkerClusterGroup);
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof L.MarkerClusterGroup;
-        } else {
-          return false;
-        }
-      },
-    },
-    GoogleLayerPlugin: {
-      isLoaded: function() {
-        return angular.isDefined(L.Google);
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof L.Google;
-        } else {
-          return false;
-        }
-      },
-    },
-    LeafletProviderPlugin: {
-      isLoaded: function() {
-        return angular.isDefined(L.TileLayer.Provider);
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof L.TileLayer.Provider;
-        } else {
-          return false;
-        }
-      },
-    },
-    ChinaLayerPlugin: {
-      isLoaded: function() {
-        return angular.isDefined(L.tileLayer.chinaProvider);
-      },
-    },
-    HeatLayerPlugin: {
-      isLoaded: function() {
-        return angular.isDefined(L.heatLayer);
-      },
-    },
-    WebGLHeatMapLayerPlugin: {
-      isLoaded: function() {
-        return angular.isDefined(L.TileLayer.WebGLHeatMap);
-      },
-    },
-    BingLayerPlugin: {
-      isLoaded: function() {
-        return angular.isDefined(L.BingLayer);
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof L.BingLayer;
-        } else {
-          return false;
-        }
-      },
-    },
-    WFSLayerPlugin: {
-      isLoaded: function() {
-        return L.GeoJSON.WFS !== undefined;
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof L.GeoJSON.WFS;
-        } else {
-          return false;
-        }
-      },
-    },
-    AGSBaseLayerPlugin: {
-      isLoaded: function() {
-        return L.esri !== undefined && L.esri.basemapLayer !== undefined;
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof L.esri.basemapLayer;
-        } else {
-          return false;
-        }
-      },
-    },
-    AGSLayerPlugin: {
-      isLoaded: function() {
-        return lvector !== undefined && lvector.AGS !== undefined;
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof lvector.AGS;
-        } else {
-          return false;
-        }
-      },
-    },
-    AGSFeatureLayerPlugin: {
-      isLoaded: function() {
-        return L.esri !== undefined && L.esri.featureLayer !== undefined;
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof L.esri.featureLayer;
-        } else {
-          return false;
-        }
-      },
-    },
-    AGSTiledMapLayerPlugin: {
-      isLoaded: function() {
-        return L.esri !== undefined && L.esri.tiledMapLayer !== undefined;
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof L.esri.tiledMapLayer;
-        } else {
-          return false;
-        }
-      },
-    },
-    AGSDynamicMapLayerPlugin: {
-      isLoaded: function() {
-        return L.esri !== undefined && L.esri.dynamicMapLayer !== undefined;
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof L.esri.dynamicMapLayer;
-        } else {
-          return false;
-        }
-      },
-    },
-    AGSImageMapLayerPlugin: {
-      isLoaded: function() {
-        return L.esri !== undefined && L.esri.imageMapLayer !== undefined;
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof L.esri.imageMapLayer;
-        } else {
-          return false;
-        }
-      },
-    },
-    AGSClusteredLayerPlugin: {
-      isLoaded: function() {
-        return L.esri !== undefined && L.esri.clusteredFeatureLayer !== undefined;
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof L.esri.clusteredFeatureLayer;
-        } else {
-          return false;
-        }
-      },
-    },
-    AGSHeatmapLayerPlugin: {
-      isLoaded: function() {
-        return L.esri !== undefined && L.esri.heatmapFeatureLayer !== undefined;
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof L.esri.heatmapFeatureLayer;
-        } else {
-          return false;
-        }
-      },
-    },
-    YandexLayerPlugin: {
-      isLoaded: function() {
-        return angular.isDefined(L.Yandex);
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof L.Yandex;
-        } else {
-          return false;
-        }
-      },
-    },
-    GeoJSONPlugin: {
-      isLoaded: function() {
-        return angular.isDefined(L.TileLayer.GeoJSON);
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof L.TileLayer.GeoJSON;
-        } else {
-          return false;
-        }
-      },
-    },
-    UTFGridPlugin: {
-      isLoaded: function() {
-        return angular.isDefined(L.UtfGrid);
-      },
-
-      is: function(layer) {
-        if (this.isLoaded()) {
-          return layer instanceof L.UtfGrid;
-        } else {
-          $log.error('[AngularJS - Leaflet] No UtfGrid plugin found.');
-          return false;
-        }
-      },
-    },
-    CartoDB: {
-      isLoaded: function() {
-        return cartodb;
-      },
-
-      is: function(/*layer*/) {
-        return true;
-        /*
-        if (this.isLoaded()) {
-            return layer instanceof L.TileLayer.GeoJSON;
-        } else {
-            return false;
-        }*/
-      },
-    },
-    Leaflet: {
-      DivIcon: {
-        is: function(icon) {
-          return icon instanceof L.DivIcon;
-        },
-
-        equal: function(iconA, iconB) {
-          if (this.is(iconA)) {
-            return angular.equals(iconA, iconB);
-          } else {
-            return false;
-          }
-        },
-      },
-      Icon: {
-        is: function(icon) {
-          return icon instanceof L.Icon;
-        },
-
-        equal: function(iconA, iconB) {
-          if (this.is(iconA)) {
-            return angular.equals(iconA, iconB);
-          } else {
-            return false;
-          }
-        },
-      },
-    },
-    /*
-     watchOptions - object to set deep nested watches and turn off watches all together
-     (rely on control / functional updates)
-     watchOptions - Object
-         doWatch:boolean
-         isDeep:boolean (sets $watch(function,isDeep))
-         individual
-             doWatch:boolean
-             isDeep:boolean
-     */
-
-    //legacy defaults
-    watchOptions: {
-      doWatch:true,
-      isDeep: true,
-      individual:{
-        doWatch:true,
-        isDeep: true,
-      },
-    },
-  };
-}]);
-
-angular.module('leaflet-directive').service('leafletIterators', ["$log", "leafletHelpers", function($log, leafletHelpers) {
-
-  var lHlp = leafletHelpers;
-  var errorHeader = leafletHelpers.errorHeader + 'leafletIterators: ';
-
-  //BEGIN COPY from underscore
-  var _keys = Object.keys;
-  var _isFunction = lHlp.isFunction;
-  var _isObject = lHlp.isObject;
-
-  // Helper for collection methods to determine whether a collection
-  // should be iterated as an array or as an object
-  // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
-  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
-
-  var _isArrayLike = function(collection) {
-    var length = collection !== null && collection.length;
-    return lHlp.isNumber(length) && length >= 0 && length <= MAX_ARRAY_INDEX;
-  };
-
-  // Keep the identity function around for default iteratees.
-  var _identity = function(value) {
-    return value;
-  };
-
-  var _property = function(key) {
-    return function(obj) {
-      return obj === null ? void 0 : obj[key];
-    };
-  };
-
-  // Internal function that returns an efficient (for current engines) version
-  // of the passed-in callback, to be repeatedly applied in other Underscore
-  // functions.
-  var optimizeCb = function(func, context, argCount) {
-    if (context === void 0) return func;
-    switch (argCount === null ? 3 : argCount) {
-      case 1: return function(value) {
-        return func.call(context, value);
-      };
-
-      case 2: return function(value, other) {
-        return func.call(context, value, other);
-      };
-
-      case 3: return function(value, index, collection) {
-        return func.call(context, value, index, collection);
-      };
-
-      case 4: return function(accumulator, value, index, collection) {
-        return func.call(context, accumulator, value, index, collection);
-      };
-    }
-    return function() {
-      return func.apply(context, arguments);
-    };
-  };
-
-  // An internal function for creating assigner functions.
-  var createAssigner = function(keysFunc, undefinedOnly) {
-    return function(obj) {
-      var length = arguments.length;
-      if (length < 2 || obj === null) return obj;
-      for (var index = 1; index < length; index++) {
-        var source = arguments[index];
-        var keys = keysFunc(source);
-        var l = keys.length;
-
-        for (var i = 0; i < l; i++) {
-          var key = keys[i];
-          if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
-        }
-      }
-
-      return obj;
-    };
-  };
-
-  // Assigns a given object with all the own properties in the passed-in object(s)
-  // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
-  var _extendOwn;
-  var _assign = null;
-  _extendOwn = _assign = createAssigner(_keys);
-
-  // Returns whether an object has a given set of `key:value` pairs.
-  var _isMatch = function(object, attrs) {
-    var keys = _keys(attrs);
-    var length = keys.length;
-    if (object === null) return !length;
-    var obj = Object(object);
-    for (var i = 0; i < length; i++) {
-      var key = keys[i];
-      if (attrs[key] !== obj[key] || !(key in obj)) return false;
-    }
-
-    return true;
-  };
-
-  // Returns a predicate for checking whether an object has a given set of
-  // `key:value` pairs.
-  var _matcher;
-  var _matches = null;
-  _matcher = _matches = function(attrs) {
-    attrs = _extendOwn({}, attrs);
-    return function(obj) {
-      return _isMatch(obj, attrs);
-    };
-  };
-
-  // A mostly-internal function to generate callbacks that can be applied
-  // to each element in a collection, returning the desired result — either
-  // identity, an arbitrary callback, a property matcher, or a property accessor.
-  var cb = function(value, context, argCount) {
-    if (value === null) return _identity;
-    if (_isFunction(value)) return optimizeCb(value, context, argCount);
-    if (_isObject(value)) return _matcher(value);
-    return _property(value);
-  };
-
-  var _every;
-  var _all = null;
-  _every = _all = function(obj, predicate, context) {
-    predicate = cb(predicate, context);
-    var keys = !_isArrayLike(obj) && _keys(obj);
-    var length = (keys || obj).length;
-    for (var index = 0; index < length; index++) {
-      var currentKey = keys ? keys[index] : index;
-      if (!predicate(obj[currentKey], currentKey, obj)) return false;
-    }
-
-    return true;
-  };
-
-  //END COPY fron underscore
-
-  var _hasErrors = function(collection, cb, ignoreCollection, cbName) {
-    if (!ignoreCollection) {
-      if (!lHlp.isDefined(collection) || !lHlp.isDefined(cb)) {
-        return true;
-      }
-    }
-
-    if (!lHlp.isFunction(cb)) {
-      cbName = lHlp.defaultTo(cb, 'cb');
-      $log.error(errorHeader + cbName + ' is not a function');
-      return true;
-    }
-
-    return false;
-  };
-
-  var _iterate = function(collection, externalCb, internalCb) {
-    if (_hasErrors(undefined, internalCb, true, 'internalCb')) {
-      return;
-    }
-
-    if (!_hasErrors(collection, externalCb)) {
-      for (var key in collection) {
-        if (collection.hasOwnProperty(key)) {
-          internalCb(collection[key], key);
-        }
-      }
-    }
-  };
-
-  //see http://jsperf.com/iterators/3
-  //utilizing for in is way faster
-  var _each = function(collection, cb) {
-    _iterate(collection, cb, function(val, key) {
-      cb(val, key);
-    });
-  };
-
-  return {
-    each:_each,
-    forEach: _each,
-    every: _every,
-    all: _all,
-  };
-}]);
-
-angular.module('leaflet-directive')
-.factory('leafletLayerHelpers', ["$rootScope", "$log", "$q", "leafletHelpers", "leafletIterators", function($rootScope, $log, $q, leafletHelpers, leafletIterators) {
-  var Helpers = leafletHelpers;
-  var isString = leafletHelpers.isString;
-  var isObject = leafletHelpers.isObject;
-  var isArray = leafletHelpers.isArray;
-  var isDefined = leafletHelpers.isDefined;
-  var errorHeader = leafletHelpers.errorHeader;
-  var $it = leafletIterators;
-
-  var utfGridCreateLayer = function(params) {
-    if (!Helpers.UTFGridPlugin.isLoaded()) {
-      $log.error('[AngularJS - Leaflet] The UTFGrid plugin is not loaded.');
-      return;
-    }
-
-    var utfgrid = new L.UtfGrid(params.url, params.pluginOptions);
-
-    utfgrid.on('mouseover', function(e) {
-      $rootScope.$broadcast('leafletDirectiveMap.utfgridMouseover', e);
-    });
-
-    utfgrid.on('mouseout', function(e) {
-      $rootScope.$broadcast('leafletDirectiveMap.utfgridMouseout', e);
-    });
-
-    utfgrid.on('click', function(e) {
-      $rootScope.$broadcast('leafletDirectiveMap.utfgridClick', e);
-    });
-
-    utfgrid.on('mousemove', function(e) {
-      $rootScope.$broadcast('leafletDirectiveMap.utfgridMousemove', e);
-    });
-
-    return utfgrid;
-  };
-
-  var layerTypes = {
-    xyz: {
-      mustHaveUrl: true,
-      createLayer: function(params) {
-        return L.tileLayer(params.url, params.options);
-      },
-    },
-    mapbox: {
-      mustHaveKey: true,
-      createLayer: function(params) {
-        var version = 3;
-        if (isDefined(params.options.version) && params.options.version === 4) {
-          version = params.options.version;
-        }
-
-        var url = version === 3 ?
-            '//{s}.tiles.mapbox.com/v3/' + params.key + '/{z}/{x}/{y}.png' :
-            '//api.tiles.mapbox.com/v4/' + params.key + '/{z}/{x}/{y}.png?access_token=' + params.apiKey;
-        return L.tileLayer(url, params.options);
-      },
-    },
-    geoJSON: {
-      mustHaveUrl: true,
-      createLayer: function(params) {
-        if (!Helpers.GeoJSONPlugin.isLoaded()) {
-          return;
-        }
-
-        return new L.TileLayer.GeoJSON(params.url, params.pluginOptions, params.options);
-      },
-    },
-    geoJSONShape: {
-      mustHaveUrl: false,
-      createLayer: function(params) {
-        return new L.GeoJSON(params.data,
-            params.options);
-      },
-    },
-    geoJSONAwesomeMarker: {
-      mustHaveUrl: false,
-      createLayer: function(params) {
-        return new L.geoJson(params.data, {
-          pointToLayer: function(feature, latlng) {
-            return L.marker(latlng, {icon: L.AwesomeMarkers.icon(params.icon)});
-          },
-        });
-      },
-    },
-    geoJSONVectorMarker: {
-      mustHaveUrl: false,
-      createLayer: function(params) {
-        return new L.geoJson(params.data, {
-          pointToLayer: function(feature, latlng) {
-            return L.marker(latlng, {icon: L.VectorMarkers.icon(params.icon)});
-          },
-        });
-      },
-    },
-    utfGrid: {
-      mustHaveUrl: true,
-      createLayer: utfGridCreateLayer,
-    },
-    cartodbTiles: {
-      mustHaveKey: true,
-      createLayer: function(params) {
-        var url = '//' + params.user + '.cartodb.com/api/v1/map/' + params.key + '/{z}/{x}/{y}.png';
-        return L.tileLayer(url, params.options);
-      },
-    },
-    cartodbUTFGrid: {
-      mustHaveKey: true,
-      mustHaveLayer: true,
-      createLayer: function(params) {
-        params.url = '//' + params.user + '.cartodb.com/api/v1/map/' + params.key + '/' + params.layer + '/{z}/{x}/{y}.grid.json';
-        return utfGridCreateLayer(params);
-      },
-    },
-    cartodbInteractive: {
-      mustHaveKey: true,
-      mustHaveLayer: true,
-      createLayer: function(params) {
-        var tilesURL = '//' + params.user + '.cartodb.com/api/v1/map/' + params.key + '/{z}/{x}/{y}.png';
-        var tileLayer = L.tileLayer(tilesURL, params.options);
-        params.url = '//' + params.user + '.cartodb.com/api/v1/map/' + params.key + '/' + params.layer + '/{z}/{x}/{y}.grid.json';
-        var utfLayer = utfGridCreateLayer(params);
-        return L.layerGroup([tileLayer, utfLayer]);
-      },
-    },
-    wms: {
-      mustHaveUrl: true,
-      createLayer: function(params) {
-        return L.tileLayer.wms(params.url, params.options);
-      },
-    },
-    wmts: {
-      mustHaveUrl: true,
-      createLayer: function(params) {
-        return L.tileLayer.wmts(params.url, params.options);
-      },
-    },
-    wfs: {
-      mustHaveUrl: true,
-      mustHaveLayer: true,
-      createLayer: function(params) {
-        if (!Helpers.WFSLayerPlugin.isLoaded()) {
-          return;
-        }
-
-        var options = angular.copy(params.options);
-        if (options.crs && typeof options.crs === 'string') {
-          /*jshint -W061 */
-          options.crs = eval(options.crs);
-        }
-
-        return new L.GeoJSON.WFS(params.url, params.layer, options);
-      },
-    },
-    group: {
-      mustHaveUrl: false,
-      createLayer: function(params) {
-        var lyrs = [];
-        $it.each(params.options.layers, function(l) {
-                  lyrs.push(createLayer(l));
-                });
-
-        params.options.loadedDefer = function() {
-          var defers = [];
-          if (isDefined(params.options.layers)) {
-            for (var i = 0; i < params.options.layers.length; i++) {
-              var d = params.options.layers[i].layerOptions.loadedDefer;
-              if (isDefined(d)) {
-                defers.push(d);
-              }
-            }
-          }
-
-          return defers;
-        };
-
-        return L.layerGroup(lyrs);
-      },
-    },
-    featureGroup: {
-      mustHaveUrl: false,
-      createLayer: function() {
-        return L.featureGroup();
-      },
-    },
-    google: {
-      mustHaveUrl: false,
-      createLayer: function(params) {
-        var type = params.type || 'SATELLITE';
-        if (!Helpers.GoogleLayerPlugin.isLoaded()) {
-          return;
-        }
-
-        return new L.Google(type, params.options);
-      },
-    },
-    here: {
-      mustHaveUrl: false,
-      createLayer: function(params) {
-        var provider = params.provider || 'HERE.terrainDay';
-        if (!Helpers.LeafletProviderPlugin.isLoaded()) {
-          return;
-        }
-
-        return new L.TileLayer.Provider(provider, params.options);
-      },
-    },
-    china:{
-      mustHaveUrl:false,
-      createLayer:function(params) {
-        var type = params.type || '';
-        if (!Helpers.ChinaLayerPlugin.isLoaded()) {
-          return;
-        }
-
-        return L.tileLayer.chinaProvider(type, params.options);
-      },
-    },
-    agsBase: {
-      mustHaveLayer: true,
-      createLayer: function(params) {
-        if (!Helpers.AGSBaseLayerPlugin.isLoaded()) {
-          return;
-        }
-
-        return L.esri.basemapLayer(params.layer, params.options);
-      },
-    },
-    ags: {
-      mustHaveUrl: true,
-      createLayer: function(params) {
-        if (!Helpers.AGSLayerPlugin.isLoaded()) {
-          return;
-        }
-
-        var options = angular.copy(params.options);
-        angular.extend(options, {
-          url: params.url,
-        });
-        var layer = new lvector.AGS(options);
-        layer.onAdd = function(map) {
-          this.setMap(map);
-        };
-
-        layer.onRemove = function() {
-          this.setMap(null);
-        };
-
-        return layer;
-      },
-    },
-    agsFeature: {
-      mustHaveUrl: true,
-      createLayer: function(params) {
-        if (!Helpers.AGSFeatureLayerPlugin.isLoaded()) {
-          $log.warn(errorHeader + ' The esri plugin is not loaded.');
-          return;
-        }
-
-        params.options.url = params.url;
-
-        var layer = L.esri.featureLayer(params.options);
-        var load = function() {
-          if (isDefined(params.options.loadedDefer)) {
-            params.options.loadedDefer.resolve();
-          }
-        };
-
-        layer.on('loading', function() {
-          params.options.loadedDefer = $q.defer();
-          layer.off('load', load);
-          layer.on('load', load);
-        });
-
-        return layer;
-      },
-    },
-    agsTiled: {
-      mustHaveUrl: true,
-      createLayer: function(params) {
-        if (!Helpers.AGSTiledMapLayerPlugin.isLoaded()) {
-          $log.warn(errorHeader + ' The esri plugin is not loaded.');
-          return;
-        }
-
-        params.options.url = params.url;
-
-        return L.esri.tiledMapLayer(params.options);
-      },
-    },
-    agsDynamic: {
-      mustHaveUrl: true,
-      createLayer: function(params) {
-        if (!Helpers.AGSDynamicMapLayerPlugin.isLoaded()) {
-          $log.warn(errorHeader + ' The esri plugin is not loaded.');
-          return;
-        }
-
-        params.options.url = params.url;
-
-        return L.esri.dynamicMapLayer(params.options);
-      },
-    },
-    agsImage: {
-      mustHaveUrl: true,
-      createLayer: function(params) {
-        if (!Helpers.AGSImageMapLayerPlugin.isLoaded()) {
-          $log.warn(errorHeader + ' The esri plugin is not loaded.');
-          return;
-        }
-
-        params.options.url = params.url;
-
-        return L.esri.imageMapLayer(params.options);
-      },
-    },
-    agsClustered: {
-      mustHaveUrl: true,
-      createLayer: function(params) {
-        if (!Helpers.AGSClusteredLayerPlugin.isLoaded()) {
-          $log.warn(errorHeader + ' The esri clustered layer plugin is not loaded.');
-          return;
-        }
-
-        if (!Helpers.MarkerClusterPlugin.isLoaded()) {
-          $log.warn(errorHeader + ' The markercluster plugin is not loaded.');
-          return;
-        }
-
-        return L.esri.clusteredFeatureLayer(params.url, params.options);
-      },
-    },
-    agsHeatmap: {
-      mustHaveUrl: true,
-      createLayer: function(params) {
-        if (!Helpers.AGSHeatmapLayerPlugin.isLoaded()) {
-          $log.warn(errorHeader + ' The esri heatmap layer plugin is not loaded.');
-          return;
-        }
-
-        if (!Helpers.HeatLayerPlugin.isLoaded()) {
-          $log.warn(errorHeader + ' The heatlayer plugin is not loaded.');
-          return;
-        }
-
-        return L.esri.heatmapFeatureLayer(params.url, params.options);
-      },
-    },
-    markercluster: {
-      mustHaveUrl: false,
-      createLayer: function(params) {
-        if (!Helpers.MarkerClusterPlugin.isLoaded()) {
-          $log.warn(errorHeader + ' The markercluster plugin is not loaded.');
-          return;
-        }
-
-        return new L.MarkerClusterGroup(params.options);
-      },
-    },
-    bing: {
-      mustHaveUrl: false,
-      createLayer: function(params) {
-        if (!Helpers.BingLayerPlugin.isLoaded()) {
-          return;
-        }
-
-        return new L.BingLayer(params.key, params.options);
-      },
-    },
-    webGLHeatmap: {
-      mustHaveUrl: false,
-      mustHaveData: true,
-      createLayer: function(params) {
-        if (!Helpers.WebGLHeatMapLayerPlugin.isLoaded()) {
-          return;
-        }
-
-        var layer = new L.TileLayer.WebGLHeatMap(params.options);
-        if (isDefined(params.data)) {
-          layer.setData(params.data);
-        }
-
-        return layer;
-      },
-    },
-    heat: {
-      mustHaveUrl: false,
-      mustHaveData: true,
-      createLayer: function(params) {
-        if (!Helpers.HeatLayerPlugin.isLoaded()) {
-          return;
-        }
-
-        var layer = new L.heatLayer();
-
-        if (isArray(params.data)) {
-          layer.setLatLngs(params.data);
-        }
-
-        if (isObject(params.options)) {
-          layer.setOptions(params.options);
-        }
-
-        return layer;
-      },
-    },
-    yandex: {
-      mustHaveUrl: false,
-      createLayer: function(params) {
-        var type = params.type || 'map';
-        if (!Helpers.YandexLayerPlugin.isLoaded()) {
-          return;
-        }
-
-        return new L.Yandex(type, params.options);
-      },
-    },
-    imageOverlay: {
-      mustHaveUrl: true,
-      mustHaveBounds: true,
-      createLayer: function(params) {
-        return L.imageOverlay(params.url, params.bounds, params.options);
-      },
-    },
-    iip: {
-      mustHaveUrl: true,
-      createLayer: function(params) {
-        return L.tileLayer.iip(params.url, params.options);
-      },
-    },
-
-    // This "custom" type is used to accept every layer that user want to define himself.
-    // We can wrap these custom layers like heatmap or yandex, but it means a lot of work/code to wrap the world,
-    // so we let user to define their own layer outside the directive,
-    // and pass it on "createLayer" result for next processes
-    custom: {
-      createLayer: function(params) {
-        if (params.layer instanceof L.Class) {
-          return angular.copy(params.layer);
-        }        else {
-          $log.error('[AngularJS - Leaflet] A custom layer must be a leaflet Class');
-        }
-      },
-    },
-    cartodb: {
-      mustHaveUrl: true,
-      createLayer: function(params) {
-        return cartodb.createLayer(params.map, params.url);
-      },
-    },
-  };
-
-  function isValidLayerType(layerDefinition) {
-    // Check if the baselayer has a valid type
-    if (!isString(layerDefinition.type)) {
-      $log.error('[AngularJS - Leaflet] A layer must have a valid type defined.');
-      return false;
-    }
-
-    if (Object.keys(layerTypes).indexOf(layerDefinition.type) === -1) {
-      $log.error('[AngularJS - Leaflet] A layer must have a valid type: ' + Object.keys(layerTypes));
-      return false;
-    }
-
-    // Check if the layer must have an URL
-    if (layerTypes[layerDefinition.type].mustHaveUrl && !isString(layerDefinition.url)) {
-      $log.error('[AngularJS - Leaflet] A base layer must have an url');
-      return false;
-    }
-
-    if (layerTypes[layerDefinition.type].mustHaveData && !isDefined(layerDefinition.data)) {
-      $log.error('[AngularJS - Leaflet] The base layer must have a "data" array attribute');
-      return false;
-    }
-
-    if (layerTypes[layerDefinition.type].mustHaveLayer && !isDefined(layerDefinition.layer)) {
-      $log.error('[AngularJS - Leaflet] The type of layer ' + layerDefinition.type + ' must have an layer defined');
-      return false;
-    }
-
-    if (layerTypes[layerDefinition.type].mustHaveBounds && !isDefined(layerDefinition.bounds)) {
-      $log.error('[AngularJS - Leaflet] The type of layer ' + layerDefinition.type + ' must have bounds defined');
-      return false;
-    }
-
-    if (layerTypes[layerDefinition.type].mustHaveKey && !isDefined(layerDefinition.key)) {
-      $log.error('[AngularJS - Leaflet] The type of layer ' + layerDefinition.type + ' must have key defined');
-      return false;
-    }
-
-    return true;
-  }
-
-  function createLayer(layerDefinition) {
-    if (!isValidLayerType(layerDefinition)) {
-      return;
-    }
-
-    if (!isString(layerDefinition.name)) {
-      $log.error('[AngularJS - Leaflet] A base layer must have a name');
-      return;
-    }
-
-    if (!isObject(layerDefinition.layerParams)) {
-      layerDefinition.layerParams = {};
-    }
-
-    if (!isObject(layerDefinition.layerOptions)) {
-      layerDefinition.layerOptions = {};
-    }
-
-    // Mix the layer specific parameters with the general Leaflet options. Although this is an overhead
-    // the definition of a base layers is more 'clean' if the two types of parameters are differentiated
-    for (var attrname in layerDefinition.layerParams) {
-      layerDefinition.layerOptions[attrname] = layerDefinition.layerParams[attrname];
-    }
-
-    var params = {
-      url: layerDefinition.url,
-      data: layerDefinition.data,
-      options: layerDefinition.layerOptions,
-      layer: layerDefinition.layer,
-      icon: layerDefinition.icon,
-      type: layerDefinition.layerType,
-      bounds: layerDefinition.bounds,
-      key: layerDefinition.key,
-      apiKey: layerDefinition.apiKey,
-      pluginOptions: layerDefinition.pluginOptions,
-      user: layerDefinition.user,
-    };
-
-    //TODO Add $watch to the layer properties
-    return layerTypes[layerDefinition.type].createLayer(params);
-  }
-
-  function safeAddLayer(map, layer) {
-    if (layer && typeof layer.addTo === 'function') {
-      layer.addTo(map);
-    } else {
-      map.addLayer(layer);
-    }
-  }
-
-  function safeRemoveLayer(map, layer, layerOptions) {
-    if (isDefined(layerOptions) && isDefined(layerOptions.loadedDefer)) {
-      if (angular.isFunction(layerOptions.loadedDefer)) {
-        var defers = layerOptions.loadedDefer();
-        $log.debug('Loaded Deferred', defers);
-        var count = defers.length;
-        if (count > 0) {
-          var resolve = function() {
-            count--;
-            if (count === 0) {
-              map.removeLayer(layer);
-            }
-          };
-
-          for (var i = 0; i < defers.length; i++) {
-            defers[i].promise.then(resolve);
-          }
-        } else {
-          map.removeLayer(layer);
-        }
-      } else {
-        layerOptions.loadedDefer.promise.then(function() {
-          map.removeLayer(layer);
-        });
-      }
-    } else {
-      map.removeLayer(layer);
-    }
-  }
-
-  return {
-    createLayer: createLayer,
-    safeAddLayer: safeAddLayer,
-    safeRemoveLayer: safeRemoveLayer,
-  };
-}]);
-
-angular.module('leaflet-directive').factory('leafletLegendHelpers', function() {
-  var _updateLegend = function(div, legendData, type, url) {
-    div.innerHTML = '';
-    if (legendData.error) {
-      div.innerHTML += '<div class="info-title alert alert-danger">' + legendData.error.message + '</div>';
-    } else {
-      if (type === 'arcgis') {
-        for (var i = 0; i < legendData.layers.length; i++) {
-          var layer = legendData.layers[i];
-          div.innerHTML += '<div class="info-title" data-layerid="' + layer.layerId + '">' + layer.layerName + '</div>';
-          for (var j = 0; j < layer.legend.length; j++) {
-            var leg = layer.legend[j];
-            div.innerHTML +=
-            '<div class="inline" data-layerid="' + layer.layerId + '"><img src="data:' + leg.contentType + ';base64,' + leg.imageData + '" /></div>' +
-            '<div class="info-label" data-layerid="' + layer.layerId + '">' + leg.label + '</div>';
-          }
-        }
-      }      else if (type === 'image') {
-        div.innerHTML = '<img src="' + url + '"/>';
-      }
-    }
-  };
-
-  var _getOnAddLegend = function(legendData, legendClass, type, url) {
-    return function(/*map*/) {
-      var div = L.DomUtil.create('div', legendClass);
-
-      if (!L.Browser.touch) {
-        L.DomEvent.disableClickPropagation(div);
-        L.DomEvent.on(div, 'mousewheel', L.DomEvent.stopPropagation);
-      } else {
-        L.DomEvent.on(div, 'click', L.DomEvent.stopPropagation);
-      }
-
-      _updateLegend(div, legendData, type, url);
-      return div;
-    };
-  };
-
-  var _getOnAddArrayLegend = function(legend, legendClass) {
-    return function(/*map*/) {
-      var div = L.DomUtil.create('div', legendClass);
-      for (var i = 0; i < legend.colors.length; i++) {
-        div.innerHTML +=
-            '<div class="outline"><i style="background:' + legend.colors[i] + '"></i></div>' +
-            '<div class="info-label">' + legend.labels[i] + '</div>';
-      }
-
-      if (!L.Browser.touch) {
-        L.DomEvent.disableClickPropagation(div);
-        L.DomEvent.on(div, 'mousewheel', L.DomEvent.stopPropagation);
-      } else {
-        L.DomEvent.on(div, 'click', L.DomEvent.stopPropagation);
-      }
-
-      return div;
-    };
-  };
-
-  return {
-    getOnAddLegend: _getOnAddLegend,
-    getOnAddArrayLegend: _getOnAddArrayLegend,
-    updateLegend: _updateLegend,
-  };
-});
-
-angular.module('leaflet-directive').factory('leafletMapDefaults', ["$q", "leafletHelpers", function($q, leafletHelpers) {
-  function _getDefaults() {
-    return {
-      keyboard: true,
-      dragging: true,
-      worldCopyJump: false,
-      doubleClickZoom: true,
-      scrollWheelZoom: true,
-      tap: true,
-      touchZoom: true,
-      zoomControl: true,
-      zoomsliderControl: false,
-      zoomControlPosition: 'topleft',
-      attributionControl: true,
-      controls: {
-        layers: {
-          visible: true,
-          position: 'topright',
-          collapsed: true,
-        },
-      },
-      nominatim: {
-        server: ' http://nominatim.openstreetmap.org/search',
-      },
-      crs: L.CRS.EPSG3857,
-      tileLayer: '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
-      tileLayerOptions: {
-        attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
-      },
-      path: {
-        weight: 10,
-        opacity: 1,
-        color: '#0000ff',
-      },
-      center: {
-        lat: 0,
-        lng: 0,
-        zoom: 1,
-      },
-    };
-  }
-
-  var isDefined = leafletHelpers.isDefined;
-  var isObject = leafletHelpers.isObject;
-  var obtainEffectiveMapId = leafletHelpers.obtainEffectiveMapId;
-  var defaults = {};
-
-  // Get the _defaults dictionary, and override the properties defined by the user
-  return {
-    reset: function() {
-      defaults = {};
-    },
-
-    getDefaults: function(scopeId) {
-      var mapId = obtainEffectiveMapId(defaults, scopeId);
-      return defaults[mapId];
-    },
-
-    getMapCreationDefaults: function(scopeId) {
-      var mapId = obtainEffectiveMapId(defaults, scopeId);
-      var d = defaults[mapId];
-
-      var mapDefaults = {
-        maxZoom: d.maxZoom,
-        keyboard: d.keyboard,
-        dragging: d.dragging,
-        zoomControl: d.zoomControl,
-        doubleClickZoom: d.doubleClickZoom,
-        scrollWheelZoom: d.scrollWheelZoom,
-        tap: d.tap,
-        touchZoom: d.touchZoom,
-        attributionControl: d.attributionControl,
-        worldCopyJump: d.worldCopyJump,
-        crs: d.crs,
-      };
-
-      if (isDefined(d.minZoom)) {
-        mapDefaults.minZoom = d.minZoom;
-      }
-
-      if (isDefined(d.zoomAnimation)) {
-        mapDefaults.zoomAnimation = d.zoomAnimation;
-      }
-
-      if (isDefined(d.fadeAnimation)) {
-        mapDefaults.fadeAnimation = d.fadeAnimation;
-      }
-
-      if (isDefined(d.markerZoomAnimation)) {
-        mapDefaults.markerZoomAnimation = d.markerZoomAnimation;
-      }
-
-      if (d.map) {
-        for (var option in d.map) {
-          mapDefaults[option] = d.map[option];
-        }
-      }
-
-      return mapDefaults;
-    },
-
-    setDefaults: function(userDefaults, scopeId) {
-      var newDefaults = _getDefaults();
-
-      if (isDefined(userDefaults)) {
-        newDefaults.doubleClickZoom = isDefined(userDefaults.doubleClickZoom) ? userDefaults.doubleClickZoom : newDefaults.doubleClickZoom;
-        newDefaults.scrollWheelZoom = isDefined(userDefaults.scrollWheelZoom) ? userDefaults.scrollWheelZoom : newDefaults.doubleClickZoom;
-        newDefaults.tap = isDefined(userDefaults.tap) ? userDefaults.tap : newDefaults.tap;
-        newDefaults.touchZoom = isDefined(userDefaults.touchZoom) ? userDefaults.touchZoom : newDefaults.doubleClickZoom;
-        newDefaults.zoomControl = isDefined(userDefaults.zoomControl) ? userDefaults.zoomControl : newDefaults.zoomControl;
-        newDefaults.zoomsliderControl = isDefined(userDefaults.zoomsliderControl) ? userDefaults.zoomsliderControl : newDefaults.zoomsliderControl;
-        newDefaults.attributionControl = isDefined(userDefaults.attributionControl) ? userDefaults.attributionControl : newDefaults.attributionControl;
-        newDefaults.tileLayer = isDefined(userDefaults.tileLayer) ? userDefaults.tileLayer : newDefaults.tileLayer;
-        newDefaults.zoomControlPosition = isDefined(userDefaults.zoomControlPosition) ? userDefaults.zoomControlPosition : newDefaults.zoomControlPosition;
-        newDefaults.keyboard = isDefined(userDefaults.keyboard) ? userDefaults.keyboard : newDefaults.keyboard;
-        newDefaults.dragging = isDefined(userDefaults.dragging) ? userDefaults.dragging : newDefaults.dragging;
-
-        if (isDefined(userDefaults.controls)) {
-          angular.extend(newDefaults.controls, userDefaults.controls);
-        }
-
-        if (isObject(userDefaults.crs)) {
-          newDefaults.crs = userDefaults.crs;
-        } else if (isDefined(L.CRS[userDefaults.crs])) {
-          newDefaults.crs = L.CRS[userDefaults.crs];
-        }
-
-        if (isDefined(userDefaults.center)) {
-          angular.copy(userDefaults.center, newDefaults.center);
-        }
-
-        if (isDefined(userDefaults.tileLayerOptions)) {
-          angular.copy(userDefaults.tileLayerOptions, newDefaults.tileLayerOptions);
-        }
-
-        if (isDefined(userDefaults.maxZoom)) {
-          newDefaults.maxZoom = userDefaults.maxZoom;
-        }
-
-        if (isDefined(userDefaults.minZoom)) {
-          newDefaults.minZoom = userDefaults.minZoom;
-        }
-
-        if (isDefined(userDefaults.zoomAnimation)) {
-          newDefaults.zoomAnimation = userDefaults.zoomAnimation;
-        }
-
-        if (isDefined(userDefaults.fadeAnimation)) {
-          newDefaults.fadeAnimation = userDefaults.fadeAnimation;
-        }
-
-        if (isDefined(userDefaults.markerZoomAnimation)) {
-          newDefaults.markerZoomAnimation = userDefaults.markerZoomAnimation;
-        }
-
-        if (isDefined(userDefaults.worldCopyJump)) {
-          newDefaults.worldCopyJump = userDefaults.worldCopyJump;
-        }
-
-        if (isDefined(userDefaults.map)) {
-          newDefaults.map = userDefaults.map;
-        }
-
-        if (isDefined(userDefaults.path)) {
-          newDefaults.path = userDefaults.path;
-        }
-      }
-
-      var mapId = obtainEffectiveMapId(defaults, scopeId);
-      defaults[mapId] = newDefaults;
-      return newDefaults;
-    },
-  };
-}]);
-
-angular.module('leaflet-directive').service('leafletMarkersHelpers', ["$rootScope", "$timeout", "leafletHelpers", "$log", "$compile", "leafletGeoJsonHelpers", function($rootScope, $timeout, leafletHelpers, $log, $compile, leafletGeoJsonHelpers) {
-  var isDefined = leafletHelpers.isDefined;
-  var defaultTo = leafletHelpers.defaultTo;
-  var MarkerClusterPlugin = leafletHelpers.MarkerClusterPlugin;
-  var AwesomeMarkersPlugin = leafletHelpers.AwesomeMarkersPlugin;
-  var VectorMarkersPlugin = leafletHelpers.VectorMarkersPlugin;
-  var MakiMarkersPlugin = leafletHelpers.MakiMarkersPlugin;
-  var ExtraMarkersPlugin = leafletHelpers.ExtraMarkersPlugin;
-  var DomMarkersPlugin = leafletHelpers.DomMarkersPlugin;
-  var safeApply = leafletHelpers.safeApply;
-  var Helpers = leafletHelpers;
-  var isString = leafletHelpers.isString;
-  var isNumber = leafletHelpers.isNumber;
-  var isObject = leafletHelpers.isObject;
-  var groups = {};
-  var geoHlp = leafletGeoJsonHelpers;
-  var errorHeader = leafletHelpers.errorHeader;
-
-  var _string = function(marker) {
-    //this exists since JSON.stringify barfs on cyclic
-    var retStr = '';
-    ['_icon', '_latlng', '_leaflet_id', '_map', '_shadow'].forEach(function(prop) {
-      retStr += prop + ': ' + defaultTo(marker[prop], 'undefined') + ' \n';
-    });
-
-    return '[leafletMarker] : \n' + retStr;
-  };
-
-  var _log = function(marker, useConsole) {
-    var logger = useConsole ? console : $log;
-    logger.debug(_string(marker));
-  };
-
-  var createLeafletIcon = function(iconData) {
-    if (isDefined(iconData) && isDefined(iconData.type) && iconData.type === 'awesomeMarker') {
-      if (!AwesomeMarkersPlugin.isLoaded()) {
-        $log.error(errorHeader + ' The AwesomeMarkers Plugin is not loaded.');
-      }
-
-      return new L.AwesomeMarkers.icon(iconData);
-    }
-
-    if (isDefined(iconData) && isDefined(iconData.type) && iconData.type === 'vectorMarker') {
-      if (!VectorMarkersPlugin.isLoaded()) {
-        $log.error(errorHeader + ' The VectorMarkers Plugin is not loaded.');
-      }
-
-      return new L.VectorMarkers.icon(iconData);
-    }
-
-    if (isDefined(iconData) && isDefined(iconData.type) && iconData.type === 'makiMarker') {
-      if (!MakiMarkersPlugin.isLoaded()) {
-        $log.error(errorHeader + 'The MakiMarkers Plugin is not loaded.');
-      }
-
-      return new L.MakiMarkers.icon(iconData);
-    }
-
-    if (isDefined(iconData) && isDefined(iconData.type) && iconData.type === 'extraMarker') {
-      if (!ExtraMarkersPlugin.isLoaded()) {
-        $log.error(errorHeader + 'The ExtraMarkers Plugin is not loaded.');
-      }
-
-      return new L.ExtraMarkers.icon(iconData);
-    }
-
-    if (isDefined(iconData) && isDefined(iconData.type) && iconData.type === 'div') {
-      return new L.divIcon(iconData);
-    }
-
-    if (isDefined(iconData) && isDefined(iconData.type) && iconData.type === 'dom') {
-      if (!DomMarkersPlugin.isLoaded()) {
-        $log.error(errorHeader + 'The DomMarkers Plugin is not loaded.');
-      }
-
-      var markerScope = angular.isFunction(iconData.getMarkerScope) ? iconData.getMarkerScope() : $rootScope;
-      var template = $compile(iconData.template)(markerScope);
-      var iconDataCopy = angular.copy(iconData);
-      iconDataCopy.element = template[0];
-      return new L.DomMarkers.icon(iconDataCopy);
-    }
-
-    // allow for any custom icon to be used... assumes the icon has already been initialized
-    if (isDefined(iconData) && isDefined(iconData.type) && iconData.type === 'icon') {
-      return iconData.icon;
-    }
-
-    var base64icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAGmklEQVRYw7VXeUyTZxjvNnfELFuyIzOabermMZEeQC/OclkO49CpOHXOLJl/CAURuYbQi3KLgEhbrhZ1aDwmaoGqKII6odATmH/scDFbdC7LvFqOCc+e95s2VG50X/LLm/f4/Z7neY/ne18aANCmAr5E/xZf1uDOkTcGcWR6hl9247tT5U7Y6SNvWsKT63P58qbfeLJG8M5qcgTknrvvrdDbsT7Ml+tv82X6vVxJE33aRmgSyYtcWVMqX97Yv2JvW39UhRE2HuyBL+t+gK1116ly06EeWFNlAmHxlQE0OMiV6mQCScusKRlhS3QLeVJdl1+23h5dY4FNB3thrbYboqptEFlphTC1hSpJnbRvxP4NWgsE5Jyz86QNNi/5qSUTGuFk1gu54tN9wuK2wc3o+Wc13RCmsoBwEqzGcZsxsvCSy/9wJKf7UWf1mEY8JWfewc67UUoDbDjQC+FqK4QqLVMGGR9d2wurKzqBk3nqIT/9zLxRRjgZ9bqQgub+DdoeCC03Q8j+0QhFhBHR/eP3U/zCln7Uu+hihJ1+bBNffLIvmkyP0gpBZWYXhKussK6mBz5HT6M1Nqpcp+mBCPXosYQfrekGvrjewd59/GvKCE7TbK/04/ZV5QZYVWmDwH1mF3xa2Q3ra3DBC5vBT1oP7PTj4C0+CcL8c7C2CtejqhuCnuIQHaKHzvcRfZpnylFfXsYJx3pNLwhKzRAwAhEqG0SpusBHfAKkxw3w4627MPhoCH798z7s0ZnBJ/MEJbZSbXPhER2ih7p2ok/zSj2cEJDd4CAe+5WYnBCgR2uruyEw6zRoW6/DWJ/OeAP8pd/BGtzOZKpG8oke0SX6GMmRk6GFlyAc59K32OTEinILRJRchah8HQwND8N435Z9Z0FY1EqtxUg+0SO6RJ/mmXz4VuS+DpxXC3gXmZwIL7dBSH4zKE50wESf8qwVgrP1EIlTO5JP9Igu0aexdh28F1lmAEGJGfh7jE6ElyM5Rw/FDcYJjWhbeiBYoYNIpc2FT/SILivp0F1ipDWk4BIEo2VuodEJUifhbiltnNBIXPUFCMpthtAyqws/BPlEF/VbaIxErdxPphsU7rcCp8DohC+GvBIPJS/tW2jtvTmmAeuNO8BNOYQeG8G/2OzCJ3q+soYB5i6NhMaKr17FSal7GIHheuV3uSCY8qYVuEm1cOzqdWr7ku/R0BDoTT+DT+ohCM6/CCvKLKO4RI+dXPeAuaMqksaKrZ7L3FE5FIFbkIceeOZ2OcHO6wIhTkNo0ffgjRGxEqogXHYUPHfWAC/lADpwGcLRY3aeK4/oRGCKYcZXPVoeX/kelVYY8dUGf8V5EBRbgJXT5QIPhP9ePJi428JKOiEYhYXFBqou2Guh+p/mEB1/RfMw6rY7cxcjTrneI1FrDyuzUSRm9miwEJx8E/gUmqlyvHGkneiwErR21F3tNOK5Tf0yXaT+O7DgCvALTUBXdM4YhC/IawPU+2PduqMvuaR6eoxSwUk75ggqsYJ7VicsnwGIkZBSXKOUww73WGXyqP+J2/b9c+gi1YAg/xpwck3gJuucNrh5JvDPvQr0WFXf0piyt8f8/WI0hV4pRxxkQZdJDfDJNOAmM0Ag8jyT6hz0WGXWuP94Yh2jcfjmXAGvHCMslRimDHYuHuDsy2QtHuIavznhbYURq5R57KpzBBRZKPJi8eQg48h4j8SDdowifdIrEVdU+gbO6QNvRRt4ZBthUaZhUnjlYObNagV3keoeru3rU7rcuceqU1mJBxy+BWZYlNEBH+0eH4vRiB+OYybU2hnblYlTvkHinM4m54YnxSyaZYSF6R3jwgP7udKLGIX6r/lbNa9N6y5MFynjWDtrHd75ZvTYAPO/6RgF0k76mQla3FGq7dO+cH8sKn0Vo7nDllwAhqwLPkxrHwWmHJOo+AKJ4rab5OgrM7rVu8eWb2Pu0Dh4eDgXoOfvp7Y7QeqknRmvcTBEyq9m/HQQSCSz6LHq3z0yzsNySRfMS253wl2KyRDbcZPcfJKjZmSEOjcxyi+Y8dUOtsIEH6R2wNykdqrkYJ0RV92H0W58pkfQk7cKevsLK10Py8SdMGfXNXATY+pPbyJR/ET6n9nIfztNtZYRV9XniQu9IA2vOVgy4ir7GCLVmmd+zjkH0eAF9Po6K61pmCXHxU5rHMYd1ftc3owjwRSVRzLjKvqZEty6cRUD7jGqiOdu5HG6MdHjNcNYGqfDm5YRzLBBCCDl/2bk8a8gdbqcfwECu62Fg/HrggAAAABJRU5ErkJggg==';
-    var base64shadow = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACkAAAApCAYAAACoYAD2AAAC5ElEQVRYw+2YW4/TMBCF45S0S1luXZCABy5CgLQgwf//S4BYBLTdJLax0fFqmB07nnQfEGqkIydpVH85M+NLjPe++dcPc4Q8Qh4hj5D/AaQJx6H/4TMwB0PeBNwU7EGQAmAtsNfAzoZkgIa0ZgLMa4Aj6CxIAsjhjOCoL5z7Glg1JAOkaicgvQBXuncwJAWjksLtBTWZe04CnYRktUGdilALppZBOgHGZcBzL6OClABvMSVIzyBjazOgrvACf1ydC5mguqAVg6RhdkSWQFj2uxfaq/BrIZOLEWgZdALIDvcMcZLD8ZbLC9de4yR1sYMi4G20S4Q/PWeJYxTOZn5zJXANZHIxAd4JWhPIloTJZhzMQduM89WQ3MUVAE/RnhAXpTycqys3NZALOBbB7kFrgLesQl2h45Fcj8L1tTSohUwuxhy8H/Qg6K7gIs+3kkaigQCOcyEXCHN07wyQazhrmIulvKMQAwMcmLNqyCVyMAI+BuxSMeTk3OPikLY2J1uE+VHQk6ANrhds+tNARqBeaGc72cK550FP4WhXmFmcMGhTwAR1ifOe3EvPqIegFmF+C8gVy0OfAaWQPMR7gF1OQKqGoBjq90HPMP01BUjPOqGFksC4emE48tWQAH0YmvOgF3DST6xieJgHAWxPAHMuNhrImIdvoNOKNWIOcE+UXE0pYAnkX6uhWsgVXDxHdTfCmrEEmMB2zMFimLVOtiiajxiGWrbU52EeCdyOwPEQD8LqyPH9Ti2kgYMf4OhSKB7qYILbBv3CuVTJ11Y80oaseiMWOONc/Y7kJYe0xL2f0BaiFTxknHO5HaMGMublKwxFGzYdWsBF174H/QDknhTHmHHN39iWFnkZx8lPyM8WHfYELmlLKtgWNmFNzQcC1b47gJ4hL19i7o65dhH0Negbca8vONZoP7doIeOC9zXm8RjuL0Gf4d4OYaU5ljo3GYiqzrWQHfJxA6ALhDpVKv9qYeZA8eM3EhfPSCmpuD0AAAAASUVORK5CYII=';
-
-    if (!isDefined(iconData) || !isDefined(iconData.iconUrl)) {
-      return new L.Icon.Default({
-        iconUrl: base64icon,
-        shadowUrl: base64shadow,
-        iconSize: [25, 41],
-        iconAnchor: [12, 41],
-        popupAnchor: [1, -34],
-        shadowSize: [41, 41],
-      });
-    }
-
-    return new L.Icon(iconData);
-  };
-
-  var _resetMarkerGroup = function(groupName) {
-    if (isDefined(groups[groupName])) {
-      groups.splice(groupName, 1);
-    }
-  };
-
-  var _resetMarkerGroups = function() {
-    groups = {};
-  };
-
-  var _deleteMarker = function(marker, map, layers) {
-    marker.closePopup();
-
-    // There is no easy way to know if a marker is added to a layer, so we search for it
-    // if there are overlays
-    if (isDefined(layers) && isDefined(layers.overlays)) {
-      for (var key in layers.overlays) {
-        if (layers.overlays[key] instanceof L.LayerGroup || layers.overlays[key] instanceof L.FeatureGroup) {
-          if (layers.overlays[key].hasLayer(marker)) {
-            layers.overlays[key].removeLayer(marker);
-            return;
-          }
-        }
-      }
-    }
-
-    if (isDefined(groups)) {
-      for (var groupKey in groups) {
-        if (groups[groupKey].hasLayer(marker)) {
-          groups[groupKey].removeLayer(marker);
-        }
-      }
-    }
-
-    if (map.hasLayer(marker)) {
-      map.removeLayer(marker);
-    }
-  };
-
-  var adjustPopupPan = function(marker, map) {
-    var containerHeight = marker._popup._container.offsetHeight;
-    var layerPos = new L.Point(marker._popup._containerLeft, -containerHeight - marker._popup._containerBottom);
-    var containerPos = map.layerPointToContainerPoint(layerPos);
-    if (containerPos !== null) {
-      marker._popup._adjustPan();
-    }
-  };
-
-  var compilePopup = function(marker, markerScope) {
-    $compile(marker._popup._contentNode)(markerScope);
-  };
-
-  var updatePopup = function(marker, markerScope, map) {
-    //The innerText should be more than 1 once angular has compiled.
-    //We need to keep trying until angular has compiled before we _updateLayout and _updatePosition
-    //This should take care of any scenario , eg ngincludes, whatever.
-    //Is there a better way to check for this?
-    var innerText = marker._popup._contentNode.innerText || marker._popup._contentNode.textContent;
-    if (innerText.length < 1) {
-      $timeout(function() {
-        updatePopup(marker, markerScope, map);
-      });
-    }
-
-    //cause a reflow - this is also very important - if we don't do this then the widths are from before $compile
-    var reflow = marker._popup._contentNode.offsetWidth;
-
-    marker._popup._updateLayout();
-    marker._popup._updatePosition();
-
-    if (marker._popup.options.autoPan) {
-      adjustPopupPan(marker, map);
-    }
-
-    //using / returning reflow so jshint doesn't moan
-    return reflow;
-  };
-
-  var _manageOpenPopup = function(marker, markerData, map) {
-    // The marker may provide a scope returning function used to compile the message
-    // default to $rootScope otherwise
-    var markerScope = angular.isFunction(markerData.getMessageScope) ? markerData.getMessageScope() : $rootScope;
-    var compileMessage = isDefined(markerData.compileMessage) ? markerData.compileMessage : true;
-
-    if (compileMessage) {
-      if (!isDefined(marker._popup) || !isDefined(marker._popup._contentNode)) {
-        $log.error(errorHeader + 'Popup is invalid or does not have any content.');
-        return false;
-      }
-
-      compilePopup(marker, markerScope);
-      updatePopup(marker, markerData, map);
-    }
-  };
-
-  var _manageOpenLabel = function(marker, markerData) {
-    var markerScope = angular.isFunction(markerData.getMessageScope) ? markerData.getMessageScope() : $rootScope;
-    var labelScope = angular.isFunction(markerData.getLabelScope) ? markerData.getLabelScope() : markerScope;
-    var compileMessage = isDefined(markerData.compileMessage) ? markerData.compileMessage : true;
-
-    if (Helpers.LabelPlugin.isLoaded() && isDefined(markerData.label)) {
-      if (isDefined(markerData.label.options) && markerData.label.options.noHide === true) {
-        marker.showLabel();
-      }
-
-      if (compileMessage && isDefined(marker.label)) {
-        $compile(marker.label._container)(labelScope);
-      }
-    }
-  };
-
-  var _updateMarker = function(markerData, oldMarkerData, marker, name, leafletScope, layers, map) {
-    if (!isDefined(oldMarkerData)) {
-      return;
-    }
-
-    // Update the lat-lng property (always present in marker properties)
-    if (!geoHlp.validateCoords(markerData)) {
-      $log.warn('There are problems with lat-lng data, please verify your marker model');
-      _deleteMarker(marker, map, layers);
-      return;
-    }
-
-    // watch is being initialized if old and new object is the same
-    var isInitializing = markerData === oldMarkerData;
-
-    // Update marker rotation
-    if (isDefined(markerData.iconAngle) && oldMarkerData.iconAngle !== markerData.iconAngle) {
-      marker.setIconAngle(markerData.iconAngle);
-    }
-
-    // It is possible that the layer has been removed or the layer marker does not exist
-    // Update the layer group if present or move it to the map if not
-    if (!isString(markerData.layer)) {
-      // There is no layer information, we move the marker to the map if it was in a layer group
-      if (isString(oldMarkerData.layer)) {
-        // Remove from the layer group that is supposed to be
-        if (isDefined(layers.overlays[oldMarkerData.layer]) && layers.overlays[oldMarkerData.layer].hasLayer(marker)) {
-          layers.overlays[oldMarkerData.layer].removeLayer(marker);
-          marker.closePopup();
-        }
-
-        // Test if it is not on the map and add it
-        if (!map.hasLayer(marker)) {
-          map.addLayer(marker);
-        }
-      }
-    }
-
-    if ((isNumber(markerData.opacity) || isNumber(parseFloat(markerData.opacity))) && markerData.opacity !== oldMarkerData.opacity) {
-      // There was a different opacity so we update it
-      marker.setOpacity(markerData.opacity);
-    }
-
-    if (isString(markerData.layer) && oldMarkerData.layer !== markerData.layer) {
-      // If it was on a layer group we have to remove it
-      if (isString(oldMarkerData.layer) && isDefined(layers.overlays[oldMarkerData.layer]) && layers.overlays[oldMarkerData.layer].hasLayer(marker)) {
-        layers.overlays[oldMarkerData.layer].removeLayer(marker);
-      }
-
-      marker.closePopup();
-
-      // Remove it from the map in case the new layer is hidden or there is an error in the new layer
-      if (map.hasLayer(marker)) {
-        map.removeLayer(marker);
-      }
-
-      // The markerData.layer is defined so we add the marker to the layer if it is different from the old data
-      if (!isDefined(layers.overlays[markerData.layer])) {
-        $log.error(errorHeader + 'You must use a name of an existing layer');
-        return;
-      }
-
-      // Is a group layer?
-      var layerGroup = layers.overlays[markerData.layer];
-      if (!(layerGroup instanceof L.LayerGroup || layerGroup instanceof L.FeatureGroup)) {
-        $log.error(errorHeader + 'A marker can only be added to a layer of type "group" or "featureGroup"');
-        return;
-      }
-
-      // The marker goes to a correct layer group, so first of all we add it
-      layerGroup.addLayer(marker);
-
-      // The marker is automatically added to the map depending on the visibility
-      // of the layer, so we only have to open the popup if the marker is in the map
-      if (map.hasLayer(marker) && markerData.focus === true) {
-        marker.openPopup();
-      }
-    }
-
-    // Update the draggable property
-    if (markerData.draggable !== true && oldMarkerData.draggable === true && (isDefined(marker.dragging))) {
-      marker.dragging.disable();
-    }
-
-    if (markerData.draggable === true && oldMarkerData.draggable !== true) {
-      // The markerData.draggable property must be true so we update if there wasn't a previous value or it wasn't true
-      if (marker.dragging) {
-        marker.dragging.enable();
-      } else {
-        if (L.Handler.MarkerDrag) {
-          marker.dragging = new L.Handler.MarkerDrag(marker);
-          marker.options.draggable = true;
-          marker.dragging.enable();
-        }
-      }
-    }
-
-    // Update the icon property
-    if (!isObject(markerData.icon)) {
-      // If there is no icon property or it's not an object
-      if (isObject(oldMarkerData.icon)) {
-        // If there was an icon before restore to the default
-        marker.setIcon(createLeafletIcon());
-        marker.closePopup();
-        marker.unbindPopup();
-        if (isString(markerData.message)) {
-          marker.bindPopup(markerData.message, markerData.popupOptions);
-        }
-      }
-    }
-
-    if (isObject(markerData.icon) && isObject(oldMarkerData.icon) && !angular.equals(markerData.icon, oldMarkerData.icon)) {
-      var dragG = false;
-      if (marker.dragging) {
-        dragG = marker.dragging.enabled();
-      }
-
-      marker.setIcon(createLeafletIcon(markerData.icon));
-      if (dragG) {
-        marker.dragging.enable();
-      }
-
-      marker.closePopup();
-      marker.unbindPopup();
-      if (isString(markerData.message)) {
-        marker.bindPopup(markerData.message, markerData.popupOptions);
-
-        // if marker has been already focused, reopen popup
-        if (map.hasLayer(marker) && markerData.focus === true) {
-          marker.openPopup();
-        }
-      }
-    }
-
-    // Update the Popup message property
-    if (!isString(markerData.message) && isString(oldMarkerData.message)) {
-      marker.closePopup();
-      marker.unbindPopup();
-    }
-
-    // Update the label content or bind a new label if the old one has been removed.
-    if (Helpers.LabelPlugin.isLoaded()) {
-      if (isDefined(markerData.label) && isDefined(markerData.label.message)) {
-        if ('label' in oldMarkerData && 'message' in oldMarkerData.label && !angular.equals(markerData.label.message, oldMarkerData.label.message)) {
-          marker.updateLabelContent(markerData.label.message);
-        } else if (!angular.isFunction(marker.getLabel) || angular.isFunction(marker.getLabel) && !isDefined(marker.getLabel())) {
-          marker.bindLabel(markerData.label.message, markerData.label.options);
-          _manageOpenLabel(marker, markerData);
-        } else {
-          _manageOpenLabel(marker, markerData);
-        }
-      } else if (!('label' in markerData && !('message' in markerData.label))) {
-        if (angular.isFunction(marker.unbindLabel)) {
-          marker.unbindLabel();
-        }
-      }
-    }
-
-    // There is some text in the popup, so we must show the text or update existing
-    if (isString(markerData.message) && !isString(oldMarkerData.message)) {
-      // There was no message before so we create it
-      marker.bindPopup(markerData.message, markerData.popupOptions);
-    }
-
-    if (isString(markerData.message) && isString(oldMarkerData.message) && markerData.message !== oldMarkerData.message) {
-      // There was a different previous message so we update it
-      marker.setPopupContent(markerData.message);
-    }
-
-    // Update the focus property
-    var updatedFocus = false;
-    if (markerData.focus !== true && oldMarkerData.focus === true) {
-      // If there was a focus property and was true we turn it off
-      marker.closePopup();
-      updatedFocus = true;
-    }
-
-    // The markerData.focus property must be true so we update if there wasn't a previous value or it wasn't true
-    if (markerData.focus === true && (!isDefined(oldMarkerData.focus) || oldMarkerData.focus === false) || (isInitializing && markerData.focus === true)) {
-      // Reopen the popup when focus is still true
-      marker.openPopup();
-      updatedFocus = true;
-    }
-
-    // zIndexOffset adjustment
-    if (oldMarkerData.zIndexOffset !== markerData.zIndexOffset) {
-      marker.setZIndexOffset(markerData.zIndexOffset);
-    }
-
-    var markerLatLng = marker.getLatLng();
-    var isCluster = (isString(markerData.layer) && Helpers.MarkerClusterPlugin.is(layers.overlays[markerData.layer]));
-
-    // If the marker is in a cluster it has to be removed and added to the layer when the location is changed
-    if (isCluster) {
-      // The focus has changed even by a user click or programatically
-      if (updatedFocus) {
-        // We only have to update the location if it was changed programatically, because it was
-        // changed by a user drag the marker data has already been updated by the internal event
-        // listened by the directive
-        if ((markerData.lat !== oldMarkerData.lat) || (markerData.lng !== oldMarkerData.lng)) {
-          layers.overlays[markerData.layer].removeLayer(marker);
-          marker.setLatLng([markerData.lat, markerData.lng]);
-          layers.overlays[markerData.layer].addLayer(marker);
-        }
-      } else {
-        // The marker has possibly moved. It can be moved by a user drag (marker location and data are equal but old
-        // data is diferent) or programatically (marker location and data are diferent)
-        if ((markerLatLng.lat !== markerData.lat) || (markerLatLng.lng !== markerData.lng)) {
-          // The marker was moved by a user drag
-          layers.overlays[markerData.layer].removeLayer(marker);
-          marker.setLatLng([markerData.lat, markerData.lng]);
-          layers.overlays[markerData.layer].addLayer(marker);
-        } else if ((markerData.lat !== oldMarkerData.lat) || (markerData.lng !== oldMarkerData.lng)) {
-          // The marker was moved programatically
-          layers.overlays[markerData.layer].removeLayer(marker);
-          marker.setLatLng([markerData.lat, markerData.lng]);
-          layers.overlays[markerData.layer].addLayer(marker);
-        } else if (isObject(markerData.icon) && isObject(oldMarkerData.icon) && !angular.equals(markerData.icon, oldMarkerData.icon)) {
-          layers.overlays[markerData.layer].removeLayer(marker);
-          layers.overlays[markerData.layer].addLayer(marker);
-        }
-      }
-    } else if (markerLatLng.lat !== markerData.lat || markerLatLng.lng !== markerData.lng) {
-      marker.setLatLng([markerData.lat, markerData.lng]);
-    }
-  };
-
-  return {
-    resetMarkerGroup: _resetMarkerGroup,
-
-    resetMarkerGroups: _resetMarkerGroups,
-
-    deleteMarker: _deleteMarker,
-
-    manageOpenPopup: _manageOpenPopup,
-
-    manageOpenLabel: _manageOpenLabel,
-
-    createMarker: function(markerData) {
-      if (!isDefined(markerData) || !geoHlp.validateCoords(markerData)) {
-        $log.error(errorHeader + 'The marker definition is not valid.');
-        return;
-      }
-
-      var coords = geoHlp.getCoords(markerData);
-
-      if (!isDefined(coords)) {
-        $log.error(errorHeader + 'Unable to get coordinates from markerData.');
-        return;
-      }
-
-      var markerOptions = {
-        icon: createLeafletIcon(markerData.icon),
-        title: isDefined(markerData.title) ? markerData.title : '',
-        draggable: isDefined(markerData.draggable) ? markerData.draggable : false,
-        clickable: isDefined(markerData.clickable) ? markerData.clickable : true,
-        riseOnHover: isDefined(markerData.riseOnHover) ? markerData.riseOnHover : false,
-        zIndexOffset: isDefined(markerData.zIndexOffset) ? markerData.zIndexOffset : 0,
-        iconAngle: isDefined(markerData.iconAngle) ? markerData.iconAngle : 0,
-      };
-
-      // Add any other options not added above to markerOptions
-      for (var markerDatum in markerData) {
-        if (markerData.hasOwnProperty(markerDatum) && !markerOptions.hasOwnProperty(markerDatum)) {
-          markerOptions[markerDatum] = markerData[markerDatum];
-        }
-      }
-
-      var marker = new L.marker(coords, markerOptions);
-
-      if (!isString(markerData.message)) {
-        marker.unbindPopup();
-      }
-
-      return marker;
-    },
-
-    addMarkerToGroup: function(marker, groupName, groupOptions, map) {
-      if (!isString(groupName)) {
-        $log.error(errorHeader + 'The marker group you have specified is invalid.');
-        return;
-      }
-
-      if (!MarkerClusterPlugin.isLoaded()) {
-        $log.error(errorHeader + 'The MarkerCluster plugin is not loaded.');
-        return;
-      }
-
-      if (!isDefined(groups[groupName])) {
-        groups[groupName] = new L.MarkerClusterGroup(groupOptions);
-        map.addLayer(groups[groupName]);
-      }
-
-      groups[groupName].addLayer(marker);
-    },
-
-    listenMarkerEvents: function(marker, markerData, leafletScope, doWatch, map) {
-      marker.on('popupopen', function(/* event */) {
-        safeApply(leafletScope, function() {
-          if (isDefined(marker._popup) || isDefined(marker._popup._contentNode)) {
-            markerData.focus = true;
-            _manageOpenPopup(marker, markerData, map);//needed since markerData is now a copy
-          }
-        });
-      });
-
-      marker.on('popupclose', function(/* event */) {
-        safeApply(leafletScope, function() {
-          markerData.focus = false;
-        });
-      });
-
-      marker.on('add', function(/* event */) {
-        safeApply(leafletScope, function() {
-          if ('label' in markerData)
-              _manageOpenLabel(marker, markerData);
-        });
-      });
-    },
-
-    updateMarker: _updateMarker,
-
-    addMarkerWatcher: function(marker, name, leafletScope, layers, map, isDeepWatch) {
-      var markerWatchPath = Helpers.getObjectArrayPath('markers.' + name);
-      isDeepWatch = defaultTo(isDeepWatch, true);
-
-      var clearWatch = leafletScope.$watch(markerWatchPath, function(markerData, oldMarkerData) {
-        if (!isDefined(markerData)) {
-          _deleteMarker(marker, map, layers);
-          clearWatch();
-          return;
-        }
-
-        _updateMarker(markerData, oldMarkerData, marker, name, leafletScope, layers, map);
-      }, isDeepWatch);
-    },
-
-    string: _string,
-    log: _log,
-  };
-}]);
-
-angular.module('leaflet-directive').factory('leafletPathsHelpers', ["$rootScope", "$log", "leafletHelpers", function($rootScope, $log, leafletHelpers) {
-  var isDefined = leafletHelpers.isDefined;
-  var isArray = leafletHelpers.isArray;
-  var isNumber = leafletHelpers.isNumber;
-  var isValidPoint = leafletHelpers.isValidPoint;
-
-  var availableOptions = [
-
-      // Path options
-      'stroke', 'weight', 'color', 'opacity',
-      'fill', 'fillColor', 'fillOpacity',
-      'dashArray', 'lineCap', 'lineJoin', 'clickable',
-      'pointerEvents', 'className',
-
-      // Polyline options
-      'smoothFactor', 'noClip',
-  ];
-  function _convertToLeafletLatLngs(latlngs) {
-    return latlngs.filter(function(latlng) {
-      return isValidPoint(latlng);
-    }).map(function(latlng) {
-      return _convertToLeafletLatLng(latlng);
-    });
-  }
-
-  function _convertToLeafletLatLng(latlng) {
-    if (isArray(latlng)) {
-      return new L.LatLng(latlng[0], latlng[1]);
-    } else {
-      return new L.LatLng(latlng.lat, latlng.lng);
-    }
-  }
-
-  function _convertToLeafletMultiLatLngs(paths) {
-    return paths.map(function(latlngs) {
-      return _convertToLeafletLatLngs(latlngs);
-    });
-  }
-
-  function _getOptions(path, defaults) {
-    var options = {};
-    for (var i = 0; i < availableOptions.length; i++) {
-      var optionName = availableOptions[i];
-
-      if (isDefined(path[optionName])) {
-        options[optionName] = path[optionName];
-      } else if (isDefined(defaults.path[optionName])) {
-        options[optionName] = defaults.path[optionName];
-      }
-    }
-
-    return options;
-  }
-
-  var _updatePathOptions = function(path, data) {
-    var updatedStyle = {};
-    for (var i = 0; i < availableOptions.length; i++) {
-      var optionName = availableOptions[i];
-      if (isDefined(data[optionName])) {
-        updatedStyle[optionName] = data[optionName];
-      }
-    }
-
-    path.setStyle(data);
-  };
-
-  var _isValidPolyline = function(latlngs) {
-    if (!isArray(latlngs)) {
-      return false;
-    }
-
-    for (var i = 0; i < latlngs.length; i++) {
-      var point = latlngs[i];
-      if (!isValidPoint(point)) {
-        return false;
-      }
-    }
-
-    return true;
-  };
-
-  var pathTypes = {
-    polyline: {
-      isValid: function(pathData) {
-        var latlngs = pathData.latlngs;
-        return _isValidPolyline(latlngs);
-      },
-
-      createPath: function(options) {
-        return new L.Polyline([], options);
-      },
-
-      setPath: function(path, data) {
-        path.setLatLngs(_convertToLeafletLatLngs(data.latlngs));
-        _updatePathOptions(path, data);
-        return;
-      },
-    },
-    multiPolyline: {
-      isValid: function(pathData) {
-        var latlngs = pathData.latlngs;
-        if (!isArray(latlngs)) {
-          return false;
-        }
-
-        for (var i in latlngs) {
-          var polyline = latlngs[i];
-          if (!_isValidPolyline(polyline)) {
-            return false;
-          }
-        }
-
-        return true;
-      },
-
-      createPath: function(options) {
-        return new L.multiPolyline([[[0, 0], [1, 1]]], options);
-      },
-
-      setPath: function(path, data) {
-        path.setLatLngs(_convertToLeafletMultiLatLngs(data.latlngs));
-        _updatePathOptions(path, data);
-        return;
-      },
-    },
-    polygon: {
-      isValid: function(pathData) {
-        var latlngs = pathData.latlngs;
-        return _isValidPolyline(latlngs);
-      },
-
-      createPath: function(options) {
-        return new L.Polygon([], options);
-      },
-
-      setPath: function(path, data) {
-        path.setLatLngs(_convertToLeafletLatLngs(data.latlngs));
-        _updatePathOptions(path, data);
-        return;
-      },
-    },
-    multiPolygon: {
-      isValid: function(pathData) {
-        var latlngs = pathData.latlngs;
-
-        if (!isArray(latlngs)) {
-          return false;
-        }
-
-        for (var i in latlngs) {
-          var polyline = latlngs[i];
-          if (!_isValidPolyline(polyline)) {
-            return false;
-          }
-        }
-
-        return true;
-      },
-
-      createPath: function(options) {
-        return new L.MultiPolygon([[[0, 0], [1, 1], [0, 1]]], options);
-      },
-
-      setPath: function(path, data) {
-        path.setLatLngs(_convertToLeafletMultiLatLngs(data.latlngs));
-        _updatePathOptions(path, data);
-        return;
-      },
-    },
-    rectangle: {
-      isValid: function(pathData) {
-        var latlngs = pathData.latlngs;
-
-        if (!isArray(latlngs) || latlngs.length !== 2) {
-          return false;
-        }
-
-        for (var i in latlngs) {
-          var point = latlngs[i];
-          if (!isValidPoint(point)) {
-            return false;
-          }
-        }
-
-        return true;
-      },
-
-      createPath: function(options) {
-        return new L.Rectangle([[0, 0], [1, 1]], options);
-      },
-
-      setPath: function(path, data) {
-        path.setBounds(new L.LatLngBounds(_convertToLeafletLatLngs(data.latlngs)));
-        _updatePathOptions(path, data);
-      },
-    },
-    circle: {
-      isValid: function(pathData) {
-        var point = pathData.latlngs;
-        return isValidPoint(point) && isNumber(pathData.radius);
-      },
-
-      createPath: function(options) {
-        return new L.Circle([0, 0], 1, options);
-      },
-
-      setPath: function(path, data) {
-        path.setLatLng(_convertToLeafletLatLng(data.latlngs));
-        if (isDefined(data.radius)) {
-          path.setRadius(data.radius);
-        }
-
-        _updatePathOptions(path, data);
-      },
-    },
-    circleMarker: {
-      isValid: function(pathData) {
-        var point = pathData.latlngs;
-        return isValidPoint(point) && isNumber(pathData.radius);
-      },
-
-      createPath: function(options) {
-        return new L.CircleMarker([0, 0], options);
-      },
-
-      setPath: function(path, data) {
-        path.setLatLng(_convertToLeafletLatLng(data.latlngs));
-        if (isDefined(data.radius)) {
-          path.setRadius(data.radius);
-        }
-
-        _updatePathOptions(path, data);
-      },
-    },
-  };
-
-  var _getPathData = function(path) {
-    var pathData = {};
-    if (path.latlngs) {
-      pathData.latlngs = path.latlngs;
-    }
-
-    if (path.radius) {
-      pathData.radius = path.radius;
-    }
-
-    return pathData;
-  };
-
-  return {
-    setPathOptions: function(leafletPath, pathType, data) {
-      if (!isDefined(pathType)) {
-        pathType = 'polyline';
-      }
-
-      pathTypes[pathType].setPath(leafletPath, data);
-    },
-
-    createPath: function(name, path, defaults) {
-      if (!isDefined(path.type)) {
-        path.type = 'polyline';
-      }
-
-      var options = _getOptions(path, defaults);
-      var pathData = _getPathData(path);
-
-      if (!pathTypes[path.type].isValid(pathData)) {
-        $log.error('[AngularJS - Leaflet] Invalid data passed to the ' + path.type + ' path');
-        return;
-      }
-
-      return pathTypes[path.type].createPath(options);
-    },
-  };
-}]);
-
-angular.module('leaflet-directive')
-.service('leafletWatchHelpers', function() {
-
-  var _maybe = function(scope, watchFunctionName, thingToWatchStr, watchOptions, initCb) {
-    //watchOptions.isDeep is/should be ignored in $watchCollection
-    var unWatch = scope[watchFunctionName](thingToWatchStr, function(newValue, oldValue) {
-      initCb(newValue, oldValue);
-      if (!watchOptions.doWatch)
-          unWatch();
-    }, watchOptions.isDeep);
-
-    return unWatch;
-  };
-
-  /*
-  @name: maybeWatch
-  @description: Utility to watch something once or forever.
-  @returns unWatch function
-  @param watchOptions - see markersWatchOptions and or derrivatives. This object is used
-  to set watching to once and its watch depth.
-  */
-  var _maybeWatch = function(scope, thingToWatchStr, watchOptions, initCb) {
-    return _maybe(scope, '$watch', thingToWatchStr, watchOptions, initCb);
-  };
-
-  /*
-  @name: _maybeWatchCollection
-  @description: Utility to watch something once or forever.
-  @returns unWatch function
-  @param watchOptions - see markersWatchOptions and or derrivatives. This object is used
-  to set watching to once and its watch depth.
-  */
-  var _maybeWatchCollection = function(scope, thingToWatchStr, watchOptions, initCb) {
-    return _maybe(scope, '$watchCollection', thingToWatchStr, watchOptions, initCb);
-  };
-
-  return {
-    maybeWatch: _maybeWatch,
-    maybeWatchCollection: _maybeWatchCollection,
-  };
-});
-
-angular.module('leaflet-directive').factory('nominatimService', ["$q", "$http", "leafletHelpers", "leafletMapDefaults", function($q, $http, leafletHelpers, leafletMapDefaults) {
-  var isDefined = leafletHelpers.isDefined;
-
-  return {
-    query: function(address, mapId) {
-      var defaults = leafletMapDefaults.getDefaults(mapId);
-      var url = defaults.nominatim.server;
-      var df = $q.defer();
-
-      $http.get(url, { params: { format: 'json', limit: 1, q: address } }).success(function(data) {
-        if (data.length > 0 && isDefined(data[0].boundingbox)) {
-          df.resolve(data[0]);
-        } else {
-          df.reject('[Nominatim] Invalid address');
-        }
-      });
-
-      return df.promise;
-    },
-  };
-}]);
-
-angular.module('leaflet-directive').directive('bounds', ["$log", "$timeout", "$http", "leafletHelpers", "nominatimService", "leafletBoundsHelpers", function($log, $timeout, $http, leafletHelpers, nominatimService, leafletBoundsHelpers) {
-
-  return {
-    restrict: 'A',
-    scope: false,
-    replace: false,
-    require: ['leaflet'],
-
-    link: function(scope, element, attrs, controller) {
-      var isDefined = leafletHelpers.isDefined;
-      var createLeafletBounds = leafletBoundsHelpers.createLeafletBounds;
-      var leafletScope = controller[0].getLeafletScope();
-      var mapController = controller[0];
-      var errorHeader = leafletHelpers.errorHeader + ' [Bounds] ';
-
-      var emptyBounds = function(bounds) {
-        return (bounds._southWest.lat === 0 && bounds._southWest.lng === 0 &&
-                bounds._northEast.lat === 0 && bounds._northEast.lng === 0);
-      };
-
-      mapController.getMap().then(function(map) {
-        leafletScope.$on('boundsChanged', function(event) {
-          var scope = event.currentScope;
-          var bounds = map.getBounds();
-
-          if (emptyBounds(bounds) || scope.settingBoundsFromScope) {
-            return;
-          }
-
-          scope.settingBoundsFromLeaflet = true;
-          var newScopeBounds = {
-            northEast: {
-              lat: bounds._northEast.lat,
-              lng: bounds._northEast.lng,
-            },
-            southWest: {
-              lat: bounds._southWest.lat,
-              lng: bounds._southWest.lng,
-            },
-            options: bounds.options,
-          };
-          if (!angular.equals(scope.bounds, newScopeBounds)) {
-            scope.bounds = newScopeBounds;
-          }
-
-          $timeout(function() {
-            scope.settingBoundsFromLeaflet = false;
-          });
-        });
-
-        var lastNominatimQuery;
-        leafletScope.$watch('bounds', function(bounds) {
-          if (scope.settingBoundsFromLeaflet)
-              return;
-          if (isDefined(bounds.address) && bounds.address !== lastNominatimQuery) {
-            scope.settingBoundsFromScope = true;
-            nominatimService.query(bounds.address, attrs.id).then(function(data) {
-              var b = data.boundingbox;
-              var newBounds = [[b[0], b[2]], [b[1], b[3]]];
-              map.fitBounds(newBounds);
-            }, function(errMsg) {
-
-              $log.error(errorHeader + ' ' + errMsg + '.');
-            });
-
-            lastNominatimQuery = bounds.address;
-            $timeout(function() {
-              scope.settingBoundsFromScope = false;
-            });
-
-            return;
-          }
-
-          var leafletBounds = createLeafletBounds(bounds);
-          if (leafletBounds && !map.getBounds().equals(leafletBounds)) {
-            scope.settingBoundsFromScope = true;
-            map.fitBounds(leafletBounds, bounds.options);
-            $timeout(function() {
-              scope.settingBoundsFromScope = false;
-            });
-          }
-        }, true);
-      });
-    },
-  };
-}]);
-
-var centerDirectiveTypes = ['center', 'lfCenter'];
-var centerDirectives = {};
-
-centerDirectiveTypes.forEach(function(directiveName) {
-  centerDirectives[directiveName] = ['$log', '$q', '$location', '$timeout', 'leafletMapDefaults', 'leafletHelpers',
-      'leafletBoundsHelpers', 'leafletMapEvents',
-        function($log, $q, $location, $timeout, leafletMapDefaults, leafletHelpers,
-      leafletBoundsHelpers, leafletMapEvents) {
-
-      var isDefined = leafletHelpers.isDefined;
-      var isNumber = leafletHelpers.isNumber;
-      var isSameCenterOnMap = leafletHelpers.isSameCenterOnMap;
-      var safeApply = leafletHelpers.safeApply;
-      var isValidCenter = leafletHelpers.isValidCenter;
-      var isValidBounds = leafletBoundsHelpers.isValidBounds;
-      var isUndefinedOrEmpty = leafletHelpers.isUndefinedOrEmpty;
-      var errorHeader = leafletHelpers.errorHeader;
-
-      var shouldInitializeMapWithBounds = function(bounds, center) {
-        return isDefined(bounds) && isValidBounds(bounds) && isUndefinedOrEmpty(center);
-      };
-
-      var _leafletCenter;
-      return {
-        restrict: 'A',
-        scope: false,
-        replace: false,
-        require: 'leaflet',
-        controller: function() {
-          _leafletCenter = $q.defer();
-          this.getCenter = function() {
-            return _leafletCenter.promise;
-          };
-        },
-
-        link: function(scope, element, attrs, controller) {
-          var leafletScope = controller.getLeafletScope();
-          var centerModel = leafletScope[directiveName];
-
-          controller.getMap().then(function(map) {
-            var defaults = leafletMapDefaults.getDefaults(attrs.id);
-
-            if (attrs[directiveName].search('-') !== -1) {
-              $log.error(errorHeader + ' The "center" variable can\'t use a "-" on its key name: "' + attrs[directiveName] + '".');
-              map.setView([defaults.center.lat, defaults.center.lng], defaults.center.zoom);
-              return;
-            } else if (shouldInitializeMapWithBounds(leafletScope.bounds, centerModel)) {
-              map.fitBounds(leafletBoundsHelpers.createLeafletBounds(leafletScope.bounds), leafletScope.bounds.options);
-              centerModel = map.getCenter();
-              safeApply(leafletScope, function(scope) {
-                angular.extend(scope[directiveName], {
-                  lat: map.getCenter().lat,
-                  lng: map.getCenter().lng,
-                  zoom: map.getZoom(),
-                  autoDiscover: false,
-                });
-              });
-
-              safeApply(leafletScope, function(scope) {
-                var mapBounds = map.getBounds();
-                scope.bounds = {
-                  northEast: {
-                    lat: mapBounds._northEast.lat,
-                    lng: mapBounds._northEast.lng,
-                  },
-                  southWest: {
-                    lat: mapBounds._southWest.lat,
-                    lng: mapBounds._southWest.lng,
-                  },
-                };
-              });
-            } else if (!isDefined(centerModel)) {
-              $log.error(errorHeader + ' The "center" property is not defined in the main scope');
-              map.setView([defaults.center.lat, defaults.center.lng], defaults.center.zoom);
-              return;
-            } else if (!(isDefined(centerModel.lat) && isDefined(centerModel.lng)) && !isDefined(centerModel.autoDiscover)) {
-              angular.copy(defaults.center, centerModel);
-            }
-
-            var urlCenterHash;
-            var mapReady;
-            if (attrs.urlHashCenter === 'yes') {
-              var extractCenterFromUrl = function() {
-                var search = $location.search();
-                var centerParam;
-                if (isDefined(search.c)) {
-                  var cParam = search.c.split(':');
-                  if (cParam.length === 3) {
-                    centerParam = {
-                      lat: parseFloat(cParam[0]),
-                      lng: parseFloat(cParam[1]),
-                      zoom: parseInt(cParam[2], 10),
-                    };
-                  }
-                }
-
-                return centerParam;
-              };
-
-              urlCenterHash = extractCenterFromUrl();
-
-              leafletScope.$on('$locationChangeSuccess', function(event) {
-                var scope = event.currentScope;
-
-                //$log.debug("updated location...");
-                var urlCenter = extractCenterFromUrl();
-                if (isDefined(urlCenter) && !isSameCenterOnMap(urlCenter, map)) {
-                  //$log.debug("updating center model...", urlCenter);
-                  angular.extend(scope[directiveName], {
-                    lat: urlCenter.lat,
-                    lng: urlCenter.lng,
-                    zoom: urlCenter.zoom,
-                  });
-                }
-              });
-            }
-
-            leafletScope.$watch(directiveName, function(center) {
-              if (leafletScope.settingCenterFromLeaflet)
-                  return;
-
-              //$log.debug("updated center model...");
-              // The center from the URL has priority
-              if (isDefined(urlCenterHash)) {
-                angular.copy(urlCenterHash, center);
-                urlCenterHash = undefined;
-              }
-
-              if (!isValidCenter(center) && center.autoDiscover !== true) {
-                $log.warn(errorHeader + ' invalid \'center\'');
-
-                //map.setView([defaults.center.lat, defaults.center.lng], defaults.center.zoom);
-                return;
-              }
-
-              if (center.autoDiscover === true) {
-                if (!isNumber(center.zoom)) {
-                  map.setView([defaults.center.lat, defaults.center.lng], defaults.center.zoom);
-                }
-
-                if (isNumber(center.zoom) && center.zoom > defaults.center.zoom) {
-                  map.locate({
-                    setView: true,
-                    maxZoom: center.zoom,
-                  });
-                } else if (isDefined(defaults.maxZoom)) {
-                  map.locate({
-                    setView: true,
-                    maxZoom: defaults.maxZoom,
-                  });
-                } else {
-                  map.locate({
-                    setView: true,
-                  });
-                }
-
-                return;
-              }
-
-              if (mapReady && isSameCenterOnMap(center, map)) {
-                //$log.debug("no need to update map again.");
-                return;
-              }
-
-              //$log.debug("updating map center...", center);
-              leafletScope.settingCenterFromScope = true;
-              map.setView([center.lat, center.lng], center.zoom);
-              leafletMapEvents.notifyCenterChangedToBounds(leafletScope, map);
-              $timeout(function() {
-                leafletScope.settingCenterFromScope = false;
-
-                //$log.debug("allow center scope updates");
-              });
-            }, true);
-
-            map.whenReady(function() {
-              mapReady = true;
-            });
-
-            map.on('moveend', function(/* event */) {
-              // Resolve the center after the first map position
-              _leafletCenter.resolve();
-              leafletMapEvents.notifyCenterUrlHashChanged(leafletScope, map, attrs, $location.search());
-
-              //$log.debug("updated center on map...");
-              if (isSameCenterOnMap(centerModel, map) || leafletScope.settingCenterFromScope) {
-                //$log.debug("same center in model, no need to update again.");
-                return;
-              }
-
-              leafletScope.settingCenterFromLeaflet = true;
-              safeApply(leafletScope, function(scope) {
-                if (!leafletScope.settingCenterFromScope) {
-                  //$log.debug("updating center model...", map.getCenter(), map.getZoom());
-                  angular.extend(scope[directiveName], {
-                    lat: map.getCenter().lat,
-                    lng: map.getCenter().lng,
-                    zoom: map.getZoom(),
-                    autoDiscover: false,
-                  });
-                }
-
-                leafletMapEvents.notifyCenterChangedToBounds(leafletScope, map);
-                $timeout(function() {
-                  leafletScope.settingCenterFromLeaflet = false;
-                });
-              });
-            });
-
-            if (centerModel.autoDiscover === true) {
-              map.on('locationerror', function() {
-                $log.warn(errorHeader + ' The Geolocation API is unauthorized on this page.');
-                if (isValidCenter(centerModel)) {
-                  map.setView([centerModel.lat, centerModel.lng], centerModel.zoom);
-                  leafletMapEvents.notifyCenterChangedToBounds(leafletScope, map);
-                } else {
-                  map.setView([defaults.center.lat, defaults.center.lng], defaults.center.zoom);
-                  leafletMapEvents.notifyCenterChangedToBounds(leafletScope, map);
-                }
-              });
-            }
-          });
-        },
-      };
-    },
-    ];
-});
-
-centerDirectiveTypes.forEach(function(dirType) {
-  angular.module('leaflet-directive').directive(dirType, centerDirectives[dirType]);
-});
-
-angular.module('leaflet-directive').directive('controls', ["$log", "leafletHelpers", "leafletControlHelpers", function($log, leafletHelpers, leafletControlHelpers) {
-
-  return {
-    restrict: 'A',
-    scope: false,
-    replace: false,
-    require: '?^leaflet',
-
-    link: function(scope, element, attrs, controller) {
-      if (!controller) {
-        return;
-      }
-
-      var createControl = leafletControlHelpers.createControl;
-      var isValidControlType = leafletControlHelpers.isValidControlType;
-      var leafletScope  = controller.getLeafletScope();
-      var isDefined = leafletHelpers.isDefined;
-      var isArray = leafletHelpers.isArray;
-      var leafletControls = {};
-      var errorHeader = leafletHelpers.errorHeader + ' [Controls] ';
-
-      controller.getMap().then(function(map) {
-
-        leafletScope.$watchCollection('controls', function(newControls) {
-
-          // Delete controls from the array
-          for (var name in leafletControls) {
-            if (!isDefined(newControls[name])) {
-              if (map.hasControl(leafletControls[name])) {
-                map.removeControl(leafletControls[name]);
-              }
-
-              delete leafletControls[name];
-            }
-          }
-
-          for (var newName in newControls) {
-            var control;
-
-            var controlType = isDefined(newControls[newName].type) ? newControls[newName].type : newName;
-
-            if (!isValidControlType(controlType)) {
-              $log.error(errorHeader + ' Invalid control type: ' + controlType + '.');
-              return;
-            }
-
-            if (controlType !== 'custom') {
-              control = createControl(controlType, newControls[newName]);
-              map.addControl(control);
-              leafletControls[newName] = control;
-            } else {
-              var customControlValue = newControls[newName];
-              if (isArray(customControlValue)) {
-                for (var i in customControlValue) {
-                  var customControl = customControlValue[i];
-                  map.addControl(customControl);
-                  leafletControls[newName] = !isDefined(leafletControls[newName]) ? [customControl] : leafletControls[newName].concat([customControl]);
-                }
-              } else {
-                map.addControl(customControlValue);
-                leafletControls[newName] = customControlValue;
-              }
-            }
-          }
-
-        });
-
-      });
-    },
-  };
-}]);
-
-angular.module('leaflet-directive').directive('decorations', ["$log", "leafletHelpers", function($log, leafletHelpers) {
-
-  return {
-    restrict: 'A',
-    scope: false,
-    replace: false,
-    require: 'leaflet',
-
-    link: function(scope, element, attrs, controller) {
-      var leafletScope = controller.getLeafletScope();
-      var PolylineDecoratorPlugin = leafletHelpers.PolylineDecoratorPlugin;
-      var isDefined = leafletHelpers.isDefined;
-      var leafletDecorations = {};
-
-      /* Creates an "empty" decoration with a set of coordinates, but no pattern. */
-      function createDecoration(options) {
-        if (isDefined(options) && isDefined(options.coordinates)) {
-          if (!PolylineDecoratorPlugin.isLoaded()) {
-            $log.error('[AngularJS - Leaflet] The PolylineDecorator Plugin is not loaded.');
-          }
-        }
-
-        return L.polylineDecorator(options.coordinates);
-      }
-
-      /* Updates the path and the patterns for the provided decoration, and returns the decoration. */
-      function setDecorationOptions(decoration, options) {
-        if (isDefined(decoration) && isDefined(options)) {
-          if (isDefined(options.coordinates) && isDefined(options.patterns)) {
-            decoration.setPaths(options.coordinates);
-            decoration.setPatterns(options.patterns);
-            return decoration;
-          }
-        }
-      }
-
-      controller.getMap().then(function(map) {
-        leafletScope.$watch('decorations', function(newDecorations) {
-          for (var name in leafletDecorations) {
-            if (!isDefined(newDecorations[name]) || !angular.equals(newDecorations[name], leafletDecorations)) {
-              map.removeLayer(leafletDecorations[name]);
-              delete leafletDecorations[name];
-            }
-          }
-
-          for (var newName in newDecorations) {
-            var decorationData = newDecorations[newName];
-            var newDecoration = createDecoration(decorationData);
-
-            if (isDefined(newDecoration)) {
-              leafletDecorations[newName] = newDecoration;
-              map.addLayer(newDecoration);
-              setDecorationOptions(newDecoration, decorationData);
-            }
-          }
-        }, true);
-      });
-    },
-  };
-}]);
-
-angular.module('leaflet-directive').directive('eventBroadcast', ["$log", "$rootScope", "leafletHelpers", "leafletMapEvents", "leafletIterators", function($log, $rootScope, leafletHelpers, leafletMapEvents, leafletIterators) {
-
-  return {
-    restrict: 'A',
-    scope: false,
-    replace: false,
-    require: 'leaflet',
-
-    link: function(scope, element, attrs, controller) {
-      var isObject = leafletHelpers.isObject;
-      var isDefined = leafletHelpers.isDefined;
-      var leafletScope  = controller.getLeafletScope();
-      var eventBroadcast = leafletScope.eventBroadcast;
-      var availableMapEvents = leafletMapEvents.getAvailableMapEvents();
-      var addEvents = leafletMapEvents.addEvents;
-
-      controller.getMap().then(function(map) {
-
-        var mapEvents = [];
-        var logic = 'broadcast';
-
-        // We have a possible valid object
-        if (!isDefined(eventBroadcast.map)) {
-          // We do not have events enable/disable do we do nothing (all enabled by default)
-          mapEvents = availableMapEvents;
-        } else if (!isObject(eventBroadcast.map)) {
-          // Not a valid object
-          $log.warn('[AngularJS - Leaflet] event-broadcast.map must be an object check your model.');
-        } else {
-          // We have a possible valid map object
-          // Event propadation logic
-          if (eventBroadcast.map.logic !== 'emit' && eventBroadcast.map.logic !== 'broadcast') {
-            // This is an error
-            $log.warn('[AngularJS - Leaflet] Available event propagation logic are: \'emit\' or \'broadcast\'.');
-          } else {
-            logic = eventBroadcast.map.logic;
-          }
-
-          if (!(isObject(eventBroadcast.map.enable) && eventBroadcast.map.enable.length >= 0)) {
-            $log.warn('[AngularJS - Leaflet] event-broadcast.map.enable must be an object check your model.');
-          } else {
-            // Enable events
-            leafletIterators.each(eventBroadcast.map.enable, function(eventName) {
-              // Do we have already the event enabled?
-              if (mapEvents.indexOf(eventName) === -1 && availableMapEvents.indexOf(eventName) !== -1) {
-                mapEvents.push(eventName);
-              }
-            });
-          }
-
-        }
-
-        // as long as the map is removed in the root leaflet directive we
-        // do not need ot clean up the events as leaflet does it itself
-        addEvents(map, mapEvents, 'eventName', leafletScope, logic);
-      });
-    },
-  };
-}]);
-
-angular.module('leaflet-directive')
-.directive('geojson', ["$log", "$rootScope", "leafletData", "leafletHelpers", "leafletWatchHelpers", "leafletDirectiveControlsHelpers", "leafletIterators", "leafletGeoJsonEvents", function($log, $rootScope, leafletData, leafletHelpers,
-    leafletWatchHelpers, leafletDirectiveControlsHelpers, leafletIterators, leafletGeoJsonEvents) {
-  var _maybeWatch = leafletWatchHelpers.maybeWatch;
-  var _watchOptions = leafletHelpers.watchOptions;
-  var _extendDirectiveControls = leafletDirectiveControlsHelpers.extend;
-  var hlp = leafletHelpers;
-  var $it = leafletIterators;
-
-  return {
-    restrict: 'A',
-    scope: false,
-    replace: false,
-    require: 'leaflet',
-
-    link: function(scope, element, attrs, controller) {
-      var isDefined = leafletHelpers.isDefined;
-      var leafletScope  = controller.getLeafletScope();
-      var leafletGeoJSON = {};
-      var _hasSetLeafletData = false;
-
-      controller.getMap().then(function(map) {
-        var watchOptions = leafletScope.geojsonWatchOptions || _watchOptions;
-
-        var _hookUpEvents = function(geojson, maybeName) {
-          var onEachFeature;
-
-          if (angular.isFunction(geojson.onEachFeature)) {
-            onEachFeature = geojson.onEachFeature;
-          } else {
-            onEachFeature = function(feature, layer) {
-              if (leafletHelpers.LabelPlugin.isLoaded() && isDefined(feature.properties.description)) {
-                layer.bindLabel(feature.properties.description);
-              }
-
-              leafletGeoJsonEvents.bindEvents(attrs.id, layer, null, feature,
-                  leafletScope, maybeName,
-                  {resetStyleOnMouseout: geojson.resetStyleOnMouseout,
-                  mapId: attrs.id, });
-            };
-          }
-
-          return onEachFeature;
-        };
-
-        var isNested = (hlp.isDefined(attrs.geojsonNested) &&
-            hlp.isTruthy(attrs.geojsonNested));
-
-        var _clean = function() {
-          if (!leafletGeoJSON)
-              return;
-          var _remove = function(lObject) {
-            if (isDefined(lObject) && map.hasLayer(lObject)) {
-              map.removeLayer(lObject);
-            }
-          };
-
-          if (isNested) {
-            $it.each(leafletGeoJSON, function(lObject) {
-              _remove(lObject);
-            });
-
-            return;
-          }
-
-          _remove(leafletGeoJSON);
-        };
-
-        var _addGeojson = function(model, maybeName) {
-          var geojson = angular.copy(model);
-          if (!(isDefined(geojson) && isDefined(geojson.data))) {
-            return;
-          }
-
-          var onEachFeature = _hookUpEvents(geojson, maybeName);
-
-          if (!isDefined(geojson.options)) {
-            //right here is why we use a clone / copy (we modify and thus)
-            //would kick of a watcher.. we need to be more careful everywhere
-            //for stuff like this
-            geojson.options = {
-              style: geojson.style,
-              filter: geojson.filter,
-              onEachFeature: onEachFeature,
-              pointToLayer: geojson.pointToLayer,
-            };
-          }
-
-          var lObject = L.geoJson(geojson.data, geojson.options);
-
-          if (maybeName && hlp.isString(maybeName)) {
-            leafletGeoJSON[maybeName] = lObject;
-          }          else {
-            leafletGeoJSON = lObject;
-          }
-
-          lObject.addTo(map);
-
-          if (!_hasSetLeafletData) {//only do this once and play with the same ref forever
-            _hasSetLeafletData = true;
-            leafletData.setGeoJSON(leafletGeoJSON, attrs.id);
-          }
-        };
-
-        var _create = function(model) {
-          _clean();
-          if (isNested) {
-            if (!model || !Object.keys(model).length)
-                return;
-            $it.each(model, function(m, name) {
-              //name could be layerName and or groupName
-              //for now it is not tied to a layer
-              _addGeojson(m, name);
-            });
-
-            return;
-          }
-
-          _addGeojson(model);
-        };
-
-        _extendDirectiveControls(attrs.id, 'geojson', _create, _clean);
-
-        _maybeWatch(leafletScope, 'geojson', watchOptions, function(geojson) {
-          _create(geojson);
-        });
-      });
-    },
-  };
-}]);
-
-angular.module('leaflet-directive').directive('layercontrol', ["$filter", "$log", "leafletData", "leafletHelpers", function($filter, $log, leafletData, leafletHelpers) {
-
-  return {
-    restrict: 'E',
-    scope: {
-      icons: '=?',
-      autoHideOpacity: '=?', // Hide other opacity controls when one is activated.
-      showGroups: '=?', // Hide other opacity controls when one is activated.
-      title: '@',
-      baseTitle: '@',
-      overlaysTitle: '@',
-    },
-    replace: true,
-    transclude: false,
-    require: '^leaflet',
-    controller: ["$scope", "$element", "$sce", function($scope, $element, $sce) {
-      $log.debug('[Angular Directive - Layers] layers', $scope, $element);
-      var safeApply = leafletHelpers.safeApply;
-      var isDefined = leafletHelpers.isDefined;
-      angular.extend($scope, {
-        baselayer: '',
-        oldGroup: '',
-        layerProperties: {},
-        groupProperties: {},
-        rangeIsSupported: leafletHelpers.rangeIsSupported(),
-        changeBaseLayer: function(key, e) {
-          leafletHelpers.safeApply($scope, function(scp) {
-            scp.baselayer = key;
-            leafletData.getMap().then(function(map) {
-              leafletData.getLayers().then(function(leafletLayers) {
-                if (map.hasLayer(leafletLayers.baselayers[key])) {
-                  return;
-                }
-
-                for (var i in scp.layers.baselayers) {
-                  scp.layers.baselayers[i].icon = scp.icons.unradio;
-                  if (map.hasLayer(leafletLayers.baselayers[i])) {
-                    map.removeLayer(leafletLayers.baselayers[i]);
-                  }
-                }
-
-                map.addLayer(leafletLayers.baselayers[key]);
-                scp.layers.baselayers[key].icon = $scope.icons.radio;
-              });
-            });
-          });
-
-          e.preventDefault();
-        },
-
-        moveLayer: function(ly, newIndex, e) {
-          var delta = Object.keys($scope.layers.baselayers).length;
-          if (newIndex >= (1 + delta) && newIndex <= ($scope.overlaysArray.length + delta)) {
-            var oldLy;
-            for (var key in $scope.layers.overlays) {
-              if ($scope.layers.overlays[key].index === newIndex) {
-                oldLy = $scope.layers.overlays[key];
-                break;
-              }
-            }
-
-            if (oldLy) {
-              safeApply($scope, function() {
-                oldLy.index = ly.index;
-                ly.index = newIndex;
-              });
-            }
-          }
-
-          e.stopPropagation();
-          e.preventDefault();
-        },
-
-        initIndex: function(layer, idx) {
-          var delta = Object.keys($scope.layers.baselayers).length;
-          layer.index = isDefined(layer.index) ? layer.index : idx + delta + 1;
-        },
-
-        initGroup: function(groupName) {
-          $scope.groupProperties[groupName] = $scope.groupProperties[groupName] ? $scope.groupProperties[groupName] : {};
-        },
-
-        toggleOpacity: function(e, layer) {
-          if (layer.visible) {
-            if ($scope.autoHideOpacity && !$scope.layerProperties[layer.name].opacityControl) {
-              for (var k in $scope.layerProperties) {
-                $scope.layerProperties[k].opacityControl = false;
-              }
-            }
-
-            $scope.layerProperties[layer.name].opacityControl = !$scope.layerProperties[layer.name].opacityControl;
-          }
-
-          e.stopPropagation();
-          e.preventDefault();
-        },
-
-        toggleLegend: function(layer) {
-          $scope.layerProperties[layer.name].showLegend = !$scope.layerProperties[layer.name].showLegend;
-        },
-
-        showLegend: function(layer) {
-          return layer.legend && $scope.layerProperties[layer.name].showLegend;
-        },
-
-        unsafeHTML: function(html) {
-          return $sce.trustAsHtml(html);
-        },
-
-        getOpacityIcon: function(layer) {
-          return layer.visible && $scope.layerProperties[layer.name].opacityControl ? $scope.icons.close : $scope.icons.open;
-        },
-
-        getGroupIcon: function(group) {
-          return group.visible ? $scope.icons.check : $scope.icons.uncheck;
-        },
-
-        changeOpacity: function(layer) {
-          var op = $scope.layerProperties[layer.name].opacity;
-          leafletData.getMap().then(function(map) {
-            leafletData.getLayers().then(function(leafletLayers) {
-              var ly;
-              for (var k in $scope.layers.overlays) {
-                if ($scope.layers.overlays[k] === layer) {
-                  ly = leafletLayers.overlays[k];
-                  break;
-                }
-              }
-
-              if (map.hasLayer(ly)) {
-                if (ly.setOpacity) {
-                  ly.setOpacity(op / 100);
-                }
-
-                if (ly.getLayers && ly.eachLayer) {
-                  ly.eachLayer(function(lay) {
-                    if (lay.setOpacity) {
-                      lay.setOpacity(op / 100);
-                    }
-                  });
-                }
-              }
-            });
-          });
-        },
-
-        changeGroupVisibility: function(groupName) {
-          if (!isDefined($scope.groupProperties[groupName])) {
-            return;
-          }
-
-          var visible = $scope.groupProperties[groupName].visible;
-          for (var k in $scope.layers.overlays) {
-            var layer = $scope.layers.overlays[k];
-            if (layer.group === groupName) {
-              layer.visible = visible;
-            }
-          }
-        },
-      });
-
-      var div = $element.get(0);
-      if (!L.Browser.touch) {
-        L.DomEvent.disableClickPropagation(div);
-        L.DomEvent.on(div, 'mousewheel', L.DomEvent.stopPropagation);
-      } else {
-        L.DomEvent.on(div, 'click', L.DomEvent.stopPropagation);
-      }
-    }],
-
-    template:
-    '<div class="angular-leaflet-control-layers" ng-show="overlaysArray.length">' +
-        '<h4 ng-if="title">{{ title }}</h4>' +
-        '<div class="lf-baselayers">' +
-            '<h5 class="lf-title" ng-if="baseTitle">{{ baseTitle }}</h5>' +
-            '<div class="lf-row" ng-repeat="(key, layer) in baselayersArray">' +
-                '<label class="lf-icon-bl" ng-click="changeBaseLayer(key, $event)">' +
-                    '<input class="leaflet-control-layers-selector" type="radio" name="lf-radio" ' +
-                        'ng-show="false" ng-checked="baselayer === key" ng-value="key" /> ' +
-                    '<i class="lf-icon lf-icon-radio" ng-class="layer.icon"></i>' +
-                    '<div class="lf-text">{{layer.name}}</div>' +
-                '</label>' +
-            '</div>' +
-        '</div>' +
-        '<div class="lf-overlays">' +
-            '<h5 class="lf-title" ng-if="overlaysTitle">{{ overlaysTitle }}</h5>' +
-            '<div class="lf-container">' +
-                '<div class="lf-row" ng-repeat="layer in (o = (overlaysArray | orderBy:\'index\':order))" ng-init="initIndex(layer, $index)">' +
-                    '<label class="lf-icon-ol-group" ng-if="showGroups &amp;&amp; layer.group &amp;&amp; layer.group != o[$index-1].group">' +
-                        '<input class="lf-control-layers-selector" type="checkbox" ng-show="false" ' +
-                            'ng-change="changeGroupVisibility(layer.group)" ng-model="groupProperties[layer.group].visible"/> ' +
-                        '<i class="lf-icon lf-icon-check" ng-class="getGroupIcon(groupProperties[layer.group])"></i>' +
-                        '<div class="lf-text">{{ layer.group }}</div>' +
-                    '</label>' +
-                    '<label class="lf-icon-ol">' +
-                        '<input class="lf-control-layers-selector" type="checkbox" ng-show="false" ng-model="layer.visible"/> ' +
-                        '<i class="lf-icon lf-icon-check" ng-class="layer.icon"></i>' +
-                        '<div class="lf-text">{{layer.name}}</div>' +
-                    '</label>' +
-                    '<div class="lf-icons">' +
-                        '<i class="lf-icon lf-up" ng-class="icons.up" ng-click="moveLayer(layer, layer.index - orderNumber, $event)"></i> ' +
-                        '<i class="lf-icon lf-down" ng-class="icons.down" ng-click="moveLayer(layer, layer.index + orderNumber, $event)"></i> ' +
-                        '<i class="lf-icon lf-toggle-legend" ng-class="icons.toggleLegend" ng-if="layer.legend" ng-click="toggleLegend(layer)"></i> ' +
-                        '<i class="lf-icon lf-open" ng-class="getOpacityIcon(layer)" ng-click="toggleOpacity($event, layer)"></i>' +
-                    '</div>' +
-                    '<div class="lf-legend" ng-if="showLegend(layer)" ng-bind-html="unsafeHTML(layer.legend)"></div>' +
-                    '<div class="lf-opacity clearfix" ng-if="layer.visible &amp;&amp; layerProperties[layer.name].opacityControl">' +
-                        '<label ng-if="rangeIsSupported" class="pull-left" style="width: 50%">0</label>' +
-                        '<label ng-if="rangeIsSupported" class="pull-left text-right" style="width: 50%">100</label>' +
-                        '<input ng-if="rangeIsSupported" class="clearfix" type="range" min="0" max="100" class="lf-opacity-control" ' +
-                            'ng-model="layerProperties[layer.name].opacity" ng-change="changeOpacity(layer)"/>' +
-                        '<h6 ng-if="!rangeIsSupported">Range is not supported in this browser</h6>' +
-                    '</div>' +
-                '</div>' +
-            '</div>' +
-        '</div>' +
-    '</div>',
-    link: function(scope, element, attrs, controller) {
-      var isDefined = leafletHelpers.isDefined;
-      var leafletScope = controller.getLeafletScope();
-      var layers = leafletScope.layers;
-
-      scope.$watch('icons', function() {
-        var defaultIcons = {
-          uncheck: 'fa fa-square-o',
-          check: 'fa fa-check-square-o',
-          radio: 'fa fa-dot-circle-o',
-          unradio: 'fa fa-circle-o',
-          up: 'fa fa-angle-up',
-          down: 'fa fa-angle-down',
-          open: 'fa fa-angle-double-down',
-          close: 'fa fa-angle-double-up',
-          toggleLegend: 'fa fa-pencil-square-o',
-        };
-        if (isDefined(scope.icons)) {
-          angular.extend(defaultIcons, scope.icons);
-          angular.extend(scope.icons, defaultIcons);
-        } else {
-          scope.icons = defaultIcons;
-        }
-      });
-
-      // Setting layer stack order.
-      attrs.order = (isDefined(attrs.order) && (attrs.order === 'normal' || attrs.order === 'reverse')) ? attrs.order : 'normal';
-      scope.order = attrs.order === 'normal';
-      scope.orderNumber = attrs.order === 'normal' ? -1 : 1;
-
-      scope.layers = layers;
-      controller.getMap().then(function(map) {
-        leafletScope.$watch('layers.baselayers', function(newBaseLayers) {
-          var baselayersArray = {};
-          leafletData.getLayers().then(function(leafletLayers) {
-            var key;
-            for (key in newBaseLayers) {
-              var layer = newBaseLayers[key];
-              layer.icon = scope.icons[map.hasLayer(leafletLayers.baselayers[key]) ? 'radio' : 'unradio'];
-              baselayersArray[key] = layer;
-            }
-
-            scope.baselayersArray = baselayersArray;
-          });
-        });
-
-        leafletScope.$watch('layers.overlays', function(newOverlayLayers) {
-          var overlaysArray = [];
-          var groupVisibleCount = {};
-          leafletData.getLayers().then(function(leafletLayers) {
-            var key;
-            for (key in newOverlayLayers) {
-              var layer = newOverlayLayers[key];
-              layer.icon = scope.icons[(layer.visible ? 'check' : 'uncheck')];
-              overlaysArray.push(layer);
-              if (!isDefined(scope.layerProperties[layer.name])) {
-                scope.layerProperties[layer.name] = {
-                  opacity: isDefined(layer.layerOptions.opacity) ? layer.layerOptions.opacity * 100 : 100,
-                  opacityControl: false,
-                  showLegend: true,
-                };
-              }
-
-              if (isDefined(layer.group)) {
-                if (!isDefined(scope.groupProperties[layer.group])) {
-                  scope.groupProperties[layer.group] = {
-                    visible: false,
-                  };
-                }
-
-                groupVisibleCount[layer.group] = isDefined(groupVisibleCount[layer.group]) ? groupVisibleCount[layer.group] : {
-                  count: 0,
-                  visibles: 0,
-                };
-                groupVisibleCount[layer.group].count++;
-                if (layer.visible) {
-                  groupVisibleCount[layer.group].visibles++;
-                }
-              }
-
-              if (isDefined(layer.index) && leafletLayers.overlays[key].setZIndex) {
-                leafletLayers.overlays[key].setZIndex(newOverlayLayers[key].index);
-              }
-            }
-
-            for (key in groupVisibleCount) {
-              scope.groupProperties[key].visible = groupVisibleCount[key].visibles === groupVisibleCount[key].count;
-            }
-
-            scope.overlaysArray = overlaysArray;
-          });
-        }, true);
-      });
-    },
-  };
-}]);
-
-angular.module('leaflet-directive').directive('layers', ["$log", "$q", "leafletData", "leafletHelpers", "leafletLayerHelpers", "leafletControlHelpers", function($log, $q, leafletData, leafletHelpers, leafletLayerHelpers, leafletControlHelpers) {
-
-  return {
-    restrict: 'A',
-    scope: false,
-    replace: false,
-    require: 'leaflet',
-    controller: ["$scope", function($scope) {
-      $scope._leafletLayers = $q.defer();
-      this.getLayers = function() {
-        return $scope._leafletLayers.promise;
-      };
-    }],
-
-    link: function(scope, element, attrs, controller) {
-      var isDefined = leafletHelpers.isDefined;
-      var leafletLayers = {};
-      var leafletScope  = controller.getLeafletScope();
-      var layers = leafletScope.layers;
-      var createLayer = leafletLayerHelpers.createLayer;
-      var safeAddLayer = leafletLayerHelpers.safeAddLayer;
-      var safeRemoveLayer = leafletLayerHelpers.safeRemoveLayer;
-      var updateLayersControl = leafletControlHelpers.updateLayersControl;
-      var isLayersControlVisible = false;
-
-      controller.getMap().then(function(map) {
-
-        // We have baselayers to add to the map
-        scope._leafletLayers.resolve(leafletLayers);
-        leafletData.setLayers(leafletLayers, attrs.id);
-
-        leafletLayers.baselayers = {};
-        leafletLayers.overlays = {};
-
-        var mapId = attrs.id;
-
-        // Setup all baselayers definitions
-        var oneVisibleLayer = false;
-        for (var layerName in layers.baselayers) {
-          var newBaseLayer = createLayer(layers.baselayers[layerName]);
-          if (!isDefined(newBaseLayer)) {
-            delete layers.baselayers[layerName];
-            continue;
-          }
-
-          leafletLayers.baselayers[layerName] = newBaseLayer;
-
-          // Only add the visible layer to the map, layer control manages the addition to the map
-          // of layers in its control
-          if (layers.baselayers[layerName].top === true) {
-            safeAddLayer(map, leafletLayers.baselayers[layerName]);
-            oneVisibleLayer = true;
-          }
-        }
-
-        // If there is no visible layer add first to the map
-        if (!oneVisibleLayer && Object.keys(leafletLayers.baselayers).length > 0) {
-          safeAddLayer(map, leafletLayers.baselayers[Object.keys(layers.baselayers)[0]]);
-        }
-
-        // Setup the Overlays
-        for (layerName in layers.overlays) {
-          //if (layers.overlays[layerName].type === 'cartodb') {
-          //
-          //}
-
-          var newOverlayLayer = createLayer(layers.overlays[layerName]);
-          if (!isDefined(newOverlayLayer)) {
-            delete layers.overlays[layerName];
-            continue;
-          }
-
-          leafletLayers.overlays[layerName] = newOverlayLayer;
-
-          // Only add the visible overlays to the map
-          if (layers.overlays[layerName].visible === true) {
-            safeAddLayer(map, leafletLayers.overlays[layerName]);
-          }
-        }
-
-        // Watch for the base layers
-        leafletScope.$watch('layers.baselayers', function(newBaseLayers, oldBaseLayers) {
-          if (angular.equals(newBaseLayers, oldBaseLayers)) {
-            isLayersControlVisible = updateLayersControl(map, mapId, isLayersControlVisible, newBaseLayers, layers.overlays, leafletLayers);
-            return true;
-          }
-
-          // Delete layers from the array
-          for (var name in leafletLayers.baselayers) {
-            if (!isDefined(newBaseLayers[name]) || newBaseLayers[name].doRefresh) {
-              // Remove from the map if it's on it
-              if (map.hasLayer(leafletLayers.baselayers[name])) {
-                map.removeLayer(leafletLayers.baselayers[name]);
-              }
-
-              delete leafletLayers.baselayers[name];
-
-              if (newBaseLayers[name] && newBaseLayers[name].doRefresh) {
-                newBaseLayers[name].doRefresh = false;
-              }
-            }
-          }
-
-          // add new layers
-          for (var newName in newBaseLayers) {
-            if (!isDefined(leafletLayers.baselayers[newName])) {
-              var testBaseLayer = createLayer(newBaseLayers[newName]);
-              if (isDefined(testBaseLayer)) {
-                leafletLayers.baselayers[newName] = testBaseLayer;
-
-                // Only add the visible layer to the map
-                if (newBaseLayers[newName].top === true) {
-                  safeAddLayer(map, leafletLayers.baselayers[newName]);
-                }
-              }
-            } else {
-              if (newBaseLayers[newName].top === true && !map.hasLayer(leafletLayers.baselayers[newName])) {
-                safeAddLayer(map, leafletLayers.baselayers[newName]);
-              } else if (newBaseLayers[newName].top === false && map.hasLayer(leafletLayers.baselayers[newName])) {
-                map.removeLayer(leafletLayers.baselayers[newName]);
-              }
-            }
-          }
-
-          //we have layers, so we need to make, at least, one active
-          var found = false;
-
-          // search for an active layer
-          for (var key in leafletLayers.baselayers) {
-            if (map.hasLayer(leafletLayers.baselayers[key])) {
-              found = true;
-              break;
-            }
-          }
-
-          // If there is no active layer make one active
-          if (!found && Object.keys(leafletLayers.baselayers).length > 0) {
-            safeAddLayer(map, leafletLayers.baselayers[Object.keys(leafletLayers.baselayers)[0]]);
-          }
-
-          // Only show the layers switch selector control if we have more than one baselayer + overlay
-          isLayersControlVisible = updateLayersControl(map, mapId, isLayersControlVisible, newBaseLayers, layers.overlays, leafletLayers);
-        }, true);
-
-        // Watch for the overlay layers
-        leafletScope.$watch('layers.overlays', function(newOverlayLayers, oldOverlayLayers) {
-          if (angular.equals(newOverlayLayers, oldOverlayLayers)) {
-            isLayersControlVisible = updateLayersControl(map, mapId, isLayersControlVisible, layers.baselayers, newOverlayLayers, leafletLayers);
-            return true;
-          }
-
-          // Delete layers from the array
-          for (var name in leafletLayers.overlays) {
-            if (!isDefined(newOverlayLayers[name]) || newOverlayLayers[name].doRefresh) {
-              // Remove from the map if it's on it
-              if (map.hasLayer(leafletLayers.overlays[name])) {
-                // Safe remove when ArcGIS layers is loading.
-                var options = isDefined(newOverlayLayers[name]) ?
-                    newOverlayLayers[name].layerOptions : null;
-                safeRemoveLayer(map, leafletLayers.overlays[name], options);
-              }
-
-              // TODO: Depending on the layer type we will have to delete what's included on it
-              delete leafletLayers.overlays[name];
-
-              if (newOverlayLayers[name] && newOverlayLayers[name].doRefresh) {
-                newOverlayLayers[name].doRefresh = false;
-              }
-            }
-          }
-
-          // add new overlays
-          for (var newName in newOverlayLayers) {
-            if (!isDefined(leafletLayers.overlays[newName])) {
-              var testOverlayLayer = createLayer(newOverlayLayers[newName]);
-              if (!isDefined(testOverlayLayer)) {
-                // If the layer creation fails, continue to the next overlay
-                continue;
-              }
-
-              leafletLayers.overlays[newName] = testOverlayLayer;
-              if (newOverlayLayers[newName].visible === true) {
-                safeAddLayer(map, leafletLayers.overlays[newName]);
-              }
-            } else {
-              // check for the .visible property to hide/show overLayers
-              if (newOverlayLayers[newName].visible && !map.hasLayer(leafletLayers.overlays[newName])) {
-                safeAddLayer(map, leafletLayers.overlays[newName]);
-              } else if (newOverlayLayers[newName].visible === false && map.hasLayer(leafletLayers.overlays[newName])) {
-                // Safe remove when ArcGIS layers is loading.
-                safeRemoveLayer(map, leafletLayers.overlays[newName], newOverlayLayers[newName].layerOptions);
-              }
-            }
-
-            //refresh heatmap data if present
-            if (newOverlayLayers[newName].visible && map._loaded && newOverlayLayers[newName].data && newOverlayLayers[newName].type === 'heatmap') {
-              leafletLayers.overlays[newName].setData(newOverlayLayers[newName].data);
-              leafletLayers.overlays[newName].update();
-            }
-          }
-
-          // Only add the layers switch selector control if we have more than one baselayer + overlay
-          isLayersControlVisible = updateLayersControl(map, mapId, isLayersControlVisible, layers.baselayers, newOverlayLayers, leafletLayers);
-        }, true);
-      });
-    },
-  };
-}]);
-
-angular.module('leaflet-directive').directive('legend', ["$log", "$http", "leafletHelpers", "leafletLegendHelpers", function($log, $http, leafletHelpers, leafletLegendHelpers) {
-
-  return {
-    restrict: 'A',
-    scope: false,
-    replace: false,
-    require: 'leaflet',
-
-    link: function(scope, element, attrs, controller) {
-
-      var isArray = leafletHelpers.isArray;
-      var isDefined = leafletHelpers.isDefined;
-      var isFunction = leafletHelpers.isFunction;
-      var leafletScope = controller.getLeafletScope();
-      var legend = leafletScope.legend;
-
-      var legendClass;
-      var position;
-      var leafletLegend;
-      var type;
-
-      leafletScope.$watch('legend', function(newLegend) {
-
-        if (isDefined(newLegend)) {
-
-          legendClass = newLegend.legendClass ? newLegend.legendClass : 'legend';
-
-          position = newLegend.position || 'bottomright';
-
-          // default to arcgis
-          type = newLegend.type || 'arcgis';
-        }
-
-      }, true);
-
-      controller.getMap().then(function(map) {
-
-        leafletScope.$watch('legend', function(newLegend) {
-
-          if (!isDefined(newLegend)) {
-
-            if (isDefined(leafletLegend)) {
-              leafletLegend.removeFrom(map);
-              leafletLegend = null;
-            }
-
-            return;
-          }
-
-          if (!isDefined(newLegend.url) && (type === 'arcgis') && (!isArray(newLegend.colors) || !isArray(newLegend.labels) || newLegend.colors.length !== newLegend.labels.length)) {
-
-            $log.warn('[AngularJS - Leaflet] legend.colors and legend.labels must be set.');
-
-            return;
-          }
-
-          if (isDefined(newLegend.url)) {
-
-            $log.info('[AngularJS - Leaflet] loading legend service.');
-
-            return;
-          }
-
-          if (isDefined(leafletLegend)) {
-            leafletLegend.removeFrom(map);
-            leafletLegend = null;
-          }
-
-          leafletLegend = L.control({
-            position: position,
-          });
-          if (type === 'arcgis') {
-            leafletLegend.onAdd = leafletLegendHelpers.getOnAddArrayLegend(newLegend, legendClass);
-          }
-
-          leafletLegend.addTo(map);
-
-        });
-
-        leafletScope.$watch('legend.url', function(newURL) {
-
-          if (!isDefined(newURL)) {
-            return;
-          }
-
-          $http.get(newURL)
-                            .success(function(legendData) {
-
-                              if (isDefined(leafletLegend)) {
-
-                                leafletLegendHelpers.updateLegend(leafletLegend.getContainer(), legendData, type, newURL);
-
-                              } else {
-
-                                leafletLegend = L.control({
-                                  position: position,
-                                });
-                                leafletLegend.onAdd = leafletLegendHelpers.getOnAddLegend(legendData, legendClass, type, newURL);
-                                leafletLegend.addTo(map);
-                              }
-
-                              if (isDefined(legend.loadedData) && isFunction(legend.loadedData)) {
-                                legend.loadedData();
-                              }
-                            })
-                            .error(function() {
-                              $log.warn('[AngularJS - Leaflet] legend.url not loaded.');
-                            });
-        });
-
-      });
-    },
-  };
-}]);
-
-angular.module('leaflet-directive').directive('markers',
-    ["$log", "$rootScope", "$q", "leafletData", "leafletHelpers", "leafletMapDefaults", "leafletMarkersHelpers", "leafletMarkerEvents", "leafletIterators", "leafletWatchHelpers", "leafletDirectiveControlsHelpers", function($log, $rootScope, $q, leafletData, leafletHelpers, leafletMapDefaults,
-              leafletMarkersHelpers, leafletMarkerEvents, leafletIterators, leafletWatchHelpers,
-              leafletDirectiveControlsHelpers) {
-      //less terse vars to helpers
-      var isDefined = leafletHelpers.isDefined;
-      var errorHeader = leafletHelpers.errorHeader;
-      var Helpers = leafletHelpers;
-      var isString = leafletHelpers.isString;
-      var addMarkerWatcher = leafletMarkersHelpers.addMarkerWatcher;
-      var updateMarker = leafletMarkersHelpers.updateMarker;
-      var listenMarkerEvents = leafletMarkersHelpers.listenMarkerEvents;
-      var addMarkerToGroup = leafletMarkersHelpers.addMarkerToGroup;
-      var createMarker = leafletMarkersHelpers.createMarker;
-      var deleteMarker = leafletMarkersHelpers.deleteMarker;
-      var $it = leafletIterators;
-      var _markersWatchOptions = leafletHelpers.watchOptions;
-      var maybeWatch = leafletWatchHelpers.maybeWatch;
-      var extendDirectiveControls = leafletDirectiveControlsHelpers.extend;
-
-      var _getLMarker = function(leafletMarkers, name, maybeLayerName) {
-        if (!Object.keys(leafletMarkers).length) return;
-        if (maybeLayerName && isString(maybeLayerName)) {
-          if (!leafletMarkers[maybeLayerName] || !Object.keys(leafletMarkers[maybeLayerName]).length)
-              return;
-          return leafletMarkers[maybeLayerName][name];
-        }
-
-        return leafletMarkers[name];
-      };
-
-      var _setLMarker = function(lObject, leafletMarkers, name, maybeLayerName) {
-        if (maybeLayerName && isString(maybeLayerName)) {
-          if (!isDefined(leafletMarkers[maybeLayerName]))
-              leafletMarkers[maybeLayerName] = {};
-          leafletMarkers[maybeLayerName][name] = lObject;
-        } else
-            leafletMarkers[name] = lObject;
-        return lObject;
-      };
-
-      var _maybeAddMarkerToLayer = function(layerName, layers, model, marker, doIndividualWatch, map) {
-
-        if (!isString(layerName)) {
-          $log.error(errorHeader + ' A layername must be a string');
-          return false;
-        }
-
-        if (!isDefined(layers)) {
-          $log.error(errorHeader + ' You must add layers to the directive if the markers are going to use this functionality.');
-          return false;
-        }
-
-        if (!isDefined(layers.overlays) || !isDefined(layers.overlays[layerName])) {
-          $log.error(errorHeader + ' A marker can only be added to a layer of type "group"');
-          return false;
-        }
-
-        var layerGroup = layers.overlays[layerName];
-        if (!(layerGroup instanceof L.LayerGroup || layerGroup instanceof L.FeatureGroup)) {
-          $log.error(errorHeader + ' Adding a marker to an overlay needs a overlay of the type "group" or "featureGroup"');
-          return false;
-        }
-
-        // The marker goes to a correct layer group, so first of all we add it
-        layerGroup.addLayer(marker);
-
-        // The marker is automatically added to the map depending on the visibility
-        // of the layer, so we only have to open the popup if the marker is in the map
-        if (!doIndividualWatch && map.hasLayer(marker) && model.focus === true) {
-          marker.openPopup();
-        }
-
-        return true;
-      };
-
-      //TODO: move to leafletMarkersHelpers??? or make a new class/function file (leafletMarkersHelpers is large already)
-      var _addMarkers = function(mapId, markersToRender, oldModels, map, layers, leafletMarkers, leafletScope,
-                                 watchOptions, maybeLayerName, skips) {
-        for (var newName in markersToRender) {
-          if (skips[newName])
-              continue;
-
-          if (newName.search('-') !== -1) {
-            $log.error('The marker can\'t use a "-" on his key name: "' + newName + '".');
-            continue;
-          }
-
-          var model = Helpers.copy(markersToRender[newName]);
-          var pathToMarker = Helpers.getObjectDotPath(maybeLayerName ? [maybeLayerName, newName] : [newName]);
-          var maybeLMarker = _getLMarker(leafletMarkers, newName, maybeLayerName);
-          if (!isDefined(maybeLMarker)) {
-            //(nmccready) very important to not have model changes when lObject is changed
-            //this might be desirable in some cases but it causes two-way binding to lObject which is not ideal
-            //if it is left as the reference then all changes from oldModel vs newModel are ignored
-            //see _destroy (where modelDiff becomes meaningless if we do not copy here)
-            var marker = createMarker(model);
-            var layerName = (model ? model.layer : undefined) || maybeLayerName; //original way takes pref
-            if (!isDefined(marker)) {
-              $log.error(errorHeader + ' Received invalid data on the marker ' + newName + '.');
-              continue;
-            }
-
-            _setLMarker(marker, leafletMarkers, newName, maybeLayerName);
-
-            // Bind message
-            if (isDefined(model.message)) {
-              marker.bindPopup(model.message, model.popupOptions);
-            }
-
-            // Add the marker to a cluster group if needed
-            if (isDefined(model.group)) {
-              var groupOptions = isDefined(model.groupOption) ? model.groupOption : null;
-              addMarkerToGroup(marker, model.group, groupOptions, map);
-            }
-
-            // Show label if defined
-            if (Helpers.LabelPlugin.isLoaded() && isDefined(model.label) && isDefined(model.label.message)) {
-              marker.bindLabel(model.label.message, model.label.options);
-            }
-
-            // Check if the marker should be added to a layer
-            if (isDefined(model) && (isDefined(model.layer) || isDefined(maybeLayerName))) {
-
-              var pass = _maybeAddMarkerToLayer(layerName, layers, model, marker,
-                  watchOptions.individual.doWatch, map);
-              if (!pass)
-                  continue; //something went wrong move on in the loop
-            } else if (!isDefined(model.group)) {
-              // We do not have a layer attr, so the marker goes to the map layer
-              map.addLayer(marker);
-              if (!watchOptions.individual.doWatch && model.focus === true) {
-                marker.openPopup();
-              }
-            }
-
-            if (watchOptions.individual.doWatch) {
-              addMarkerWatcher(marker, pathToMarker, leafletScope, layers, map,
-                  watchOptions.individual.isDeep);
-            }
-
-            listenMarkerEvents(marker, model, leafletScope, watchOptions.individual.doWatch, map);
-            leafletMarkerEvents.bindEvents(mapId, marker, pathToMarker, model, leafletScope, layerName);
-          }          else {
-            var oldModel = isDefined(oldModel) ? oldModels[newName] : undefined;
-            updateMarker(model, oldModel, maybeLMarker, pathToMarker, leafletScope, layers, map);
-          }
-        }
-      };
-
-      var _seeWhatWeAlreadyHave = function(markerModels, oldMarkerModels, lMarkers, isEqual, cb) {
-        var hasLogged = false;
-        var equals = false;
-        var oldMarker;
-        var newMarker;
-
-        var doCheckOldModel =  isDefined(oldMarkerModels);
-        for (var name in lMarkers) {
-          if (!hasLogged) {
-            $log.debug(errorHeader + '[markers] destroy: ');
-            hasLogged = true;
-          }
-
-          if (doCheckOldModel) {
-            //might want to make the option (in watch options) to disable deep checking
-            //ie the options to only check !== (reference check) instead of angular.equals (slow)
-            newMarker = markerModels[name];
-            oldMarker = oldMarkerModels[name];
-            equals = angular.equals(newMarker, oldMarker) && isEqual;
-          }
-
-          if (!isDefined(markerModels) ||
-              !Object.keys(markerModels).length ||
-              !isDefined(markerModels[name]) ||
-              !Object.keys(markerModels[name]).length ||
-              equals) {
-            if (cb && Helpers.isFunction(cb))
-                cb(newMarker, oldMarker, name);
-          }
-        }
-      };
-
-      var _destroy = function(markerModels, oldMarkerModels, lMarkers, map, layers) {
-        _seeWhatWeAlreadyHave(markerModels, oldMarkerModels, lMarkers, false,
-            function(newMarker, oldMarker, lMarkerName) {
-              $log.debug(errorHeader + '[marker] is deleting marker: ' + lMarkerName);
-              deleteMarker(lMarkers[lMarkerName], map, layers);
-              delete lMarkers[lMarkerName];
-            });
-      };
-
-      var _getNewModelsToSkipp =  function(newModels, oldModels, lMarkers) {
-        var skips = {};
-        _seeWhatWeAlreadyHave(newModels, oldModels, lMarkers, true,
-            function(newMarker, oldMarker, lMarkerName) {
-              $log.debug(errorHeader + '[marker] is already rendered, marker: ' + lMarkerName);
-              skips[lMarkerName] = newMarker;
-            });
-
-        return skips;
-      };
-
-      return {
-        restrict: 'A',
-        scope: false,
-        replace: false,
-        require: ['leaflet', '?layers'],
-
-        link: function(scope, element, attrs, controller) {
-          var mapController = controller[0];
-          var leafletScope  = mapController.getLeafletScope();
-
-          mapController.getMap().then(function(map) {
-            var leafletMarkers = {};
-            var getLayers;
-
-            // If the layers attribute is used, we must wait until the layers are created
-            if (isDefined(controller[1])) {
-              getLayers = controller[1].getLayers;
-            } else {
-              getLayers = function() {
-                var deferred = $q.defer();
-                deferred.resolve();
-                return deferred.promise;
-              };
-            }
-
-            var watchOptions = leafletScope.markersWatchOptions || _markersWatchOptions;
-
-            // backwards compat
-            if (isDefined(attrs.watchMarkers))
-                watchOptions.doWatch = watchOptions.individual.doWatch =
-                    (!isDefined(attrs.watchMarkers) || Helpers.isTruthy(attrs.watchMarkers));
-
-            var isNested = (isDefined(attrs.markersNested) && Helpers.isTruthy(attrs.markersNested));
-
-            getLayers().then(function(layers) {
-              var _clean = function(models, oldModels) {
-                if (isNested) {
-                  $it.each(models, function(markerToMaybeDel, layerName) {
-                    var oldModel = isDefined(oldModel) ? oldModels[layerName] : undefined;
-                    _destroy(markerToMaybeDel, oldModel, leafletMarkers[layerName], map, layers);
-                  });
-
-                  return;
-                }
-
-                _destroy(models, oldModels, leafletMarkers, map, layers);
-              };
-
-              var _create = function(models, oldModels) {
-                _clean(models, oldModels);
-                var skips = null;
-                if (isNested) {
-                  $it.each(models, function(markersToAdd, layerName) {
-                    var oldModel = isDefined(oldModel) ? oldModels[layerName] : undefined;
-                    skips = _getNewModelsToSkipp(models[layerName], oldModel, leafletMarkers[layerName]);
-                    _addMarkers(attrs.id, markersToAdd, oldModels, map, layers, leafletMarkers, leafletScope,
-                        watchOptions, layerName, skips);
-                  });
-
-                  return;
-                }
-
-                skips = _getNewModelsToSkipp(models, oldModels, leafletMarkers);
-                _addMarkers(attrs.id, models, oldModels, map, layers, leafletMarkers, leafletScope,
-                    watchOptions, undefined, skips);
-              };
-
-              extendDirectiveControls(attrs.id, 'markers', _create, _clean);
-              leafletData.setMarkers(leafletMarkers, attrs.id);
-
-              maybeWatch(leafletScope, 'markers', watchOptions, function(newMarkers, oldMarkers) {
-                _create(newMarkers, oldMarkers);
-              });
-            });
-          });
-        },
-      };
-    }]);
-
-angular.module('leaflet-directive').directive('maxbounds', ["$log", "leafletMapDefaults", "leafletBoundsHelpers", "leafletHelpers", function($log, leafletMapDefaults, leafletBoundsHelpers, leafletHelpers) {
-
-  return {
-    restrict: 'A',
-    scope: false,
-    replace: false,
-    require: 'leaflet',
-
-    link: function(scope, element, attrs, controller) {
-      var leafletScope  = controller.getLeafletScope();
-      var isValidBounds = leafletBoundsHelpers.isValidBounds;
-      var isNumber = leafletHelpers.isNumber;
-
-      controller.getMap().then(function(map) {
-        leafletScope.$watch('maxbounds', function(maxbounds) {
-          if (!isValidBounds(maxbounds)) {
-            // Unset any previous maxbounds
-            map.setMaxBounds();
-            return;
-          }
-
-          var leafletBounds = leafletBoundsHelpers.createLeafletBounds(maxbounds);
-          if (isNumber(maxbounds.pad)) {
-            leafletBounds = leafletBounds.pad(maxbounds.pad);
-          }
-
-          map.setMaxBounds(leafletBounds);
-          if (!attrs.center && !attrs.lfCenter) {
-            map.fitBounds(leafletBounds);
-          }
-        });
-      });
-    },
-  };
-}]);
-
-angular.module('leaflet-directive').directive('paths', ["$log", "$q", "leafletData", "leafletMapDefaults", "leafletHelpers", "leafletPathsHelpers", "leafletPathEvents", function($log, $q, leafletData, leafletMapDefaults, leafletHelpers, leafletPathsHelpers, leafletPathEvents) {
-
-  return {
-    restrict: 'A',
-    scope: false,
-    replace: false,
-    require: ['leaflet', '?layers'],
-
-    link: function(scope, element, attrs, controller) {
-      var mapController = controller[0];
-      var isDefined = leafletHelpers.isDefined;
-      var isString = leafletHelpers.isString;
-      var leafletScope  = mapController.getLeafletScope();
-      var paths     = leafletScope.paths;
-      var createPath = leafletPathsHelpers.createPath;
-      var bindPathEvents = leafletPathEvents.bindPathEvents;
-      var setPathOptions = leafletPathsHelpers.setPathOptions;
-
-      mapController.getMap().then(function(map) {
-        var defaults = leafletMapDefaults.getDefaults(attrs.id);
-        var getLayers;
-
-        // If the layers attribute is used, we must wait until the layers are created
-        if (isDefined(controller[1])) {
-          getLayers = controller[1].getLayers;
-        } else {
-          getLayers = function() {
-            var deferred = $q.defer();
-            deferred.resolve();
-            return deferred.promise;
-          };
-        }
-
-        if (!isDefined(paths)) {
-          return;
-        }
-
-        getLayers().then(function(layers) {
-
-          var leafletPaths = {};
-          leafletData.setPaths(leafletPaths, attrs.id);
-
-          // Should we watch for every specific marker on the map?
-          var shouldWatch = (!isDefined(attrs.watchPaths) || attrs.watchPaths === 'true');
-
-          // Function for listening every single path once created
-          var watchPathFn = function(leafletPath, name) {
-            var clearWatch = leafletScope.$watch('paths["' + name + '"]', function(pathData, old) {
-              if (!isDefined(pathData)) {
-                if (isDefined(old.layer)) {
-                  for (var i in layers.overlays) {
-                    var overlay = layers.overlays[i];
-                    overlay.removeLayer(leafletPath);
-                  }
-                }
-
-                map.removeLayer(leafletPath);
-                clearWatch();
-                return;
-              }
-
-              setPathOptions(leafletPath, pathData.type, pathData);
-            }, true);
-          };
-
-          leafletScope.$watchCollection('paths', function(newPaths) {
-
-            // Delete paths (by name) from the array
-            for (var name in leafletPaths) {
-              if (!isDefined(newPaths[name])) {
-                map.removeLayer(leafletPaths[name]);
-                delete leafletPaths[name];
-              }
-            }
-
-            // Create the new paths
-            for (var newName in newPaths) {
-              if (newName.search('\\$') === 0) {
-                continue;
-              }
-
-              if (newName.search('-') !== -1) {
-                $log.error('[AngularJS - Leaflet] The path name "' + newName + '" is not valid. It must not include "-" and a number.');
-                continue;
-              }
-
-              if (!isDefined(leafletPaths[newName])) {
-                var pathData = newPaths[newName];
-                var newPath = createPath(newName, newPaths[newName], defaults);
-
-                // bind popup if defined
-                if (isDefined(newPath) && isDefined(pathData.message)) {
-                  newPath.bindPopup(pathData.message, pathData.popupOptions);
-                }
-
-                // Show label if defined
-                if (leafletHelpers.LabelPlugin.isLoaded() && isDefined(pathData.label) && isDefined(pathData.label.message)) {
-                  newPath.bindLabel(pathData.label.message, pathData.label.options);
-                }
-
-                // Check if the marker should be added to a layer
-                if (isDefined(pathData) && isDefined(pathData.layer)) {
-
-                  if (!isString(pathData.layer)) {
-                    $log.error('[AngularJS - Leaflet] A layername must be a string');
-                    continue;
-                  }
-
-                  if (!isDefined(layers)) {
-                    $log.error('[AngularJS - Leaflet] You must add layers to the directive if the markers are going to use this functionality.');
-                    continue;
-                  }
-
-                  if (!isDefined(layers.overlays) || !isDefined(layers.overlays[pathData.layer])) {
-                    $log.error('[AngularJS - Leaflet] A path can only be added to a layer of type "group"');
-                    continue;
-                  }
-
-                  var layerGroup = layers.overlays[pathData.layer];
-                  if (!(layerGroup instanceof L.LayerGroup || layerGroup instanceof L.FeatureGroup)) {
-                    $log.error('[AngularJS - Leaflet] Adding a path to an overlay needs a overlay of the type "group" or "featureGroup"');
-                    continue;
-                  }
-
-                  // Listen for changes on the new path
-                  leafletPaths[newName] = newPath;
-
-                  // The path goes to a correct layer group, so first of all we add it
-                  layerGroup.addLayer(newPath);
-
-                  if (shouldWatch) {
-                    watchPathFn(newPath, newName);
-                  } else {
-                    setPathOptions(newPath, pathData.type, pathData);
-                  }
-                } else if (isDefined(newPath)) {
-                  // Listen for changes on the new path
-                  leafletPaths[newName] = newPath;
-                  map.addLayer(newPath);
-
-                  if (shouldWatch) {
-                    watchPathFn(newPath, newName);
-                  } else {
-                    setPathOptions(newPath, pathData.type, pathData);
-                  }
-                }
-
-                bindPathEvents(attrs.id, newPath, newName, pathData, leafletScope);
-              }
-            }
-          });
-        });
-      });
-    },
-  };
-}]);
-
-angular.module('leaflet-directive').directive('tiles', ["$log", "leafletData", "leafletMapDefaults", "leafletHelpers", function($log, leafletData, leafletMapDefaults, leafletHelpers) {
-
-  return {
-    restrict: 'A',
-    scope: false,
-    replace: false,
-    require: 'leaflet',
-
-    link: function(scope, element, attrs, controller) {
-      var isDefined = leafletHelpers.isDefined;
-      var leafletScope  = controller.getLeafletScope();
-      var tiles = leafletScope.tiles;
-
-      if (!isDefined(tiles) ||  !isDefined(tiles.url)) {
-        $log.warn('[AngularJS - Leaflet] The \'tiles\' definition doesn\'t have the \'url\' property.');
-        return;
-      }
-
-      controller.getMap().then(function(map) {
-        var defaults = leafletMapDefaults.getDefaults(attrs.id);
-        var tileLayerObj;
-        leafletScope.$watch('tiles', function(tiles, oldtiles) {
-          var tileLayerOptions = defaults.tileLayerOptions;
-          var tileLayerUrl = defaults.tileLayer;
-
-          // If no valid tiles are in the scope, remove the last layer
-          if (!isDefined(tiles.url) && isDefined(tileLayerObj)) {
-            map.removeLayer(tileLayerObj);
-            return;
-          }
-
-          // No leafletTiles object defined yet
-          if (!isDefined(tileLayerObj)) {
-            if (isDefined(tiles.options)) {
-              angular.copy(tiles.options, tileLayerOptions);
-            }
-
-            if (isDefined(tiles.url)) {
-              tileLayerUrl = tiles.url;
-            }
-
-            if (tiles.type === 'wms') {
-              tileLayerObj = L.tileLayer.wms(tileLayerUrl, tileLayerOptions);
-            } else {
-              tileLayerObj = L.tileLayer(tileLayerUrl, tileLayerOptions);
-            }
-
-            tileLayerObj.addTo(map);
-            leafletData.setTiles(tileLayerObj, attrs.id);
-            return;
-          }
-
-          // If the options of the tilelayer is changed, we need to redraw the layer
-          if (isDefined(tiles.url) && isDefined(tiles.options) &&
-              (tiles.type !== oldtiles.type || !angular.equals(tiles.options, tileLayerOptions))) {
-            map.removeLayer(tileLayerObj);
-            tileLayerOptions = defaults.tileLayerOptions;
-            angular.copy(tiles.options, tileLayerOptions);
-            tileLayerUrl = tiles.url;
-
-            if (tiles.type === 'wms') {
-              tileLayerObj = L.tileLayer.wms(tileLayerUrl, tileLayerOptions);
-            } else {
-              tileLayerObj = L.tileLayer(tileLayerUrl, tileLayerOptions);
-            }
-
-            tileLayerObj.addTo(map);
-            leafletData.setTiles(tileLayerObj, attrs.id);
-            return;
-          }
-
-          // Only the URL of the layer is changed, update the tiles object
-          if (isDefined(tiles.url)) {
-            tileLayerObj.setUrl(tiles.url);
-          }
-        }, true);
-      });
-    },
-  };
-}]);
-
-/*
-    Create multiple similar directives for watchOptions to support directiveControl
-    instead. (when watches are disabled)
-    NgAnnotate does not work here due to the functional creation
-*/
-['markers', 'geojson'].forEach(function(name) {
-  angular.module('leaflet-directive').directive(name + 'WatchOptions', [
-      '$log', '$rootScope', '$q', 'leafletData', 'leafletHelpers',
-        function($log, $rootScope, $q, leafletData, leafletHelpers) {
-
-          var isDefined = leafletHelpers.isDefined,
-              errorHeader = leafletHelpers.errorHeader,
-              isObject = leafletHelpers.isObject,
-              _watchOptions = leafletHelpers.watchOptions;
-
-          return {
-            restrict: 'A',
-            scope: false,
-            replace: false,
-            require: ['leaflet'],
-
-            link: function(scope, element, attrs, controller) {
-              var mapController = controller[0],
-                  leafletScope = mapController.getLeafletScope();
-
-              mapController.getMap().then(function() {
-                if (isDefined(scope[name + 'WatchOptions'])) {
-                  if (isObject(scope[name + 'WatchOptions']))
-                      angular.extend(_watchOptions, scope[name + 'WatchOptions']);
-                  else
-                      $log.error(errorHeader + '[' + name + 'WatchOptions] is not an object');
-                  leafletScope[name + 'WatchOptions'] = _watchOptions;
-                }
-              });
-            },
-          };
-        },]);
-});
-
-angular.module('leaflet-directive')
-.factory('LeafletEventsHelpersFactory', ["$rootScope", "$q", "$log", "leafletHelpers", function($rootScope, $q, $log, leafletHelpers) {
-  var safeApply = leafletHelpers.safeApply;
-  var isDefined = leafletHelpers.isDefined;
-  var isObject = leafletHelpers.isObject;
-  var isArray = leafletHelpers.isArray;
-  var errorHeader = leafletHelpers.errorHeader;
-
-  var EventsHelper = function(rootBroadcastName, lObjectType) {
-    this.rootBroadcastName = rootBroadcastName;
-    $log.debug('LeafletEventsHelpersFactory: lObjectType: ' + lObjectType + 'rootBroadcastName: ' + rootBroadcastName);
-
-    //used to path/key out certain properties based on the type , "markers", "geojson"
-    this.lObjectType = lObjectType;
-  };
-
-  EventsHelper.prototype.getAvailableEvents = function() {return [];};
-
-  /*
-   argument: name: Note this can be a single string or dot notation
-   Example:
-   markerModel : {
-   m1: { lat:_, lon: _}
-   }
-   //would yield name of
-   name = "m1"
-
-   If nested:
-   markerModel : {
-   cars: {
-   m1: { lat:_, lon: _}
-   }
-   }
-   //would yield name of
-   name = "cars.m1"
-   */
-  EventsHelper.prototype.genDispatchEvent = function(maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName, extra) {
-    var _this = this;
-
-    maybeMapId = maybeMapId || '';
-    if (maybeMapId)
-      maybeMapId = '.' + maybeMapId;
-
-    return function(e) {
-      var broadcastName = _this.rootBroadcastName + maybeMapId + '.' + eventName;
-      $log.debug(broadcastName);
-      _this.fire(leafletScope, broadcastName, logic, e, e.target || lObject, model, name, layerName, extra);
-    };
-  };
-
-  EventsHelper.prototype.fire = function(scope, broadcastName, logic, event, lObject, model, modelName, layerName) {
-    // Safely broadcast the event
-    safeApply(scope, function() {
-      var toSend = {
-        leafletEvent: event,
-        leafletObject: lObject,
-        modelName: modelName,
-        model: model,
-      };
-      if (isDefined(layerName))
-          angular.extend(toSend, {layerName: layerName});
-
-      if (logic === 'emit') {
-        scope.$emit(broadcastName, toSend);
-      } else {
-        $rootScope.$broadcast(broadcastName, toSend);
-      }
-    });
-  };
-
-  EventsHelper.prototype.bindEvents = function(maybeMapId, lObject, name, model, leafletScope, layerName, extra) {
-    var events = [];
-    var logic = 'emit';
-    var _this = this;
-
-    if (!isDefined(leafletScope.eventBroadcast)) {
-      // Backward compatibility, if no event-broadcast attribute, all events are broadcasted
-      events = this.getAvailableEvents();
-    } else if (!isObject(leafletScope.eventBroadcast)) {
-      // Not a valid object
-      $log.error(errorHeader + 'event-broadcast must be an object check your model.');
-    } else {
-      // We have a possible valid object
-      if (!isDefined(leafletScope.eventBroadcast[_this.lObjectType])) {
-        // We do not have events enable/disable do we do nothing (all enabled by default)
-        events = this.getAvailableEvents();
-      } else if (!isObject(leafletScope.eventBroadcast[_this.lObjectType])) {
-        // Not a valid object
-        $log.warn(errorHeader + 'event-broadcast.' + [_this.lObjectType]  + ' must be an object check your model.');
-      } else {
-        // We have a possible valid map object
-        // Event propadation logic
-        if (isDefined(leafletScope.eventBroadcast[this.lObjectType].logic)) {
-          // We take care of possible propagation logic
-          if (leafletScope.eventBroadcast[_this.lObjectType].logic !== 'emit' &&
-              leafletScope.eventBroadcast[_this.lObjectType].logic !== 'broadcast')
-                  $log.warn(errorHeader + 'Available event propagation logic are: \'emit\' or \'broadcast\'.');
-        }
-
-        // Enable / Disable
-        var eventsEnable = false;
-        var eventsDisable = false;
-        if (isDefined(leafletScope.eventBroadcast[_this.lObjectType].enable) &&
-            isArray(leafletScope.eventBroadcast[_this.lObjectType].enable))
-                eventsEnable = true;
-        if (isDefined(leafletScope.eventBroadcast[_this.lObjectType].disable) &&
-            isArray(leafletScope.eventBroadcast[_this.lObjectType].disable))
-                eventsDisable = true;
-
-        if (eventsEnable && eventsDisable) {
-          // Both are active, this is an error
-          $log.warn(errorHeader + 'can not enable and disable events at the same time');
-        } else if (!eventsEnable && !eventsDisable) {
-          // Both are inactive, this is an error
-          $log.warn(errorHeader + 'must enable or disable events');
-        } else {
-          // At this point the object is OK, lets enable or disable events
-          if (eventsEnable) {
-            // Enable events
-            leafletScope.eventBroadcast[this.lObjectType].enable.forEach(function(eventName) {
-              // Do we have already the event enabled?
-              if (events.indexOf(eventName) !== -1) {
-                // Repeated event, this is an error
-                $log.warn(errorHeader + 'This event ' + eventName + ' is already enabled');
-              } else {
-                // Does the event exists?
-                if (_this.getAvailableEvents().indexOf(eventName) === -1) {
-                  // The event does not exists, this is an error
-                  $log.warn(errorHeader + 'This event ' + eventName + ' does not exist');
-                } else {
-                  // All ok enable the event
-                  events.push(eventName);
-                }
-              }
-            });
-          } else {
-            // Disable events
-            events = this.getAvailableEvents();
-            leafletScope.eventBroadcast[_this.lObjectType].disable.forEach(function(eventName) {
-              var index = events.indexOf(eventName);
-              if (index === -1) {
-                // The event does not exist
-                $log.warn(errorHeader + 'This event ' + eventName + ' does not exist or has been already disabled');
-
-              } else {
-                events.splice(index, 1);
-              }
-            });
-          }
-        }
-      }
-    }
-
-    events.forEach(function(eventName) {
-      lObject.on(eventName, _this.genDispatchEvent(maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName, extra));
-    });
-
-    return logic;
-  };
-
-  return EventsHelper;
-}])
-.service('leafletEventsHelpers', ["LeafletEventsHelpersFactory", function(LeafletEventsHelpersFactory) {
-  return new LeafletEventsHelpersFactory();
-}]);
-
-angular.module('leaflet-directive')
-.factory('leafletGeoJsonEvents', ["$rootScope", "$q", "$log", "leafletHelpers", "LeafletEventsHelpersFactory", "leafletData", function($rootScope, $q, $log, leafletHelpers,
-  LeafletEventsHelpersFactory, leafletData) {
-  var safeApply = leafletHelpers.safeApply;
-  var EventsHelper = LeafletEventsHelpersFactory;
-
-  var GeoJsonEvents = function() {
-      EventsHelper.call(this, 'leafletDirectiveGeoJson', 'geojson');
-    };
-
-  GeoJsonEvents.prototype =  new EventsHelper();
-
-  GeoJsonEvents.prototype.genDispatchEvent = function(maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName, extra) {
-    var base = EventsHelper.prototype.genDispatchEvent.call(this, maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName);
-    var _this = this;
-
-    return function(e) {
-      if (eventName === 'mouseout') {
-        if (extra.resetStyleOnMouseout) {
-          leafletData.getGeoJSON(extra.mapId)
-                    .then(function(leafletGeoJSON) {
-                      //this is broken on nested needs to traverse or user layerName (nested)
-                      var lobj = layerName ? leafletGeoJSON[layerName] : leafletGeoJSON;
-                      lobj.resetStyle(e.target);
-                    });
-
-        }
-
-        safeApply(leafletScope, function() {
-          $rootScope.$broadcast(_this.rootBroadcastName + '.mouseout', e);
-        });
-      }
-
-      base(e); //common
-    };
-  };
-
-  GeoJsonEvents.prototype.getAvailableEvents = function() { return [
-      'click',
-      'dblclick',
-      'mouseover',
-      'mouseout',
-      ];
-  };
-
-  return new GeoJsonEvents();
-}]);
-
-angular.module('leaflet-directive')
-.factory('leafletLabelEvents', ["$rootScope", "$q", "$log", "leafletHelpers", "LeafletEventsHelpersFactory", function($rootScope, $q, $log, leafletHelpers, LeafletEventsHelpersFactory) {
-  var Helpers = leafletHelpers;
-  var EventsHelper = LeafletEventsHelpersFactory;
-
-  var LabelEvents = function() {
-          EventsHelper.call(this, 'leafletDirectiveLabel', 'markers');
-        };
-
-  LabelEvents.prototype =  new EventsHelper();
-
-  LabelEvents.prototype.genDispatchEvent = function(maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName) {
-    var markerName = name.replace('markers.', '');
-    return EventsHelper.prototype
-        .genDispatchEvent.call(this, maybeMapId, eventName, logic, leafletScope, lObject, markerName, model, layerName);
-  };
-
-  LabelEvents.prototype.getAvailableEvents = function() {
-    return [
-        'click',
-        'dblclick',
-        'mousedown',
-        'mouseover',
-        'mouseout',
-        'contextmenu',
-    ];
-  };
-
-  LabelEvents.prototype.genEvents = function(maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName) {
-    var _this = this;
-    var labelEvents = this.getAvailableEvents();
-    var scopeWatchName = Helpers.getObjectArrayPath('markers.' + name);
-    labelEvents.forEach(function(eventName) {
-      lObject.label.on(eventName, _this.genDispatchEvent(
-          maybeMapId, eventName, logic, leafletScope, lObject.label, scopeWatchName, model, layerName));
-    });
-  };
-
-  LabelEvents.prototype.bindEvents = function() {};
-
-  return new LabelEvents();
-}]);
-
-angular.module('leaflet-directive')
-.factory('leafletMapEvents', ["$rootScope", "$q", "$log", "leafletHelpers", "leafletEventsHelpers", "leafletIterators", function($rootScope, $q, $log, leafletHelpers, leafletEventsHelpers, leafletIterators) {
-  var isDefined = leafletHelpers.isDefined;
-  var fire = leafletEventsHelpers.fire;
-
-  var _getAvailableMapEvents = function() {
-    return [
-        'click',
-        'dblclick',
-        'mousedown',
-        'mouseup',
-        'mouseover',
-        'mouseout',
-        'mousemove',
-        'contextmenu',
-        'focus',
-        'blur',
-        'preclick',
-        'load',
-        'unload',
-        'viewreset',
-        'movestart',
-        'move',
-        'moveend',
-        'dragstart',
-        'drag',
-        'dragend',
-        'zoomstart',
-        'zoomanim',
-        'zoomend',
-        'zoomlevelschange',
-        'resize',
-        'autopanstart',
-        'layeradd',
-        'layerremove',
-        'baselayerchange',
-        'overlayadd',
-        'overlayremove',
-        'locationfound',
-        'locationerror',
-        'popupopen',
-        'popupclose',
-        'draw:created',
-        'draw:edited',
-        'draw:deleted',
-        'draw:drawstart',
-        'draw:drawstop',
-        'draw:editstart',
-        'draw:editstop',
-        'draw:deletestart',
-        'draw:deletestop',
-    ];
-  };
-
-  var _genDispatchMapEvent = function(scope, eventName, logic, maybeMapId) {
-    if (maybeMapId)
-      maybeMapId = maybeMapId + '.';
-    return function(e) {
-      // Put together broadcast name
-      var broadcastName = 'leafletDirectiveMap.' + maybeMapId + eventName;
-      $log.debug(broadcastName);
-
-      // Safely broadcast the event
-      fire(scope, broadcastName, logic, e, e.target, scope);
-    };
-  };
-
-  var _notifyCenterChangedToBounds = function(scope) {
-    scope.$broadcast('boundsChanged');
-  };
-
-  var _notifyCenterUrlHashChanged = function(scope, map, attrs, search) {
-    if (!isDefined(attrs.urlHashCenter)) {
-      return;
-    }
-
-    var center = map.getCenter();
-    var centerUrlHash = (center.lat).toFixed(4) + ':' + (center.lng).toFixed(4) + ':' + map.getZoom();
-    if (!isDefined(search.c) || search.c !== centerUrlHash) {
-      //$log.debug("notified new center...");
-      scope.$emit('centerUrlHash', centerUrlHash);
-    }
-  };
-
-  var _addEvents =  function(map, mapEvents, contextName, scope, logic) {
-    leafletIterators.each(mapEvents, function(eventName) {
-      var context = {};
-      context[contextName] = eventName;
-      map.on(eventName, _genDispatchMapEvent(scope, eventName, logic, map._container.id || ''), context);
-    });
-  };
-
-  return {
-    getAvailableMapEvents: _getAvailableMapEvents,
-    genDispatchMapEvent: _genDispatchMapEvent,
-    notifyCenterChangedToBounds: _notifyCenterChangedToBounds,
-    notifyCenterUrlHashChanged: _notifyCenterUrlHashChanged,
-    addEvents: _addEvents,
-  };
-}]);
-
-angular.module('leaflet-directive')
-.factory('leafletMarkerEvents', ["$rootScope", "$q", "$log", "leafletHelpers", "LeafletEventsHelpersFactory", "leafletLabelEvents", function($rootScope, $q, $log, leafletHelpers, LeafletEventsHelpersFactory, leafletLabelEvents) {
-  var safeApply = leafletHelpers.safeApply;
-  var isDefined = leafletHelpers.isDefined;
-  var Helpers = leafletHelpers;
-  var lblHelp = leafletLabelEvents;
-  var EventsHelper = LeafletEventsHelpersFactory;
-
-  var MarkerEvents = function() {
-      EventsHelper.call(this, 'leafletDirectiveMarker', 'markers');
-    };
-
-  MarkerEvents.prototype = new EventsHelper();
-
-  MarkerEvents.prototype.genDispatchEvent = function(maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName) {
-    var handle = EventsHelper.prototype
-        .genDispatchEvent.call(this, maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName);
-    return function(e) {
-      // Broadcast old marker click name for backwards compatibility
-      if (eventName === 'click') {
-        safeApply(leafletScope, function() {
-          $rootScope.$broadcast('leafletDirectiveMarkersClick', name);
-        });
-      } else if (eventName === 'dragend') {
-        safeApply(leafletScope, function() {
-          model.lat = lObject.getLatLng().lat;
-          model.lng = lObject.getLatLng().lng;
-        });
-
-        if (model.message && model.focus === true) {
-          lObject.openPopup();
-        }
-      }
-
-      handle(e); //common
-    };
-  };
-
-  MarkerEvents.prototype.getAvailableEvents = function() { return [
-      'click',
-      'dblclick',
-      'mousedown',
-      'mouseover',
-      'mouseout',
-      'contextmenu',
-      'dragstart',
-      'drag',
-      'dragend',
-      'move',
-      'remove',
-      'popupopen',
-      'popupclose',
-      'touchend',
-      'touchstart',
-      'touchmove',
-      'touchcancel',
-      'touchleave',
-      ];
-  };
-
-  MarkerEvents.prototype.bindEvents = function(maybeMapId, lObject, name, model, leafletScope, layerName) {
-      var logic = EventsHelper.prototype.bindEvents.call(this, maybeMapId, lObject, name, model, leafletScope, layerName);
-
-      if (Helpers.LabelPlugin.isLoaded() && isDefined(lObject.label)) {
-        lblHelp.genEvents(maybeMapId, name, logic, leafletScope, lObject, model, layerName);
-      }
-    };
-
-  return new MarkerEvents();
-}]);
-
-angular.module('leaflet-directive')
-.factory('leafletPathEvents', ["$rootScope", "$q", "$log", "leafletHelpers", "leafletLabelEvents", "leafletEventsHelpers", function($rootScope, $q, $log, leafletHelpers, leafletLabelEvents, leafletEventsHelpers) {
-  var isDefined = leafletHelpers.isDefined;
-  var isObject = leafletHelpers.isObject;
-  var Helpers = leafletHelpers;
-  var errorHeader = leafletHelpers.errorHeader;
-  var lblHelp = leafletLabelEvents;
-  var fire = leafletEventsHelpers.fire;
-
-  /*
-  TODO (nmccready) This EventsHelper needs to be derrived from leafletEventsHelpers to elminate copy and paste code.
-  */
-
-  var _genDispatchPathEvent = function(maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName) {
-    maybeMapId = maybeMapId || '';
-
-    if (maybeMapId)
-      maybeMapId = '.' + maybeMapId;
-
-    return function(e) {
-      var broadcastName = 'leafletDirectivePath' + maybeMapId + '.' + eventName;
-      $log.debug(broadcastName);
-      fire(leafletScope, broadcastName, logic, e, e.target || lObject, model, name, layerName);
-    };
-  };
-
-  var _bindPathEvents = function(maybeMapId, lObject, name, model, leafletScope) {
-    var pathEvents = [];
-    var i;
-    var eventName;
-    var logic = 'broadcast';
-
-    if (!isDefined(leafletScope.eventBroadcast)) {
-      // Backward compatibility, if no event-broadcast attribute, all events are broadcasted
-      pathEvents = _getAvailablePathEvents();
-    } else if (!isObject(leafletScope.eventBroadcast)) {
-      // Not a valid object
-      $log.error(errorHeader + 'event-broadcast must be an object check your model.');
-    } else {
-      // We have a possible valid object
-      if (!isDefined(leafletScope.eventBroadcast.path)) {
-        // We do not have events enable/disable do we do nothing (all enabled by default)
-        pathEvents = _getAvailablePathEvents();
-      } else if (isObject(leafletScope.eventBroadcast.paths)) {
-        // Not a valid object
-        $log.warn(errorHeader + 'event-broadcast.path must be an object check your model.');
-      } else {
-        // We have a possible valid map object
-        // Event propadation logic
-        if (leafletScope.eventBroadcast.path.logic !== undefined && leafletScope.eventBroadcast.path.logic !== null) {
-          // We take care of possible propagation logic
-          if (leafletScope.eventBroadcast.path.logic !== 'emit' && leafletScope.eventBroadcast.path.logic !== 'broadcast') {
-            // This is an error
-            $log.warn(errorHeader + 'Available event propagation logic are: \'emit\' or \'broadcast\'.');
-          } else if (leafletScope.eventBroadcast.path.logic === 'emit') {
-            logic = 'emit';
-          }
-        }
-
-        // Enable / Disable
-        var pathEventsEnable = false;
-        var pathEventsDisable = false;
-        if (leafletScope.eventBroadcast.path.enable !== undefined && leafletScope.eventBroadcast.path.enable !== null) {
-          if (typeof leafletScope.eventBroadcast.path.enable === 'object') {
-            pathEventsEnable = true;
-          }
-        }
-
-        if (leafletScope.eventBroadcast.path.disable !== undefined && leafletScope.eventBroadcast.path.disable !== null) {
-          if (typeof leafletScope.eventBroadcast.path.disable === 'object') {
-            pathEventsDisable = true;
-          }
-        }
-
-        if (pathEventsEnable && pathEventsDisable) {
-          // Both are active, this is an error
-          $log.warn(errorHeader + 'can not enable and disable events at the same time');
-        } else if (!pathEventsEnable && !pathEventsDisable) {
-          // Both are inactive, this is an error
-          $log.warn(errorHeader + 'must enable or disable events');
-        } else {
-          // At this point the path object is OK, lets enable or disable events
-          if (pathEventsEnable) {
-            // Enable events
-            for (i = 0; i < leafletScope.eventBroadcast.path.enable.length; i++) {
-              eventName = leafletScope.eventBroadcast.path.enable[i];
-
-              // Do we have already the event enabled?
-              if (pathEvents.indexOf(eventName) !== -1) {
-                // Repeated event, this is an error
-                $log.warn(errorHeader + 'This event ' + eventName + ' is already enabled');
-              } else {
-                // Does the event exists?
-                if (_getAvailablePathEvents().indexOf(eventName) === -1) {
-                  // The event does not exists, this is an error
-                  $log.warn(errorHeader + 'This event ' + eventName + ' does not exist');
-                } else {
-                  // All ok enable the event
-                  pathEvents.push(eventName);
-                }
-              }
-            }
-          } else {
-            // Disable events
-            pathEvents = _getAvailablePathEvents();
-            for (i = 0; i < leafletScope.eventBroadcast.path.disable.length; i++) {
-              eventName = leafletScope.eventBroadcast.path.disable[i];
-              var index = pathEvents.indexOf(eventName);
-              if (index === -1) {
-                // The event does not exist
-                $log.warn(errorHeader + 'This event ' + eventName + ' does not exist or has been already disabled');
-
-              } else {
-                pathEvents.splice(index, 1);
-              }
-            }
-          }
-        }
-      }
-    }
-
-    for (i = 0; i < pathEvents.length; i++) {
-      eventName = pathEvents[i];
-      lObject.on(eventName, _genDispatchPathEvent(maybeMapId, eventName, logic, leafletScope, pathEvents, name));
-    }
-
-    if (Helpers.LabelPlugin.isLoaded() && isDefined(lObject.label)) {
-      lblHelp.genEvents(maybeMapId, name, logic, leafletScope, lObject, model);
-    }
-  };
-
-  var _getAvailablePathEvents = function() {
-    return [
-        'click',
-        'dblclick',
-        'mousedown',
-        'mouseover',
-        'mouseout',
-        'contextmenu',
-        'add',
-        'remove',
-        'popupopen',
-        'popupclose',
-    ];
-  };
-
-  return {
-    getAvailablePathEvents: _getAvailablePathEvents,
-    bindPathEvents: _bindPathEvents,
-  };
-}]);
-
-}(angular));
\ No newline at end of file
diff --git a/www/lib/ionic/js/angular/angular-leaflet-directive.min.js b/www/lib/ionic/js/angular/angular-leaflet-directive.min.js
deleted file mode 100644
index cbcd8406be149aaae201c2494995eaf73f51273a..0000000000000000000000000000000000000000
--- a/www/lib/ionic/js/angular/angular-leaflet-directive.min.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/**!
- * The MIT License
- *
- * Copyright (c) 2013 the angular-leaflet-directive Team, http://tombatossals.github.io/angular-leaflet-directive
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- * angular-leaflet-directive
- * https://github.com/tombatossals/angular-leaflet-directive
- *
- * @authors https://github.com/tombatossals/angular-leaflet-directive/graphs/contributors
- */
-
-/*!
-*  angular-leaflet-directive  2015-11-06
-*  angular-leaflet-directive - An AngularJS directive to easily interact with Leaflet maps
-*  git: https://github.com/tombatossals/angular-leaflet-directive
-*/
-(function(angular){
-'use strict';
-!function(angular){"use strict";angular.module("leaflet-directive",[]).directive("leaflet",["$q","leafletData","leafletMapDefaults","leafletHelpers","leafletMapEvents",function(a,b,c,d,e){return{restrict:"EA",replace:!0,scope:{center:"=",lfCenter:"=",defaults:"=",maxbounds:"=",bounds:"=",markers:"=",legend:"=",geojson:"=",paths:"=",tiles:"=",layers:"=",controls:"=",decorations:"=",eventBroadcast:"=",markersWatchOptions:"=",geojsonWatchOptions:"="},transclude:!0,template:'<div class="angular-leaflet-map"><div ng-transclude></div></div>',controller:["$scope",function(b){this._leafletMap=a.defer(),this.getMap=function(){return this._leafletMap.promise},this.getLeafletScope=function(){return b}}],link:function(a,f,g,h){function i(){isNaN(g.width)?f.css("width",g.width):f.css("width",g.width+"px")}function j(){isNaN(g.height)?f.css("height",g.height):f.css("height",g.height+"px")}var k=d.isDefined,l=c.setDefaults(a.defaults,g.id),m=e.getAvailableMapEvents(),n=e.addEvents;a.mapId=g.id,b.setDirectiveControls({},g.id),k(g.width)&&(i(),a.$watch(function(){return f[0].getAttribute("width")},function(){i(),o.invalidateSize()})),k(g.height)&&(j(),a.$watch(function(){return f[0].getAttribute("height")},function(){j(),o.invalidateSize()}));var o=new L.Map(f[0],c.getMapCreationDefaults(g.id));if(h._leafletMap.resolve(o),k(g.center)||k(g.lfCenter)||o.setView([l.center.lat,l.center.lng],l.center.zoom),!k(g.tiles)&&!k(g.layers)){var p=L.tileLayer(l.tileLayer,l.tileLayerOptions);p.addTo(o),b.setTiles(p,g.id)}if(k(o.zoomControl)&&k(l.zoomControlPosition)&&o.zoomControl.setPosition(l.zoomControlPosition),k(o.zoomControl)&&l.zoomControl===!1&&o.zoomControl.removeFrom(o),k(o.zoomsliderControl)&&k(l.zoomsliderControl)&&l.zoomsliderControl===!1&&o.zoomsliderControl.removeFrom(o),!k(g.eventBroadcast)){var q="broadcast";n(o,m,"eventName",a,q)}o.whenReady(function(){b.setMap(o,g.id)}),a.$on("$destroy",function(){c.reset(),o.remove(),b.unresolveMap(g.id)}),a.$on("invalidateSize",function(){o.invalidateSize()})}}}]),angular.module("leaflet-directive").factory("leafletBoundsHelpers",["$log","leafletHelpers",function(a,b){function c(a){return angular.isDefined(a)&&angular.isDefined(a.southWest)&&angular.isDefined(a.northEast)&&angular.isNumber(a.southWest.lat)&&angular.isNumber(a.southWest.lng)&&angular.isNumber(a.northEast.lat)&&angular.isNumber(a.northEast.lng)}var d=b.isArray,e=b.isNumber,f=b.isFunction,g=b.isDefined;return{createLeafletBounds:function(a){return c(a)?L.latLngBounds([a.southWest.lat,a.southWest.lng],[a.northEast.lat,a.northEast.lng]):void 0},isValidBounds:c,createBoundsFromArray:function(b){return d(b)&&2===b.length&&d(b[0])&&d(b[1])&&2===b[0].length&&2===b[1].length&&e(b[0][0])&&e(b[0][1])&&e(b[1][0])&&e(b[1][1])?{northEast:{lat:b[0][0],lng:b[0][1]},southWest:{lat:b[1][0],lng:b[1][1]}}:void a.error("[AngularJS - Leaflet] The bounds array is not valid.")},createBoundsFromLeaflet:function(b){if(!(g(b)&&f(b.getNorthEast)&&f(b.getSouthWest)))return void a.error("[AngularJS - Leaflet] The leaflet bounds is not valid object.");var c=b.getNorthEast(),d=b.getSouthWest();return{northEast:{lat:c.lat,lng:c.lng},southWest:{lat:d.lat,lng:d.lng}}}}}]),angular.module("leaflet-directive").factory("leafletControlHelpers",["$rootScope","$log","leafletHelpers","leafletLayerHelpers","leafletMapDefaults",function(a,b,c,d,e){var f=c.isDefined,g=c.isObject,h=d.createLayer,i={},j=c.errorHeader+" [Controls] ",k=function(a,b,c){var d=e.getDefaults(c);if(!d.controls.layers.visible)return!1;var h=!1;return g(a)&&Object.keys(a).forEach(function(b){var c=a[b];f(c.layerOptions)&&c.layerOptions.showOnSelector===!1||(h=!0)}),g(b)&&Object.keys(b).forEach(function(a){var c=b[a];f(c.layerParams)&&c.layerParams.showOnSelector===!1||(h=!0)}),h},l=function(a){var b=e.getDefaults(a),c={collapsed:b.controls.layers.collapsed,position:b.controls.layers.position,autoZIndex:!1};angular.extend(c,b.controls.layers.options);var d;return d=b.controls.layers&&f(b.controls.layers.control)?b.controls.layers.control.apply(this,[[],[],c]):new L.control.layers([],[],c)},m={draw:{isPluginLoaded:function(){return angular.isDefined(L.Control.Draw)?!0:(b.error(j+" Draw plugin is not loaded."),!1)},checkValidParams:function(){return!0},createControl:function(a){return new L.Control.Draw(a)}},scale:{isPluginLoaded:function(){return!0},checkValidParams:function(){return!0},createControl:function(a){return new L.control.scale(a)}},fullscreen:{isPluginLoaded:function(){return angular.isDefined(L.Control.Fullscreen)?!0:(b.error(j+" Fullscreen plugin is not loaded."),!1)},checkValidParams:function(){return!0},createControl:function(a){return new L.Control.Fullscreen(a)}},search:{isPluginLoaded:function(){return angular.isDefined(L.Control.Search)?!0:(b.error(j+" Search plugin is not loaded."),!1)},checkValidParams:function(){return!0},createControl:function(a){return new L.Control.Search(a)}},custom:{},minimap:{isPluginLoaded:function(){return angular.isDefined(L.Control.MiniMap)?!0:(b.error(j+" Minimap plugin is not loaded."),!1)},checkValidParams:function(a){return f(a.layer)?!0:(b.warn(j+' minimap "layer" option should be defined.'),!1)},createControl:function(a){var c=h(a.layer);return f(c)?new L.Control.MiniMap(c,a):void b.warn(j+' minimap control "layer" could not be created.')}}};return{layersControlMustBeVisible:k,isValidControlType:function(a){return-1!==Object.keys(m).indexOf(a)},createControl:function(a,b){return m[a].checkValidParams(b)?m[a].createControl(b):void 0},updateLayersControl:function(a,b,c,d,e,g){var h,j=i[b],m=k(d,e,b);if(f(j)&&c){for(h in g.baselayers)j.removeLayer(g.baselayers[h]);for(h in g.overlays)j.removeLayer(g.overlays[h]);a.removeControl(j),delete i[b]}if(m){j=l(b),i[b]=j;for(h in d){var n=f(d[h].layerOptions)&&d[h].layerOptions.showOnSelector===!1;!n&&f(g.baselayers[h])&&j.addBaseLayer(g.baselayers[h],d[h].name)}for(h in e){var o=f(e[h].layerParams)&&e[h].layerParams.showOnSelector===!1;!o&&f(g.overlays[h])&&j.addOverlay(g.overlays[h],e[h].name)}a.addControl(j)}return m}}}]),angular.module("leaflet-directive").service("leafletData",["$log","$q","leafletHelpers",function(a,b,c){var d=c.getDefer,e=c.getUnresolvedDefer,f=c.setResolvedDefer,g={},h=this,i=function(a){return a.charAt(0).toUpperCase()+a.slice(1)},j=["map","tiles","layers","paths","markers","geoJSON","UTFGrid","decorations","directiveControls"];j.forEach(function(a){g[a]={}}),this.unresolveMap=function(a){var b=c.obtainEffectiveMapId(g.map,a);j.forEach(function(a){g[a][b]=void 0})},j.forEach(function(a){var b=i(a);h["set"+b]=function(b,c){var d=e(g[a],c);d.resolve(b),f(g[a],c)},h["get"+b]=function(b){var c=d(g[a],b);return c.promise}})}]),angular.module("leaflet-directive").service("leafletDirectiveControlsHelpers",["$log","leafletData","leafletHelpers",function(a,b,c){var d=c.isDefined,e=c.isString,f=c.isObject,g=c.errorHeader,h=g+"[leafletDirectiveControlsHelpers",i=function(c,g,i,j){var k=h+".extend] ",l={};if(!d(g))return void a.error(k+"thingToAddName cannot be undefined");if(e(g)&&d(i)&&d(j))l[g]={create:i,clean:j};else{if(!f(g)||d(i)||d(j))return void a.error(k+"incorrect arguments");l=g}b.getDirectiveControls().then(function(a){angular.extend(a,l),b.setDirectiveControls(a,c)})};return{extend:i}}]),angular.module("leaflet-directive").service("leafletGeoJsonHelpers",["leafletHelpers","leafletIterators",function(a,b){var c=a,d=b,e=function(a,b){return this.lat=a,this.lng=b,this},f=function(a){return Array.isArray(a)&&2===a.length?a[1]:c.isDefined(a.type)&&"Point"===a.type?+a.coordinates[1]:+a.lat},g=function(a){return Array.isArray(a)&&2===a.length?a[0]:c.isDefined(a.type)&&"Point"===a.type?+a.coordinates[0]:+a.lng},h=function(a){if(c.isUndefined(a))return!1;if(c.isArray(a)){if(2===a.length&&c.isNumber(a[0])&&c.isNumber(a[1]))return!0}else if(c.isDefined(a.type)&&"Point"===a.type&&c.isArray(a.coordinates)&&2===a.coordinates.length&&c.isNumber(a.coordinates[0])&&c.isNumber(a.coordinates[1]))return!0;var b=d.all(["lat","lng"],function(b){return c.isDefined(a[b])&&c.isNumber(a[b])});return b},i=function(a){if(a&&h(a)){var b=null;if(Array.isArray(a)&&2===a.length)b=new e(a[1],a[0]);else{if(!c.isDefined(a.type)||"Point"!==a.type)return a;b=new e(a.coordinates[1],a.coordinates[0])}return angular.extend(a,b)}};return{getLat:f,getLng:g,validateCoords:h,getCoords:i}}]),angular.module("leaflet-directive").service("leafletHelpers",["$q","$log",function(a,b){function c(a,c){var d,f;if(angular.isDefined(c))d=c;else if(0===Object.keys(a).length)d="main";else if(Object.keys(a).length>=1)for(f in a)a.hasOwnProperty(f)&&(d=f);else b.error(e+"- You have more than 1 map on the DOM, you must provide the map ID to the leafletData.getXXX call");return d}function d(b,d){var e,f=c(b,d);return angular.isDefined(b[f])&&b[f].resolvedDefer!==!0?e=b[f].defer:(e=a.defer(),b[f]={defer:e,resolvedDefer:!1}),e}var e="[AngularJS - Leaflet] ",f=angular.copy,g=f,h=function(a,b){var c;if(a&&angular.isObject(a))return null!==b&&angular.isString(b)?(c=a,b.split(".").forEach(function(a){c&&(c=c[a])}),c):b},i=function(a){return a.split(".").reduce(function(a,b){return a+'["'+b+'"]'})},j=function(a){return a.reduce(function(a,b){return a+"."+b})},k=function(a){return angular.isDefined(a)&&null!==a},l=function(a){return!k(a)},m=/([\:\-\_]+(.))/g,n=/^moz([A-Z])/,o=/^((?:x|data)[\:\-_])/i,p=function(a){return a.replace(m,function(a,b,c,d){return d?c.toUpperCase():c}).replace(n,"Moz$1")},q=function(a){return p(a.replace(o,""))};return{camelCase:p,directiveNormalize:q,copy:f,clone:g,errorHeader:e,getObjectValue:h,getObjectArrayPath:i,getObjectDotPath:j,defaultTo:function(a,b){return k(a)?a:b},isTruthy:function(a){return"true"===a||a===!0},isEmpty:function(a){return 0===Object.keys(a).length},isUndefinedOrEmpty:function(a){return angular.isUndefined(a)||null===a||0===Object.keys(a).length},isDefined:k,isUndefined:l,isNumber:angular.isNumber,isString:angular.isString,isArray:angular.isArray,isObject:angular.isObject,isFunction:angular.isFunction,equals:angular.equals,isValidCenter:function(a){return angular.isDefined(a)&&angular.isNumber(a.lat)&&angular.isNumber(a.lng)&&angular.isNumber(a.zoom)},isValidPoint:function(a){return angular.isDefined(a)?angular.isArray(a)?2===a.length&&angular.isNumber(a[0])&&angular.isNumber(a[1]):angular.isNumber(a.lat)&&angular.isNumber(a.lng):!1},isSameCenterOnMap:function(a,b){var c=b.getCenter(),d=b.getZoom();return a.lat&&a.lng&&c.lat.toFixed(4)===a.lat.toFixed(4)&&c.lng.toFixed(4)===a.lng.toFixed(4)&&d===a.zoom?!0:!1},safeApply:function(a,b){var c=a.$root.$$phase;"$apply"===c||"$digest"===c?a.$eval(b):a.$evalAsync(b)},obtainEffectiveMapId:c,getDefer:function(a,b){var e,f=c(a,b);return e=angular.isDefined(a[f])&&a[f].resolvedDefer!==!1?a[f].defer:d(a,b)},getUnresolvedDefer:d,setResolvedDefer:function(a,b){var d=c(a,b);a[d].resolvedDefer=!0},rangeIsSupported:function(){var a=document.createElement("input");return a.setAttribute("type","range"),"range"===a.type},FullScreenControlPlugin:{isLoaded:function(){return angular.isDefined(L.Control.Fullscreen)}},MiniMapControlPlugin:{isLoaded:function(){return angular.isDefined(L.Control.MiniMap)}},AwesomeMarkersPlugin:{isLoaded:function(){return angular.isDefined(L.AwesomeMarkers)&&angular.isDefined(L.AwesomeMarkers.Icon)},is:function(a){return this.isLoaded()?a instanceof L.AwesomeMarkers.Icon:!1},equal:function(a,b){return this.isLoaded()&&this.is(a)?angular.equals(a,b):!1}},VectorMarkersPlugin:{isLoaded:function(){return angular.isDefined(L.VectorMarkers)&&angular.isDefined(L.VectorMarkers.Icon)},is:function(a){return this.isLoaded()?a instanceof L.VectorMarkers.Icon:!1},equal:function(a,b){return this.isLoaded()&&this.is(a)?angular.equals(a,b):!1}},DomMarkersPlugin:{isLoaded:function(){return angular.isDefined(L.DomMarkers)&&angular.isDefined(L.DomMarkers.Icon)?!0:!1},is:function(a){return this.isLoaded()?a instanceof L.DomMarkers.Icon:!1},equal:function(a,b){return this.isLoaded()&&this.is(a)?angular.equals(a,b):!1}},PolylineDecoratorPlugin:{isLoaded:function(){return angular.isDefined(L.PolylineDecorator)?!0:!1},is:function(a){return this.isLoaded()?a instanceof L.PolylineDecorator:!1},equal:function(a,b){return this.isLoaded()&&this.is(a)?angular.equals(a,b):!1}},MakiMarkersPlugin:{isLoaded:function(){return angular.isDefined(L.MakiMarkers)&&angular.isDefined(L.MakiMarkers.Icon)?!0:!1},is:function(a){return this.isLoaded()?a instanceof L.MakiMarkers.Icon:!1},equal:function(a,b){return this.isLoaded()&&this.is(a)?angular.equals(a,b):!1}},ExtraMarkersPlugin:{isLoaded:function(){return angular.isDefined(L.ExtraMarkers)&&angular.isDefined(L.ExtraMarkers.Icon)?!0:!1},is:function(a){return this.isLoaded()?a instanceof L.ExtraMarkers.Icon:!1},equal:function(a,b){return this.isLoaded()&&this.is(a)?angular.equals(a,b):!1}},LabelPlugin:{isLoaded:function(){return angular.isDefined(L.Label)},is:function(a){return this.isLoaded()?a instanceof L.MarkerClusterGroup:!1}},MarkerClusterPlugin:{isLoaded:function(){return angular.isDefined(L.MarkerClusterGroup)},is:function(a){return this.isLoaded()?a instanceof L.MarkerClusterGroup:!1}},GoogleLayerPlugin:{isLoaded:function(){return angular.isDefined(L.Google)},is:function(a){return this.isLoaded()?a instanceof L.Google:!1}},LeafletProviderPlugin:{isLoaded:function(){return angular.isDefined(L.TileLayer.Provider)},is:function(a){return this.isLoaded()?a instanceof L.TileLayer.Provider:!1}},ChinaLayerPlugin:{isLoaded:function(){return angular.isDefined(L.tileLayer.chinaProvider)}},HeatLayerPlugin:{isLoaded:function(){return angular.isDefined(L.heatLayer)}},WebGLHeatMapLayerPlugin:{isLoaded:function(){return angular.isDefined(L.TileLayer.WebGLHeatMap)}},BingLayerPlugin:{isLoaded:function(){return angular.isDefined(L.BingLayer)},is:function(a){return this.isLoaded()?a instanceof L.BingLayer:!1}},WFSLayerPlugin:{isLoaded:function(){return void 0!==L.GeoJSON.WFS},is:function(a){return this.isLoaded()?a instanceof L.GeoJSON.WFS:!1}},AGSBaseLayerPlugin:{isLoaded:function(){return void 0!==L.esri&&void 0!==L.esri.basemapLayer},is:function(a){return this.isLoaded()?a instanceof L.esri.basemapLayer:!1}},AGSLayerPlugin:{isLoaded:function(){return void 0!==lvector&&void 0!==lvector.AGS},is:function(a){return this.isLoaded()?a instanceof lvector.AGS:!1}},AGSFeatureLayerPlugin:{isLoaded:function(){return void 0!==L.esri&&void 0!==L.esri.featureLayer},is:function(a){return this.isLoaded()?a instanceof L.esri.featureLayer:!1}},AGSTiledMapLayerPlugin:{isLoaded:function(){return void 0!==L.esri&&void 0!==L.esri.tiledMapLayer},is:function(a){return this.isLoaded()?a instanceof L.esri.tiledMapLayer:!1}},AGSDynamicMapLayerPlugin:{isLoaded:function(){return void 0!==L.esri&&void 0!==L.esri.dynamicMapLayer},is:function(a){return this.isLoaded()?a instanceof L.esri.dynamicMapLayer:!1}},AGSImageMapLayerPlugin:{isLoaded:function(){return void 0!==L.esri&&void 0!==L.esri.imageMapLayer},is:function(a){return this.isLoaded()?a instanceof L.esri.imageMapLayer:!1}},AGSClusteredLayerPlugin:{isLoaded:function(){return void 0!==L.esri&&void 0!==L.esri.clusteredFeatureLayer},is:function(a){return this.isLoaded()?a instanceof L.esri.clusteredFeatureLayer:!1}},AGSHeatmapLayerPlugin:{isLoaded:function(){return void 0!==L.esri&&void 0!==L.esri.heatmapFeatureLayer},is:function(a){return this.isLoaded()?a instanceof L.esri.heatmapFeatureLayer:!1}},YandexLayerPlugin:{isLoaded:function(){return angular.isDefined(L.Yandex)},is:function(a){return this.isLoaded()?a instanceof L.Yandex:!1}},GeoJSONPlugin:{isLoaded:function(){return angular.isDefined(L.TileLayer.GeoJSON)},is:function(a){return this.isLoaded()?a instanceof L.TileLayer.GeoJSON:!1}},UTFGridPlugin:{isLoaded:function(){return angular.isDefined(L.UtfGrid)},is:function(a){return this.isLoaded()?a instanceof L.UtfGrid:(b.error("[AngularJS - Leaflet] No UtfGrid plugin found."),!1)}},CartoDB:{isLoaded:function(){return cartodb},is:function(){return!0}},Leaflet:{DivIcon:{is:function(a){return a instanceof L.DivIcon},equal:function(a,b){return this.is(a)?angular.equals(a,b):!1}},Icon:{is:function(a){return a instanceof L.Icon},equal:function(a,b){return this.is(a)?angular.equals(a,b):!1}}},watchOptions:{doWatch:!0,isDeep:!0,individual:{doWatch:!0,isDeep:!0}}}}]),angular.module("leaflet-directive").service("leafletIterators",["$log","leafletHelpers",function(a,b){var c,d=b,e=b.errorHeader+"leafletIterators: ",f=Object.keys,g=d.isFunction,h=d.isObject,i=Math.pow(2,53)-1,j=function(a){var b=null!==a&&a.length;return d.isNumber(b)&&b>=0&&i>=b},k=function(a){return a},l=function(a){return function(b){return null===b?void 0:b[a]}},m=function(a,b,c){if(void 0===b)return a;switch(null===c?3:c){case 1:return function(c){return a.call(b,c)};case 2:return function(c,d){return a.call(b,c,d)};case 3:return function(c,d,e){return a.call(b,c,d,e)};case 4:return function(c,d,e,f){return a.call(b,c,d,e,f)}}return function(){return a.apply(b,arguments)}},n=function(a,b){return function(c){var d=arguments.length;if(2>d||null===c)return c;for(var e=1;d>e;e++)for(var f=arguments[e],g=a(f),h=g.length,i=0;h>i;i++){var j=g[i];b&&void 0!==c[j]||(c[j]=f[j])}return c}},o=null;c=o=n(f);var p,q=function(a,b){var c=f(b),d=c.length;if(null===a)return!d;for(var e=Object(a),g=0;d>g;g++){var h=c[g];if(b[h]!==e[h]||!(h in e))return!1}return!0},r=null;p=r=function(a){return a=c({},a),function(b){return q(b,a)}};var s,t=function(a,b,c){return null===a?k:g(a)?m(a,b,c):h(a)?p(a):l(a)},u=null;s=u=function(a,b,c){b=t(b,c);for(var d=!j(a)&&f(a),e=(d||a).length,g=0;e>g;g++){var h=d?d[g]:g;if(!b(a[h],h,a))return!1}return!0};var v=function(b,c,f,g){return f||d.isDefined(b)&&d.isDefined(c)?d.isFunction(c)?!1:(g=d.defaultTo(c,"cb"),a.error(e+g+" is not a function"),!0):!0},w=function(a,b,c){if(!v(void 0,c,!0,"internalCb")&&!v(a,b))for(var d in a)a.hasOwnProperty(d)&&c(a[d],d)},x=function(a,b){w(a,b,function(a,c){b(a,c)})};return{each:x,forEach:x,every:s,all:u}}]),angular.module("leaflet-directive").factory("leafletLayerHelpers",["$rootScope","$log","$q","leafletHelpers","leafletIterators",function($rootScope,$log,$q,leafletHelpers,leafletIterators){function isValidLayerType(a){return isString(a.type)?-1===Object.keys(layerTypes).indexOf(a.type)?($log.error("[AngularJS - Leaflet] A layer must have a valid type: "+Object.keys(layerTypes)),!1):layerTypes[a.type].mustHaveUrl&&!isString(a.url)?($log.error("[AngularJS - Leaflet] A base layer must have an url"),!1):layerTypes[a.type].mustHaveData&&!isDefined(a.data)?($log.error('[AngularJS - Leaflet] The base layer must have a "data" array attribute'),!1):layerTypes[a.type].mustHaveLayer&&!isDefined(a.layer)?($log.error("[AngularJS - Leaflet] The type of layer "+a.type+" must have an layer defined"),!1):layerTypes[a.type].mustHaveBounds&&!isDefined(a.bounds)?($log.error("[AngularJS - Leaflet] The type of layer "+a.type+" must have bounds defined"),!1):layerTypes[a.type].mustHaveKey&&!isDefined(a.key)?($log.error("[AngularJS - Leaflet] The type of layer "+a.type+" must have key defined"),!1):!0:($log.error("[AngularJS - Leaflet] A layer must have a valid type defined."),!1)}function createLayer(a){if(isValidLayerType(a)){if(!isString(a.name))return void $log.error("[AngularJS - Leaflet] A base layer must have a name");isObject(a.layerParams)||(a.layerParams={}),isObject(a.layerOptions)||(a.layerOptions={});for(var b in a.layerParams)a.layerOptions[b]=a.layerParams[b];var c={url:a.url,data:a.data,options:a.layerOptions,layer:a.layer,icon:a.icon,type:a.layerType,bounds:a.bounds,key:a.key,apiKey:a.apiKey,pluginOptions:a.pluginOptions,user:a.user};return layerTypes[a.type].createLayer(c)}}function safeAddLayer(a,b){b&&"function"==typeof b.addTo?b.addTo(a):a.addLayer(b)}function safeRemoveLayer(a,b,c){if(isDefined(c)&&isDefined(c.loadedDefer))if(angular.isFunction(c.loadedDefer)){var d=c.loadedDefer();$log.debug("Loaded Deferred",d);var e=d.length;if(e>0)for(var f=function(){e--,0===e&&a.removeLayer(b)},g=0;g<d.length;g++)d[g].promise.then(f);else a.removeLayer(b)}else c.loadedDefer.promise.then(function(){a.removeLayer(b)});else a.removeLayer(b)}var Helpers=leafletHelpers,isString=leafletHelpers.isString,isObject=leafletHelpers.isObject,isArray=leafletHelpers.isArray,isDefined=leafletHelpers.isDefined,errorHeader=leafletHelpers.errorHeader,$it=leafletIterators,utfGridCreateLayer=function(a){if(!Helpers.UTFGridPlugin.isLoaded())return void $log.error("[AngularJS - Leaflet] The UTFGrid plugin is not loaded.");var b=new L.UtfGrid(a.url,a.pluginOptions);return b.on("mouseover",function(a){$rootScope.$broadcast("leafletDirectiveMap.utfgridMouseover",a)}),b.on("mouseout",function(a){$rootScope.$broadcast("leafletDirectiveMap.utfgridMouseout",a)}),b.on("click",function(a){$rootScope.$broadcast("leafletDirectiveMap.utfgridClick",a)}),b.on("mousemove",function(a){$rootScope.$broadcast("leafletDirectiveMap.utfgridMousemove",a)}),b},layerTypes={xyz:{mustHaveUrl:!0,createLayer:function(a){return L.tileLayer(a.url,a.options)}},mapbox:{mustHaveKey:!0,createLayer:function(a){var b=3;isDefined(a.options.version)&&4===a.options.version&&(b=a.options.version);var c=3===b?"//{s}.tiles.mapbox.com/v3/"+a.key+"/{z}/{x}/{y}.png":"//api.tiles.mapbox.com/v4/"+a.key+"/{z}/{x}/{y}.png?access_token="+a.apiKey;return L.tileLayer(c,a.options)}},geoJSON:{mustHaveUrl:!0,createLayer:function(a){return Helpers.GeoJSONPlugin.isLoaded()?new L.TileLayer.GeoJSON(a.url,a.pluginOptions,a.options):void 0}},geoJSONShape:{mustHaveUrl:!1,createLayer:function(a){return new L.GeoJSON(a.data,a.options)}},geoJSONAwesomeMarker:{mustHaveUrl:!1,createLayer:function(a){return new L.geoJson(a.data,{pointToLayer:function(b,c){return L.marker(c,{icon:L.AwesomeMarkers.icon(a.icon)})}})}},geoJSONVectorMarker:{mustHaveUrl:!1,createLayer:function(a){return new L.geoJson(a.data,{pointToLayer:function(b,c){return L.marker(c,{icon:L.VectorMarkers.icon(a.icon)})}})}},utfGrid:{mustHaveUrl:!0,createLayer:utfGridCreateLayer},cartodbTiles:{mustHaveKey:!0,createLayer:function(a){var b="//"+a.user+".cartodb.com/api/v1/map/"+a.key+"/{z}/{x}/{y}.png";return L.tileLayer(b,a.options)}},cartodbUTFGrid:{mustHaveKey:!0,mustHaveLayer:!0,createLayer:function(a){return a.url="//"+a.user+".cartodb.com/api/v1/map/"+a.key+"/"+a.layer+"/{z}/{x}/{y}.grid.json",utfGridCreateLayer(a)}},cartodbInteractive:{mustHaveKey:!0,mustHaveLayer:!0,createLayer:function(a){var b="//"+a.user+".cartodb.com/api/v1/map/"+a.key+"/{z}/{x}/{y}.png",c=L.tileLayer(b,a.options);a.url="//"+a.user+".cartodb.com/api/v1/map/"+a.key+"/"+a.layer+"/{z}/{x}/{y}.grid.json";var d=utfGridCreateLayer(a);return L.layerGroup([c,d])}},wms:{mustHaveUrl:!0,createLayer:function(a){return L.tileLayer.wms(a.url,a.options)}},wmts:{mustHaveUrl:!0,createLayer:function(a){return L.tileLayer.wmts(a.url,a.options)}},wfs:{mustHaveUrl:!0,mustHaveLayer:!0,createLayer:function(params){if(Helpers.WFSLayerPlugin.isLoaded()){var options=angular.copy(params.options);return options.crs&&"string"==typeof options.crs&&(options.crs=eval(options.crs)),new L.GeoJSON.WFS(params.url,params.layer,options)}}},group:{mustHaveUrl:!1,createLayer:function(a){var b=[];return $it.each(a.options.layers,function(a){b.push(createLayer(a))}),a.options.loadedDefer=function(){var b=[];if(isDefined(a.options.layers))for(var c=0;c<a.options.layers.length;c++){var d=a.options.layers[c].layerOptions.loadedDefer;isDefined(d)&&b.push(d)}return b},L.layerGroup(b)}},featureGroup:{mustHaveUrl:!1,createLayer:function(){return L.featureGroup()}},google:{mustHaveUrl:!1,createLayer:function(a){var b=a.type||"SATELLITE";if(Helpers.GoogleLayerPlugin.isLoaded())return new L.Google(b,a.options)}},here:{mustHaveUrl:!1,createLayer:function(a){var b=a.provider||"HERE.terrainDay";if(Helpers.LeafletProviderPlugin.isLoaded())return new L.TileLayer.Provider(b,a.options)}},china:{mustHaveUrl:!1,createLayer:function(a){var b=a.type||"";if(Helpers.ChinaLayerPlugin.isLoaded())return L.tileLayer.chinaProvider(b,a.options)}},agsBase:{mustHaveLayer:!0,createLayer:function(a){return Helpers.AGSBaseLayerPlugin.isLoaded()?L.esri.basemapLayer(a.layer,a.options):void 0}},ags:{mustHaveUrl:!0,createLayer:function(a){if(Helpers.AGSLayerPlugin.isLoaded()){var b=angular.copy(a.options);angular.extend(b,{url:a.url});var c=new lvector.AGS(b);return c.onAdd=function(a){this.setMap(a)},c.onRemove=function(){this.setMap(null)},c}}},agsFeature:{mustHaveUrl:!0,createLayer:function(a){if(!Helpers.AGSFeatureLayerPlugin.isLoaded())return void $log.warn(errorHeader+" The esri plugin is not loaded.");a.options.url=a.url;var b=L.esri.featureLayer(a.options),c=function(){isDefined(a.options.loadedDefer)&&a.options.loadedDefer.resolve()};return b.on("loading",function(){a.options.loadedDefer=$q.defer(),b.off("load",c),b.on("load",c)}),b}},agsTiled:{mustHaveUrl:!0,createLayer:function(a){return Helpers.AGSTiledMapLayerPlugin.isLoaded()?(a.options.url=a.url,L.esri.tiledMapLayer(a.options)):void $log.warn(errorHeader+" The esri plugin is not loaded.")}},agsDynamic:{mustHaveUrl:!0,createLayer:function(a){return Helpers.AGSDynamicMapLayerPlugin.isLoaded()?(a.options.url=a.url,L.esri.dynamicMapLayer(a.options)):void $log.warn(errorHeader+" The esri plugin is not loaded.")}},agsImage:{mustHaveUrl:!0,createLayer:function(a){return Helpers.AGSImageMapLayerPlugin.isLoaded()?(a.options.url=a.url,L.esri.imageMapLayer(a.options)):void $log.warn(errorHeader+" The esri plugin is not loaded.")}},agsClustered:{mustHaveUrl:!0,createLayer:function(a){return Helpers.AGSClusteredLayerPlugin.isLoaded()?Helpers.MarkerClusterPlugin.isLoaded()?L.esri.clusteredFeatureLayer(a.url,a.options):void $log.warn(errorHeader+" The markercluster plugin is not loaded."):void $log.warn(errorHeader+" The esri clustered layer plugin is not loaded.")}},agsHeatmap:{mustHaveUrl:!0,createLayer:function(a){return Helpers.AGSHeatmapLayerPlugin.isLoaded()?Helpers.HeatLayerPlugin.isLoaded()?L.esri.heatmapFeatureLayer(a.url,a.options):void $log.warn(errorHeader+" The heatlayer plugin is not loaded."):void $log.warn(errorHeader+" The esri heatmap layer plugin is not loaded.")}},markercluster:{mustHaveUrl:!1,createLayer:function(a){return Helpers.MarkerClusterPlugin.isLoaded()?new L.MarkerClusterGroup(a.options):void $log.warn(errorHeader+" The markercluster plugin is not loaded.")}},bing:{mustHaveUrl:!1,createLayer:function(a){return Helpers.BingLayerPlugin.isLoaded()?new L.BingLayer(a.key,a.options):void 0}},webGLHeatmap:{mustHaveUrl:!1,mustHaveData:!0,createLayer:function(a){if(Helpers.WebGLHeatMapLayerPlugin.isLoaded()){var b=new L.TileLayer.WebGLHeatMap(a.options);return isDefined(a.data)&&b.setData(a.data),b}}},heat:{mustHaveUrl:!1,mustHaveData:!0,createLayer:function(a){if(Helpers.HeatLayerPlugin.isLoaded()){var b=new L.heatLayer;return isArray(a.data)&&b.setLatLngs(a.data),isObject(a.options)&&b.setOptions(a.options),b}}},yandex:{mustHaveUrl:!1,createLayer:function(a){var b=a.type||"map";if(Helpers.YandexLayerPlugin.isLoaded())return new L.Yandex(b,a.options)}},imageOverlay:{mustHaveUrl:!0,mustHaveBounds:!0,createLayer:function(a){return L.imageOverlay(a.url,a.bounds,a.options)}},iip:{mustHaveUrl:!0,createLayer:function(a){return L.tileLayer.iip(a.url,a.options)}},custom:{createLayer:function(a){return a.layer instanceof L.Class?angular.copy(a.layer):void $log.error("[AngularJS - Leaflet] A custom layer must be a leaflet Class")}},cartodb:{mustHaveUrl:!0,createLayer:function(a){return cartodb.createLayer(a.map,a.url)}}};return{createLayer:createLayer,safeAddLayer:safeAddLayer,safeRemoveLayer:safeRemoveLayer}}]),angular.module("leaflet-directive").factory("leafletLegendHelpers",function(){var a=function(a,b,c,d){if(a.innerHTML="",b.error)a.innerHTML+='<div class="info-title alert alert-danger">'+b.error.message+"</div>";else if("arcgis"===c)for(var e=0;e<b.layers.length;e++){var f=b.layers[e];a.innerHTML+='<div class="info-title" data-layerid="'+f.layerId+'">'+f.layerName+"</div>";for(var g=0;g<f.legend.length;g++){var h=f.legend[g];a.innerHTML+='<div class="inline" data-layerid="'+f.layerId+'"><img src="data:'+h.contentType+";base64,"+h.imageData+'" /></div><div class="info-label" data-layerid="'+f.layerId+'">'+h.label+"</div>"}}else"image"===c&&(a.innerHTML='<img src="'+d+'"/>')},b=function(b,c,d,e){return function(){var f=L.DomUtil.create("div",c);return L.Browser.touch?L.DomEvent.on(f,"click",L.DomEvent.stopPropagation):(L.DomEvent.disableClickPropagation(f),L.DomEvent.on(f,"mousewheel",L.DomEvent.stopPropagation)),a(f,b,d,e),f}},c=function(a,b){return function(){for(var c=L.DomUtil.create("div",b),d=0;d<a.colors.length;d++)c.innerHTML+='<div class="outline"><i style="background:'+a.colors[d]+'"></i></div><div class="info-label">'+a.labels[d]+"</div>";return L.Browser.touch?L.DomEvent.on(c,"click",L.DomEvent.stopPropagation):(L.DomEvent.disableClickPropagation(c),L.DomEvent.on(c,"mousewheel",L.DomEvent.stopPropagation)),c}};return{getOnAddLegend:b,getOnAddArrayLegend:c,updateLegend:a}}),angular.module("leaflet-directive").factory("leafletMapDefaults",["$q","leafletHelpers",function(a,b){function c(){return{keyboard:!0,dragging:!0,worldCopyJump:!1,doubleClickZoom:!0,scrollWheelZoom:!0,tap:!0,touchZoom:!0,zoomControl:!0,zoomsliderControl:!1,zoomControlPosition:"topleft",attributionControl:!0,controls:{layers:{visible:!0,position:"topright",collapsed:!0}},nominatim:{server:" http://nominatim.openstreetmap.org/search"},crs:L.CRS.EPSG3857,tileLayer:"//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",tileLayerOptions:{attribution:'&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'},path:{weight:10,opacity:1,color:"#0000ff"},center:{lat:0,lng:0,zoom:1}}}var d=b.isDefined,e=b.isObject,f=b.obtainEffectiveMapId,g={};return{reset:function(){g={}},getDefaults:function(a){var b=f(g,a);return g[b]},getMapCreationDefaults:function(a){var b=f(g,a),c=g[b],e={maxZoom:c.maxZoom,keyboard:c.keyboard,dragging:c.dragging,zoomControl:c.zoomControl,doubleClickZoom:c.doubleClickZoom,scrollWheelZoom:c.scrollWheelZoom,tap:c.tap,touchZoom:c.touchZoom,attributionControl:c.attributionControl,worldCopyJump:c.worldCopyJump,crs:c.crs};if(d(c.minZoom)&&(e.minZoom=c.minZoom),d(c.zoomAnimation)&&(e.zoomAnimation=c.zoomAnimation),d(c.fadeAnimation)&&(e.fadeAnimation=c.fadeAnimation),d(c.markerZoomAnimation)&&(e.markerZoomAnimation=c.markerZoomAnimation),c.map)for(var h in c.map)e[h]=c.map[h];return e},setDefaults:function(a,b){var h=c();d(a)&&(h.doubleClickZoom=d(a.doubleClickZoom)?a.doubleClickZoom:h.doubleClickZoom,h.scrollWheelZoom=d(a.scrollWheelZoom)?a.scrollWheelZoom:h.doubleClickZoom,h.tap=d(a.tap)?a.tap:h.tap,h.touchZoom=d(a.touchZoom)?a.touchZoom:h.doubleClickZoom,h.zoomControl=d(a.zoomControl)?a.zoomControl:h.zoomControl,h.zoomsliderControl=d(a.zoomsliderControl)?a.zoomsliderControl:h.zoomsliderControl,h.attributionControl=d(a.attributionControl)?a.attributionControl:h.attributionControl,h.tileLayer=d(a.tileLayer)?a.tileLayer:h.tileLayer,h.zoomControlPosition=d(a.zoomControlPosition)?a.zoomControlPosition:h.zoomControlPosition,h.keyboard=d(a.keyboard)?a.keyboard:h.keyboard,h.dragging=d(a.dragging)?a.dragging:h.dragging,d(a.controls)&&angular.extend(h.controls,a.controls),e(a.crs)?h.crs=a.crs:d(L.CRS[a.crs])&&(h.crs=L.CRS[a.crs]),d(a.center)&&angular.copy(a.center,h.center),d(a.tileLayerOptions)&&angular.copy(a.tileLayerOptions,h.tileLayerOptions),d(a.maxZoom)&&(h.maxZoom=a.maxZoom),d(a.minZoom)&&(h.minZoom=a.minZoom),d(a.zoomAnimation)&&(h.zoomAnimation=a.zoomAnimation),d(a.fadeAnimation)&&(h.fadeAnimation=a.fadeAnimation),d(a.markerZoomAnimation)&&(h.markerZoomAnimation=a.markerZoomAnimation),d(a.worldCopyJump)&&(h.worldCopyJump=a.worldCopyJump),d(a.map)&&(h.map=a.map),d(a.path)&&(h.path=a.path));var i=f(g,b);return g[i]=h,h}}}]),angular.module("leaflet-directive").service("leafletMarkersHelpers",["$rootScope","$timeout","leafletHelpers","$log","$compile","leafletGeoJsonHelpers",function(a,b,c,d,e,f){var g=c.isDefined,h=c.defaultTo,i=c.MarkerClusterPlugin,j=c.AwesomeMarkersPlugin,k=c.VectorMarkersPlugin,l=c.MakiMarkersPlugin,m=c.ExtraMarkersPlugin,n=c.DomMarkersPlugin,o=c.safeApply,p=c,q=c.isString,r=c.isNumber,s=c.isObject,t={},u=f,v=c.errorHeader,w=function(a){
-var b="";return["_icon","_latlng","_leaflet_id","_map","_shadow"].forEach(function(c){b+=c+": "+h(a[c],"undefined")+" \n"}),"[leafletMarker] : \n"+b},x=function(a,b){var c=b?console:d;c.debug(w(a))},y=function(b){if(g(b)&&g(b.type)&&"awesomeMarker"===b.type)return j.isLoaded()||d.error(v+" The AwesomeMarkers Plugin is not loaded."),new L.AwesomeMarkers.icon(b);if(g(b)&&g(b.type)&&"vectorMarker"===b.type)return k.isLoaded()||d.error(v+" The VectorMarkers Plugin is not loaded."),new L.VectorMarkers.icon(b);if(g(b)&&g(b.type)&&"makiMarker"===b.type)return l.isLoaded()||d.error(v+"The MakiMarkers Plugin is not loaded."),new L.MakiMarkers.icon(b);if(g(b)&&g(b.type)&&"extraMarker"===b.type)return m.isLoaded()||d.error(v+"The ExtraMarkers Plugin is not loaded."),new L.ExtraMarkers.icon(b);if(g(b)&&g(b.type)&&"div"===b.type)return new L.divIcon(b);if(g(b)&&g(b.type)&&"dom"===b.type){n.isLoaded()||d.error(v+"The DomMarkers Plugin is not loaded.");var c=angular.isFunction(b.getMarkerScope)?b.getMarkerScope():a,f=e(b.template)(c),h=angular.copy(b);return h.element=f[0],new L.DomMarkers.icon(h)}if(g(b)&&g(b.type)&&"icon"===b.type)return b.icon;var i="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAGmklEQVRYw7VXeUyTZxjvNnfELFuyIzOabermMZEeQC/OclkO49CpOHXOLJl/CAURuYbQi3KLgEhbrhZ1aDwmaoGqKII6odATmH/scDFbdC7LvFqOCc+e95s2VG50X/LLm/f4/Z7neY/ne18aANCmAr5E/xZf1uDOkTcGcWR6hl9247tT5U7Y6SNvWsKT63P58qbfeLJG8M5qcgTknrvvrdDbsT7Ml+tv82X6vVxJE33aRmgSyYtcWVMqX97Yv2JvW39UhRE2HuyBL+t+gK1116ly06EeWFNlAmHxlQE0OMiV6mQCScusKRlhS3QLeVJdl1+23h5dY4FNB3thrbYboqptEFlphTC1hSpJnbRvxP4NWgsE5Jyz86QNNi/5qSUTGuFk1gu54tN9wuK2wc3o+Wc13RCmsoBwEqzGcZsxsvCSy/9wJKf7UWf1mEY8JWfewc67UUoDbDjQC+FqK4QqLVMGGR9d2wurKzqBk3nqIT/9zLxRRjgZ9bqQgub+DdoeCC03Q8j+0QhFhBHR/eP3U/zCln7Uu+hihJ1+bBNffLIvmkyP0gpBZWYXhKussK6mBz5HT6M1Nqpcp+mBCPXosYQfrekGvrjewd59/GvKCE7TbK/04/ZV5QZYVWmDwH1mF3xa2Q3ra3DBC5vBT1oP7PTj4C0+CcL8c7C2CtejqhuCnuIQHaKHzvcRfZpnylFfXsYJx3pNLwhKzRAwAhEqG0SpusBHfAKkxw3w4627MPhoCH798z7s0ZnBJ/MEJbZSbXPhER2ih7p2ok/zSj2cEJDd4CAe+5WYnBCgR2uruyEw6zRoW6/DWJ/OeAP8pd/BGtzOZKpG8oke0SX6GMmRk6GFlyAc59K32OTEinILRJRchah8HQwND8N435Z9Z0FY1EqtxUg+0SO6RJ/mmXz4VuS+DpxXC3gXmZwIL7dBSH4zKE50wESf8qwVgrP1EIlTO5JP9Igu0aexdh28F1lmAEGJGfh7jE6ElyM5Rw/FDcYJjWhbeiBYoYNIpc2FT/SILivp0F1ipDWk4BIEo2VuodEJUifhbiltnNBIXPUFCMpthtAyqws/BPlEF/VbaIxErdxPphsU7rcCp8DohC+GvBIPJS/tW2jtvTmmAeuNO8BNOYQeG8G/2OzCJ3q+soYB5i6NhMaKr17FSal7GIHheuV3uSCY8qYVuEm1cOzqdWr7ku/R0BDoTT+DT+ohCM6/CCvKLKO4RI+dXPeAuaMqksaKrZ7L3FE5FIFbkIceeOZ2OcHO6wIhTkNo0ffgjRGxEqogXHYUPHfWAC/lADpwGcLRY3aeK4/oRGCKYcZXPVoeX/kelVYY8dUGf8V5EBRbgJXT5QIPhP9ePJi428JKOiEYhYXFBqou2Guh+p/mEB1/RfMw6rY7cxcjTrneI1FrDyuzUSRm9miwEJx8E/gUmqlyvHGkneiwErR21F3tNOK5Tf0yXaT+O7DgCvALTUBXdM4YhC/IawPU+2PduqMvuaR6eoxSwUk75ggqsYJ7VicsnwGIkZBSXKOUww73WGXyqP+J2/b9c+gi1YAg/xpwck3gJuucNrh5JvDPvQr0WFXf0piyt8f8/WI0hV4pRxxkQZdJDfDJNOAmM0Ag8jyT6hz0WGXWuP94Yh2jcfjmXAGvHCMslRimDHYuHuDsy2QtHuIavznhbYURq5R57KpzBBRZKPJi8eQg48h4j8SDdowifdIrEVdU+gbO6QNvRRt4ZBthUaZhUnjlYObNagV3keoeru3rU7rcuceqU1mJBxy+BWZYlNEBH+0eH4vRiB+OYybU2hnblYlTvkHinM4m54YnxSyaZYSF6R3jwgP7udKLGIX6r/lbNa9N6y5MFynjWDtrHd75ZvTYAPO/6RgF0k76mQla3FGq7dO+cH8sKn0Vo7nDllwAhqwLPkxrHwWmHJOo+AKJ4rab5OgrM7rVu8eWb2Pu0Dh4eDgXoOfvp7Y7QeqknRmvcTBEyq9m/HQQSCSz6LHq3z0yzsNySRfMS253wl2KyRDbcZPcfJKjZmSEOjcxyi+Y8dUOtsIEH6R2wNykdqrkYJ0RV92H0W58pkfQk7cKevsLK10Py8SdMGfXNXATY+pPbyJR/ET6n9nIfztNtZYRV9XniQu9IA2vOVgy4ir7GCLVmmd+zjkH0eAF9Po6K61pmCXHxU5rHMYd1ftc3owjwRSVRzLjKvqZEty6cRUD7jGqiOdu5HG6MdHjNcNYGqfDm5YRzLBBCCDl/2bk8a8gdbqcfwECu62Fg/HrggAAAABJRU5ErkJggg==",o="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACkAAAApCAYAAACoYAD2AAAC5ElEQVRYw+2YW4/TMBCF45S0S1luXZCABy5CgLQgwf//S4BYBLTdJLax0fFqmB07nnQfEGqkIydpVH85M+NLjPe++dcPc4Q8Qh4hj5D/AaQJx6H/4TMwB0PeBNwU7EGQAmAtsNfAzoZkgIa0ZgLMa4Aj6CxIAsjhjOCoL5z7Glg1JAOkaicgvQBXuncwJAWjksLtBTWZe04CnYRktUGdilALppZBOgHGZcBzL6OClABvMSVIzyBjazOgrvACf1ydC5mguqAVg6RhdkSWQFj2uxfaq/BrIZOLEWgZdALIDvcMcZLD8ZbLC9de4yR1sYMi4G20S4Q/PWeJYxTOZn5zJXANZHIxAd4JWhPIloTJZhzMQduM89WQ3MUVAE/RnhAXpTycqys3NZALOBbB7kFrgLesQl2h45Fcj8L1tTSohUwuxhy8H/Qg6K7gIs+3kkaigQCOcyEXCHN07wyQazhrmIulvKMQAwMcmLNqyCVyMAI+BuxSMeTk3OPikLY2J1uE+VHQk6ANrhds+tNARqBeaGc72cK550FP4WhXmFmcMGhTwAR1ifOe3EvPqIegFmF+C8gVy0OfAaWQPMR7gF1OQKqGoBjq90HPMP01BUjPOqGFksC4emE48tWQAH0YmvOgF3DST6xieJgHAWxPAHMuNhrImIdvoNOKNWIOcE+UXE0pYAnkX6uhWsgVXDxHdTfCmrEEmMB2zMFimLVOtiiajxiGWrbU52EeCdyOwPEQD8LqyPH9Ti2kgYMf4OhSKB7qYILbBv3CuVTJ11Y80oaseiMWOONc/Y7kJYe0xL2f0BaiFTxknHO5HaMGMublKwxFGzYdWsBF174H/QDknhTHmHHN39iWFnkZx8lPyM8WHfYELmlLKtgWNmFNzQcC1b47gJ4hL19i7o65dhH0Negbca8vONZoP7doIeOC9zXm8RjuL0Gf4d4OYaU5ljo3GYiqzrWQHfJxA6ALhDpVKv9qYeZA8eM3EhfPSCmpuD0AAAAASUVORK5CYII=";return g(b)&&g(b.iconUrl)?new L.Icon(b):new L.Icon.Default({iconUrl:i,shadowUrl:o,iconSize:[25,41],iconAnchor:[12,41],popupAnchor:[1,-34],shadowSize:[41,41]})},z=function(a){g(t[a])&&t.splice(a,1)},A=function(){t={}},B=function(a,b,c){if(a.closePopup(),g(c)&&g(c.overlays))for(var d in c.overlays)if((c.overlays[d]instanceof L.LayerGroup||c.overlays[d]instanceof L.FeatureGroup)&&c.overlays[d].hasLayer(a))return void c.overlays[d].removeLayer(a);if(g(t))for(var e in t)t[e].hasLayer(a)&&t[e].removeLayer(a);b.hasLayer(a)&&b.removeLayer(a)},C=function(a,b){var c=a._popup._container.offsetHeight,d=new L.Point(a._popup._containerLeft,-c-a._popup._containerBottom),e=b.layerPointToContainerPoint(d);null!==e&&a._popup._adjustPan()},D=function(a,b){e(a._popup._contentNode)(b)},E=function(a,c,d){var e=a._popup._contentNode.innerText||a._popup._contentNode.textContent;e.length<1&&b(function(){E(a,c,d)});var f=a._popup._contentNode.offsetWidth;return a._popup._updateLayout(),a._popup._updatePosition(),a._popup.options.autoPan&&C(a,d),f},F=function(b,c,e){var f=angular.isFunction(c.getMessageScope)?c.getMessageScope():a,h=g(c.compileMessage)?c.compileMessage:!0;if(h){if(!g(b._popup)||!g(b._popup._contentNode))return d.error(v+"Popup is invalid or does not have any content."),!1;D(b,f),E(b,c,e)}},G=function(b,c){var d=angular.isFunction(c.getMessageScope)?c.getMessageScope():a,f=angular.isFunction(c.getLabelScope)?c.getLabelScope():d,h=g(c.compileMessage)?c.compileMessage:!0;p.LabelPlugin.isLoaded()&&g(c.label)&&(g(c.label.options)&&c.label.options.noHide===!0&&b.showLabel(),h&&g(b.label)&&e(b.label._container)(f))},H=function(a,b,c,e,f,h,i){if(g(b)){if(!u.validateCoords(a))return d.warn("There are problems with lat-lng data, please verify your marker model"),void B(c,i,h);var j=a===b;if(g(a.iconAngle)&&b.iconAngle!==a.iconAngle&&c.setIconAngle(a.iconAngle),q(a.layer)||q(b.layer)&&(g(h.overlays[b.layer])&&h.overlays[b.layer].hasLayer(c)&&(h.overlays[b.layer].removeLayer(c),c.closePopup()),i.hasLayer(c)||i.addLayer(c)),(r(a.opacity)||r(parseFloat(a.opacity)))&&a.opacity!==b.opacity&&c.setOpacity(a.opacity),q(a.layer)&&b.layer!==a.layer){if(q(b.layer)&&g(h.overlays[b.layer])&&h.overlays[b.layer].hasLayer(c)&&h.overlays[b.layer].removeLayer(c),c.closePopup(),i.hasLayer(c)&&i.removeLayer(c),!g(h.overlays[a.layer]))return void d.error(v+"You must use a name of an existing layer");var k=h.overlays[a.layer];if(!(k instanceof L.LayerGroup||k instanceof L.FeatureGroup))return void d.error(v+'A marker can only be added to a layer of type "group" or "featureGroup"');k.addLayer(c),i.hasLayer(c)&&a.focus===!0&&c.openPopup()}if(a.draggable!==!0&&b.draggable===!0&&g(c.dragging)&&c.dragging.disable(),a.draggable===!0&&b.draggable!==!0&&(c.dragging?c.dragging.enable():L.Handler.MarkerDrag&&(c.dragging=new L.Handler.MarkerDrag(c),c.options.draggable=!0,c.dragging.enable())),s(a.icon)||s(b.icon)&&(c.setIcon(y()),c.closePopup(),c.unbindPopup(),q(a.message)&&c.bindPopup(a.message,a.popupOptions)),s(a.icon)&&s(b.icon)&&!angular.equals(a.icon,b.icon)){var l=!1;c.dragging&&(l=c.dragging.enabled()),c.setIcon(y(a.icon)),l&&c.dragging.enable(),c.closePopup(),c.unbindPopup(),q(a.message)&&(c.bindPopup(a.message,a.popupOptions),i.hasLayer(c)&&a.focus===!0&&c.openPopup())}!q(a.message)&&q(b.message)&&(c.closePopup(),c.unbindPopup()),p.LabelPlugin.isLoaded()&&(g(a.label)&&g(a.label.message)?"label"in b&&"message"in b.label&&!angular.equals(a.label.message,b.label.message)?c.updateLabelContent(a.label.message):!angular.isFunction(c.getLabel)||angular.isFunction(c.getLabel)&&!g(c.getLabel())?(c.bindLabel(a.label.message,a.label.options),G(c,a)):G(c,a):(!("label"in a)||"message"in a.label)&&angular.isFunction(c.unbindLabel)&&c.unbindLabel()),q(a.message)&&!q(b.message)&&c.bindPopup(a.message,a.popupOptions),q(a.message)&&q(b.message)&&a.message!==b.message&&c.setPopupContent(a.message);var m=!1;a.focus!==!0&&b.focus===!0&&(c.closePopup(),m=!0),(a.focus===!0&&(!g(b.focus)||b.focus===!1)||j&&a.focus===!0)&&(c.openPopup(),m=!0),b.zIndexOffset!==a.zIndexOffset&&c.setZIndexOffset(a.zIndexOffset);var n=c.getLatLng(),o=q(a.layer)&&p.MarkerClusterPlugin.is(h.overlays[a.layer]);o?m?(a.lat!==b.lat||a.lng!==b.lng)&&(h.overlays[a.layer].removeLayer(c),c.setLatLng([a.lat,a.lng]),h.overlays[a.layer].addLayer(c)):n.lat!==a.lat||n.lng!==a.lng?(h.overlays[a.layer].removeLayer(c),c.setLatLng([a.lat,a.lng]),h.overlays[a.layer].addLayer(c)):a.lat!==b.lat||a.lng!==b.lng?(h.overlays[a.layer].removeLayer(c),c.setLatLng([a.lat,a.lng]),h.overlays[a.layer].addLayer(c)):s(a.icon)&&s(b.icon)&&!angular.equals(a.icon,b.icon)&&(h.overlays[a.layer].removeLayer(c),h.overlays[a.layer].addLayer(c)):(n.lat!==a.lat||n.lng!==a.lng)&&c.setLatLng([a.lat,a.lng])}};return{resetMarkerGroup:z,resetMarkerGroups:A,deleteMarker:B,manageOpenPopup:F,manageOpenLabel:G,createMarker:function(a){if(!g(a)||!u.validateCoords(a))return void d.error(v+"The marker definition is not valid.");var b=u.getCoords(a);if(!g(b))return void d.error(v+"Unable to get coordinates from markerData.");var c={icon:y(a.icon),title:g(a.title)?a.title:"",draggable:g(a.draggable)?a.draggable:!1,clickable:g(a.clickable)?a.clickable:!0,riseOnHover:g(a.riseOnHover)?a.riseOnHover:!1,zIndexOffset:g(a.zIndexOffset)?a.zIndexOffset:0,iconAngle:g(a.iconAngle)?a.iconAngle:0};for(var e in a)a.hasOwnProperty(e)&&!c.hasOwnProperty(e)&&(c[e]=a[e]);var f=new L.marker(b,c);return q(a.message)||f.unbindPopup(),f},addMarkerToGroup:function(a,b,c,e){return q(b)?i.isLoaded()?(g(t[b])||(t[b]=new L.MarkerClusterGroup(c),e.addLayer(t[b])),void t[b].addLayer(a)):void d.error(v+"The MarkerCluster plugin is not loaded."):void d.error(v+"The marker group you have specified is invalid.")},listenMarkerEvents:function(a,b,c,d,e){a.on("popupopen",function(){o(c,function(){(g(a._popup)||g(a._popup._contentNode))&&(b.focus=!0,F(a,b,e))})}),a.on("popupclose",function(){o(c,function(){b.focus=!1})}),a.on("add",function(){o(c,function(){"label"in b&&G(a,b)})})},updateMarker:H,addMarkerWatcher:function(a,b,c,d,e,f){var i=p.getObjectArrayPath("markers."+b);f=h(f,!0);var j=c.$watch(i,function(f,h){return g(f)?void H(f,h,a,b,c,d,e):(B(a,e,d),void j())},f)},string:w,log:x}}]),angular.module("leaflet-directive").factory("leafletPathsHelpers",["$rootScope","$log","leafletHelpers",function(a,b,c){function d(a){return a.filter(function(a){return k(a)}).map(function(a){return e(a)})}function e(a){return i(a)?new L.LatLng(a[0],a[1]):new L.LatLng(a.lat,a.lng)}function f(a){return a.map(function(a){return d(a)})}function g(a,b){for(var c={},d=0;d<l.length;d++){var e=l[d];h(a[e])?c[e]=a[e]:h(b.path[e])&&(c[e]=b.path[e])}return c}var h=c.isDefined,i=c.isArray,j=c.isNumber,k=c.isValidPoint,l=["stroke","weight","color","opacity","fill","fillColor","fillOpacity","dashArray","lineCap","lineJoin","clickable","pointerEvents","className","smoothFactor","noClip"],m=function(a,b){for(var c={},d=0;d<l.length;d++){var e=l[d];h(b[e])&&(c[e]=b[e])}a.setStyle(b)},n=function(a){if(!i(a))return!1;for(var b=0;b<a.length;b++){var c=a[b];if(!k(c))return!1}return!0},o={polyline:{isValid:function(a){var b=a.latlngs;return n(b)},createPath:function(a){return new L.Polyline([],a)},setPath:function(a,b){a.setLatLngs(d(b.latlngs)),m(a,b)}},multiPolyline:{isValid:function(a){var b=a.latlngs;if(!i(b))return!1;for(var c in b){var d=b[c];if(!n(d))return!1}return!0},createPath:function(a){return new L.multiPolyline([[[0,0],[1,1]]],a)},setPath:function(a,b){a.setLatLngs(f(b.latlngs)),m(a,b)}},polygon:{isValid:function(a){var b=a.latlngs;return n(b)},createPath:function(a){return new L.Polygon([],a)},setPath:function(a,b){a.setLatLngs(d(b.latlngs)),m(a,b)}},multiPolygon:{isValid:function(a){var b=a.latlngs;if(!i(b))return!1;for(var c in b){var d=b[c];if(!n(d))return!1}return!0},createPath:function(a){return new L.MultiPolygon([[[0,0],[1,1],[0,1]]],a)},setPath:function(a,b){a.setLatLngs(f(b.latlngs)),m(a,b)}},rectangle:{isValid:function(a){var b=a.latlngs;if(!i(b)||2!==b.length)return!1;for(var c in b){var d=b[c];if(!k(d))return!1}return!0},createPath:function(a){return new L.Rectangle([[0,0],[1,1]],a)},setPath:function(a,b){a.setBounds(new L.LatLngBounds(d(b.latlngs))),m(a,b)}},circle:{isValid:function(a){var b=a.latlngs;return k(b)&&j(a.radius)},createPath:function(a){return new L.Circle([0,0],1,a)},setPath:function(a,b){a.setLatLng(e(b.latlngs)),h(b.radius)&&a.setRadius(b.radius),m(a,b)}},circleMarker:{isValid:function(a){var b=a.latlngs;return k(b)&&j(a.radius)},createPath:function(a){return new L.CircleMarker([0,0],a)},setPath:function(a,b){a.setLatLng(e(b.latlngs)),h(b.radius)&&a.setRadius(b.radius),m(a,b)}}},p=function(a){var b={};return a.latlngs&&(b.latlngs=a.latlngs),a.radius&&(b.radius=a.radius),b};return{setPathOptions:function(a,b,c){h(b)||(b="polyline"),o[b].setPath(a,c)},createPath:function(a,c,d){h(c.type)||(c.type="polyline");var e=g(c,d),f=p(c);return o[c.type].isValid(f)?o[c.type].createPath(e):void b.error("[AngularJS - Leaflet] Invalid data passed to the "+c.type+" path")}}}]),angular.module("leaflet-directive").service("leafletWatchHelpers",function(){var a=function(a,b,c,d,e){var f=a[b](c,function(a,b){e(a,b),d.doWatch||f()},d.isDeep);return f},b=function(b,c,d,e){return a(b,"$watch",c,d,e)},c=function(b,c,d,e){return a(b,"$watchCollection",c,d,e)};return{maybeWatch:b,maybeWatchCollection:c}}),angular.module("leaflet-directive").factory("nominatimService",["$q","$http","leafletHelpers","leafletMapDefaults",function(a,b,c,d){var e=c.isDefined;return{query:function(c,f){var g=d.getDefaults(f),h=g.nominatim.server,i=a.defer();return b.get(h,{params:{format:"json",limit:1,q:c}}).success(function(a){a.length>0&&e(a[0].boundingbox)?i.resolve(a[0]):i.reject("[Nominatim] Invalid address")}),i.promise}}}]),angular.module("leaflet-directive").directive("bounds",["$log","$timeout","$http","leafletHelpers","nominatimService","leafletBoundsHelpers",function(a,b,c,d,e,f){return{restrict:"A",scope:!1,replace:!1,require:["leaflet"],link:function(c,g,h,i){var j=d.isDefined,k=f.createLeafletBounds,l=i[0].getLeafletScope(),m=i[0],n=d.errorHeader+" [Bounds] ",o=function(a){return 0===a._southWest.lat&&0===a._southWest.lng&&0===a._northEast.lat&&0===a._northEast.lng};m.getMap().then(function(d){l.$on("boundsChanged",function(a){var c=a.currentScope,e=d.getBounds();if(!o(e)&&!c.settingBoundsFromScope){c.settingBoundsFromLeaflet=!0;var f={northEast:{lat:e._northEast.lat,lng:e._northEast.lng},southWest:{lat:e._southWest.lat,lng:e._southWest.lng},options:e.options};angular.equals(c.bounds,f)||(c.bounds=f),b(function(){c.settingBoundsFromLeaflet=!1})}});var f;l.$watch("bounds",function(g){if(!c.settingBoundsFromLeaflet){if(j(g.address)&&g.address!==f)return c.settingBoundsFromScope=!0,e.query(g.address,h.id).then(function(a){var b=a.boundingbox,c=[[b[0],b[2]],[b[1],b[3]]];d.fitBounds(c)},function(b){a.error(n+" "+b+".")}),f=g.address,void b(function(){c.settingBoundsFromScope=!1});var i=k(g);i&&!d.getBounds().equals(i)&&(c.settingBoundsFromScope=!0,d.fitBounds(i,g.options),b(function(){c.settingBoundsFromScope=!1}))}},!0)})}}}]);var centerDirectiveTypes=["center","lfCenter"],centerDirectives={};centerDirectiveTypes.forEach(function(a){centerDirectives[a]=["$log","$q","$location","$timeout","leafletMapDefaults","leafletHelpers","leafletBoundsHelpers","leafletMapEvents",function(b,c,d,e,f,g,h,i){var j,k=g.isDefined,l=g.isNumber,m=g.isSameCenterOnMap,n=g.safeApply,o=g.isValidCenter,p=h.isValidBounds,q=g.isUndefinedOrEmpty,r=g.errorHeader,s=function(a,b){return k(a)&&p(a)&&q(b)};return{restrict:"A",scope:!1,replace:!1,require:"leaflet",controller:function(){j=c.defer(),this.getCenter=function(){return j.promise}},link:function(c,g,p,q){var t=q.getLeafletScope(),u=t[a];q.getMap().then(function(c){var g=f.getDefaults(p.id);if(-1!==p[a].search("-"))return b.error(r+' The "center" variable can\'t use a "-" on its key name: "'+p[a]+'".'),void c.setView([g.center.lat,g.center.lng],g.center.zoom);if(s(t.bounds,u))c.fitBounds(h.createLeafletBounds(t.bounds),t.bounds.options),u=c.getCenter(),n(t,function(b){angular.extend(b[a],{lat:c.getCenter().lat,lng:c.getCenter().lng,zoom:c.getZoom(),autoDiscover:!1})}),n(t,function(a){var b=c.getBounds();a.bounds={northEast:{lat:b._northEast.lat,lng:b._northEast.lng},southWest:{lat:b._southWest.lat,lng:b._southWest.lng}}});else{if(!k(u))return b.error(r+' The "center" property is not defined in the main scope'),void c.setView([g.center.lat,g.center.lng],g.center.zoom);k(u.lat)&&k(u.lng)||k(u.autoDiscover)||angular.copy(g.center,u)}var q,v;if("yes"===p.urlHashCenter){var w=function(){var a,b=d.search();if(k(b.c)){var c=b.c.split(":");3===c.length&&(a={lat:parseFloat(c[0]),lng:parseFloat(c[1]),zoom:parseInt(c[2],10)})}return a};q=w(),t.$on("$locationChangeSuccess",function(b){var d=b.currentScope,e=w();k(e)&&!m(e,c)&&angular.extend(d[a],{lat:e.lat,lng:e.lng,zoom:e.zoom})})}t.$watch(a,function(a){return t.settingCenterFromLeaflet?void 0:(k(q)&&(angular.copy(q,a),q=void 0),o(a)||a.autoDiscover===!0?a.autoDiscover===!0?(l(a.zoom)||c.setView([g.center.lat,g.center.lng],g.center.zoom),void(l(a.zoom)&&a.zoom>g.center.zoom?c.locate({setView:!0,maxZoom:a.zoom}):k(g.maxZoom)?c.locate({setView:!0,maxZoom:g.maxZoom}):c.locate({setView:!0}))):void(v&&m(a,c)||(t.settingCenterFromScope=!0,c.setView([a.lat,a.lng],a.zoom),i.notifyCenterChangedToBounds(t,c),e(function(){t.settingCenterFromScope=!1}))):void b.warn(r+" invalid 'center'"))},!0),c.whenReady(function(){v=!0}),c.on("moveend",function(){j.resolve(),i.notifyCenterUrlHashChanged(t,c,p,d.search()),m(u,c)||t.settingCenterFromScope||(t.settingCenterFromLeaflet=!0,n(t,function(b){t.settingCenterFromScope||angular.extend(b[a],{lat:c.getCenter().lat,lng:c.getCenter().lng,zoom:c.getZoom(),autoDiscover:!1}),i.notifyCenterChangedToBounds(t,c),e(function(){t.settingCenterFromLeaflet=!1})}))}),u.autoDiscover===!0&&c.on("locationerror",function(){b.warn(r+" The Geolocation API is unauthorized on this page."),o(u)?(c.setView([u.lat,u.lng],u.zoom),i.notifyCenterChangedToBounds(t,c)):(c.setView([g.center.lat,g.center.lng],g.center.zoom),i.notifyCenterChangedToBounds(t,c))})})}}}]}),centerDirectiveTypes.forEach(function(a){angular.module("leaflet-directive").directive(a,centerDirectives[a])}),angular.module("leaflet-directive").directive("controls",["$log","leafletHelpers","leafletControlHelpers",function(a,b,c){return{restrict:"A",scope:!1,replace:!1,require:"?^leaflet",link:function(d,e,f,g){if(g){var h=c.createControl,i=c.isValidControlType,j=g.getLeafletScope(),k=b.isDefined,l=b.isArray,m={},n=b.errorHeader+" [Controls] ";g.getMap().then(function(b){j.$watchCollection("controls",function(c){for(var d in m)k(c[d])||(b.hasControl(m[d])&&b.removeControl(m[d]),delete m[d]);for(var e in c){var f,g=k(c[e].type)?c[e].type:e;if(!i(g))return void a.error(n+" Invalid control type: "+g+".");if("custom"!==g)f=h(g,c[e]),b.addControl(f),m[e]=f;else{var j=c[e];if(l(j))for(var o in j){var p=j[o];b.addControl(p),m[e]=k(m[e])?m[e].concat([p]):[p]}else b.addControl(j),m[e]=j}}})})}}}}]),angular.module("leaflet-directive").directive("decorations",["$log","leafletHelpers",function(a,b){return{restrict:"A",scope:!1,replace:!1,require:"leaflet",link:function(c,d,e,f){function g(b){return k(b)&&k(b.coordinates)&&(j.isLoaded()||a.error("[AngularJS - Leaflet] The PolylineDecorator Plugin is not loaded.")),L.polylineDecorator(b.coordinates)}function h(a,b){return k(a)&&k(b)&&k(b.coordinates)&&k(b.patterns)?(a.setPaths(b.coordinates),a.setPatterns(b.patterns),a):void 0}var i=f.getLeafletScope(),j=b.PolylineDecoratorPlugin,k=b.isDefined,l={};f.getMap().then(function(a){i.$watch("decorations",function(b){for(var c in l)k(b[c])&&angular.equals(b[c],l)||(a.removeLayer(l[c]),delete l[c]);for(var d in b){var e=b[d],f=g(e);k(f)&&(l[d]=f,a.addLayer(f),h(f,e))}},!0)})}}}]),angular.module("leaflet-directive").directive("eventBroadcast",["$log","$rootScope","leafletHelpers","leafletMapEvents","leafletIterators",function(a,b,c,d,e){return{restrict:"A",scope:!1,replace:!1,require:"leaflet",link:function(b,f,g,h){var i=c.isObject,j=c.isDefined,k=h.getLeafletScope(),l=k.eventBroadcast,m=d.getAvailableMapEvents(),n=d.addEvents;h.getMap().then(function(b){var c=[],d="broadcast";j(l.map)?i(l.map)?("emit"!==l.map.logic&&"broadcast"!==l.map.logic?a.warn("[AngularJS - Leaflet] Available event propagation logic are: 'emit' or 'broadcast'."):d=l.map.logic,i(l.map.enable)&&l.map.enable.length>=0?e.each(l.map.enable,function(a){-1===c.indexOf(a)&&-1!==m.indexOf(a)&&c.push(a)}):a.warn("[AngularJS - Leaflet] event-broadcast.map.enable must be an object check your model.")):a.warn("[AngularJS - Leaflet] event-broadcast.map must be an object check your model."):c=m,n(b,c,"eventName",k,d)})}}}]),angular.module("leaflet-directive").directive("geojson",["$log","$rootScope","leafletData","leafletHelpers","leafletWatchHelpers","leafletDirectiveControlsHelpers","leafletIterators","leafletGeoJsonEvents",function(a,b,c,d,e,f,g,h){var i=e.maybeWatch,j=d.watchOptions,k=f.extend,l=d,m=g;return{restrict:"A",scope:!1,replace:!1,require:"leaflet",link:function(a,b,e,f){var g=d.isDefined,n=f.getLeafletScope(),o={},p=!1;f.getMap().then(function(a){var b=n.geojsonWatchOptions||j,f=function(a,b){var c;return c=angular.isFunction(a.onEachFeature)?a.onEachFeature:function(c,f){d.LabelPlugin.isLoaded()&&g(c.properties.description)&&f.bindLabel(c.properties.description),h.bindEvents(e.id,f,null,c,n,b,{resetStyleOnMouseout:a.resetStyleOnMouseout,mapId:e.id})}},q=l.isDefined(e.geojsonNested)&&l.isTruthy(e.geojsonNested),r=function(){if(o){var b=function(b){g(b)&&a.hasLayer(b)&&a.removeLayer(b)};return q?void m.each(o,function(a){b(a)}):void b(o)}},s=function(b,d){var h=angular.copy(b);if(g(h)&&g(h.data)){var i=f(h,d);g(h.options)||(h.options={style:h.style,filter:h.filter,onEachFeature:i,pointToLayer:h.pointToLayer});var j=L.geoJson(h.data,h.options);d&&l.isString(d)?o[d]=j:o=j,j.addTo(a),p||(p=!0,c.setGeoJSON(o,e.id))}},t=function(a){if(r(),q){if(!a||!Object.keys(a).length)return;return void m.each(a,function(a,b){s(a,b)})}s(a)};k(e.id,"geojson",t,r),i(n,"geojson",b,function(a){t(a)})})}}}]),angular.module("leaflet-directive").directive("layercontrol",["$filter","$log","leafletData","leafletHelpers",function(a,b,c,d){return{restrict:"E",scope:{icons:"=?",autoHideOpacity:"=?",showGroups:"=?",title:"@",baseTitle:"@",overlaysTitle:"@"},replace:!0,transclude:!1,require:"^leaflet",controller:["$scope","$element","$sce",function(a,e,f){b.debug("[Angular Directive - Layers] layers",a,e);var g=d.safeApply,h=d.isDefined;angular.extend(a,{baselayer:"",oldGroup:"",layerProperties:{},groupProperties:{},rangeIsSupported:d.rangeIsSupported(),changeBaseLayer:function(b,e){d.safeApply(a,function(d){d.baselayer=b,c.getMap().then(function(e){c.getLayers().then(function(c){if(!e.hasLayer(c.baselayers[b])){for(var f in d.layers.baselayers)d.layers.baselayers[f].icon=d.icons.unradio,e.hasLayer(c.baselayers[f])&&e.removeLayer(c.baselayers[f]);e.addLayer(c.baselayers[b]),d.layers.baselayers[b].icon=a.icons.radio}})})}),e.preventDefault()},moveLayer:function(b,c,d){var e=Object.keys(a.layers.baselayers).length;if(c>=1+e&&c<=a.overlaysArray.length+e){var f;for(var h in a.layers.overlays)if(a.layers.overlays[h].index===c){f=a.layers.overlays[h];break}f&&g(a,function(){f.index=b.index,b.index=c})}d.stopPropagation(),d.preventDefault()},initIndex:function(b,c){var d=Object.keys(a.layers.baselayers).length;b.index=h(b.index)?b.index:c+d+1},initGroup:function(b){a.groupProperties[b]=a.groupProperties[b]?a.groupProperties[b]:{}},toggleOpacity:function(b,c){if(c.visible){if(a.autoHideOpacity&&!a.layerProperties[c.name].opacityControl)for(var d in a.layerProperties)a.layerProperties[d].opacityControl=!1;a.layerProperties[c.name].opacityControl=!a.layerProperties[c.name].opacityControl}b.stopPropagation(),b.preventDefault()},toggleLegend:function(b){a.layerProperties[b.name].showLegend=!a.layerProperties[b.name].showLegend},showLegend:function(b){return b.legend&&a.layerProperties[b.name].showLegend},unsafeHTML:function(a){return f.trustAsHtml(a)},getOpacityIcon:function(b){return b.visible&&a.layerProperties[b.name].opacityControl?a.icons.close:a.icons.open},getGroupIcon:function(b){return b.visible?a.icons.check:a.icons.uncheck},changeOpacity:function(b){var d=a.layerProperties[b.name].opacity;c.getMap().then(function(e){c.getLayers().then(function(c){var f;for(var g in a.layers.overlays)if(a.layers.overlays[g]===b){f=c.overlays[g];break}e.hasLayer(f)&&(f.setOpacity&&f.setOpacity(d/100),f.getLayers&&f.eachLayer&&f.eachLayer(function(a){a.setOpacity&&a.setOpacity(d/100)}))})})},changeGroupVisibility:function(b){if(h(a.groupProperties[b])){var c=a.groupProperties[b].visible;for(var d in a.layers.overlays){var e=a.layers.overlays[d];e.group===b&&(e.visible=c)}}}});var i=e.get(0);L.Browser.touch?L.DomEvent.on(i,"click",L.DomEvent.stopPropagation):(L.DomEvent.disableClickPropagation(i),L.DomEvent.on(i,"mousewheel",L.DomEvent.stopPropagation))}],template:'<div class="angular-leaflet-control-layers" ng-show="overlaysArray.length"><h4 ng-if="title">{{ title }}</h4><div class="lf-baselayers"><h5 class="lf-title" ng-if="baseTitle">{{ baseTitle }}</h5><div class="lf-row" ng-repeat="(key, layer) in baselayersArray"><label class="lf-icon-bl" ng-click="changeBaseLayer(key, $event)"><input class="leaflet-control-layers-selector" type="radio" name="lf-radio" ng-show="false" ng-checked="baselayer === key" ng-value="key" /> <i class="lf-icon lf-icon-radio" ng-class="layer.icon"></i><div class="lf-text">{{layer.name}}</div></label></div></div><div class="lf-overlays"><h5 class="lf-title" ng-if="overlaysTitle">{{ overlaysTitle }}</h5><div class="lf-container"><div class="lf-row" ng-repeat="layer in (o = (overlaysArray | orderBy:\'index\':order))" ng-init="initIndex(layer, $index)"><label class="lf-icon-ol-group" ng-if="showGroups &amp;&amp; layer.group &amp;&amp; layer.group != o[$index-1].group"><input class="lf-control-layers-selector" type="checkbox" ng-show="false" ng-change="changeGroupVisibility(layer.group)" ng-model="groupProperties[layer.group].visible"/> <i class="lf-icon lf-icon-check" ng-class="getGroupIcon(groupProperties[layer.group])"></i><div class="lf-text">{{ layer.group }}</div></label><label class="lf-icon-ol"><input class="lf-control-layers-selector" type="checkbox" ng-show="false" ng-model="layer.visible"/> <i class="lf-icon lf-icon-check" ng-class="layer.icon"></i><div class="lf-text">{{layer.name}}</div></label><div class="lf-icons"><i class="lf-icon lf-up" ng-class="icons.up" ng-click="moveLayer(layer, layer.index - orderNumber, $event)"></i> <i class="lf-icon lf-down" ng-class="icons.down" ng-click="moveLayer(layer, layer.index + orderNumber, $event)"></i> <i class="lf-icon lf-toggle-legend" ng-class="icons.toggleLegend" ng-if="layer.legend" ng-click="toggleLegend(layer)"></i> <i class="lf-icon lf-open" ng-class="getOpacityIcon(layer)" ng-click="toggleOpacity($event, layer)"></i></div><div class="lf-legend" ng-if="showLegend(layer)" ng-bind-html="unsafeHTML(layer.legend)"></div><div class="lf-opacity clearfix" ng-if="layer.visible &amp;&amp; layerProperties[layer.name].opacityControl"><label ng-if="rangeIsSupported" class="pull-left" style="width: 50%">0</label><label ng-if="rangeIsSupported" class="pull-left text-right" style="width: 50%">100</label><input ng-if="rangeIsSupported" class="clearfix" type="range" min="0" max="100" class="lf-opacity-control" ng-model="layerProperties[layer.name].opacity" ng-change="changeOpacity(layer)"/><h6 ng-if="!rangeIsSupported">Range is not supported in this browser</h6></div></div></div></div></div>',link:function(a,b,e,f){var g=d.isDefined,h=f.getLeafletScope(),i=h.layers;a.$watch("icons",function(){var b={uncheck:"fa fa-square-o",check:"fa fa-check-square-o",radio:"fa fa-dot-circle-o",unradio:"fa fa-circle-o",up:"fa fa-angle-up",down:"fa fa-angle-down",open:"fa fa-angle-double-down",close:"fa fa-angle-double-up",toggleLegend:"fa fa-pencil-square-o"};g(a.icons)?(angular.extend(b,a.icons),angular.extend(a.icons,b)):a.icons=b}),e.order=!g(e.order)||"normal"!==e.order&&"reverse"!==e.order?"normal":e.order,a.order="normal"===e.order,a.orderNumber="normal"===e.order?-1:1,a.layers=i,f.getMap().then(function(b){h.$watch("layers.baselayers",function(d){var e={};c.getLayers().then(function(c){var f;for(f in d){var g=d[f];g.icon=a.icons[b.hasLayer(c.baselayers[f])?"radio":"unradio"],e[f]=g}a.baselayersArray=e})}),h.$watch("layers.overlays",function(b){var d=[],e={};c.getLayers().then(function(c){var f;for(f in b){var h=b[f];h.icon=a.icons[h.visible?"check":"uncheck"],d.push(h),g(a.layerProperties[h.name])||(a.layerProperties[h.name]={opacity:g(h.layerOptions.opacity)?100*h.layerOptions.opacity:100,opacityControl:!1,showLegend:!0}),g(h.group)&&(g(a.groupProperties[h.group])||(a.groupProperties[h.group]={visible:!1}),e[h.group]=g(e[h.group])?e[h.group]:{count:0,visibles:0},e[h.group].count++,h.visible&&e[h.group].visibles++),g(h.index)&&c.overlays[f].setZIndex&&c.overlays[f].setZIndex(b[f].index)}for(f in e)a.groupProperties[f].visible=e[f].visibles===e[f].count;a.overlaysArray=d})},!0)})}}}]),angular.module("leaflet-directive").directive("layers",["$log","$q","leafletData","leafletHelpers","leafletLayerHelpers","leafletControlHelpers",function(a,b,c,d,e,f){return{restrict:"A",scope:!1,replace:!1,require:"leaflet",controller:["$scope",function(a){a._leafletLayers=b.defer(),this.getLayers=function(){return a._leafletLayers.promise}}],link:function(a,b,g,h){var i=d.isDefined,j={},k=h.getLeafletScope(),l=k.layers,m=e.createLayer,n=e.safeAddLayer,o=e.safeRemoveLayer,p=f.updateLayersControl,q=!1;h.getMap().then(function(b){a._leafletLayers.resolve(j),c.setLayers(j,g.id),j.baselayers={},j.overlays={};var d=g.id,e=!1;for(var f in l.baselayers){var h=m(l.baselayers[f]);i(h)?(j.baselayers[f]=h,l.baselayers[f].top===!0&&(n(b,j.baselayers[f]),e=!0)):delete l.baselayers[f]}!e&&Object.keys(j.baselayers).length>0&&n(b,j.baselayers[Object.keys(l.baselayers)[0]]);for(f in l.overlays){var r=m(l.overlays[f]);i(r)?(j.overlays[f]=r,l.overlays[f].visible===!0&&n(b,j.overlays[f])):delete l.overlays[f]}k.$watch("layers.baselayers",function(a,c){if(angular.equals(a,c))return q=p(b,d,q,a,l.overlays,j),!0;for(var e in j.baselayers)(!i(a[e])||a[e].doRefresh)&&(b.hasLayer(j.baselayers[e])&&b.removeLayer(j.baselayers[e]),delete j.baselayers[e],a[e]&&a[e].doRefresh&&(a[e].doRefresh=!1));for(var f in a)if(i(j.baselayers[f]))a[f].top!==!0||b.hasLayer(j.baselayers[f])?a[f].top===!1&&b.hasLayer(j.baselayers[f])&&b.removeLayer(j.baselayers[f]):n(b,j.baselayers[f]);else{var g=m(a[f]);i(g)&&(j.baselayers[f]=g,a[f].top===!0&&n(b,j.baselayers[f]))}var h=!1;for(var k in j.baselayers)if(b.hasLayer(j.baselayers[k])){h=!0;break}!h&&Object.keys(j.baselayers).length>0&&n(b,j.baselayers[Object.keys(j.baselayers)[0]]),q=p(b,d,q,a,l.overlays,j)},!0),k.$watch("layers.overlays",function(a,c){if(angular.equals(a,c))return q=p(b,d,q,l.baselayers,a,j),!0;for(var e in j.overlays)if(!i(a[e])||a[e].doRefresh){if(b.hasLayer(j.overlays[e])){var f=i(a[e])?a[e].layerOptions:null;o(b,j.overlays[e],f)}delete j.overlays[e],a[e]&&a[e].doRefresh&&(a[e].doRefresh=!1)}for(var g in a){if(i(j.overlays[g]))a[g].visible&&!b.hasLayer(j.overlays[g])?n(b,j.overlays[g]):a[g].visible===!1&&b.hasLayer(j.overlays[g])&&o(b,j.overlays[g],a[g].layerOptions);else{
-var h=m(a[g]);if(!i(h))continue;j.overlays[g]=h,a[g].visible===!0&&n(b,j.overlays[g])}a[g].visible&&b._loaded&&a[g].data&&"heatmap"===a[g].type&&(j.overlays[g].setData(a[g].data),j.overlays[g].update())}q=p(b,d,q,l.baselayers,a,j)},!0)})}}}]),angular.module("leaflet-directive").directive("legend",["$log","$http","leafletHelpers","leafletLegendHelpers",function(a,b,c,d){return{restrict:"A",scope:!1,replace:!1,require:"leaflet",link:function(e,f,g,h){var i,j,k,l,m=c.isArray,n=c.isDefined,o=c.isFunction,p=h.getLeafletScope(),q=p.legend;p.$watch("legend",function(a){n(a)&&(i=a.legendClass?a.legendClass:"legend",j=a.position||"bottomright",l=a.type||"arcgis")},!0),h.getMap().then(function(c){p.$watch("legend",function(b){return n(b)?n(b.url)||"arcgis"!==l||m(b.colors)&&m(b.labels)&&b.colors.length===b.labels.length?n(b.url)?void a.info("[AngularJS - Leaflet] loading legend service."):(n(k)&&(k.removeFrom(c),k=null),k=L.control({position:j}),"arcgis"===l&&(k.onAdd=d.getOnAddArrayLegend(b,i)),void k.addTo(c)):void a.warn("[AngularJS - Leaflet] legend.colors and legend.labels must be set."):void(n(k)&&(k.removeFrom(c),k=null))}),p.$watch("legend.url",function(e){n(e)&&b.get(e).success(function(a){n(k)?d.updateLegend(k.getContainer(),a,l,e):(k=L.control({position:j}),k.onAdd=d.getOnAddLegend(a,i,l,e),k.addTo(c)),n(q.loadedData)&&o(q.loadedData)&&q.loadedData()}).error(function(){a.warn("[AngularJS - Leaflet] legend.url not loaded.")})})})}}}]),angular.module("leaflet-directive").directive("markers",["$log","$rootScope","$q","leafletData","leafletHelpers","leafletMapDefaults","leafletMarkersHelpers","leafletMarkerEvents","leafletIterators","leafletWatchHelpers","leafletDirectiveControlsHelpers",function(a,b,c,d,e,f,g,h,i,j,k){var l=e.isDefined,m=e.errorHeader,n=e,o=e.isString,p=g.addMarkerWatcher,q=g.updateMarker,r=g.listenMarkerEvents,s=g.addMarkerToGroup,t=g.createMarker,u=g.deleteMarker,v=i,w=e.watchOptions,x=j.maybeWatch,y=k.extend,z=function(a,b,c){if(Object.keys(a).length){if(c&&o(c)){if(!a[c]||!Object.keys(a[c]).length)return;return a[c][b]}return a[b]}},A=function(a,b,c,d){return d&&o(d)?(l(b[d])||(b[d]={}),b[d][c]=a):b[c]=a,a},B=function(b,c,d,e,f,g){if(!o(b))return a.error(m+" A layername must be a string"),!1;if(!l(c))return a.error(m+" You must add layers to the directive if the markers are going to use this functionality."),!1;if(!l(c.overlays)||!l(c.overlays[b]))return a.error(m+' A marker can only be added to a layer of type "group"'),!1;var h=c.overlays[b];return h instanceof L.LayerGroup||h instanceof L.FeatureGroup?(h.addLayer(e),!f&&g.hasLayer(e)&&d.focus===!0&&e.openPopup(),!0):(a.error(m+' Adding a marker to an overlay needs a overlay of the type "group" or "featureGroup"'),!1)},C=function(b,c,d,e,f,g,i,j,k,o){for(var u in c)if(!o[u])if(-1===u.search("-")){var v=n.copy(c[u]),w=n.getObjectDotPath(k?[k,u]:[u]),x=z(g,u,k);if(l(x)){var y=l(y)?d[u]:void 0;q(v,y,x,w,i,f,e)}else{var C=t(v),D=(v?v.layer:void 0)||k;if(!l(C)){a.error(m+" Received invalid data on the marker "+u+".");continue}if(A(C,g,u,k),l(v.message)&&C.bindPopup(v.message,v.popupOptions),l(v.group)){var E=l(v.groupOption)?v.groupOption:null;s(C,v.group,E,e)}if(n.LabelPlugin.isLoaded()&&l(v.label)&&l(v.label.message)&&C.bindLabel(v.label.message,v.label.options),l(v)&&(l(v.layer)||l(k))){var F=B(D,f,v,C,j.individual.doWatch,e);if(!F)continue}else l(v.group)||(e.addLayer(C),j.individual.doWatch||v.focus!==!0||C.openPopup());j.individual.doWatch&&p(C,w,i,f,e,j.individual.isDeep),r(C,v,i,j.individual.doWatch,e),h.bindEvents(b,C,w,v,i,D)}}else a.error('The marker can\'t use a "-" on his key name: "'+u+'".')},D=function(b,c,d,e,f){var g,h,i=!1,j=!1,k=l(c);for(var o in d)i||(a.debug(m+"[markers] destroy: "),i=!0),k&&(h=b[o],g=c[o],j=angular.equals(h,g)&&e),l(b)&&Object.keys(b).length&&l(b[o])&&Object.keys(b[o]).length&&!j||f&&n.isFunction(f)&&f(h,g,o)},E=function(b,c,d,e,f){D(b,c,d,!1,function(b,c,g){a.debug(m+"[marker] is deleting marker: "+g),u(d[g],e,f),delete d[g]})},F=function(b,c,d){var e={};return D(b,c,d,!0,function(b,c,d){a.debug(m+"[marker] is already rendered, marker: "+d),e[d]=b}),e};return{restrict:"A",scope:!1,replace:!1,require:["leaflet","?layers"],link:function(a,b,e,f){var g=f[0],h=g.getLeafletScope();g.getMap().then(function(a){var b,g={};b=l(f[1])?f[1].getLayers:function(){var a=c.defer();return a.resolve(),a.promise};var i=h.markersWatchOptions||w;l(e.watchMarkers)&&(i.doWatch=i.individual.doWatch=!l(e.watchMarkers)||n.isTruthy(e.watchMarkers));var j=l(e.markersNested)&&n.isTruthy(e.markersNested);b().then(function(b){var c=function(c,d){return j?void v.each(c,function(c,e){var f=l(f)?d[e]:void 0;E(c,f,g[e],a,b)}):void E(c,d,g,a,b)},f=function(d,f){c(d,f);var k=null;return j?void v.each(d,function(c,j){var m=l(m)?f[j]:void 0;k=F(d[j],m,g[j]),C(e.id,c,f,a,b,g,h,i,j,k)}):(k=F(d,f,g),void C(e.id,d,f,a,b,g,h,i,void 0,k))};y(e.id,"markers",f,c),d.setMarkers(g,e.id),x(h,"markers",i,function(a,b){f(a,b)})})})}}}]),angular.module("leaflet-directive").directive("maxbounds",["$log","leafletMapDefaults","leafletBoundsHelpers","leafletHelpers",function(a,b,c,d){return{restrict:"A",scope:!1,replace:!1,require:"leaflet",link:function(a,b,e,f){var g=f.getLeafletScope(),h=c.isValidBounds,i=d.isNumber;f.getMap().then(function(a){g.$watch("maxbounds",function(b){if(!h(b))return void a.setMaxBounds();var d=c.createLeafletBounds(b);i(b.pad)&&(d=d.pad(b.pad)),a.setMaxBounds(d),e.center||e.lfCenter||a.fitBounds(d)})})}}}]),angular.module("leaflet-directive").directive("paths",["$log","$q","leafletData","leafletMapDefaults","leafletHelpers","leafletPathsHelpers","leafletPathEvents",function(a,b,c,d,e,f,g){return{restrict:"A",scope:!1,replace:!1,require:["leaflet","?layers"],link:function(h,i,j,k){var l=k[0],m=e.isDefined,n=e.isString,o=l.getLeafletScope(),p=o.paths,q=f.createPath,r=g.bindPathEvents,s=f.setPathOptions;l.getMap().then(function(f){var g,h=d.getDefaults(j.id);g=m(k[1])?k[1].getLayers:function(){var a=b.defer();return a.resolve(),a.promise},m(p)&&g().then(function(b){var d={};c.setPaths(d,j.id);var g=!m(j.watchPaths)||"true"===j.watchPaths,i=function(a,c){var d=o.$watch('paths["'+c+'"]',function(c,e){if(!m(c)){if(m(e.layer))for(var g in b.overlays){var h=b.overlays[g];h.removeLayer(a)}return f.removeLayer(a),void d()}s(a,c.type,c)},!0)};o.$watchCollection("paths",function(c){for(var k in d)m(c[k])||(f.removeLayer(d[k]),delete d[k]);for(var l in c)if(0!==l.search("\\$"))if(-1===l.search("-")){if(!m(d[l])){var p=c[l],t=q(l,c[l],h);if(m(t)&&m(p.message)&&t.bindPopup(p.message,p.popupOptions),e.LabelPlugin.isLoaded()&&m(p.label)&&m(p.label.message)&&t.bindLabel(p.label.message,p.label.options),m(p)&&m(p.layer)){if(!n(p.layer)){a.error("[AngularJS - Leaflet] A layername must be a string");continue}if(!m(b)){a.error("[AngularJS - Leaflet] You must add layers to the directive if the markers are going to use this functionality.");continue}if(!m(b.overlays)||!m(b.overlays[p.layer])){a.error('[AngularJS - Leaflet] A path can only be added to a layer of type "group"');continue}var u=b.overlays[p.layer];if(!(u instanceof L.LayerGroup||u instanceof L.FeatureGroup)){a.error('[AngularJS - Leaflet] Adding a path to an overlay needs a overlay of the type "group" or "featureGroup"');continue}d[l]=t,u.addLayer(t),g?i(t,l):s(t,p.type,p)}else m(t)&&(d[l]=t,f.addLayer(t),g?i(t,l):s(t,p.type,p));r(j.id,t,l,p,o)}}else a.error('[AngularJS - Leaflet] The path name "'+l+'" is not valid. It must not include "-" and a number.')})})})}}}]),angular.module("leaflet-directive").directive("tiles",["$log","leafletData","leafletMapDefaults","leafletHelpers",function(a,b,c,d){return{restrict:"A",scope:!1,replace:!1,require:"leaflet",link:function(e,f,g,h){var i=d.isDefined,j=h.getLeafletScope(),k=j.tiles;return i(k)&&i(k.url)?void h.getMap().then(function(a){var d,e=c.getDefaults(g.id);j.$watch("tiles",function(c,f){var h=e.tileLayerOptions,j=e.tileLayer;return!i(c.url)&&i(d)?void a.removeLayer(d):i(d)?!i(c.url)||!i(c.options)||c.type===f.type&&angular.equals(c.options,h)?void(i(c.url)&&d.setUrl(c.url)):(a.removeLayer(d),h=e.tileLayerOptions,angular.copy(c.options,h),j=c.url,d="wms"===c.type?L.tileLayer.wms(j,h):L.tileLayer(j,h),d.addTo(a),void b.setTiles(d,g.id)):(i(c.options)&&angular.copy(c.options,h),i(c.url)&&(j=c.url),d="wms"===c.type?L.tileLayer.wms(j,h):L.tileLayer(j,h),d.addTo(a),void b.setTiles(d,g.id))},!0)}):void a.warn("[AngularJS - Leaflet] The 'tiles' definition doesn't have the 'url' property.")}}}]),["markers","geojson"].forEach(function(a){angular.module("leaflet-directive").directive(a+"WatchOptions",["$log","$rootScope","$q","leafletData","leafletHelpers",function(b,c,d,e,f){var g=f.isDefined,h=f.errorHeader,i=f.isObject,j=f.watchOptions;return{restrict:"A",scope:!1,replace:!1,require:["leaflet"],link:function(c,d,e,f){var k=f[0],l=k.getLeafletScope();k.getMap().then(function(){g(c[a+"WatchOptions"])&&(i(c[a+"WatchOptions"])?angular.extend(j,c[a+"WatchOptions"]):b.error(h+"["+a+"WatchOptions] is not an object"),l[a+"WatchOptions"]=j)})}}}])}),angular.module("leaflet-directive").factory("LeafletEventsHelpersFactory",["$rootScope","$q","$log","leafletHelpers",function(a,b,c,d){var e=d.safeApply,f=d.isDefined,g=d.isObject,h=d.isArray,i=d.errorHeader,j=function(a,b){this.rootBroadcastName=a,c.debug("LeafletEventsHelpersFactory: lObjectType: "+b+"rootBroadcastName: "+a),this.lObjectType=b};return j.prototype.getAvailableEvents=function(){return[]},j.prototype.genDispatchEvent=function(a,b,d,e,f,g,h,i,j){var k=this;return a=a||"",a&&(a="."+a),function(l){var m=k.rootBroadcastName+a+"."+b;c.debug(m),k.fire(e,m,d,l,l.target||f,h,g,i,j)}},j.prototype.fire=function(b,c,d,g,h,i,j,k){e(b,function(){var e={leafletEvent:g,leafletObject:h,modelName:j,model:i};f(k)&&angular.extend(e,{layerName:k}),"emit"===d?b.$emit(c,e):a.$broadcast(c,e)})},j.prototype.bindEvents=function(a,b,d,e,j,k,l){var m=[],n="emit",o=this;if(f(j.eventBroadcast))if(g(j.eventBroadcast))if(f(j.eventBroadcast[o.lObjectType]))if(g(j.eventBroadcast[o.lObjectType])){f(j.eventBroadcast[this.lObjectType].logic)&&"emit"!==j.eventBroadcast[o.lObjectType].logic&&"broadcast"!==j.eventBroadcast[o.lObjectType].logic&&c.warn(i+"Available event propagation logic are: 'emit' or 'broadcast'.");var p=!1,q=!1;f(j.eventBroadcast[o.lObjectType].enable)&&h(j.eventBroadcast[o.lObjectType].enable)&&(p=!0),f(j.eventBroadcast[o.lObjectType].disable)&&h(j.eventBroadcast[o.lObjectType].disable)&&(q=!0),p&&q?c.warn(i+"can not enable and disable events at the same time"):p||q?p?j.eventBroadcast[this.lObjectType].enable.forEach(function(a){-1!==m.indexOf(a)?c.warn(i+"This event "+a+" is already enabled"):-1===o.getAvailableEvents().indexOf(a)?c.warn(i+"This event "+a+" does not exist"):m.push(a)}):(m=this.getAvailableEvents(),j.eventBroadcast[o.lObjectType].disable.forEach(function(a){var b=m.indexOf(a);-1===b?c.warn(i+"This event "+a+" does not exist or has been already disabled"):m.splice(b,1)})):c.warn(i+"must enable or disable events")}else c.warn(i+"event-broadcast."+[o.lObjectType]+" must be an object check your model.");else m=this.getAvailableEvents();else c.error(i+"event-broadcast must be an object check your model.");else m=this.getAvailableEvents();return m.forEach(function(c){b.on(c,o.genDispatchEvent(a,c,n,j,b,d,e,k,l))}),n},j}]).service("leafletEventsHelpers",["LeafletEventsHelpersFactory",function(a){return new a}]),angular.module("leaflet-directive").factory("leafletGeoJsonEvents",["$rootScope","$q","$log","leafletHelpers","LeafletEventsHelpersFactory","leafletData",function(a,b,c,d,e,f){var g=d.safeApply,h=e,i=function(){h.call(this,"leafletDirectiveGeoJson","geojson")};return i.prototype=new h,i.prototype.genDispatchEvent=function(b,c,d,e,i,j,k,l,m){var n=h.prototype.genDispatchEvent.call(this,b,c,d,e,i,j,k,l),o=this;return function(b){"mouseout"===c&&(m.resetStyleOnMouseout&&f.getGeoJSON(m.mapId).then(function(a){var c=l?a[l]:a;c.resetStyle(b.target)}),g(e,function(){a.$broadcast(o.rootBroadcastName+".mouseout",b)})),n(b)}},i.prototype.getAvailableEvents=function(){return["click","dblclick","mouseover","mouseout"]},new i}]),angular.module("leaflet-directive").factory("leafletLabelEvents",["$rootScope","$q","$log","leafletHelpers","LeafletEventsHelpersFactory",function(a,b,c,d,e){var f=d,g=e,h=function(){g.call(this,"leafletDirectiveLabel","markers")};return h.prototype=new g,h.prototype.genDispatchEvent=function(a,b,c,d,e,f,h,i){var j=f.replace("markers.","");return g.prototype.genDispatchEvent.call(this,a,b,c,d,e,j,h,i)},h.prototype.getAvailableEvents=function(){return["click","dblclick","mousedown","mouseover","mouseout","contextmenu"]},h.prototype.genEvents=function(a,b,c,d,e,g,h,i){var j=this,k=this.getAvailableEvents(),l=f.getObjectArrayPath("markers."+g);k.forEach(function(b){e.label.on(b,j.genDispatchEvent(a,b,c,d,e.label,l,h,i))})},h.prototype.bindEvents=function(){},new h}]),angular.module("leaflet-directive").factory("leafletMapEvents",["$rootScope","$q","$log","leafletHelpers","leafletEventsHelpers","leafletIterators",function(a,b,c,d,e,f){var g=d.isDefined,h=e.fire,i=function(){return["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","contextmenu","focus","blur","preclick","load","unload","viewreset","movestart","move","moveend","dragstart","drag","dragend","zoomstart","zoomanim","zoomend","zoomlevelschange","resize","autopanstart","layeradd","layerremove","baselayerchange","overlayadd","overlayremove","locationfound","locationerror","popupopen","popupclose","draw:created","draw:edited","draw:deleted","draw:drawstart","draw:drawstop","draw:editstart","draw:editstop","draw:deletestart","draw:deletestop"]},j=function(a,b,d,e){return e&&(e+="."),function(f){var g="leafletDirectiveMap."+e+b;c.debug(g),h(a,g,d,f,f.target,a)}},k=function(a){a.$broadcast("boundsChanged")},l=function(a,b,c,d){if(g(c.urlHashCenter)){var e=b.getCenter(),f=e.lat.toFixed(4)+":"+e.lng.toFixed(4)+":"+b.getZoom();g(d.c)&&d.c===f||a.$emit("centerUrlHash",f)}},m=function(a,b,c,d,e){f.each(b,function(b){var f={};f[c]=b,a.on(b,j(d,b,e,a._container.id||""),f)})};return{getAvailableMapEvents:i,genDispatchMapEvent:j,notifyCenterChangedToBounds:k,notifyCenterUrlHashChanged:l,addEvents:m}}]),angular.module("leaflet-directive").factory("leafletMarkerEvents",["$rootScope","$q","$log","leafletHelpers","LeafletEventsHelpersFactory","leafletLabelEvents",function(a,b,c,d,e,f){var g=d.safeApply,h=d.isDefined,i=d,j=f,k=e,l=function(){k.call(this,"leafletDirectiveMarker","markers")};return l.prototype=new k,l.prototype.genDispatchEvent=function(b,c,d,e,f,h,i,j){var l=k.prototype.genDispatchEvent.call(this,b,c,d,e,f,h,i,j);return function(b){"click"===c?g(e,function(){a.$broadcast("leafletDirectiveMarkersClick",h)}):"dragend"===c&&(g(e,function(){i.lat=f.getLatLng().lat,i.lng=f.getLatLng().lng}),i.message&&i.focus===!0&&f.openPopup()),l(b)}},l.prototype.getAvailableEvents=function(){return["click","dblclick","mousedown","mouseover","mouseout","contextmenu","dragstart","drag","dragend","move","remove","popupopen","popupclose","touchend","touchstart","touchmove","touchcancel","touchleave"]},l.prototype.bindEvents=function(a,b,c,d,e,f){var g=k.prototype.bindEvents.call(this,a,b,c,d,e,f);i.LabelPlugin.isLoaded()&&h(b.label)&&j.genEvents(a,c,g,e,b,d,f)},new l}]),angular.module("leaflet-directive").factory("leafletPathEvents",["$rootScope","$q","$log","leafletHelpers","leafletLabelEvents","leafletEventsHelpers",function(a,b,c,d,e,f){var g=d.isDefined,h=d.isObject,i=d,j=d.errorHeader,k=e,l=f.fire,m=function(a,b,d,e,f,g,h,i){return a=a||"",a&&(a="."+a),function(j){var k="leafletDirectivePath"+a+"."+b;c.debug(k),l(e,k,d,j,j.target||f,h,g,i)}},n=function(a,b,d,e,f){var l,n,p=[],q="broadcast";if(g(f.eventBroadcast))if(h(f.eventBroadcast))if(g(f.eventBroadcast.path))if(h(f.eventBroadcast.paths))c.warn(j+"event-broadcast.path must be an object check your model.");else{void 0!==f.eventBroadcast.path.logic&&null!==f.eventBroadcast.path.logic&&("emit"!==f.eventBroadcast.path.logic&&"broadcast"!==f.eventBroadcast.path.logic?c.warn(j+"Available event propagation logic are: 'emit' or 'broadcast'."):"emit"===f.eventBroadcast.path.logic&&(q="emit"));var r=!1,s=!1;if(void 0!==f.eventBroadcast.path.enable&&null!==f.eventBroadcast.path.enable&&"object"==typeof f.eventBroadcast.path.enable&&(r=!0),void 0!==f.eventBroadcast.path.disable&&null!==f.eventBroadcast.path.disable&&"object"==typeof f.eventBroadcast.path.disable&&(s=!0),r&&s)c.warn(j+"can not enable and disable events at the same time");else if(r||s)if(r)for(l=0;l<f.eventBroadcast.path.enable.length;l++)n=f.eventBroadcast.path.enable[l],-1!==p.indexOf(n)?c.warn(j+"This event "+n+" is already enabled"):-1===o().indexOf(n)?c.warn(j+"This event "+n+" does not exist"):p.push(n);else for(p=o(),l=0;l<f.eventBroadcast.path.disable.length;l++){n=f.eventBroadcast.path.disable[l];var t=p.indexOf(n);-1===t?c.warn(j+"This event "+n+" does not exist or has been already disabled"):p.splice(t,1)}else c.warn(j+"must enable or disable events")}else p=o();else c.error(j+"event-broadcast must be an object check your model.");else p=o();for(l=0;l<p.length;l++)n=p[l],b.on(n,m(a,n,q,f,p,d));i.LabelPlugin.isLoaded()&&g(b.label)&&k.genEvents(a,d,q,f,b,e)},o=function(){return["click","dblclick","mousedown","mouseover","mouseout","contextmenu","add","remove","popupopen","popupclose"]};return{getAvailablePathEvents:o,bindPathEvents:n}}])}(angular);
-}(angular));
\ No newline at end of file
diff --git a/www/lib/ionic/js/angular/ui-leaflet.js b/www/lib/ionic/js/angular/ui-leaflet.js
new file mode 100644
index 0000000000000000000000000000000000000000..da8956916b63eeb101acf2d0be66e4f2e2dfcb62
--- /dev/null
+++ b/www/lib/ionic/js/angular/ui-leaflet.js
@@ -0,0 +1,5189 @@
+/*!
+*  ui-leaflet 2.0.0 2016-10-04
+*  ui-leaflet - An AngularJS directive to easily interact with Leaflet maps
+*  git: https://github.com/angular-ui/ui-leaflet
+*/
+(function(angular){
+'use strict';
+'use strict';
+
+angular.module('ui-leaflet', ['nemLogging']).directive('leaflet', ["$q", "leafletData", "leafletMapDefaults", "leafletHelpers", "leafletMapEvents", function ($q, leafletData, leafletMapDefaults, leafletHelpers, leafletMapEvents) {
+    return {
+        restrict: "EA",
+        replace: true,
+        scope: {
+            center: '=',
+            lfCenter: '=',
+            defaults: '=',
+            maxbounds: '=',
+            bounds: '=',
+            markers: '=',
+            legend: '=',
+            geojson: '=',
+            paths: '=',
+            tiles: '=',
+            layers: '=',
+            controls: '=',
+            decorations: '=',
+            eventBroadcast: '=',
+            watchOptions: '=',
+            id: '@'
+        },
+        transclude: true,
+        template: '<div class="angular-leaflet-map"><div ng-transclude></div></div>',
+        controller: ["$scope", function controller($scope) {
+            this._leafletMap = $q.defer();
+            this.getMap = function () {
+                return this._leafletMap.promise;
+            };
+
+            this.getLeafletScope = function () {
+                return $scope;
+            };
+        }],
+
+        link: function link(scope, element, attrs, ctrl) {
+            var isDefined = leafletHelpers.isDefined,
+                defaults = leafletMapDefaults.setDefaults(scope.defaults, attrs.id),
+                mapEvents = leafletMapEvents.getAvailableMapEvents(),
+                addEvents = leafletMapEvents.addEvents;
+
+            scope.mapId = attrs.id;
+            leafletData.setDirectiveControls({}, attrs.id);
+
+            // Set width and height utility functions
+            function updateWidth() {
+                if (isNaN(attrs.width)) {
+                    element.css('width', attrs.width);
+                } else {
+                    element.css('width', attrs.width + 'px');
+                }
+            }
+
+            function updateHeight() {
+                if (isNaN(attrs.height)) {
+                    element.css('height', attrs.height);
+                } else {
+                    element.css('height', attrs.height + 'px');
+                }
+            }
+
+            // Create the Leaflet Map Object with the options
+            var map = new L.Map(element[0], leafletMapDefaults.getMapCreationDefaults(attrs.id));
+            ctrl._leafletMap.resolve(map);
+
+            // If the width attribute defined update css
+            // Then watch if bound property changes and update css
+            if (isDefined(attrs.width)) {
+                updateWidth();
+
+                scope.$watch(function () {
+                    return element[0].getAttribute('width');
+                }, function () {
+                    updateWidth();
+                    map.invalidateSize();
+                });
+            }
+
+            // If the height attribute defined update css
+            // Then watch if bound property changes and update css
+            if (isDefined(attrs.height)) {
+                updateHeight();
+
+                scope.$watch(function () {
+                    return element[0].getAttribute('height');
+                }, function () {
+                    updateHeight();
+                    map.invalidateSize();
+                });
+            }
+
+            if (!isDefined(attrs.center) && !isDefined(attrs.lfCenter)) {
+                map.setView([defaults.center.lat, defaults.center.lng], defaults.center.zoom);
+            }
+
+            // If no layers nor tiles defined, set the default tileLayer
+            if (!isDefined(attrs.tiles) && !isDefined(attrs.layers)) {
+                var tileLayerObj = L.tileLayer(defaults.tileLayer, defaults.tileLayerOptions);
+                tileLayerObj.addTo(map);
+                leafletData.setTiles(tileLayerObj, attrs.id);
+            }
+
+            // Set zoom control configuration
+            if (isDefined(map.zoomControl) && isDefined(defaults.zoomControlPosition)) {
+                map.zoomControl.setPosition(defaults.zoomControlPosition);
+            }
+
+            if (isDefined(map.zoomControl) && defaults.zoomControl === false) {
+                map.zoomControl.removeFrom(map);
+            }
+
+            if (isDefined(map.zoomsliderControl) && isDefined(defaults.zoomsliderControl) && defaults.zoomsliderControl === false) {
+                map.zoomsliderControl.removeFrom(map);
+            }
+
+            // if no event-broadcast attribute, all events are broadcasted
+            if (!isDefined(attrs.eventBroadcast)) {
+                var logic = "broadcast";
+                addEvents(map, attrs.id, mapEvents, "eventName", scope, logic);
+            }
+
+            // Resolve the map object to the promises
+            map.whenReady(function () {
+                leafletData.setMap(map, attrs.id);
+            });
+
+            scope.$on('$destroy', function () {
+                leafletMapDefaults.reset();
+                map.remove();
+                leafletData.unresolveMap(attrs.id);
+            });
+
+            //Handle request to invalidate the map size
+            //Up scope using $scope.$emit('invalidateSize')
+            //Down scope using $scope.$broadcast('invalidateSize')
+            scope.$on('invalidateSize', function () {
+                map.invalidateSize();
+            });
+        }
+    };
+}]);
+
+'use strict';
+
+(function () {
+    angular.module('ui-leaflet').factory('eventManager', [function () {
+        var EventManager = function EventManager() {
+            this.listeners = {};
+        };
+
+        EventManager.prototype = {
+            addEventListener: function addEventListener(type, callback, scope) {
+                var args = [];
+                var numOfArgs = arguments.length;
+                for (var i = 0; i < numOfArgs; i++) {
+                    args.push(arguments[i]);
+                }
+                args = args.length > 3 ? args.splice(3, args.length - 1) : [];
+                if (typeof this.listeners[type] !== "undefined") {
+                    this.listeners[type].push({ scope: scope, callback: callback, args: args });
+                } else {
+                    this.listeners[type] = [{ scope: scope, callback: callback, args: args }];
+                }
+            },
+            removeEventListener: function removeEventListener(type, callback, scope) {
+                if (typeof this.listeners[type] !== "undefined") {
+                    var numOfCallbacks = this.listeners[type].length;
+                    var newArray = [];
+                    for (var i = 0; i < numOfCallbacks; i++) {
+                        var listener = this.listeners[type][i];
+                        if (listener.scope === scope && listener.callback === callback) {} else {
+                            newArray.push(listener);
+                        }
+                    }
+                    this.listeners[type] = newArray;
+                }
+            },
+            hasEventListener: function hasEventListener(type, callback, scope) {
+                if (typeof this.listeners[type] !== "undefined") {
+                    var numOfCallbacks = this.listeners[type].length;
+                    if (callback === undefined && scope === undefined) {
+                        return numOfCallbacks > 0;
+                    }
+                    for (var i = 0; i < numOfCallbacks; i++) {
+                        var listener = this.listeners[type][i];
+                        if ((scope ? listener.scope === scope : true) && listener.callback === callback) {
+                            return true;
+                        }
+                    }
+                }
+                return false;
+            },
+            dispatch: function dispatch(type, target) {
+                var numOfListeners = 0;
+                var event = {
+                    type: type,
+                    target: target
+                };
+                var args = [];
+                var numOfArgs = arguments.length;
+                for (var i = 0; i < numOfArgs; i++) {
+                    args.push(arguments[i]);
+                }
+                args = args.length > 2 ? args.splice(2, args.length - 1) : [];
+                args = [event].concat(args);
+                if (typeof this.listeners[type] !== "undefined") {
+                    var numOfCallbacks = this.listeners[type].length;
+                    for (var x = 0; x < numOfCallbacks; x++) {
+                        var listener = this.listeners[type][x];
+                        if (listener && listener.callback) {
+                            var concatArgs = args.concat(listener.args);
+                            listener.callback.apply(listener.scope, concatArgs);
+                            numOfListeners += 1;
+                        }
+                    }
+                }
+            },
+            getEvents: function getEvents() {
+                var str = "";
+                for (var type in this.listeners) {
+                    var numOfCallbacks = this.listeners[type].length;
+                    for (var i = 0; i < numOfCallbacks; i++) {
+                        var listener = this.listeners[type][i];
+                        str += listener.scope && listener.scope.className ? listener.scope.className : "anonymous";
+                        str += " listen for '" + type + "'\n";
+                    }
+                }
+                return str;
+            }
+        };
+        return EventManager;
+    }]).service('eventManager', ["EventManager", function (EventManager) {
+        return new EventManager();
+    }]);
+})();
+
+'use strict';
+
+angular.module('ui-leaflet').factory('leafletBoundsHelpers', ["leafletLogger", "leafletHelpers", function (leafletLogger, leafletHelpers) {
+
+    var isArray = leafletHelpers.isArray,
+        isNumber = leafletHelpers.isNumber,
+        isFunction = leafletHelpers.isFunction,
+        isDefined = leafletHelpers.isDefined,
+        $log = leafletLogger;
+
+    function _isValidBounds(bounds) {
+        return angular.isDefined(bounds) && angular.isDefined(bounds.southWest) && angular.isDefined(bounds.northEast) && angular.isNumber(bounds.southWest.lat) && angular.isNumber(bounds.southWest.lng) && angular.isNumber(bounds.northEast.lat) && angular.isNumber(bounds.northEast.lng);
+    }
+
+    return {
+        createLeafletBounds: function createLeafletBounds(bounds) {
+            if (_isValidBounds(bounds)) {
+                return L.latLngBounds([bounds.southWest.lat, bounds.southWest.lng], [bounds.northEast.lat, bounds.northEast.lng]);
+            }
+        },
+
+        isValidBounds: _isValidBounds,
+
+        createBoundsFromArray: function createBoundsFromArray(boundsArray) {
+            if (!(isArray(boundsArray) && boundsArray.length === 2 && isArray(boundsArray[0]) && isArray(boundsArray[1]) && boundsArray[0].length === 2 && boundsArray[1].length === 2 && isNumber(boundsArray[0][0]) && isNumber(boundsArray[0][1]) && isNumber(boundsArray[1][0]) && isNumber(boundsArray[1][1]))) {
+                $log.error("[AngularJS - Leaflet] The bounds array is not valid.");
+                return;
+            }
+
+            return {
+                northEast: {
+                    lat: boundsArray[0][0],
+                    lng: boundsArray[0][1]
+                },
+                southWest: {
+                    lat: boundsArray[1][0],
+                    lng: boundsArray[1][1]
+                }
+            };
+        },
+
+        createBoundsFromLeaflet: function createBoundsFromLeaflet(lfBounds) {
+            if (!(isDefined(lfBounds) && isFunction(lfBounds.getNorthEast) && isFunction(lfBounds.getSouthWest))) {
+                $log.error("[AngularJS - Leaflet] The leaflet bounds is not valid object.");
+                return;
+            }
+
+            var northEast = lfBounds.getNorthEast(),
+                southWest = lfBounds.getSouthWest();
+
+            return {
+                northEast: {
+                    lat: northEast.lat,
+                    lng: northEast.lng
+                },
+                southWest: {
+                    lat: southWest.lat,
+                    lng: southWest.lng
+                }
+            };
+        }
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').factory('leafletControlHelpers', ["$rootScope", "leafletLogger", "leafletHelpers", "leafletLayerHelpers", "leafletMapDefaults", function ($rootScope, leafletLogger, leafletHelpers, leafletLayerHelpers, leafletMapDefaults) {
+    var isDefined = leafletHelpers.isDefined,
+        isObject = leafletHelpers.isObject,
+        createLayer = leafletLayerHelpers.createLayer,
+        _controls = {},
+        errorHeader = leafletHelpers.errorHeader + ' [Controls] ',
+        $log = leafletLogger;
+
+    var _controlLayersMustBeVisible = function _controlLayersMustBeVisible(baselayers, overlays, mapId) {
+        var defaults = leafletMapDefaults.getDefaults(mapId);
+        if (!defaults.controls.layers.visible) {
+            return false;
+        }
+
+        var atLeastOneControlItemMustBeShown = false;
+
+        if (isObject(baselayers)) {
+            Object.keys(baselayers).forEach(function (key) {
+                var layer = baselayers[key];
+                if (!isDefined(layer.layerOptions) || layer.layerOptions.showOnSelector !== false) {
+                    atLeastOneControlItemMustBeShown = true;
+                }
+            });
+        }
+
+        if (isObject(overlays)) {
+            Object.keys(overlays).forEach(function (key) {
+                var layer = overlays[key];
+                if (!isDefined(layer.layerParams) || layer.layerParams.showOnSelector !== false) {
+                    atLeastOneControlItemMustBeShown = true;
+                }
+            });
+        }
+
+        return atLeastOneControlItemMustBeShown;
+    };
+
+    var _createLayersControl = function _createLayersControl(mapId) {
+        var defaults = leafletMapDefaults.getDefaults(mapId);
+        var controlOptions = {
+            collapsed: defaults.controls.layers.collapsed,
+            position: defaults.controls.layers.position,
+            autoZIndex: false
+        };
+
+        angular.extend(controlOptions, defaults.controls.layers.options);
+
+        var control;
+        if (defaults.controls.layers && isDefined(defaults.controls.layers.control)) {
+            control = defaults.controls.layers.control.apply(this, [[], [], controlOptions]);
+        } else {
+            control = new L.control.layers([], [], controlOptions);
+        }
+
+        return control;
+    };
+
+    var controlTypes = {
+        draw: {
+            isPluginLoaded: function isPluginLoaded() {
+                if (!angular.isDefined(L.Control.Draw)) {
+                    $log.error(errorHeader + ' Draw plugin is not loaded.');
+                    return false;
+                }
+                return true;
+            },
+            checkValidParams: function checkValidParams() /* params */{
+                return true;
+            },
+            createControl: function createControl(params) {
+                return new L.Control.Draw(params);
+            }
+        },
+        scale: {
+            isPluginLoaded: function isPluginLoaded() {
+                return true;
+            },
+            checkValidParams: function checkValidParams() /* params */{
+                return true;
+            },
+            createControl: function createControl(params) {
+                return new L.control.scale(params);
+            }
+        },
+        fullscreen: {
+            isPluginLoaded: function isPluginLoaded() {
+                if (!angular.isDefined(L.Control.Fullscreen)) {
+                    $log.error(errorHeader + ' Fullscreen plugin is not loaded.');
+                    return false;
+                }
+                return true;
+            },
+            checkValidParams: function checkValidParams() /* params */{
+                return true;
+            },
+            createControl: function createControl(params) {
+                return new L.Control.Fullscreen(params);
+            }
+        },
+        search: {
+            isPluginLoaded: function isPluginLoaded() {
+                if (!angular.isDefined(L.Control.Search)) {
+                    $log.error(errorHeader + ' Search plugin is not loaded.');
+                    return false;
+                }
+                return true;
+            },
+            checkValidParams: function checkValidParams() /* params */{
+                return true;
+            },
+            createControl: function createControl(params) {
+                return new L.Control.Search(params);
+            }
+        },
+        custom: {},
+        minimap: {
+            isPluginLoaded: function isPluginLoaded() {
+                if (!angular.isDefined(L.Control.MiniMap)) {
+                    $log.error(errorHeader + ' Minimap plugin is not loaded.');
+                    return false;
+                }
+
+                return true;
+            },
+            checkValidParams: function checkValidParams(params) {
+                if (!isDefined(params.layer)) {
+                    $log.warn(errorHeader + ' minimap "layer" option should be defined.');
+                    return false;
+                }
+                return true;
+            },
+            createControl: function createControl(params) {
+                var layer = createLayer(params.layer);
+
+                if (!isDefined(layer)) {
+                    $log.warn(errorHeader + ' minimap control "layer" could not be created.');
+                    return;
+                }
+
+                return new L.Control.MiniMap(layer, params);
+            }
+        }
+    };
+
+    return {
+        layersControlMustBeVisible: _controlLayersMustBeVisible,
+
+        isValidControlType: function isValidControlType(type) {
+            return Object.keys(controlTypes).indexOf(type) !== -1;
+        },
+
+        createControl: function createControl(type, params) {
+            if (!controlTypes[type].checkValidParams(params)) {
+                return;
+            }
+
+            return controlTypes[type].createControl(params);
+        },
+
+        updateLayersControl: function updateLayersControl(map, mapId, loaded, baselayers, overlays, leafletLayers) {
+            var i;
+            var _layersControl = _controls[mapId];
+            var mustBeLoaded = _controlLayersMustBeVisible(baselayers, overlays, mapId);
+
+            if (isDefined(_layersControl) && loaded) {
+                for (i in leafletLayers.baselayers) {
+                    _layersControl.removeLayer(leafletLayers.baselayers[i]);
+                }
+                for (i in leafletLayers.overlays) {
+                    _layersControl.removeLayer(leafletLayers.overlays[i]);
+                }
+                map.removeControl(_layersControl);
+                delete _controls[mapId];
+            }
+
+            if (mustBeLoaded) {
+                _layersControl = _createLayersControl(mapId);
+                _controls[mapId] = _layersControl;
+                for (i in baselayers) {
+                    var hideOnSelector = isDefined(baselayers[i].layerOptions) && baselayers[i].layerOptions.showOnSelector === false;
+                    if (!hideOnSelector && isDefined(leafletLayers.baselayers[i])) {
+                        _layersControl.addBaseLayer(leafletLayers.baselayers[i], baselayers[i].name);
+                    }
+                }
+                for (i in overlays) {
+                    var hideOverlayOnSelector = isDefined(overlays[i].layerParams) && overlays[i].layerParams.showOnSelector === false;
+                    if (!hideOverlayOnSelector && isDefined(leafletLayers.overlays[i])) {
+                        _layersControl.addOverlay(leafletLayers.overlays[i], overlays[i].name);
+                    }
+                }
+
+                map.addControl(_layersControl);
+            }
+            return mustBeLoaded;
+        },
+
+        destroyMapLayersControl: function destroyMapLayersControl(mapId) {
+            delete _controls[mapId];
+        }
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').service('leafletData', ["leafletLogger", "$q", "leafletHelpers", function (leafletLogger, $q, leafletHelpers) {
+    var getDefer = leafletHelpers.getDefer,
+        getUnresolvedDefer = leafletHelpers.getUnresolvedDefer,
+        setResolvedDefer = leafletHelpers.setResolvedDefer;
+    // $log = leafletLogger;
+
+    var _private = {};
+    var self = this;
+
+    var upperFirst = function upperFirst(string) {
+        return string.charAt(0).toUpperCase() + string.slice(1);
+    };
+
+    var _privateItems = ['map', 'tiles', 'layers', 'paths', 'markers', 'geoJSON', 'UTFGrid', //odd ball on naming convention keeping to not break
+    'decorations', 'directiveControls'];
+
+    //init
+    _privateItems.forEach(function (itemName) {
+        _private[itemName] = {};
+    });
+
+    this.unresolveMap = function (scopeId) {
+        var id = leafletHelpers.obtainEffectiveMapId(_private.map, scopeId);
+        _privateItems.forEach(function (itemName) {
+            _private[itemName][id] = undefined;
+        });
+    };
+
+    //int repetitive stuff (get and sets)
+    _privateItems.forEach(function (itemName) {
+        var name = upperFirst(itemName);
+        self['set' + name] = function (lObject, scopeId) {
+            var defer = getUnresolvedDefer(_private[itemName], scopeId);
+            defer.resolve(lObject);
+            setResolvedDefer(_private[itemName], scopeId);
+        };
+
+        self['get' + name] = function (scopeId) {
+            var defer = getDefer(_private[itemName], scopeId);
+            return defer.promise;
+        };
+    });
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').service('leafletDirectiveControlsHelpers', ["leafletLogger", "leafletData", "leafletHelpers", function (leafletLogger, leafletData, leafletHelpers) {
+    var _isDefined = leafletHelpers.isDefined,
+        _isString = leafletHelpers.isString,
+        _isObject = leafletHelpers.isObject,
+        _mainErrorHeader = leafletHelpers.errorHeader,
+        $log = leafletLogger;
+
+    var _errorHeader = _mainErrorHeader + '[leafletDirectiveControlsHelpers';
+
+    var _extend = function _extend(id, thingToAddName, createFn, cleanFn) {
+        var _fnHeader = _errorHeader + '.extend] ';
+        var extender = {};
+        if (!_isDefined(thingToAddName)) {
+            $log.error(_fnHeader + 'thingToAddName cannot be undefined');
+            return;
+        }
+
+        if (_isString(thingToAddName) && _isDefined(createFn) && _isDefined(cleanFn)) {
+            extender[thingToAddName] = {
+                create: createFn,
+                clean: cleanFn
+            };
+        } else if (_isObject(thingToAddName) && !_isDefined(createFn) && !_isDefined(cleanFn)) {
+            extender = thingToAddName;
+        } else {
+            $log.error(_fnHeader + 'incorrect arguments');
+            return;
+        }
+
+        //add external control to create / destroy markers without a watch
+        leafletData.getDirectiveControls().then(function (controls) {
+            angular.extend(controls, extender);
+            leafletData.setDirectiveControls(controls, id);
+        });
+    };
+
+    return {
+        extend: _extend
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').service('leafletGeoJsonHelpers', ["leafletHelpers", "leafletIterators", function (leafletHelpers, leafletIterators) {
+    var lHlp = leafletHelpers,
+        lIt = leafletIterators;
+    var Point = function Point(lat, lng) {
+        this.lat = lat;
+        this.lng = lng;
+        return this;
+    };
+
+    var _getLat = function _getLat(value) {
+        if (Array.isArray(value) && value.length === 2) {
+            return value[1];
+        } else if (lHlp.isDefined(value.type) && value.type === 'Point') {
+            return +value.coordinates[1];
+        } else {
+            return +value.lat;
+        }
+    };
+
+    var _getLng = function _getLng(value) {
+        if (Array.isArray(value) && value.length === 2) {
+            return value[0];
+        } else if (lHlp.isDefined(value.type) && value.type === 'Point') {
+            return +value.coordinates[0];
+        } else {
+            return +value.lng;
+        }
+    };
+
+    var _validateCoords = function _validateCoords(coords) {
+        if (lHlp.isUndefined(coords)) {
+            return false;
+        }
+        if (lHlp.isArray(coords)) {
+            if (coords.length === 2 && lHlp.isNumber(coords[0]) && lHlp.isNumber(coords[1])) {
+                return true;
+            }
+        } else if (lHlp.isDefined(coords.type)) {
+            if (coords.type === 'Point' && lHlp.isArray(coords.coordinates) && coords.coordinates.length === 2 && lHlp.isNumber(coords.coordinates[0]) && lHlp.isNumber(coords.coordinates[1])) {
+                return true;
+            }
+        }
+
+        var ret = lIt.all(['lat', 'lng'], function (pos) {
+            return lHlp.isDefined(coords[pos]) && lHlp.isNumber(coords[pos]);
+        });
+        return ret;
+    };
+
+    var _getCoords = function _getCoords(value) {
+        if (!value || !_validateCoords(value)) {
+            return;
+        }
+        var p = null;
+        if (Array.isArray(value) && value.length === 2) {
+            p = new Point(value[1], value[0]);
+        } else if (lHlp.isDefined(value.type) && value.type === 'Point') {
+            p = new Point(value.coordinates[1], value.coordinates[0]);
+        } else {
+            return value;
+        }
+        //note angular.merge is avail in angular 1.4.X we might want to fill it here
+        return angular.extend(value, p); //tap on lat, lng if it doesnt exist
+    };
+
+    return {
+        getLat: _getLat,
+        getLng: _getLng,
+        validateCoords: _validateCoords,
+        getCoords: _getCoords
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').service('leafletHelpers', ["$q", "$log", "$timeout", function ($q, $log, $timeout) {
+    var _errorHeader = '[ui-leaflet] ';
+    var _copy = angular.copy;
+    var _clone = _copy;
+    /*
+    For parsing paths to a field in an object
+     Example:
+    var obj = {
+        bike:{
+         1: 'hi'
+         2: 'foo'
+        }
+    };
+    _getObjectValue(obj,"bike.1") returns 'hi'
+    this is getPath in ui-gmap
+     */
+    var _getObjectValue = function _getObjectValue(object, pathStr) {
+        var obj;
+        if (!object || !angular.isObject(object)) return;
+        //if the key is not a sting then we already have the value
+        if (pathStr === null || !angular.isString(pathStr)) {
+            return pathStr;
+        }
+        obj = object;
+        pathStr.split('.').forEach(function (value) {
+            if (obj) {
+                obj = obj[value];
+            }
+        });
+        return obj;
+    };
+
+    /*
+     Object Array Notation
+     _getObjectArrayPath("bike.one.two")
+     returns:
+     'bike["one"]["two"]'
+     */
+    var _getObjectArrayPath = function _getObjectArrayPath(pathStr) {
+        return pathStr.split('.').reduce(function (previous, current) {
+            return previous + '["' + current + '"]';
+        });
+    };
+
+    /* Object Dot Notation
+     _getObjectPath(["bike","one","two"])
+     returns:
+     "bike.one.two"
+     */
+    var _getObjectDotPath = function _getObjectDotPath(arrayOfStrings) {
+        return arrayOfStrings.reduce(function (previous, current) {
+            return previous + '.' + current;
+        });
+    };
+
+    function _obtainEffectiveMapId(d, mapId) {
+        var id, i;
+        if (!angular.isDefined(mapId)) {
+            if (Object.keys(d).length === 0) {
+                id = "main";
+            } else if (Object.keys(d).length >= 1) {
+                for (i in d) {
+                    if (d.hasOwnProperty(i)) {
+                        id = i;
+                    }
+                }
+            } else {
+                $log.error(_errorHeader + "- You have more than 1 map on the DOM, you must provide the map ID to the leafletData.getXXX call");
+            }
+        } else {
+            id = mapId;
+        }
+
+        return id;
+    }
+
+    function _getUnresolvedDefer(d, mapId) {
+        var id = _obtainEffectiveMapId(d, mapId),
+            defer;
+
+        if (!angular.isDefined(d[id]) || d[id].resolvedDefer === true) {
+            defer = $q.defer();
+            d[id] = {
+                defer: defer,
+                resolvedDefer: false
+            };
+        } else {
+            defer = d[id].defer;
+        }
+
+        return defer;
+    }
+
+    var _isDefined = function _isDefined(value) {
+        return angular.isDefined(value) && value !== null;
+    };
+    var _isUndefined = function _isUndefined(value) {
+        return !_isDefined(value);
+    };
+
+    // BEGIN DIRECT PORT FROM AngularJS code base
+
+    var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
+
+    var MOZ_HACK_REGEXP = /^moz([A-Z])/;
+
+    var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
+
+    /**
+    Converts snake_case to camelCase.
+    Also there is special case for Moz prefix starting with upper case letter.
+    @param name Name to normalize
+     */
+
+    var camelCase = function camelCase(name) {
+        return name.replace(SPECIAL_CHARS_REGEXP, function (_, separator, letter, offset) {
+            if (offset) {
+                return letter.toUpperCase();
+            } else {
+                return letter;
+            }
+        }).replace(MOZ_HACK_REGEXP, "Moz$1");
+    };
+
+    /**
+        Converts all accepted directives format into proper directive name.
+        @param name Name to normalize
+    */
+
+    var directiveNormalize = function directiveNormalize(name) {
+        return camelCase(name.replace(PREFIX_REGEXP, ""));
+    };
+    // END AngularJS port
+
+    var _watchTrapDelayMilliSec = 10;
+
+    var _modelChangeInDirective = function _modelChangeInDirective(trapObj, trapField, cbToExec) {
+        if (!trapObj) throw new Error(_errorHeader + 'trapObj is undefined');
+        if (!trapField) throw new Error(_errorHeader + 'trapField is undefined');
+
+        trapObj[trapField] = true;
+        var ret = cbToExec();
+        $timeout(function () {
+            trapObj[trapField] = false;
+        }, _watchTrapDelayMilliSec);
+        return ret;
+    };
+
+    return {
+        watchTrapDelayMilliSec: _watchTrapDelayMilliSec,
+        modelChangeInDirective: _modelChangeInDirective,
+        camelCase: camelCase,
+        directiveNormalize: directiveNormalize,
+        copy: _copy,
+        clone: _clone,
+        errorHeader: _errorHeader,
+        getObjectValue: _getObjectValue,
+        getObjectArrayPath: _getObjectArrayPath,
+        getObjectDotPath: _getObjectDotPath,
+        defaultTo: function defaultTo(val, _default) {
+            return _isDefined(val) ? val : _default;
+        },
+        //mainly for checking attributes of directives lets keep this minimal (on what we accept)
+        isTruthy: function isTruthy(val) {
+            return val === 'true' || val === true;
+        },
+        //Determine if a reference is {}
+        isEmpty: function isEmpty(value) {
+            return Object.keys(value).length === 0;
+        },
+
+        //Determine if a reference is undefined or {}
+        isUndefinedOrEmpty: function isUndefinedOrEmpty(value) {
+            return angular.isUndefined(value) || value === null || Object.keys(value).length === 0;
+        },
+
+        // Determine if a reference is defined
+        isDefined: _isDefined,
+        isUndefined: _isUndefined,
+        isNumber: angular.isNumber,
+        isString: angular.isString,
+        isArray: angular.isArray,
+        isObject: angular.isObject,
+        isFunction: angular.isFunction,
+        equals: angular.equals,
+
+        isValidCenter: function isValidCenter(center) {
+            return angular.isDefined(center) && angular.isNumber(center.lat) && angular.isNumber(center.lng) && angular.isNumber(center.zoom);
+        },
+
+        isValidPoint: function isValidPoint(point) {
+            if (!angular.isDefined(point)) {
+                return false;
+            }
+            if (angular.isArray(point)) {
+                return point.length === 2 && angular.isNumber(point[0]) && angular.isNumber(point[1]);
+            }
+            return angular.isNumber(point.lat) && angular.isNumber(point.lng);
+        },
+
+        isSameCenterOnMap: function isSameCenterOnMap(centerModel, map) {
+            var mapCenter = map.getCenter();
+            var zoom = map.getZoom();
+            if (centerModel.lat && centerModel.lng && mapCenter.lat.toFixed(4) === centerModel.lat.toFixed(4) && mapCenter.lng.toFixed(4) === centerModel.lng.toFixed(4) && zoom === centerModel.zoom) {
+                return true;
+            }
+            return false;
+        },
+
+        safeApply: function safeApply($scope, fn) {
+            var phase = $scope.$root.$$phase;
+            if (phase === '$apply' || phase === '$digest') {
+                $scope.$eval(fn);
+            } else {
+                $scope.$evalAsync(fn);
+            }
+        },
+
+        obtainEffectiveMapId: _obtainEffectiveMapId,
+
+        getDefer: function getDefer(d, mapId) {
+            var id = _obtainEffectiveMapId(d, mapId),
+                defer;
+            if (!angular.isDefined(d[id]) || d[id].resolvedDefer === false) {
+                defer = _getUnresolvedDefer(d, mapId);
+            } else {
+                defer = d[id].defer;
+            }
+            return defer;
+        },
+
+        getUnresolvedDefer: _getUnresolvedDefer,
+
+        setResolvedDefer: function setResolvedDefer(d, mapId) {
+            var id = _obtainEffectiveMapId(d, mapId);
+            d[id].resolvedDefer = true;
+        },
+
+        rangeIsSupported: function rangeIsSupported() {
+            var testrange = document.createElement('input');
+            testrange.setAttribute('type', 'range');
+            return testrange.type === 'range';
+        },
+
+        FullScreenControlPlugin: {
+            isLoaded: function isLoaded() {
+                return angular.isDefined(L.Control.Fullscreen);
+            }
+        },
+
+        MiniMapControlPlugin: {
+            isLoaded: function isLoaded() {
+                return angular.isDefined(L.Control.MiniMap);
+            }
+        },
+
+        AwesomeMarkersPlugin: {
+            isLoaded: function isLoaded() {
+                return angular.isDefined(L.AwesomeMarkers) && angular.isDefined(L.AwesomeMarkers.Icon);
+            },
+            is: function is(icon) {
+                if (this.isLoaded()) {
+                    return icon instanceof L.AwesomeMarkers.Icon;
+                } else {
+                    return false;
+                }
+            },
+            equal: function equal(iconA, iconB) {
+                if (!this.isLoaded()) {
+                    return false;
+                }
+                if (this.is(iconA)) {
+                    return angular.equals(iconA, iconB);
+                } else {
+                    return false;
+                }
+            }
+        },
+
+        VectorMarkersPlugin: {
+            isLoaded: function isLoaded() {
+                return angular.isDefined(L.VectorMarkers) && angular.isDefined(L.VectorMarkers.Icon);
+            },
+            is: function is(icon) {
+                if (this.isLoaded()) {
+                    return icon instanceof L.VectorMarkers.Icon;
+                } else {
+                    return false;
+                }
+            },
+            equal: function equal(iconA, iconB) {
+                if (!this.isLoaded()) {
+                    return false;
+                }
+                if (this.is(iconA)) {
+                    return angular.equals(iconA, iconB);
+                } else {
+                    return false;
+                }
+            }
+        },
+
+        DomMarkersPlugin: {
+            isLoaded: function isLoaded() {
+                if (angular.isDefined(L.DomMarkers) && angular.isDefined(L.DomMarkers.Icon)) {
+                    return true;
+                } else {
+                    return false;
+                }
+            },
+            is: function is(icon) {
+                if (this.isLoaded()) {
+                    return icon instanceof L.DomMarkers.Icon;
+                } else {
+                    return false;
+                }
+            },
+            equal: function equal(iconA, iconB) {
+                if (!this.isLoaded()) {
+                    return false;
+                }
+                if (this.is(iconA)) {
+                    return angular.equals(iconA, iconB);
+                } else {
+                    return false;
+                }
+            }
+        },
+
+        PolylineDecoratorPlugin: {
+            isLoaded: function isLoaded() {
+                if (angular.isDefined(L.PolylineDecorator)) {
+                    return true;
+                } else {
+                    return false;
+                }
+            },
+            is: function is(decoration) {
+                if (this.isLoaded()) {
+                    return decoration instanceof L.PolylineDecorator;
+                } else {
+                    return false;
+                }
+            },
+            equal: function equal(decorationA, decorationB) {
+                if (!this.isLoaded()) {
+                    return false;
+                }
+                if (this.is(decorationA)) {
+                    return angular.equals(decorationA, decorationB);
+                } else {
+                    return false;
+                }
+            }
+        },
+
+        MakiMarkersPlugin: {
+            isLoaded: function isLoaded() {
+                if (angular.isDefined(L.MakiMarkers) && angular.isDefined(L.MakiMarkers.Icon)) {
+                    return true;
+                } else {
+                    return false;
+                }
+            },
+            is: function is(icon) {
+                if (this.isLoaded()) {
+                    return icon instanceof L.MakiMarkers.Icon;
+                } else {
+                    return false;
+                }
+            },
+            equal: function equal(iconA, iconB) {
+                if (!this.isLoaded()) {
+                    return false;
+                }
+                if (this.is(iconA)) {
+                    return angular.equals(iconA, iconB);
+                } else {
+                    return false;
+                }
+            }
+        },
+        ExtraMarkersPlugin: {
+            isLoaded: function isLoaded() {
+                if (angular.isDefined(L.ExtraMarkers) && angular.isDefined(L.ExtraMarkers.Icon)) {
+                    return true;
+                } else {
+                    return false;
+                }
+            },
+            is: function is(icon) {
+                if (this.isLoaded()) {
+                    return icon instanceof L.ExtraMarkers.Icon;
+                } else {
+                    return false;
+                }
+            },
+            equal: function equal(iconA, iconB) {
+                if (!this.isLoaded()) {
+                    return false;
+                }
+                if (this.is(iconA)) {
+                    return angular.equals(iconA, iconB);
+                } else {
+                    return false;
+                }
+            }
+        },
+        LabelPlugin: {
+            isLoaded: function isLoaded() {
+                return angular.isDefined(L.Label);
+            },
+            is: function is(layer) {
+                if (this.isLoaded()) {
+                    return layer instanceof L.MarkerClusterGroup;
+                } else {
+                    return false;
+                }
+            }
+        },
+        MarkerClusterPlugin: {
+            isLoaded: function isLoaded() {
+                return angular.isDefined(L.MarkerClusterGroup);
+            },
+            is: function is(layer) {
+                if (this.isLoaded()) {
+                    return layer instanceof L.MarkerClusterGroup;
+                } else {
+                    return false;
+                }
+            }
+        },
+        GeoJSONPlugin: {
+            isLoaded: function isLoaded() {
+                return angular.isDefined(L.TileLayer.GeoJSON);
+            },
+            is: function is(layer) {
+                if (this.isLoaded()) {
+                    return layer instanceof L.TileLayer.GeoJSON;
+                } else {
+                    return false;
+                }
+            }
+        },
+        CartoDB: {
+            isLoaded: function isLoaded() {
+                return cartodb;
+            },
+            is: function is() /*layer*/{
+                return true;
+                /*
+                if (this.isLoaded()) {
+                    return layer instanceof L.TileLayer.GeoJSON;
+                } else {
+                    return false;
+                }*/
+            }
+        },
+        Leaflet: {
+            DivIcon: {
+                is: function is(icon) {
+                    return icon instanceof L.DivIcon;
+                },
+                equal: function equal(iconA, iconB) {
+                    if (this.is(iconA)) {
+                        return angular.equals(iconA, iconB);
+                    } else {
+                        return false;
+                    }
+                }
+            },
+            Icon: {
+                is: function is(icon) {
+                    return icon instanceof L.Icon;
+                },
+                equal: function equal(iconA, iconB) {
+                    if (this.is(iconA)) {
+                        return angular.equals(iconA, iconB);
+                    } else {
+                        return false;
+                    }
+                }
+            }
+        },
+        /*
+         watchOptions - object to set deep nested watches and turn off watches all together
+         (rely on control / functional updates)
+         watchOptions - Object
+             type: string. //One of ['watch', 'watchCollection', 'watchDeep', null]
+             individual
+                 type: string
+         */
+        //legacy defaults
+        watchOptions: {
+            type: 'watchDeep',
+            individual: {
+                type: 'watchDeep'
+            }
+        }
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').service('leafletIterators', ["leafletLogger", "leafletHelpers", function (leafletLogger, leafletHelpers) {
+
+  var lHlp = leafletHelpers,
+      errorHeader = leafletHelpers.errorHeader + 'leafletIterators: ';
+
+  //BEGIN COPY from underscore
+  var _keys = Object.keys;
+  var _isFunction = lHlp.isFunction;
+  var _isObject = lHlp.isObject;
+  var $log = leafletLogger;
+
+  // Helper for collection methods to determine whether a collection
+  // should be iterated as an array or as an object
+  // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
+  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
+
+  var _isArrayLike = function _isArrayLike(collection) {
+    var length = collection !== null && collection.length;
+    return lHlp.isNumber(length) && length >= 0 && length <= MAX_ARRAY_INDEX;
+  };
+
+  // Keep the identity function around for default iteratees.
+  var _identity = function _identity(value) {
+    return value;
+  };
+
+  var _property = function _property(key) {
+    return function (obj) {
+      return obj === null ? void 0 : obj[key];
+    };
+  };
+
+  // Internal function that returns an efficient (for current engines) version
+  // of the passed-in callback, to be repeatedly applied in other Underscore
+  // functions.
+  var optimizeCb = function optimizeCb(func, context, argCount) {
+    if (context === void 0) return func;
+    switch (argCount === null ? 3 : argCount) {
+      case 1:
+        return function (value) {
+          return func.call(context, value);
+        };
+      case 2:
+        return function (value, other) {
+          return func.call(context, value, other);
+        };
+      case 3:
+        return function (value, index, collection) {
+          return func.call(context, value, index, collection);
+        };
+      case 4:
+        return function (accumulator, value, index, collection) {
+          return func.call(context, accumulator, value, index, collection);
+        };
+    }
+    return function () {
+      return func.apply(context, arguments);
+    };
+  };
+
+  // An internal function for creating assigner functions.
+  var createAssigner = function createAssigner(keysFunc, undefinedOnly) {
+    return function (obj) {
+      var length = arguments.length;
+      if (length < 2 || obj === null) return obj;
+      for (var index = 1; index < length; index++) {
+        var source = arguments[index],
+            keys = keysFunc(source),
+            l = keys.length;
+        for (var i = 0; i < l; i++) {
+          var key = keys[i];
+          if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
+        }
+      }
+      return obj;
+    };
+  };
+
+  // Assigns a given object with all the own properties in the passed-in object(s)
+  // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
+  var _extendOwn,
+      _assign = null;
+  _extendOwn = _assign = createAssigner(_keys);
+
+  // Returns whether an object has a given set of `key:value` pairs.
+  var _isMatch = function _isMatch(object, attrs) {
+    var keys = _keys(attrs),
+        length = keys.length;
+    if (object === null) return !length;
+    var obj = Object(object);
+    for (var i = 0; i < length; i++) {
+      var key = keys[i];
+      if (attrs[key] !== obj[key] || !(key in obj)) return false;
+    }
+    return true;
+  };
+
+  // Returns a predicate for checking whether an object has a given set of
+  // `key:value` pairs.
+  var _matcher,
+      _matches = null;
+  _matcher = _matches = function _matches(attrs) {
+    attrs = _extendOwn({}, attrs);
+    return function (obj) {
+      return _isMatch(obj, attrs);
+    };
+  };
+
+  // A mostly-internal function to generate callbacks that can be applied
+  // to each element in a collection, returning the desired result — either
+  // identity, an arbitrary callback, a property matcher, or a property accessor.
+  var cb = function cb(value, context, argCount) {
+    if (value === null) return _identity;
+    if (_isFunction(value)) return optimizeCb(value, context, argCount);
+    if (_isObject(value)) return _matcher(value);
+    return _property(value);
+  };
+
+  var _every,
+      _all = null;
+  _every = _all = function _all(obj, predicate, context) {
+    predicate = cb(predicate, context);
+    var keys = !_isArrayLike(obj) && _keys(obj),
+        length = (keys || obj).length;
+    for (var index = 0; index < length; index++) {
+      var currentKey = keys ? keys[index] : index;
+      if (!predicate(obj[currentKey], currentKey, obj)) return false;
+    }
+    return true;
+  };
+
+  //END COPY fron underscore
+
+  var _hasErrors = function _hasErrors(collection, cb, ignoreCollection, cbName) {
+    if (!ignoreCollection) {
+      if (!lHlp.isDefined(collection) || !lHlp.isDefined(cb)) {
+        return true;
+      }
+    }
+    if (!lHlp.isFunction(cb)) {
+      cbName = lHlp.defaultTo(cb, 'cb');
+      $log.error(errorHeader + cbName + ' is not a function');
+      return true;
+    }
+    return false;
+  };
+
+  var _iterate = function _iterate(collection, externalCb, internalCb) {
+    if (_hasErrors(undefined, internalCb, true, 'internalCb')) {
+      return;
+    }
+    if (!_hasErrors(collection, externalCb)) {
+      for (var key in collection) {
+        if (collection.hasOwnProperty(key)) {
+          internalCb(collection[key], key);
+        }
+      }
+    }
+  };
+
+  //see http://jsperf.com/iterators/3
+  //utilizing for in is way faster
+  var _each = function _each(collection, cb) {
+    _iterate(collection, cb, function (val, key) {
+      cb(val, key);
+    });
+  };
+
+  return {
+    each: _each,
+    forEach: _each,
+    every: _every,
+    all: _all
+  };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').factory('leafletLayerHelpers', ["$rootScope", "$q", "leafletLogger", "leafletHelpers", "leafletIterators", function ($rootScope, $q, leafletLogger, leafletHelpers, leafletIterators) {
+    var Helpers = leafletHelpers;
+    var isString = leafletHelpers.isString;
+    var isObject = leafletHelpers.isObject;
+    var isArray = leafletHelpers.isArray;
+    var isDefined = leafletHelpers.isDefined;
+    var errorHeader = leafletHelpers.errorHeader;
+    var $it = leafletIterators;
+    var $log = leafletLogger;
+
+    var utfGridCreateLayer = function utfGridCreateLayer(params) {
+        if (!Helpers.UTFGridPlugin.isLoaded()) {
+            $log.error('[AngularJS - Leaflet] The UTFGrid plugin is not loaded.');
+            return;
+        }
+        var utfgrid = new L.UtfGrid(params.url, params.pluginOptions);
+
+        var toSend = {
+            model: params.$parent
+        };
+
+        // TODO Use event manager
+        utfgrid.on('mouseover', function (e) {
+            angular.extend(toSend, {
+                leafletEvent: e,
+                leafletObject: e.target
+            });
+            $rootScope.$broadcast('leafletDirectiveMap.utfgridMouseover', toSend);
+        });
+
+        utfgrid.on('mouseout', function (e) {
+            angular.extend(toSend, {
+                leafletEvent: e,
+                leafletObject: e.target
+            });
+            $rootScope.$broadcast('leafletDirectiveMap.utfgridMouseout', toSend);
+        });
+
+        utfgrid.on('click', function (e) {
+            angular.extend(toSend, {
+                leafletEvent: e,
+                leafletObject: e.target
+            });
+            $rootScope.$broadcast('leafletDirectiveMap.utfgridClick', toSend);
+        });
+
+        utfgrid.on('mousemove', function (e) {
+            angular.extend(toSend, {
+                leafletEvent: e,
+                leafletObject: e.target
+            });
+            $rootScope.$broadcast('leafletDirectiveMap.utfgridMousemove', toSend);
+        });
+
+        return utfgrid;
+    };
+
+    var layerTypes = {
+        xyz: {
+            mustHaveUrl: true,
+            createLayer: function createLayer(params) {
+                return L.tileLayer(params.url, params.options);
+            }
+        },
+        geoJSON: {
+            mustHaveUrl: true,
+            createLayer: function createLayer(params) {
+                if (!Helpers.GeoJSONPlugin.isLoaded()) {
+                    return;
+                }
+                return new L.TileLayer.GeoJSON(params.url, params.pluginOptions, params.options);
+            }
+        },
+        geoJSONShape: {
+            mustHaveUrl: false,
+            createLayer: function createLayer(params) {
+                return new L.GeoJSON(params.data, params.options);
+            }
+        },
+        geoJSONAwesomeMarker: {
+            mustHaveUrl: false,
+            createLayer: function createLayer(params) {
+                return new L.geoJson(params.data, {
+                    pointToLayer: function pointToLayer(feature, latlng) {
+                        return L.marker(latlng, { icon: L.AwesomeMarkers.icon(params.icon) });
+                    }
+                });
+            }
+        },
+        geoJSONVectorMarker: {
+            mustHaveUrl: false,
+            createLayer: function createLayer(params) {
+                return new L.geoJson(params.data, {
+                    pointToLayer: function pointToLayer(feature, latlng) {
+                        return L.marker(latlng, { icon: L.VectorMarkers.icon(params.icon) });
+                    }
+                });
+            }
+        },
+        cartodbTiles: {
+            mustHaveKey: true,
+            createLayer: function createLayer(params) {
+                var url = isDefined(params.url) ? params.url + '/' + params.user : '//' + params.user + '.cartodb.com';
+                url += '/api/v1/map/' + params.key + '/{z}/{x}/{y}.png';
+                return L.tileLayer(url, params.options);
+            }
+        },
+        cartodbUTFGrid: {
+            mustHaveKey: true,
+            mustHaveLayer: true,
+            createLayer: function createLayer(params) {
+                var url = isDefined(params.url) ? params.url + '/' + params.user : '//' + params.user + '.cartodb.com';
+                params.url = url + '/api/v1/map/' + params.key + '/' + params.layer + '/{z}/{x}/{y}.grid.json';
+                return utfGridCreateLayer(params);
+            }
+        },
+        cartodbInteractive: {
+            mustHaveKey: true,
+            mustHaveLayer: true,
+            createLayer: function createLayer(params) {
+                var url = isDefined(params.url) ? params.url + '/' + params.user : '//' + params.user + '.cartodb.com';
+                var tilesURL = url + '/api/v1/map/' + params.key + '/{z}/{x}/{y}.png';
+                var tileLayer = L.tileLayer(tilesURL, params.options);
+                var layers = [tileLayer];
+
+                var addUtfLayer = function addUtfLayer(parent, params, layer) {
+                    var paramsCopy = angular.copy(params);
+                    paramsCopy.url = url + '/api/v1/map/' + paramsCopy.key + '/' + layer + '/{z}/{x}/{y}.grid.json';
+                    parent.push(utfGridCreateLayer(paramsCopy));
+                };
+
+                if (isArray(params.layer)) {
+                    for (var i = 0; i < params.layer.length; i++) {
+                        addUtfLayer(layers, params, params.layer[i]);
+                    }
+                } else {
+                    addUtfLayer(layers, params, params.layer);
+                }
+                return L.layerGroup(layers);
+            }
+        },
+        wms: {
+            mustHaveUrl: true,
+            createLayer: function createLayer(params) {
+                return L.tileLayer.wms(params.url, params.options);
+            }
+        },
+        wmts: {
+            mustHaveUrl: true,
+            createLayer: function createLayer(params) {
+                return L.tileLayer.wmts(params.url, params.options);
+            }
+        },
+        group: {
+            mustHaveUrl: false,
+            createLayer: function createLayer(params) {
+                var lyrs = [];
+                $it.each(params.options.layers, function (l) {
+                    lyrs.push(_createLayer(l));
+                });
+                params.options.loadedDefer = function () {
+                    var defers = [];
+                    if (isDefined(params.options.layers)) {
+                        for (var i = 0; i < params.options.layers.length; i++) {
+                            var d = params.options.layers[i].layerOptions.loadedDefer;
+                            if (isDefined(d)) {
+                                defers.push(d);
+                            }
+                        }
+                    }
+                    return defers;
+                };
+                return L.layerGroup(lyrs);
+            }
+        },
+        featureGroup: {
+            mustHaveUrl: false,
+            createLayer: function createLayer() {
+                return L.featureGroup();
+            }
+        },
+        markercluster: {
+            mustHaveUrl: false,
+            createLayer: function createLayer(params) {
+                if (!Helpers.MarkerClusterPlugin.isLoaded()) {
+                    $log.warn(errorHeader + ' The markercluster plugin is not loaded.');
+                    return;
+                }
+                return new L.MarkerClusterGroup(params.options);
+            }
+        },
+        imageOverlay: {
+            mustHaveUrl: true,
+            mustHaveBounds: true,
+            createLayer: function createLayer(params) {
+                return L.imageOverlay(params.url, params.bounds, params.options);
+            }
+        },
+        iip: {
+            mustHaveUrl: true,
+            createLayer: function createLayer(params) {
+                return L.tileLayer.iip(params.url, params.options);
+            }
+        },
+
+        // This "custom" type is used to accept every layer that user want to define himself.
+        // We can wrap these custom layers like heatmap or yandex, but it means a lot of work/code to wrap the world,
+        // so we let user to define their own layer outside the directive,
+        // and pass it on "createLayer" result for next processes
+        custom: {
+            createLayer: function createLayer(params) {
+                if (params.layer instanceof L.Class) {
+                    return angular.copy(params.layer);
+                } else {
+                    $log.error('[AngularJS - Leaflet] A custom layer must be a leaflet Class');
+                }
+            }
+        },
+        cartodb: {
+            mustHaveUrl: true,
+            createLayer: function createLayer(params) {
+                return cartodb.createLayer(params.map, params.url);
+            }
+        }
+    };
+
+    function isValidLayerType(layerDefinition) {
+        // Check if the baselayer has a valid type
+        if (!isString(layerDefinition.type)) {
+            $log.error('[AngularJS - Leaflet] A layer must have a valid type defined.');
+            return false;
+        }
+
+        if (Object.keys(layerTypes).indexOf(layerDefinition.type) === -1) {
+            $log.error('[AngularJS - Leaflet] A layer must have a valid type: ' + Object.keys(layerTypes));
+            return false;
+        }
+
+        // Check if the layer must have an URL
+        if (layerTypes[layerDefinition.type].mustHaveUrl && !isString(layerDefinition.url)) {
+            $log.error('[AngularJS - Leaflet] A base layer must have an url');
+            return false;
+        }
+
+        if (layerTypes[layerDefinition.type].mustHaveData && !isDefined(layerDefinition.data)) {
+            $log.error('[AngularJS - Leaflet] The base layer must have a "data" array attribute');
+            return false;
+        }
+
+        if (layerTypes[layerDefinition.type].mustHaveLayer && !isDefined(layerDefinition.layer)) {
+            $log.error('[AngularJS - Leaflet] The type of layer ' + layerDefinition.type + ' must have an layer defined');
+            return false;
+        }
+
+        if (layerTypes[layerDefinition.type].mustHaveBounds && !isDefined(layerDefinition.bounds)) {
+            $log.error('[AngularJS - Leaflet] The type of layer ' + layerDefinition.type + ' must have bounds defined');
+            return false;
+        }
+
+        if (layerTypes[layerDefinition.type].mustHaveKey && !isDefined(layerDefinition.key)) {
+            $log.error('[AngularJS - Leaflet] The type of layer ' + layerDefinition.type + ' must have key defined');
+            return false;
+        }
+        return true;
+    }
+
+    function _createLayer(layerDefinition) {
+        if (!isValidLayerType(layerDefinition)) {
+            return;
+        }
+
+        if (!isString(layerDefinition.name)) {
+            $log.error('[AngularJS - Leaflet] A base layer must have a name');
+            return;
+        }
+        if (!isObject(layerDefinition.layerParams)) {
+            layerDefinition.layerParams = {};
+        }
+        if (!isObject(layerDefinition.layerOptions)) {
+            layerDefinition.layerOptions = {};
+        }
+
+        // Mix the layer specific parameters with the general Leaflet options. Although this is an overhead
+        // the definition of a base layers is more 'clean' if the two types of parameters are differentiated
+        for (var attrname in layerDefinition.layerParams) {
+            layerDefinition.layerOptions[attrname] = layerDefinition.layerParams[attrname];
+        }
+
+        var params = {
+            url: layerDefinition.url,
+            data: layerDefinition.data,
+            options: layerDefinition.layerOptions,
+            layer: layerDefinition.layer,
+            icon: layerDefinition.icon,
+            type: layerDefinition.layerType,
+            bounds: layerDefinition.bounds,
+            key: layerDefinition.key,
+            apiKey: layerDefinition.apiKey,
+            pluginOptions: layerDefinition.pluginOptions,
+            user: layerDefinition.user,
+            $parent: layerDefinition
+        };
+
+        //TODO Add $watch to the layer properties
+        return layerTypes[layerDefinition.type].createLayer(params);
+    }
+
+    function safeAddLayer(map, layer) {
+        if (layer && typeof layer.addTo === 'function') {
+            layer.addTo(map);
+        } else {
+            map.addLayer(layer);
+        }
+    }
+
+    function safeRemoveLayer(map, layer, layerOptions) {
+        if (isDefined(layerOptions) && isDefined(layerOptions.loadedDefer)) {
+            if (angular.isFunction(layerOptions.loadedDefer)) {
+                var defers = layerOptions.loadedDefer();
+                $log.debug('Loaded Deferred', defers);
+                var count = defers.length;
+                if (count > 0) {
+                    var resolve = function resolve() {
+                        count--;
+                        if (count === 0) {
+                            map.removeLayer(layer);
+                        }
+                    };
+
+                    for (var i = 0; i < defers.length; i++) {
+                        defers[i].promise.then(resolve);
+                    }
+                } else {
+                    map.removeLayer(layer);
+                }
+            } else {
+                layerOptions.loadedDefer.promise.then(function () {
+                    map.removeLayer(layer);
+                });
+            }
+        } else {
+            map.removeLayer(layer);
+        }
+    }
+
+    var changeOpacityListener = function changeOpacityListener(op) {
+        return function (ly) {
+            if (isDefined(ly.setOpacity)) {
+                ly.setOpacity(op);
+            }
+        };
+    };
+
+    return {
+        createLayer: _createLayer,
+        layerTypes: layerTypes,
+        safeAddLayer: safeAddLayer,
+        safeRemoveLayer: safeRemoveLayer,
+        changeOpacityListener: changeOpacityListener
+    };
+}]);
+
+'use strict';
+
+angular.module("ui-leaflet").factory('leafletLegendHelpers', ["$http", "$q", "$log", "leafletHelpers", function ($http, $q, $log, leafletHelpers) {
+	var requestQueue = {},
+	    isDefined = leafletHelpers.isDefined;
+
+	var _execNext = function _execNext(mapId) {
+		var queue = requestQueue[mapId];
+		var task = queue[0];
+		$http(task.c).then(function (data) {
+			queue.shift();
+			task.d.resolve(data);
+			if (queue.length > 0) {
+				_execNext(mapId);
+			}
+		}, function (err) {
+			queue.shift();
+			task.d.reject(err);
+			if (queue.length > 0) {
+				_execNext(mapId);
+			}
+		});
+	};
+
+	var _updateLegend = function _updateLegend(div, legendData, type, url) {
+		div.innerHTML = '';
+		if (legendData.error) {
+			div.innerHTML += '<div class="info-title alert alert-danger">' + legendData.error.message + '</div>';
+		} else {
+			if (type === 'arcgis') {
+				for (var i = 0; i < legendData.layers.length; i++) {
+					var layer = legendData.layers[i];
+					div.innerHTML += '<div class="info-title" data-layerid="' + layer.layerId + '">' + layer.layerName + '</div>';
+					for (var j = 0; j < layer.legend.length; j++) {
+						var leg = layer.legend[j];
+						div.innerHTML += '<div class="inline" data-layerid="' + layer.layerId + '"><img src="data:' + leg.contentType + ';base64,' + leg.imageData + '" /></div>' + '<div class="info-label" data-layerid="' + layer.layerId + '">' + leg.label + '</div>';
+					}
+				}
+			} else if (type === 'image') {
+				div.innerHTML = '<img src="' + url + '"/>';
+			}
+		}
+	};
+
+	var _getOnAddLegend = function _getOnAddLegend(legendData, legendClass, type, url) {
+		return function () /*map*/{
+			var div = L.DomUtil.create('div', legendClass);
+
+			if (!L.Browser.touch) {
+				L.DomEvent.disableClickPropagation(div);
+				L.DomEvent.on(div, 'mousewheel', L.DomEvent.stopPropagation);
+			} else {
+				L.DomEvent.on(div, 'click', L.DomEvent.stopPropagation);
+			}
+			_updateLegend(div, legendData, type, url);
+			return div;
+		};
+	};
+
+	var _getOnAddArrayLegend = function _getOnAddArrayLegend(legend, legendClass) {
+		return function () /*map*/{
+			var div = L.DomUtil.create('div', legendClass);
+			for (var i = 0; i < legend.colors.length; i++) {
+				div.innerHTML += '<div class="outline"><i style="background:' + legend.colors[i] + '"></i></div>' + '<div class="info-label">' + legend.labels[i] + '</div>';
+			}
+			if (!L.Browser.touch) {
+				L.DomEvent.disableClickPropagation(div);
+				L.DomEvent.on(div, 'mousewheel', L.DomEvent.stopPropagation);
+			} else {
+				L.DomEvent.on(div, 'click', L.DomEvent.stopPropagation);
+			}
+			return div;
+		};
+	};
+
+	return {
+		getOnAddLegend: _getOnAddLegend,
+		getOnAddArrayLegend: _getOnAddArrayLegend,
+		updateLegend: _updateLegend,
+		addLegendURL: function addLegendURL(mapId, config) {
+			var d = $q.defer();
+			if (!isDefined(requestQueue[mapId])) {
+				requestQueue[mapId] = [];
+			}
+			requestQueue[mapId].push({ c: config, d: d });
+			if (requestQueue[mapId].length === 1) {
+				_execNext(mapId);
+			}
+			return d.promise;
+		}
+	};
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').factory('leafletMapDefaults', ["$q", "leafletHelpers", function ($q, leafletHelpers) {
+    function _getDefaults() {
+        return {
+            keyboard: true,
+            dragging: true,
+            worldCopyJump: false,
+            doubleClickZoom: true,
+            scrollWheelZoom: true,
+            tap: true,
+            touchZoom: true,
+            zoomControl: true,
+            zoomsliderControl: false,
+            zoomControlPosition: 'topleft',
+            attributionControl: true,
+            controls: {
+                layers: {
+                    visible: true,
+                    position: 'topright',
+                    collapsed: true
+                }
+            },
+            nominatim: {
+                server: ' http://nominatim.openstreetmap.org/search'
+            },
+            crs: L.CRS.EPSG3857,
+            tileLayer: '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
+            tileLayerOptions: {
+                attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
+            },
+            path: {
+                weight: 10,
+                opacity: 1,
+                color: '#0000ff'
+            },
+            center: {
+                lat: 0,
+                lng: 0,
+                zoom: 1
+            },
+            trackResize: true
+        };
+    }
+
+    var isDefined = leafletHelpers.isDefined,
+        isObject = leafletHelpers.isObject,
+        obtainEffectiveMapId = leafletHelpers.obtainEffectiveMapId,
+        defaults = {};
+
+    // Get the _defaults dictionary, and override the properties defined by the user
+    return {
+        reset: function reset() {
+            defaults = {};
+        },
+        getDefaults: function getDefaults(scopeId) {
+            var mapId = obtainEffectiveMapId(defaults, scopeId);
+            return defaults[mapId];
+        },
+
+        getMapCreationDefaults: function getMapCreationDefaults(scopeId) {
+            var mapId = obtainEffectiveMapId(defaults, scopeId);
+            var d = defaults[mapId];
+
+            var mapDefaults = {
+                maxZoom: d.maxZoom,
+                keyboard: d.keyboard,
+                dragging: d.dragging,
+                zoomControl: d.zoomControl,
+                doubleClickZoom: d.doubleClickZoom,
+                scrollWheelZoom: d.scrollWheelZoom,
+                tap: d.tap,
+                touchZoom: d.touchZoom,
+                attributionControl: d.attributionControl,
+                worldCopyJump: d.worldCopyJump,
+                crs: d.crs,
+                trackResize: d.trackResize
+            };
+
+            if (isDefined(d.minZoom)) {
+                mapDefaults.minZoom = d.minZoom;
+            }
+
+            if (isDefined(d.zoomAnimation)) {
+                mapDefaults.zoomAnimation = d.zoomAnimation;
+            }
+
+            if (isDefined(d.fadeAnimation)) {
+                mapDefaults.fadeAnimation = d.fadeAnimation;
+            }
+
+            if (isDefined(d.markerZoomAnimation)) {
+                mapDefaults.markerZoomAnimation = d.markerZoomAnimation;
+            }
+
+            if (d.map) {
+                for (var option in d.map) {
+                    mapDefaults[option] = d.map[option];
+                }
+            }
+
+            return mapDefaults;
+        },
+
+        setDefaults: function setDefaults(userDefaults, scopeId) {
+            var newDefaults = _getDefaults();
+
+            if (isDefined(userDefaults)) {
+                newDefaults.doubleClickZoom = isDefined(userDefaults.doubleClickZoom) ? userDefaults.doubleClickZoom : newDefaults.doubleClickZoom;
+                newDefaults.scrollWheelZoom = isDefined(userDefaults.scrollWheelZoom) ? userDefaults.scrollWheelZoom : newDefaults.doubleClickZoom;
+                newDefaults.tap = isDefined(userDefaults.tap) ? userDefaults.tap : newDefaults.tap;
+                newDefaults.touchZoom = isDefined(userDefaults.touchZoom) ? userDefaults.touchZoom : newDefaults.doubleClickZoom;
+                newDefaults.zoomControl = isDefined(userDefaults.zoomControl) ? userDefaults.zoomControl : newDefaults.zoomControl;
+                newDefaults.zoomsliderControl = isDefined(userDefaults.zoomsliderControl) ? userDefaults.zoomsliderControl : newDefaults.zoomsliderControl;
+                newDefaults.attributionControl = isDefined(userDefaults.attributionControl) ? userDefaults.attributionControl : newDefaults.attributionControl;
+                newDefaults.tileLayer = isDefined(userDefaults.tileLayer) ? userDefaults.tileLayer : newDefaults.tileLayer;
+                newDefaults.zoomControlPosition = isDefined(userDefaults.zoomControlPosition) ? userDefaults.zoomControlPosition : newDefaults.zoomControlPosition;
+                newDefaults.keyboard = isDefined(userDefaults.keyboard) ? userDefaults.keyboard : newDefaults.keyboard;
+                newDefaults.dragging = isDefined(userDefaults.dragging) ? userDefaults.dragging : newDefaults.dragging;
+                newDefaults.trackResize = isDefined(userDefaults.trackResize) ? userDefaults.trackResize : newDefaults.trackResize;
+
+                if (isDefined(userDefaults.controls)) {
+                    angular.extend(newDefaults.controls, userDefaults.controls);
+                }
+
+                if (isObject(userDefaults.crs)) {
+                    newDefaults.crs = userDefaults.crs;
+                } else if (isDefined(L.CRS[userDefaults.crs])) {
+                    newDefaults.crs = L.CRS[userDefaults.crs];
+                }
+
+                if (isDefined(userDefaults.center)) {
+                    angular.copy(userDefaults.center, newDefaults.center);
+                }
+
+                if (isDefined(userDefaults.tileLayerOptions)) {
+                    angular.copy(userDefaults.tileLayerOptions, newDefaults.tileLayerOptions);
+                }
+
+                if (isDefined(userDefaults.maxZoom)) {
+                    newDefaults.maxZoom = userDefaults.maxZoom;
+                }
+
+                if (isDefined(userDefaults.minZoom)) {
+                    newDefaults.minZoom = userDefaults.minZoom;
+                }
+
+                if (isDefined(userDefaults.zoomAnimation)) {
+                    newDefaults.zoomAnimation = userDefaults.zoomAnimation;
+                }
+
+                if (isDefined(userDefaults.fadeAnimation)) {
+                    newDefaults.fadeAnimation = userDefaults.fadeAnimation;
+                }
+
+                if (isDefined(userDefaults.markerZoomAnimation)) {
+                    newDefaults.markerZoomAnimation = userDefaults.markerZoomAnimation;
+                }
+
+                if (isDefined(userDefaults.worldCopyJump)) {
+                    newDefaults.worldCopyJump = userDefaults.worldCopyJump;
+                }
+
+                if (isDefined(userDefaults.map)) {
+                    newDefaults.map = userDefaults.map;
+                }
+
+                if (isDefined(userDefaults.path)) {
+                    newDefaults.path = userDefaults.path;
+                }
+            }
+
+            var mapId = obtainEffectiveMapId(defaults, scopeId);
+            defaults[mapId] = newDefaults;
+            return newDefaults;
+        }
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').service('leafletMarkersHelpers', ["$rootScope", "$timeout", "leafletHelpers", "leafletLogger", "$compile", "leafletGeoJsonHelpers", "leafletWatchHelpers", function ($rootScope, $timeout, leafletHelpers, leafletLogger, $compile, leafletGeoJsonHelpers, leafletWatchHelpers) {
+    var isDefined = leafletHelpers.isDefined,
+        defaultTo = leafletHelpers.defaultTo,
+        MarkerClusterPlugin = leafletHelpers.MarkerClusterPlugin,
+        AwesomeMarkersPlugin = leafletHelpers.AwesomeMarkersPlugin,
+        VectorMarkersPlugin = leafletHelpers.VectorMarkersPlugin,
+        MakiMarkersPlugin = leafletHelpers.MakiMarkersPlugin,
+        ExtraMarkersPlugin = leafletHelpers.ExtraMarkersPlugin,
+        DomMarkersPlugin = leafletHelpers.DomMarkersPlugin,
+        safeApply = leafletHelpers.safeApply,
+        Helpers = leafletHelpers,
+        isString = leafletHelpers.isString,
+        isNumber = leafletHelpers.isNumber,
+        isObject = leafletHelpers.isObject,
+        groups = {},
+        geoHlp = leafletGeoJsonHelpers,
+        errorHeader = leafletHelpers.errorHeader,
+        maybeWatch = leafletWatchHelpers.maybeWatch,
+        $log = leafletLogger;
+
+    var _string = function _string(marker) {
+        //this exists since JSON.stringify barfs on cyclic
+        var retStr = '';
+        ['_icon', '_latlng', '_leaflet_id', '_map', '_shadow'].forEach(function (prop) {
+            retStr += prop + ': ' + defaultTo(marker[prop], 'undefined') + ' \n';
+        });
+        return '[leafletMarker] : \n' + retStr;
+    };
+    var _log = function _log(marker, useConsole) {
+        var logger = useConsole ? console : $log;
+        logger.debug(_string(marker));
+    };
+
+    var existDomContainer = function existDomContainer(groupName) {
+        return angular.element(groups[groupName]._map._container).parent().length > 0;
+    };
+
+    var createLeafletIcon = function createLeafletIcon(iconData) {
+        if (isDefined(iconData) && isDefined(iconData.type) && iconData.type === 'awesomeMarker') {
+            if (!AwesomeMarkersPlugin.isLoaded()) {
+                $log.error(errorHeader + ' The AwesomeMarkers Plugin is not loaded.');
+            }
+
+            return new L.AwesomeMarkers.icon(iconData);
+        }
+
+        if (isDefined(iconData) && isDefined(iconData.type) && iconData.type === 'vectorMarker') {
+            if (!VectorMarkersPlugin.isLoaded()) {
+                $log.error(errorHeader + ' The VectorMarkers Plugin is not loaded.');
+            }
+
+            return new L.VectorMarkers.icon(iconData);
+        }
+
+        if (isDefined(iconData) && isDefined(iconData.type) && iconData.type === 'makiMarker') {
+            if (!MakiMarkersPlugin.isLoaded()) {
+                $log.error(errorHeader + 'The MakiMarkers Plugin is not loaded.');
+            }
+
+            return new L.MakiMarkers.icon(iconData);
+        }
+
+        if (isDefined(iconData) && isDefined(iconData.type) && iconData.type === 'extraMarker') {
+            if (!ExtraMarkersPlugin.isLoaded()) {
+                $log.error(errorHeader + 'The ExtraMarkers Plugin is not loaded.');
+            }
+            return new L.ExtraMarkers.icon(iconData);
+        }
+
+        if (isDefined(iconData) && isDefined(iconData.type) && iconData.type === 'div') {
+            return new L.divIcon(iconData);
+        }
+
+        if (isDefined(iconData) && isDefined(iconData.type) && iconData.type === 'dom') {
+            if (!DomMarkersPlugin.isLoaded()) {
+                $log.error(errorHeader + 'The DomMarkers Plugin is not loaded.');
+            }
+            var markerScope = angular.isFunction(iconData.getMarkerScope) ? iconData.getMarkerScope().$new() : $rootScope,
+                template = $compile(iconData.template)(markerScope),
+                iconDataCopy = angular.copy(iconData);
+            iconDataCopy.ngElement = template;
+            iconDataCopy.element = template[0];
+            if (angular.isFunction(iconData.getMarkerScope)) iconDataCopy.scope = markerScope;
+            return new L.DomMarkers.icon(iconDataCopy);
+        }
+
+        // allow for any custom icon to be used... assumes the icon has already been initialized
+        if (isDefined(iconData) && isDefined(iconData.type) && iconData.type === 'icon') {
+            return iconData.icon;
+        }
+
+        var base64icon = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAGmklEQVRYw7VXeUyTZxjvNnfELFuyIzOabermMZEeQC/OclkO49CpOHXOLJl/CAURuYbQi3KLgEhbrhZ1aDwmaoGqKII6odATmH/scDFbdC7LvFqOCc+e95s2VG50X/LLm/f4/Z7neY/ne18aANCmAr5E/xZf1uDOkTcGcWR6hl9247tT5U7Y6SNvWsKT63P58qbfeLJG8M5qcgTknrvvrdDbsT7Ml+tv82X6vVxJE33aRmgSyYtcWVMqX97Yv2JvW39UhRE2HuyBL+t+gK1116ly06EeWFNlAmHxlQE0OMiV6mQCScusKRlhS3QLeVJdl1+23h5dY4FNB3thrbYboqptEFlphTC1hSpJnbRvxP4NWgsE5Jyz86QNNi/5qSUTGuFk1gu54tN9wuK2wc3o+Wc13RCmsoBwEqzGcZsxsvCSy/9wJKf7UWf1mEY8JWfewc67UUoDbDjQC+FqK4QqLVMGGR9d2wurKzqBk3nqIT/9zLxRRjgZ9bqQgub+DdoeCC03Q8j+0QhFhBHR/eP3U/zCln7Uu+hihJ1+bBNffLIvmkyP0gpBZWYXhKussK6mBz5HT6M1Nqpcp+mBCPXosYQfrekGvrjewd59/GvKCE7TbK/04/ZV5QZYVWmDwH1mF3xa2Q3ra3DBC5vBT1oP7PTj4C0+CcL8c7C2CtejqhuCnuIQHaKHzvcRfZpnylFfXsYJx3pNLwhKzRAwAhEqG0SpusBHfAKkxw3w4627MPhoCH798z7s0ZnBJ/MEJbZSbXPhER2ih7p2ok/zSj2cEJDd4CAe+5WYnBCgR2uruyEw6zRoW6/DWJ/OeAP8pd/BGtzOZKpG8oke0SX6GMmRk6GFlyAc59K32OTEinILRJRchah8HQwND8N435Z9Z0FY1EqtxUg+0SO6RJ/mmXz4VuS+DpxXC3gXmZwIL7dBSH4zKE50wESf8qwVgrP1EIlTO5JP9Igu0aexdh28F1lmAEGJGfh7jE6ElyM5Rw/FDcYJjWhbeiBYoYNIpc2FT/SILivp0F1ipDWk4BIEo2VuodEJUifhbiltnNBIXPUFCMpthtAyqws/BPlEF/VbaIxErdxPphsU7rcCp8DohC+GvBIPJS/tW2jtvTmmAeuNO8BNOYQeG8G/2OzCJ3q+soYB5i6NhMaKr17FSal7GIHheuV3uSCY8qYVuEm1cOzqdWr7ku/R0BDoTT+DT+ohCM6/CCvKLKO4RI+dXPeAuaMqksaKrZ7L3FE5FIFbkIceeOZ2OcHO6wIhTkNo0ffgjRGxEqogXHYUPHfWAC/lADpwGcLRY3aeK4/oRGCKYcZXPVoeX/kelVYY8dUGf8V5EBRbgJXT5QIPhP9ePJi428JKOiEYhYXFBqou2Guh+p/mEB1/RfMw6rY7cxcjTrneI1FrDyuzUSRm9miwEJx8E/gUmqlyvHGkneiwErR21F3tNOK5Tf0yXaT+O7DgCvALTUBXdM4YhC/IawPU+2PduqMvuaR6eoxSwUk75ggqsYJ7VicsnwGIkZBSXKOUww73WGXyqP+J2/b9c+gi1YAg/xpwck3gJuucNrh5JvDPvQr0WFXf0piyt8f8/WI0hV4pRxxkQZdJDfDJNOAmM0Ag8jyT6hz0WGXWuP94Yh2jcfjmXAGvHCMslRimDHYuHuDsy2QtHuIavznhbYURq5R57KpzBBRZKPJi8eQg48h4j8SDdowifdIrEVdU+gbO6QNvRRt4ZBthUaZhUnjlYObNagV3keoeru3rU7rcuceqU1mJBxy+BWZYlNEBH+0eH4vRiB+OYybU2hnblYlTvkHinM4m54YnxSyaZYSF6R3jwgP7udKLGIX6r/lbNa9N6y5MFynjWDtrHd75ZvTYAPO/6RgF0k76mQla3FGq7dO+cH8sKn0Vo7nDllwAhqwLPkxrHwWmHJOo+AKJ4rab5OgrM7rVu8eWb2Pu0Dh4eDgXoOfvp7Y7QeqknRmvcTBEyq9m/HQQSCSz6LHq3z0yzsNySRfMS253wl2KyRDbcZPcfJKjZmSEOjcxyi+Y8dUOtsIEH6R2wNykdqrkYJ0RV92H0W58pkfQk7cKevsLK10Py8SdMGfXNXATY+pPbyJR/ET6n9nIfztNtZYRV9XniQu9IA2vOVgy4ir7GCLVmmd+zjkH0eAF9Po6K61pmCXHxU5rHMYd1ftc3owjwRSVRzLjKvqZEty6cRUD7jGqiOdu5HG6MdHjNcNYGqfDm5YRzLBBCCDl/2bk8a8gdbqcfwECu62Fg/HrggAAAABJRU5ErkJggg==";
+        var base64shadow = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACkAAAApCAYAAACoYAD2AAAC5ElEQVRYw+2YW4/TMBCF45S0S1luXZCABy5CgLQgwf//S4BYBLTdJLax0fFqmB07nnQfEGqkIydpVH85M+NLjPe++dcPc4Q8Qh4hj5D/AaQJx6H/4TMwB0PeBNwU7EGQAmAtsNfAzoZkgIa0ZgLMa4Aj6CxIAsjhjOCoL5z7Glg1JAOkaicgvQBXuncwJAWjksLtBTWZe04CnYRktUGdilALppZBOgHGZcBzL6OClABvMSVIzyBjazOgrvACf1ydC5mguqAVg6RhdkSWQFj2uxfaq/BrIZOLEWgZdALIDvcMcZLD8ZbLC9de4yR1sYMi4G20S4Q/PWeJYxTOZn5zJXANZHIxAd4JWhPIloTJZhzMQduM89WQ3MUVAE/RnhAXpTycqys3NZALOBbB7kFrgLesQl2h45Fcj8L1tTSohUwuxhy8H/Qg6K7gIs+3kkaigQCOcyEXCHN07wyQazhrmIulvKMQAwMcmLNqyCVyMAI+BuxSMeTk3OPikLY2J1uE+VHQk6ANrhds+tNARqBeaGc72cK550FP4WhXmFmcMGhTwAR1ifOe3EvPqIegFmF+C8gVy0OfAaWQPMR7gF1OQKqGoBjq90HPMP01BUjPOqGFksC4emE48tWQAH0YmvOgF3DST6xieJgHAWxPAHMuNhrImIdvoNOKNWIOcE+UXE0pYAnkX6uhWsgVXDxHdTfCmrEEmMB2zMFimLVOtiiajxiGWrbU52EeCdyOwPEQD8LqyPH9Ti2kgYMf4OhSKB7qYILbBv3CuVTJ11Y80oaseiMWOONc/Y7kJYe0xL2f0BaiFTxknHO5HaMGMublKwxFGzYdWsBF174H/QDknhTHmHHN39iWFnkZx8lPyM8WHfYELmlLKtgWNmFNzQcC1b47gJ4hL19i7o65dhH0Negbca8vONZoP7doIeOC9zXm8RjuL0Gf4d4OYaU5ljo3GYiqzrWQHfJxA6ALhDpVKv9qYeZA8eM3EhfPSCmpuD0AAAAASUVORK5CYII=";
+
+        if (!isDefined(iconData) || !isDefined(iconData.iconUrl)) {
+            return new L.Icon.Default({
+                iconUrl: base64icon,
+                shadowUrl: base64shadow,
+                iconSize: [25, 41],
+                iconAnchor: [12, 41],
+                popupAnchor: [1, -34],
+                shadowSize: [41, 41]
+            });
+        }
+
+        return new L.Icon(iconData);
+    };
+
+    var _resetMarkerGroup = function _resetMarkerGroup(groupName) {
+        if (isDefined(groups[groupName])) {
+            delete groups[groupName];
+        }
+    };
+
+    var _resetMarkerGroups = function _resetMarkerGroups() {
+        groups = {};
+    };
+
+    var _resetUnusedMarkerGroups = function _resetUnusedMarkerGroups() {
+        for (var groupName in groups) {
+            if (!existDomContainer(groupName)) {
+                _resetMarkerGroup(groupName);
+            }
+        }
+    };
+
+    var _cleanDomIcon = function _cleanDomIcon(marker) {
+        if (marker.options.icon.options.ngElement) {
+            marker.options.icon.options.ngElement.remove();
+        }
+        if (marker.options.icon.options.scope) {
+            marker.options.icon.options.scope.$destroy();
+        }
+    };
+
+    var _deleteMarker = function _deleteMarker(marker, map, layers) {
+        marker.closePopup();
+
+        // if it's a dom icon, clean it
+        if (marker.options.icon && marker.options.icon.options && marker.options.icon.options.type === 'dom') {
+            _cleanDomIcon(marker);
+        }
+
+        // There is no easy way to know if a marker is added to a layer, so we search for it
+        // if there are overlays
+        if (isDefined(layers) && isDefined(layers.overlays)) {
+            for (var key in layers.overlays) {
+                if (layers.overlays[key] instanceof L.LayerGroup || layers.overlays[key] instanceof L.FeatureGroup) {
+                    if (layers.overlays[key].hasLayer(marker)) {
+                        layers.overlays[key].removeLayer(marker);
+                        return;
+                    }
+                }
+            }
+        }
+
+        if (isDefined(groups)) {
+            for (var groupKey in groups) {
+                if (groups[groupKey].hasLayer(marker)) {
+                    groups[groupKey].removeLayer(marker);
+                }
+            }
+        }
+
+        if (map.hasLayer(marker)) {
+            map.removeLayer(marker);
+        }
+    };
+
+    var adjustPopupPan = function adjustPopupPan(marker, map) {
+        var containerHeight = marker._popup._container.offsetHeight,
+            layerPos = new L.Point(marker._popup._containerLeft, -containerHeight - marker._popup._containerBottom),
+            containerPos = map.layerPointToContainerPoint(layerPos);
+        if (containerPos !== null) {
+            marker._popup._adjustPan();
+        }
+    };
+
+    var compilePopup = function compilePopup(marker, markerScope) {
+        $compile(marker._popup._contentNode)(markerScope);
+    };
+
+    var updatePopup = function updatePopup(marker, markerScope, map) {
+        //The innerText should be more than 1 once angular has compiled.
+        //We need to keep trying until angular has compiled before we _updateLayout and _updatePosition
+        //This should take care of any scenario , eg ngincludes, whatever.
+        //Is there a better way to check for this?
+        var innerText = marker._popup._contentNode.innerText || marker._popup._contentNode.textContent;
+        if (innerText.length < 1) {
+            $timeout(function () {
+                updatePopup(marker, markerScope, map);
+            });
+        }
+
+        //cause a reflow - this is also very important - if we don't do this then the widths are from before $compile
+        var reflow = marker._popup._contentNode.offsetWidth;
+
+        marker._popup._updateLayout();
+        marker._popup._updatePosition();
+
+        if (marker._popup.options.autoPan) {
+            adjustPopupPan(marker, map);
+        }
+
+        //using / returning reflow so jshint doesn't moan
+        return reflow;
+    };
+
+    var _manageOpenPopup = function _manageOpenPopup(marker, markerData, map) {
+        // The marker may provide a scope returning function used to compile the message
+        // default to $rootScope otherwise
+        var markerScope = angular.isFunction(markerData.getMessageScope) ? markerData.getMessageScope() : $rootScope,
+            compileMessage = isDefined(markerData.compileMessage) ? markerData.compileMessage : true;
+
+        if (compileMessage) {
+            if (!isDefined(marker._popup) || !isDefined(marker._popup._contentNode)) {
+                $log.error(errorHeader + 'Popup is invalid or does not have any content.');
+                return false;
+            }
+
+            compilePopup(marker, markerScope);
+            updatePopup(marker, markerData, map);
+        }
+    };
+
+    var _manageOpenLabel = function _manageOpenLabel(marker, markerData) {
+        var markerScope = angular.isFunction(markerData.getMessageScope) ? markerData.getMessageScope() : $rootScope,
+            labelScope = angular.isFunction(markerData.getLabelScope) ? markerData.getLabelScope() : markerScope,
+            compileMessage = isDefined(markerData.compileMessage) ? markerData.compileMessage : true;
+
+        if (Helpers.LabelPlugin.isLoaded() && isDefined(markerData.label)) {
+            if (isDefined(markerData.label.options) && markerData.label.options.noHide === true) {
+                marker.showLabel();
+            }
+            if (compileMessage && isDefined(marker.label)) {
+                $compile(marker.label._container)(labelScope);
+            }
+        }
+    };
+
+    var _updateMarker = function _updateMarker(markerData, oldMarkerData, marker, name, leafletScope, layers, map) {
+        if (!isDefined(oldMarkerData)) {
+            return;
+        }
+
+        // Update the lat-lng property (always present in marker properties)
+        if (!geoHlp.validateCoords(markerData)) {
+            $log.warn('There are problems with lat-lng data, please verify your marker model');
+            _deleteMarker(marker, map, layers);
+            return;
+        }
+
+        // watch is being initialized if old and new object is the same
+        var isInitializing = markerData === oldMarkerData;
+
+        // Update marker rotation
+        if (isDefined(markerData.iconAngle) && oldMarkerData.iconAngle !== markerData.iconAngle) {
+            marker.setIconAngle(markerData.iconAngle);
+        }
+
+        // It is possible that the layer has been removed or the layer marker does not exist
+        // Update the layer group if present or move it to the map if not
+        if (!isString(markerData.layer)) {
+            // There is no layer information, we move the marker to the map if it was in a layer group
+            if (isString(oldMarkerData.layer)) {
+                // Remove from the layer group that is supposed to be
+                if (isDefined(layers.overlays[oldMarkerData.layer]) && layers.overlays[oldMarkerData.layer].hasLayer(marker)) {
+                    layers.overlays[oldMarkerData.layer].removeLayer(marker);
+                    marker.closePopup();
+                }
+                // Test if it is not on the map and add it
+                if (!map.hasLayer(marker)) {
+                    map.addLayer(marker);
+                }
+            }
+        }
+
+        if ((isNumber(markerData.opacity) || isNumber(parseFloat(markerData.opacity))) && markerData.opacity !== oldMarkerData.opacity) {
+            // There was a different opacity so we update it
+            marker.setOpacity(markerData.opacity);
+        }
+
+        if (isString(markerData.layer) && oldMarkerData.layer !== markerData.layer) {
+            // If it was on a layer group we have to remove it
+            if (isString(oldMarkerData.layer) && isDefined(layers.overlays[oldMarkerData.layer]) && layers.overlays[oldMarkerData.layer].hasLayer(marker)) {
+                layers.overlays[oldMarkerData.layer].removeLayer(marker);
+            }
+            marker.closePopup();
+
+            // Remove it from the map in case the new layer is hidden or there is an error in the new layer
+            if (map.hasLayer(marker)) {
+                map.removeLayer(marker);
+            }
+
+            // The markerData.layer is defined so we add the marker to the layer if it is different from the old data
+            if (!isDefined(layers.overlays[markerData.layer])) {
+                $log.error(errorHeader + 'You must use a name of an existing layer');
+                return;
+            }
+            // Is a group layer?
+            var layerGroup = layers.overlays[markerData.layer];
+            if (!(layerGroup instanceof L.LayerGroup || layerGroup instanceof L.FeatureGroup)) {
+                $log.error(errorHeader + 'A marker can only be added to a layer of type "group" or "featureGroup"');
+                return;
+            }
+            // The marker goes to a correct layer group, so first of all we add it
+            layerGroup.addLayer(marker);
+            // The marker is automatically added to the map depending on the visibility
+            // of the layer, so we only have to open the popup if the marker is in the map
+            if (map.hasLayer(marker) && markerData.focus === true) {
+                marker.openPopup();
+            }
+        }
+
+        // Update the draggable property
+        if (markerData.draggable !== true && oldMarkerData.draggable === true && isDefined(marker.dragging)) {
+            marker.dragging.disable();
+        }
+
+        if (markerData.draggable === true && oldMarkerData.draggable !== true) {
+            // The markerData.draggable property must be true so we update if there wasn't a previous value or it wasn't true
+            if (marker.dragging) {
+                marker.dragging.enable();
+            } else {
+                if (L.Handler.MarkerDrag) {
+                    marker.dragging = new L.Handler.MarkerDrag(marker);
+                    marker.options.draggable = true;
+                    marker.dragging.enable();
+                }
+            }
+        }
+
+        // Update the icon property
+        if (!isObject(markerData.icon)) {
+            // If there is no icon property or it's not an object
+            if (isObject(oldMarkerData.icon)) {
+                if (oldMarkerData.icon.type === 'dom') {
+                    // clean previous icon if it's a dom one
+                    _cleanDomIcon(marker);
+                }
+                // If there was an icon before restore to the default
+                marker.setIcon(createLeafletIcon());
+                marker.closePopup();
+                marker.unbindPopup();
+                if (isString(markerData.message)) {
+                    marker.bindPopup(markerData.message, markerData.popupOptions);
+                }
+            }
+        }
+
+        if (isObject(markerData.icon) && isObject(oldMarkerData.icon) && !angular.equals(markerData.icon, oldMarkerData.icon)) {
+            var dragG = false;
+            if (marker.dragging) {
+                dragG = marker.dragging.enabled();
+            }
+            if (oldMarkerData.icon.type === 'dom') {
+                // clean previous icon if it's a dom one
+                _cleanDomIcon(marker);
+            }
+            marker.setIcon(createLeafletIcon(markerData.icon));
+            if (dragG) {
+                marker.dragging.enable();
+            }
+            marker.closePopup();
+            marker.unbindPopup();
+            if (isString(markerData.message)) {
+                marker.bindPopup(markerData.message, markerData.popupOptions);
+                // if marker has been already focused, reopen popup
+                if (map.hasLayer(marker) && markerData.focus === true) {
+                    marker.openPopup();
+                }
+            }
+        }
+
+        // Update the Popup message property
+        if (!isString(markerData.message) && isString(oldMarkerData.message)) {
+            marker.closePopup();
+            marker.unbindPopup();
+        }
+
+        // Update the label content or bind a new label if the old one has been removed.
+        if (Helpers.LabelPlugin.isLoaded()) {
+            if (isDefined(markerData.label) && isDefined(markerData.label.message)) {
+                if ('label' in oldMarkerData && 'message' in oldMarkerData.label && !angular.equals(markerData.label.message, oldMarkerData.label.message)) {
+                    marker.updateLabelContent(markerData.label.message);
+                } else if (!angular.isFunction(marker.getLabel) || angular.isFunction(marker.getLabel) && !isDefined(marker.getLabel())) {
+                    marker.bindLabel(markerData.label.message, markerData.label.options);
+                    _manageOpenLabel(marker, markerData);
+                } else {
+                    _manageOpenLabel(marker, markerData);
+                }
+            } else if (!('label' in markerData && !('message' in markerData.label))) {
+                if (angular.isFunction(marker.unbindLabel)) {
+                    marker.unbindLabel();
+                }
+            }
+        }
+
+        // There is some text in the popup, so we must show the text or update existing
+        if (isString(markerData.message) && !isString(oldMarkerData.message)) {
+            // There was no message before so we create it
+            marker.bindPopup(markerData.message, markerData.popupOptions);
+        }
+
+        if (isString(markerData.message) && isString(oldMarkerData.message) && markerData.message !== oldMarkerData.message) {
+            // There was a different previous message so we update it
+            marker.setPopupContent(markerData.message);
+        }
+
+        // Update the focus property
+        var updatedFocus = false;
+        if (markerData.focus !== true && oldMarkerData.focus === true) {
+            // If there was a focus property and was true we turn it off
+            marker.closePopup();
+            updatedFocus = true;
+        }
+
+        // The markerData.focus property must be true so we update if there wasn't a previous value or it wasn't true
+        if (markerData.focus === true && (!isDefined(oldMarkerData.focus) || oldMarkerData.focus === false) || isInitializing && markerData.focus === true) {
+            // Reopen the popup when focus is still true
+            marker.openPopup();
+            updatedFocus = true;
+        }
+
+        // zIndexOffset adjustment
+        if (oldMarkerData.zIndexOffset !== markerData.zIndexOffset) {
+            marker.setZIndexOffset(markerData.zIndexOffset);
+        }
+
+        var markerLatLng = marker.getLatLng();
+        var isCluster = isString(markerData.layer) && Helpers.MarkerClusterPlugin.is(layers.overlays[markerData.layer]);
+        // If the marker is in a cluster it has to be removed and added to the layer when the location is changed
+        if (isCluster) {
+            // The focus has changed even by a user click or programatically
+            if (updatedFocus) {
+                // We only have to update the location if it was changed programatically, because it was
+                // changed by a user drag the marker data has already been updated by the internal event
+                // listened by the directive
+                if (markerData.lat !== oldMarkerData.lat || markerData.lng !== oldMarkerData.lng) {
+                    layers.overlays[markerData.layer].removeLayer(marker);
+                    marker.setLatLng([markerData.lat, markerData.lng]);
+                    layers.overlays[markerData.layer].addLayer(marker);
+                }
+            } else {
+                // The marker has possibly moved. It can be moved by a user drag (marker location and data are equal but old
+                // data is diferent) or programatically (marker location and data are diferent)
+                if (markerLatLng.lat !== markerData.lat || markerLatLng.lng !== markerData.lng) {
+                    // The marker was moved by a user drag
+                    layers.overlays[markerData.layer].removeLayer(marker);
+                    marker.setLatLng([markerData.lat, markerData.lng]);
+                    layers.overlays[markerData.layer].addLayer(marker);
+                } else if (markerData.lat !== oldMarkerData.lat || markerData.lng !== oldMarkerData.lng) {
+                    // The marker was moved programatically
+                    layers.overlays[markerData.layer].removeLayer(marker);
+                    marker.setLatLng([markerData.lat, markerData.lng]);
+                    layers.overlays[markerData.layer].addLayer(marker);
+                } else if (isObject(markerData.icon) && isObject(oldMarkerData.icon) && !angular.equals(markerData.icon, oldMarkerData.icon)) {
+                    layers.overlays[markerData.layer].removeLayer(marker);
+                    layers.overlays[markerData.layer].addLayer(marker);
+                }
+            }
+        } else if (markerLatLng.lat !== markerData.lat || markerLatLng.lng !== markerData.lng) {
+            marker.setLatLng([markerData.lat, markerData.lng]);
+        }
+    };
+
+    var _getLayerModels = function _getLayerModels(models, layerName) {
+        if (!isDefined(models)) return;
+        if (layerName) return models[layerName];
+        return models;
+    };
+
+    var _getModelFromModels = function _getModelFromModels(models, id, layerName) {
+        if (!isDefined(models)) return;
+        if (!id) {
+            $log.error(errorHeader + 'marker id missing in getMarker');
+            return;
+        }
+        if (layerName) return models[layerName][id];
+
+        return models[id];
+    };
+    return {
+        resetMarkerGroup: _resetMarkerGroup,
+
+        resetMarkerGroups: _resetMarkerGroups,
+
+        resetUnusedMarkerGroups: _resetUnusedMarkerGroups,
+
+        deleteMarker: _deleteMarker,
+
+        manageOpenPopup: _manageOpenPopup,
+
+        manageOpenLabel: _manageOpenLabel,
+
+        createMarker: function createMarker(markerData) {
+            if (!isDefined(markerData) || !geoHlp.validateCoords(markerData)) {
+                $log.error(errorHeader + 'The marker definition is not valid.');
+                return;
+            }
+            var coords = geoHlp.getCoords(markerData);
+
+            if (!isDefined(coords)) {
+                $log.error(errorHeader + 'Unable to get coordinates from markerData.');
+                return;
+            }
+
+            var markerOptions = {
+                icon: createLeafletIcon(markerData.icon),
+                title: isDefined(markerData.title) ? markerData.title : '',
+                draggable: isDefined(markerData.draggable) ? markerData.draggable : false,
+                clickable: isDefined(markerData.clickable) ? markerData.clickable : true,
+                riseOnHover: isDefined(markerData.riseOnHover) ? markerData.riseOnHover : false,
+                zIndexOffset: isDefined(markerData.zIndexOffset) ? markerData.zIndexOffset : 0,
+                iconAngle: isDefined(markerData.iconAngle) ? markerData.iconAngle : 0
+            };
+            // Add any other options not added above to markerOptions
+            for (var markerDatum in markerData) {
+                if (markerData.hasOwnProperty(markerDatum) && !markerOptions.hasOwnProperty(markerDatum)) {
+                    markerOptions[markerDatum] = markerData[markerDatum];
+                }
+            }
+
+            var marker = new L.marker(coords, markerOptions);
+
+            if (!isString(markerData.message)) {
+                marker.unbindPopup();
+            }
+
+            return marker;
+        },
+
+        addMarkerToGroup: function addMarkerToGroup(marker, groupName, groupOptions, map) {
+            if (!isString(groupName)) {
+                $log.error(errorHeader + 'The marker group you have specified is invalid.');
+                return;
+            }
+
+            if (!MarkerClusterPlugin.isLoaded()) {
+                $log.error(errorHeader + "The MarkerCluster plugin is not loaded.");
+                return;
+            }
+            if (!isDefined(groups[groupName])) {
+                groups[groupName] = new L.MarkerClusterGroup(groupOptions);
+                map.addLayer(groups[groupName]);
+            }
+            groups[groupName].addLayer(marker);
+        },
+
+        listenMarkerEvents: function listenMarkerEvents(marker, markerData, leafletScope, watchType, map) {
+            marker.on("popupopen", function () /* event */{
+                safeApply(leafletScope, function () {
+                    if (isDefined(marker._popup) || isDefined(marker._popup._contentNode)) {
+                        markerData.focus = true;
+                        _manageOpenPopup(marker, markerData, map); //needed since markerData is now a copy
+                    }
+                });
+            });
+            marker.on("popupclose", function () /* event */{
+                safeApply(leafletScope, function () {
+                    markerData.focus = false;
+                });
+            });
+            marker.on("add", function () /* event */{
+                safeApply(leafletScope, function () {
+                    if ('label' in markerData) _manageOpenLabel(marker, markerData);
+                });
+            });
+        },
+
+        updateMarker: _updateMarker,
+
+        addMarkerWatcher: function addMarkerWatcher(marker, name, leafletScope, layers, map, watchOptions) {
+            var markerWatchPath = Helpers.getObjectArrayPath("markers." + name);
+
+            maybeWatch(leafletScope, markerWatchPath, watchOptions, function (markerData, oldMarkerData, clearWatch) {
+                if (!isDefined(markerData)) {
+                    _deleteMarker(marker, map, layers);
+                    clearWatch();
+                    return;
+                }
+                _updateMarker(markerData, oldMarkerData, marker, name, leafletScope, layers, map);
+            });
+        },
+        string: _string,
+        log: _log,
+        getModelFromModels: _getModelFromModels,
+        getLayerModels: _getLayerModels
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').factory('leafletPathsHelpers', ["$rootScope", "leafletLogger", "leafletHelpers", function ($rootScope, leafletLogger, leafletHelpers) {
+    var isDefined = leafletHelpers.isDefined,
+        isArray = leafletHelpers.isArray,
+        isNumber = leafletHelpers.isNumber,
+        isValidPoint = leafletHelpers.isValidPoint,
+        $log = leafletLogger;
+
+    var availableOptions = [
+    // Path options
+    'stroke', 'weight', 'color', 'opacity', 'fill', 'fillColor', 'fillOpacity', 'dashArray', 'lineCap', 'lineJoin', 'clickable', 'pointerEvents', 'className',
+
+    // Polyline options
+    'smoothFactor', 'noClip'];
+    function _convertToLeafletLatLngs(latlngs) {
+        return latlngs.filter(function (latlng) {
+            return isValidPoint(latlng);
+        }).map(function (latlng) {
+            return _convertToLeafletLatLng(latlng);
+        });
+    }
+
+    function _convertToLeafletLatLng(latlng) {
+        if (isArray(latlng)) {
+            return new L.LatLng(latlng[0], latlng[1]);
+        } else {
+            return new L.LatLng(latlng.lat, latlng.lng);
+        }
+    }
+
+    function _convertToLeafletMultiLatLngs(paths) {
+        return paths.map(function (latlngs) {
+            return _convertToLeafletLatLngs(latlngs);
+        });
+    }
+
+    function _getOptions(path, defaults) {
+        var options = {};
+        for (var i = 0; i < availableOptions.length; i++) {
+            var optionName = availableOptions[i];
+
+            if (isDefined(path[optionName])) {
+                options[optionName] = path[optionName];
+            } else if (isDefined(defaults.path[optionName])) {
+                options[optionName] = defaults.path[optionName];
+            }
+        }
+
+        return options;
+    }
+
+    var _updatePathOptions = function _updatePathOptions(path, data) {
+        var updatedStyle = {};
+        for (var i = 0; i < availableOptions.length; i++) {
+            var optionName = availableOptions[i];
+            if (isDefined(data[optionName])) {
+                updatedStyle[optionName] = data[optionName];
+            }
+        }
+        path.setStyle(data);
+    };
+
+    var _isValidPolyline = function _isValidPolyline(latlngs) {
+        if (!isArray(latlngs)) {
+            return false;
+        }
+        for (var i = 0; i < latlngs.length; i++) {
+            var point = latlngs[i];
+            if (!isValidPoint(point)) {
+                return false;
+            }
+        }
+        return true;
+    };
+
+    var pathTypes = {
+        polyline: {
+            isValid: function isValid(pathData) {
+                var latlngs = pathData.latlngs;
+                return _isValidPolyline(latlngs);
+            },
+            createPath: function createPath(options) {
+                return new L.Polyline([], options);
+            },
+            setPath: function setPath(path, data) {
+                path.setLatLngs(_convertToLeafletLatLngs(data.latlngs));
+                _updatePathOptions(path, data);
+                return;
+            }
+        },
+        multiPolyline: {
+            isValid: function isValid(pathData) {
+                var latlngs = pathData.latlngs;
+                if (!isArray(latlngs)) {
+                    return false;
+                }
+
+                for (var i in latlngs) {
+                    var polyline = latlngs[i];
+                    if (!_isValidPolyline(polyline)) {
+                        return false;
+                    }
+                }
+
+                return true;
+            },
+            createPath: function createPath(options) {
+                return new L.multiPolyline([[[0, 0], [1, 1]]], options);
+            },
+            setPath: function setPath(path, data) {
+                path.setLatLngs(_convertToLeafletMultiLatLngs(data.latlngs));
+                _updatePathOptions(path, data);
+                return;
+            }
+        },
+        polygon: {
+            isValid: function isValid(pathData) {
+                var latlngs = pathData.latlngs;
+                return _isValidPolyline(latlngs);
+            },
+            createPath: function createPath(options) {
+                return new L.Polygon([], options);
+            },
+            setPath: function setPath(path, data) {
+                path.setLatLngs(_convertToLeafletLatLngs(data.latlngs));
+                _updatePathOptions(path, data);
+                return;
+            }
+        },
+        multiPolygon: {
+            isValid: function isValid(pathData) {
+                var latlngs = pathData.latlngs;
+
+                if (!isArray(latlngs)) {
+                    return false;
+                }
+
+                for (var i in latlngs) {
+                    var polyline = latlngs[i];
+                    if (!_isValidPolyline(polyline)) {
+                        return false;
+                    }
+                }
+
+                return true;
+            },
+            createPath: function createPath(options) {
+                return new L.MultiPolygon([[[0, 0], [1, 1], [0, 1]]], options);
+            },
+            setPath: function setPath(path, data) {
+                path.setLatLngs(_convertToLeafletMultiLatLngs(data.latlngs));
+                _updatePathOptions(path, data);
+                return;
+            }
+        },
+        rectangle: {
+            isValid: function isValid(pathData) {
+                var latlngs = pathData.latlngs;
+
+                if (!isArray(latlngs) || latlngs.length !== 2) {
+                    return false;
+                }
+
+                for (var i in latlngs) {
+                    var point = latlngs[i];
+                    if (!isValidPoint(point)) {
+                        return false;
+                    }
+                }
+
+                return true;
+            },
+            createPath: function createPath(options) {
+                return new L.Rectangle([[0, 0], [1, 1]], options);
+            },
+            setPath: function setPath(path, data) {
+                path.setBounds(new L.LatLngBounds(_convertToLeafletLatLngs(data.latlngs)));
+                _updatePathOptions(path, data);
+            }
+        },
+        circle: {
+            isValid: function isValid(pathData) {
+                var point = pathData.latlngs;
+                return isValidPoint(point) && isNumber(pathData.radius);
+            },
+            createPath: function createPath(options) {
+                return new L.Circle([0, 0], 1, options);
+            },
+            setPath: function setPath(path, data) {
+                path.setLatLng(_convertToLeafletLatLng(data.latlngs));
+                if (isDefined(data.radius)) {
+                    path.setRadius(data.radius);
+                }
+                _updatePathOptions(path, data);
+            }
+        },
+        circleMarker: {
+            isValid: function isValid(pathData) {
+                var point = pathData.latlngs;
+                return isValidPoint(point) && isNumber(pathData.radius);
+            },
+            createPath: function createPath(options) {
+                return new L.CircleMarker([0, 0], options);
+            },
+            setPath: function setPath(path, data) {
+                path.setLatLng(_convertToLeafletLatLng(data.latlngs));
+                if (isDefined(data.radius)) {
+                    path.setRadius(data.radius);
+                }
+                _updatePathOptions(path, data);
+            }
+        }
+    };
+
+    var _getPathData = function _getPathData(path) {
+        var pathData = {};
+        if (path.latlngs) {
+            pathData.latlngs = path.latlngs;
+        }
+
+        if (path.radius) {
+            pathData.radius = path.radius;
+        }
+
+        return pathData;
+    };
+
+    return {
+        setPathOptions: function setPathOptions(leafletPath, pathType, data) {
+            if (!isDefined(pathType)) {
+                pathType = "polyline";
+            }
+            pathTypes[pathType].setPath(leafletPath, data);
+        },
+        createPath: function createPath(name, path, defaults) {
+            if (!isDefined(path.type)) {
+                path.type = "polyline";
+            }
+            var options = _getOptions(path, defaults);
+            var pathData = _getPathData(path);
+
+            if (!pathTypes[path.type].isValid(pathData)) {
+                $log.error("[AngularJS - Leaflet] Invalid data passed to the " + path.type + " path");
+                return;
+            }
+
+            return pathTypes[path.type].createPath(options);
+        }
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').service('leafletWatchHelpers', function () {
+
+    var _maybe = function _maybe(scope, watchFunctionName, thingToWatchStr, watchOptions, initCb) {
+        var unWatch = scope[watchFunctionName](thingToWatchStr, function (newValue, oldValue) {
+            //make the unWatch function available to the callback as well.
+            initCb(newValue, oldValue, unWatch);
+            if (watchOptions.type === null) unWatch();
+        }, watchOptions.type === 'watchDeep');
+
+        return unWatch;
+    };
+
+    /*
+    @name: maybeWatch
+    @description: Utility to watch something once or forever.
+    @returns unWatch function
+    @param watchOptions - This object is used to determine the type of
+    watch used.
+    */
+    var _maybeWatch = function _maybeWatch(scope, thingToWatchStr, watchOptions, initCb) {
+        var watchMethod;
+
+        if (watchOptions.type === 'watchCollection') {
+            watchMethod = '$watchCollection';
+        } else {
+            watchMethod = '$watch';
+        }
+
+        return _maybe(scope, watchMethod, thingToWatchStr, watchOptions, initCb);
+    };
+
+    return {
+        maybeWatch: _maybeWatch
+    };
+});
+
+'use strict';
+
+angular.module('ui-leaflet').service('leafletLogger', ["nemSimpleLogger", function (nemSimpleLogger) {
+  return nemSimpleLogger.spawn();
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').factory('nominatimService', ["$q", "$http", "leafletHelpers", "leafletMapDefaults", function ($q, $http, leafletHelpers, leafletMapDefaults) {
+    var isDefined = leafletHelpers.isDefined;
+
+    return {
+        query: function query(address, mapId) {
+            var defaults = leafletMapDefaults.getDefaults(mapId);
+            var url = defaults.nominatim.server;
+            var df = $q.defer();
+
+            $http.get(url, { params: { format: 'json', limit: 1, q: address } }).success(function (data) {
+                if (data.length > 0 && isDefined(data[0].boundingbox)) {
+                    df.resolve(data[0]);
+                } else {
+                    df.reject('[Nominatim] Invalid address');
+                }
+            });
+
+            return df.promise;
+        }
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').directive('bounds', ["leafletLogger", "$timeout", "$http", "leafletHelpers", "nominatimService", "leafletBoundsHelpers", function (leafletLogger, $timeout, $http, leafletHelpers, nominatimService, leafletBoundsHelpers) {
+    var $log = leafletLogger;
+    return {
+        restrict: "A",
+        scope: false,
+        replace: false,
+        require: ['leaflet'],
+
+        link: function link(scope, element, attrs, controller) {
+            var isDefined = leafletHelpers.isDefined;
+            var createLeafletBounds = leafletBoundsHelpers.createLeafletBounds;
+            var leafletScope = controller[0].getLeafletScope();
+            var mapController = controller[0];
+            var errorHeader = leafletHelpers.errorHeader + ' [Bounds] ';
+
+            var emptyBounds = function emptyBounds(bounds) {
+                return bounds._southWest.lat === 0 && bounds._southWest.lng === 0 && bounds._northEast.lat === 0 && bounds._northEast.lng === 0;
+            };
+
+            mapController.getMap().then(function (map) {
+                leafletScope.$on('boundsChanged', function (event) {
+                    var scope = event.currentScope;
+                    var bounds = map.getBounds();
+
+                    if (emptyBounds(bounds) || scope.settingBoundsFromScope) {
+                        return;
+                    }
+                    scope.settingBoundsFromLeaflet = true;
+                    var newScopeBounds = {
+                        northEast: {
+                            lat: bounds._northEast.lat,
+                            lng: bounds._northEast.lng
+                        },
+                        southWest: {
+                            lat: bounds._southWest.lat,
+                            lng: bounds._southWest.lng
+                        },
+                        options: bounds.options
+                    };
+                    if (!angular.equals(scope.bounds, newScopeBounds)) {
+                        scope.bounds = newScopeBounds;
+                    }
+                    $timeout(function () {
+                        scope.settingBoundsFromLeaflet = false;
+                    });
+                });
+
+                var lastNominatimQuery;
+                leafletScope.$watch('bounds', function (bounds) {
+                    if (scope.settingBoundsFromLeaflet) return;
+                    if (isDefined(bounds.address) && bounds.address !== lastNominatimQuery) {
+                        scope.settingBoundsFromScope = true;
+                        nominatimService.query(bounds.address, attrs.id).then(function (data) {
+                            var b = data.boundingbox;
+                            var newBounds = [[b[0], b[2]], [b[1], b[3]]];
+                            map.fitBounds(newBounds);
+                        }, function (errMsg) {
+                            $log.error(errorHeader + ' ' + errMsg + '.');
+                        });
+                        lastNominatimQuery = bounds.address;
+                        $timeout(function () {
+                            scope.settingBoundsFromScope = false;
+                        });
+                        return;
+                    }
+
+                    var leafletBounds = createLeafletBounds(bounds);
+                    if (leafletBounds && !map.getBounds().equals(leafletBounds)) {
+                        scope.settingBoundsFromScope = true;
+                        map.fitBounds(leafletBounds, bounds.options);
+                        $timeout(function () {
+                            scope.settingBoundsFromScope = false;
+                        });
+                    }
+                }, true);
+            });
+        }
+    };
+}]);
+
+'use strict';
+
+var centerDirectiveTypes = ['center', 'lfCenter'],
+    centerDirectives = {};
+
+centerDirectiveTypes.forEach(function (directiveName) {
+    centerDirectives[directiveName] = ['leafletLogger', '$q', '$location', '$timeout', 'leafletMapDefaults', 'leafletHelpers', 'leafletBoundsHelpers', 'leafletMapEvents', function (leafletLogger, $q, $location, $timeout, leafletMapDefaults, leafletHelpers, leafletBoundsHelpers, leafletMapEvents) {
+
+        var isDefined = leafletHelpers.isDefined,
+            isNumber = leafletHelpers.isNumber,
+            isSameCenterOnMap = leafletHelpers.isSameCenterOnMap,
+            safeApply = leafletHelpers.safeApply,
+            isValidCenter = leafletHelpers.isValidCenter,
+            isValidBounds = leafletBoundsHelpers.isValidBounds,
+            isUndefinedOrEmpty = leafletHelpers.isUndefinedOrEmpty,
+            errorHeader = leafletHelpers.errorHeader,
+            $log = leafletLogger;
+
+        var shouldInitializeMapWithBounds = function shouldInitializeMapWithBounds(bounds, center) {
+            return isDefined(bounds) && isValidBounds(bounds) && isUndefinedOrEmpty(center);
+        };
+
+        var _leafletCenter;
+        return {
+            restrict: "A",
+            scope: false,
+            replace: false,
+            require: 'leaflet',
+            controller: function controller() {
+                _leafletCenter = $q.defer();
+                this.getCenter = function () {
+                    return _leafletCenter.promise;
+                };
+            },
+            link: function link(scope, element, attrs, controller) {
+                var leafletScope = controller.getLeafletScope(),
+                    centerModel = leafletScope[directiveName];
+
+                controller.getMap().then(function (map) {
+                    var defaults = leafletMapDefaults.getDefaults(attrs.id);
+
+                    if (attrs[directiveName].search("-") !== -1) {
+                        $log.error(errorHeader + ' The "center" variable can\'t use a "-" on its key name: "' + attrs[directiveName] + '".');
+                        map.setView([defaults.center.lat, defaults.center.lng], defaults.center.zoom);
+                        return;
+                    } else if (shouldInitializeMapWithBounds(leafletScope.bounds, centerModel)) {
+                        map.fitBounds(leafletBoundsHelpers.createLeafletBounds(leafletScope.bounds), leafletScope.bounds.options);
+                        centerModel = map.getCenter();
+                        safeApply(leafletScope, function (scope) {
+                            angular.extend(scope[directiveName], {
+                                lat: map.getCenter().lat,
+                                lng: map.getCenter().lng,
+                                zoom: map.getZoom(),
+                                autoDiscover: false
+                            });
+                        });
+                        safeApply(leafletScope, function (scope) {
+                            var mapBounds = map.getBounds();
+                            scope.bounds = {
+                                northEast: {
+                                    lat: mapBounds._northEast.lat,
+                                    lng: mapBounds._northEast.lng
+                                },
+                                southWest: {
+                                    lat: mapBounds._southWest.lat,
+                                    lng: mapBounds._southWest.lng
+                                }
+                            };
+                        });
+                    } else if (!isDefined(centerModel)) {
+                        $log.error(errorHeader + ' The "center" property is not defined in the main scope');
+                        map.setView([defaults.center.lat, defaults.center.lng], defaults.center.zoom);
+                        return;
+                    } else if (!(isDefined(centerModel.lat) && isDefined(centerModel.lng)) && !isDefined(centerModel.autoDiscover)) {
+                        angular.copy(defaults.center, centerModel);
+                    }
+
+                    var urlCenterHash, mapReady;
+                    if (attrs.urlHashCenter === "yes") {
+                        var extractCenterFromUrl = function extractCenterFromUrl() {
+                            var search = $location.search();
+                            var centerParam;
+                            var centerKey = attrs.urlHashParam ? attrs.urlHashParam : 'c';
+                            if (isDefined(search[centerKey])) {
+                                var cParam = search[centerKey].split(":");
+                                if (cParam.length === 3) {
+                                    centerParam = {
+                                        lat: parseFloat(cParam[0]),
+                                        lng: parseFloat(cParam[1]),
+                                        zoom: parseInt(cParam[2], 10)
+                                    };
+                                }
+                            }
+                            return centerParam;
+                        };
+                        urlCenterHash = extractCenterFromUrl();
+
+                        leafletScope.$on('$locationChangeSuccess', function (event) {
+                            var scope = event.currentScope;
+                            //$log.debug("updated location...");
+                            var urlCenter = extractCenterFromUrl();
+                            if (isDefined(urlCenter) && !isSameCenterOnMap(urlCenter, map)) {
+                                //$log.debug("updating center model...", urlCenter);
+                                angular.extend(scope[directiveName], {
+                                    lat: urlCenter.lat,
+                                    lng: urlCenter.lng,
+                                    zoom: urlCenter.zoom
+                                });
+                            }
+                        });
+                    }
+
+                    leafletScope.$watch(directiveName, function (center) {
+                        if (leafletScope.settingCenterFromLeaflet) return;
+                        //$log.debug("updated center model...");
+                        // The center from the URL has priority
+                        if (isDefined(urlCenterHash)) {
+                            angular.copy(urlCenterHash, center);
+                            urlCenterHash = undefined;
+                        }
+
+                        if (!isValidCenter(center) && center.autoDiscover !== true) {
+                            $log.warn(errorHeader + " invalid 'center'");
+                            //map.setView([defaults.center.lat, defaults.center.lng], defaults.center.zoom);
+                            return;
+                        }
+
+                        if (center.autoDiscover === true) {
+                            if (!isNumber(center.zoom)) {
+                                map.setView([defaults.center.lat, defaults.center.lng], defaults.center.zoom);
+                            }
+                            if (isNumber(center.zoom) && center.zoom > defaults.center.zoom) {
+                                map.locate({
+                                    setView: true,
+                                    maxZoom: center.zoom
+                                });
+                            } else if (isDefined(defaults.maxZoom)) {
+                                map.locate({
+                                    setView: true,
+                                    maxZoom: defaults.maxZoom
+                                });
+                            } else {
+                                map.locate({
+                                    setView: true
+                                });
+                            }
+                            return;
+                        }
+
+                        if (mapReady && isSameCenterOnMap(center, map)) {
+                            //$log.debug("no need to update map again.");
+                            return;
+                        }
+
+                        //$log.debug("updating map center...", center);
+                        leafletScope.settingCenterFromScope = true;
+                        map.setView([center.lat, center.lng], center.zoom);
+                        leafletMapEvents.notifyCenterChangedToBounds(leafletScope, map);
+                        $timeout(function () {
+                            leafletScope.settingCenterFromScope = false;
+                            //$log.debug("allow center scope updates");
+                        });
+                    }, true);
+
+                    map.whenReady(function () {
+                        mapReady = true;
+                    });
+
+                    map.on('moveend', function () /* event */{
+                        // Resolve the center after the first map position
+                        _leafletCenter.resolve();
+                        leafletMapEvents.notifyCenterUrlHashChanged(leafletScope, map, attrs, $location.search());
+                        //$log.debug("updated center on map...");
+                        if (isSameCenterOnMap(centerModel, map) || leafletScope.settingCenterFromScope) {
+                            //$log.debug("same center in model, no need to update again.");
+                            return;
+                        }
+                        leafletScope.settingCenterFromLeaflet = true;
+                        safeApply(leafletScope, function (scope) {
+                            if (!leafletScope.settingCenterFromScope) {
+                                //$log.debug("updating center model...", map.getCenter(), map.getZoom());
+                                angular.extend(scope[directiveName], {
+                                    lat: map.getCenter().lat,
+                                    lng: map.getCenter().lng,
+                                    zoom: map.getZoom(),
+                                    autoDiscover: false
+                                });
+                            }
+                            leafletMapEvents.notifyCenterChangedToBounds(leafletScope, map);
+                            $timeout(function () {
+                                leafletScope.settingCenterFromLeaflet = false;
+                            });
+                        });
+                    });
+
+                    if (centerModel.autoDiscover === true) {
+                        map.on('locationerror', function () {
+                            $log.warn(errorHeader + " The Geolocation API is unauthorized on this page.");
+                            if (isValidCenter(centerModel)) {
+                                map.setView([centerModel.lat, centerModel.lng], centerModel.zoom);
+                                leafletMapEvents.notifyCenterChangedToBounds(leafletScope, map);
+                            } else {
+                                map.setView([defaults.center.lat, defaults.center.lng], defaults.center.zoom);
+                                leafletMapEvents.notifyCenterChangedToBounds(leafletScope, map);
+                            }
+                        });
+                    }
+                });
+            }
+        };
+    }];
+});
+
+centerDirectiveTypes.forEach(function (dirType) {
+    angular.module('ui-leaflet').directive(dirType, centerDirectives[dirType]);
+});
+
+'use strict';
+
+angular.module('ui-leaflet').directive('controls', ["leafletLogger", "leafletHelpers", "leafletControlHelpers", function (leafletLogger, leafletHelpers, leafletControlHelpers) {
+    var $log = leafletLogger;
+    return {
+        restrict: "A",
+        scope: false,
+        replace: false,
+        require: '?^leaflet',
+
+        link: function link(scope, element, attrs, controller) {
+            if (!controller) {
+                return;
+            }
+
+            var createControl = leafletControlHelpers.createControl;
+            var isValidControlType = leafletControlHelpers.isValidControlType;
+            var leafletScope = controller.getLeafletScope();
+            var isDefined = leafletHelpers.isDefined;
+            var isArray = leafletHelpers.isArray;
+            var leafletControls = {};
+            var errorHeader = leafletHelpers.errorHeader + ' [Controls] ';
+
+            scope.$on('$destroy', function () {
+                leafletControlHelpers.destroyMapLayersControl(scope.mapId);
+            });
+
+            controller.getMap().then(function (map) {
+
+                leafletScope.$watchCollection('controls', function (newControls) {
+
+                    // Delete controls from the array
+                    for (var name in leafletControls) {
+                        if (!isDefined(newControls[name])) {
+                            if (map.hasControl(leafletControls[name])) {
+                                map.removeControl(leafletControls[name]);
+                            }
+                            delete leafletControls[name];
+                        }
+                    }
+
+                    for (var newName in newControls) {
+                        var control;
+
+                        var controlType = isDefined(newControls[newName].type) ? newControls[newName].type : newName;
+
+                        if (!isValidControlType(controlType)) {
+                            $log.error(errorHeader + ' Invalid control type: ' + controlType + '.');
+                            return;
+                        }
+
+                        if (controlType !== 'custom') {
+                            control = createControl(controlType, newControls[newName]);
+                            map.addControl(control);
+                            leafletControls[newName] = control;
+                        } else {
+                            var customControlValue = newControls[newName];
+                            if (isArray(customControlValue)) {
+                                for (var i = 0; i < customControlValue.length; i++) {
+                                    var customControl = customControlValue[i];
+                                    map.addControl(customControl);
+                                    leafletControls[newName] = !isDefined(leafletControls[newName]) ? [customControl] : leafletControls[newName].concat([customControl]);
+                                }
+                            } else {
+                                map.addControl(customControlValue);
+                                leafletControls[newName] = customControlValue;
+                            }
+                        }
+                    }
+                });
+            });
+        }
+    };
+}]);
+
+"use strict";
+
+angular.module('ui-leaflet').directive("decorations", ["leafletLogger", "leafletHelpers", function (leafletLogger, leafletHelpers) {
+	var $log = leafletLogger;
+	return {
+		restrict: "A",
+		scope: false,
+		replace: false,
+		require: 'leaflet',
+
+		link: function link(scope, element, attrs, controller) {
+			var leafletScope = controller.getLeafletScope(),
+			    PolylineDecoratorPlugin = leafletHelpers.PolylineDecoratorPlugin,
+			    isDefined = leafletHelpers.isDefined,
+			    leafletDecorations = {};
+
+			/* Creates an "empty" decoration with a set of coordinates, but no pattern. */
+			function createDecoration(options) {
+				if (isDefined(options) && isDefined(options.coordinates)) {
+					if (!PolylineDecoratorPlugin.isLoaded()) {
+						$log.error('[AngularJS - Leaflet] The PolylineDecorator Plugin is not loaded.');
+					}
+				}
+
+				return L.polylineDecorator(options.coordinates);
+			}
+
+			/* Updates the path and the patterns for the provided decoration, and returns the decoration. */
+			function setDecorationOptions(decoration, options) {
+				if (isDefined(decoration) && isDefined(options)) {
+					if (isDefined(options.coordinates) && isDefined(options.patterns)) {
+						decoration.setPaths(options.coordinates);
+						decoration.setPatterns(options.patterns);
+						return decoration;
+					}
+				}
+			}
+
+			controller.getMap().then(function (map) {
+				leafletScope.$watch("decorations", function (newDecorations) {
+					for (var name in leafletDecorations) {
+						if (!isDefined(newDecorations[name]) || !angular.equals(newDecorations[name], leafletDecorations)) {
+							map.removeLayer(leafletDecorations[name]);
+							delete leafletDecorations[name];
+						}
+					}
+
+					for (var newName in newDecorations) {
+						var decorationData = newDecorations[newName],
+						    newDecoration = createDecoration(decorationData);
+
+						if (isDefined(newDecoration)) {
+							leafletDecorations[newName] = newDecoration;
+							map.addLayer(newDecoration);
+							setDecorationOptions(newDecoration, decorationData);
+						}
+					}
+				}, true);
+			});
+		}
+	};
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').directive('eventBroadcast', ["leafletLogger", "$rootScope", "leafletHelpers", "leafletMapEvents", "leafletIterators", function (leafletLogger, $rootScope, leafletHelpers, leafletMapEvents, leafletIterators) {
+    var $log = leafletLogger;
+    return {
+        restrict: "A",
+        scope: false,
+        replace: false,
+        require: 'leaflet',
+
+        link: function link(scope, element, attrs, controller) {
+            var isObject = leafletHelpers.isObject,
+                isDefined = leafletHelpers.isDefined,
+                leafletScope = controller.getLeafletScope(),
+                eventBroadcast = leafletScope.eventBroadcast,
+                availableMapEvents = leafletMapEvents.getAvailableMapEvents(),
+                addEvents = leafletMapEvents.addEvents;
+
+            controller.getMap().then(function (map) {
+
+                var mapEvents = [],
+                    logic = "broadcast";
+
+                // We have a possible valid object
+                if (!isDefined(eventBroadcast.map)) {
+                    // We do not have events enable/disable do we do nothing (all enabled by default)
+                    mapEvents = availableMapEvents;
+                } else if (!isObject(eventBroadcast.map)) {
+                    // Not a valid object
+                    $log.warn("[AngularJS - Leaflet] event-broadcast.map must be an object check your model.");
+                } else {
+                    // We have a possible valid map object
+                    // Event propadation logic
+                    if (eventBroadcast.map.logic !== "emit" && eventBroadcast.map.logic !== "broadcast") {
+                        // This is an error
+                        $log.warn("[AngularJS - Leaflet] Available event propagation logic are: 'emit' or 'broadcast'.");
+                    } else {
+                        logic = eventBroadcast.map.logic;
+                    }
+
+                    if (!(isObject(eventBroadcast.map.enable) && eventBroadcast.map.enable.length >= 0)) {
+                        $log.warn("[AngularJS - Leaflet] event-broadcast.map.enable must be an object check your model.");
+                    } else {
+                        // Enable events
+                        leafletIterators.each(eventBroadcast.map.enable, function (eventName) {
+                            // Do we have already the event enabled?
+                            if (mapEvents.indexOf(eventName) === -1 && availableMapEvents.indexOf(eventName) !== -1) {
+                                mapEvents.push(eventName);
+                            }
+                        });
+                    }
+                }
+                // as long as the map is removed in the root leaflet directive we
+                // do not need ot clean up the events as leaflet does it itself
+                addEvents(map, attrs.id, mapEvents, "eventName", leafletScope, logic);
+            });
+        }
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').directive('geojson', ["$timeout", "leafletLogger", "leafletData", "leafletHelpers", "leafletWatchHelpers", "leafletDirectiveControlsHelpers", "leafletIterators", "leafletGeoJsonEvents", function ($timeout, leafletLogger, leafletData, leafletHelpers, leafletWatchHelpers, leafletDirectiveControlsHelpers, leafletIterators, leafletGeoJsonEvents) {
+    var _maybeWatch = leafletWatchHelpers.maybeWatch,
+        _defaultWatchOptions = leafletHelpers.watchOptions,
+        _extendDirectiveControls = leafletDirectiveControlsHelpers.extend,
+        hlp = leafletHelpers,
+        $it = leafletIterators,
+        watchTrap = { changeFromDirective: false };
+    // $log = leafletLogger;
+
+    return {
+        restrict: "A",
+        scope: false,
+        replace: false,
+        require: 'leaflet',
+
+        link: function link(scope, element, attrs, controller) {
+            var isDefined = leafletHelpers.isDefined,
+                leafletScope = controller.getLeafletScope(),
+                leafletGeoJSON = {},
+                _hasSetLeafletData = false;
+
+            controller.getMap().then(function (map) {
+                var watchOptions;
+                if (leafletScope.watchOptions && leafletScope.watchOptions.geojson) {
+                    watchOptions = leafletScope.watchOptions.geojson;
+                } else {
+                    watchOptions = _defaultWatchOptions;
+                }
+
+                var _hookUpEvents = function _hookUpEvents(geojson, maybeName) {
+                    var onEachFeature;
+
+                    if (angular.isFunction(geojson.onEachFeature)) {
+                        onEachFeature = geojson.onEachFeature;
+                    } else {
+                        onEachFeature = function onEachFeature(feature, layer) {
+                            if (leafletHelpers.LabelPlugin.isLoaded() && isDefined(feature.properties.description)) {
+                                layer.bindLabel(feature.properties.description);
+                            }
+
+                            leafletGeoJsonEvents.bindEvents(attrs.id, layer, null, feature, leafletScope, maybeName, { resetStyleOnMouseout: geojson.resetStyleOnMouseout,
+                                mapId: attrs.id });
+                        };
+                    }
+                    return onEachFeature;
+                };
+
+                var isNested = hlp.isDefined(attrs.geojsonNested) && hlp.isTruthy(attrs.geojsonNested);
+
+                var _clean = function _clean() {
+                    if (!leafletGeoJSON) return;
+                    var _remove = function _remove(lObject) {
+                        if (isDefined(lObject) && map.hasLayer(lObject)) {
+                            map.removeLayer(lObject);
+                        }
+                    };
+                    if (isNested) {
+                        $it.each(leafletGeoJSON, function (lObject) {
+                            _remove(lObject);
+                        });
+                        return;
+                    }
+                    _remove(leafletGeoJSON);
+                };
+
+                var _addGeojson = function _addGeojson(geojson, maybeName) {
+
+                    if (!(isDefined(geojson) && isDefined(geojson.data))) {
+                        return;
+                    }
+                    var onEachFeature = _hookUpEvents(geojson, maybeName);
+
+                    if (!isDefined(geojson.options)) {
+                        hlp.modelChangeInDirective(watchTrap, "changeFromDirective", function () {
+                            geojson.options = {
+                                style: geojson.style,
+                                filter: geojson.filter,
+                                onEachFeature: onEachFeature,
+                                pointToLayer: geojson.pointToLayer
+                            };
+                        });
+                    }
+
+                    var lObject = L.geoJson(geojson.data, geojson.options);
+
+                    if (maybeName && hlp.isString(maybeName)) {
+                        leafletGeoJSON[maybeName] = lObject;
+                    } else {
+                        leafletGeoJSON = lObject;
+                    }
+
+                    lObject.addTo(map);
+
+                    if (!_hasSetLeafletData) {
+                        //only do this once and play with the same ref forever
+                        _hasSetLeafletData = true;
+                        leafletData.setGeoJSON(leafletGeoJSON, attrs.id);
+                    }
+                };
+
+                var _create = function _create(model) {
+                    _clean();
+                    if (isNested) {
+                        if (!model || !Object.keys(model).length) return;
+                        $it.each(model, function (m, name) {
+                            //name could be layerName and or groupName
+                            //for now it is not tied to a layer
+                            _addGeojson(m, name);
+                        });
+                        return;
+                    }
+                    _addGeojson(model);
+                };
+
+                _extendDirectiveControls(attrs.id, 'geojson', _create, _clean);
+
+                _maybeWatch(leafletScope, 'geojson', watchOptions, function (geojson) {
+                    if (watchTrap.changeFromDirective) return;
+                    _create(geojson);
+                });
+            });
+        }
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').directive('layercontrol', ["$filter", "leafletLogger", "leafletData", "leafletHelpers", function ($filter, leafletLogger, leafletData, leafletHelpers) {
+    var $log = leafletLogger;
+    return {
+        restrict: "E",
+        scope: {
+            icons: '=?',
+            autoHideOpacity: '=?', // Hide other opacity controls when one is activated.
+            showGroups: '=?', // Hide other opacity controls when one is activated.
+            title: '@',
+            baseTitle: '@',
+            overlaysTitle: '@'
+        },
+        replace: true,
+        transclude: false,
+        require: '^leaflet',
+        controller: ["$scope", "$element", "$sce", function controller($scope, $element, $sce) {
+            $log.debug('[Angular Directive - Layers] layers', $scope, $element);
+            var safeApply = leafletHelpers.safeApply,
+                isDefined = leafletHelpers.isDefined;
+            angular.extend($scope, {
+                baselayer: '',
+                oldGroup: '',
+                layerProperties: {},
+                groupProperties: {},
+                rangeIsSupported: leafletHelpers.rangeIsSupported(),
+                changeBaseLayer: function changeBaseLayer(key, e) {
+                    leafletHelpers.safeApply($scope, function (scp) {
+                        scp.baselayer = key;
+                        leafletData.getMap().then(function (map) {
+                            leafletData.getLayers().then(function (leafletLayers) {
+                                if (map.hasLayer(leafletLayers.baselayers[key])) {
+                                    return;
+                                }
+                                for (var i in scp.layers.baselayers) {
+                                    scp.layers.baselayers[i].icon = scp.icons.unradio;
+                                    if (map.hasLayer(leafletLayers.baselayers[i])) {
+                                        map.removeLayer(leafletLayers.baselayers[i]);
+                                    }
+                                }
+                                map.addLayer(leafletLayers.baselayers[key]);
+                                scp.layers.baselayers[key].icon = $scope.icons.radio;
+                            });
+                        });
+                    });
+                    e.preventDefault();
+                },
+                moveLayer: function moveLayer(ly, newIndex, e) {
+                    var delta = Object.keys($scope.layers.baselayers).length;
+                    if (newIndex >= 1 + delta && newIndex <= $scope.overlaysArray.length + delta) {
+                        var oldLy;
+                        for (var key in $scope.layers.overlays) {
+                            if ($scope.layers.overlays[key].index === newIndex) {
+                                oldLy = $scope.layers.overlays[key];
+                                break;
+                            }
+                        }
+                        if (oldLy) {
+                            safeApply($scope, function () {
+                                oldLy.index = ly.index;
+                                ly.index = newIndex;
+                            });
+                        }
+                    }
+                    e.stopPropagation();
+                    e.preventDefault();
+                },
+                initIndex: function initIndex(layer, idx) {
+                    var delta = Object.keys($scope.layers.baselayers).length;
+                    layer.index = isDefined(layer.index) ? layer.index : idx + delta + 1;
+                },
+                initGroup: function initGroup(groupName) {
+                    $scope.groupProperties[groupName] = $scope.groupProperties[groupName] ? $scope.groupProperties[groupName] : {};
+                },
+                toggleOpacity: function toggleOpacity(e, layer) {
+                    if (layer.visible) {
+                        if ($scope.autoHideOpacity && !$scope.layerProperties[layer.name].opacityControl) {
+                            for (var k in $scope.layerProperties) {
+                                $scope.layerProperties[k].opacityControl = false;
+                            }
+                        }
+                        $scope.layerProperties[layer.name].opacityControl = !$scope.layerProperties[layer.name].opacityControl;
+                    }
+                    e.stopPropagation();
+                    e.preventDefault();
+                },
+                toggleLegend: function toggleLegend(layer) {
+                    $scope.layerProperties[layer.name].showLegend = !$scope.layerProperties[layer.name].showLegend;
+                },
+                showLegend: function showLegend(layer) {
+                    return layer.legend && $scope.layerProperties[layer.name].showLegend;
+                },
+                unsafeHTML: function unsafeHTML(html) {
+                    return $sce.trustAsHtml(html);
+                },
+                getOpacityIcon: function getOpacityIcon(layer) {
+                    return layer.visible && $scope.layerProperties[layer.name].opacityControl ? $scope.icons.close : $scope.icons.open;
+                },
+                getGroupIcon: function getGroupIcon(group) {
+                    return group.visible ? $scope.icons.check : $scope.icons.uncheck;
+                },
+                changeGroupVisibility: function changeGroupVisibility(groupName) {
+                    if (!isDefined($scope.groupProperties[groupName])) {
+                        return;
+                    }
+                    var visible = $scope.groupProperties[groupName].visible;
+                    for (var k in $scope.layers.overlays) {
+                        var layer = $scope.layers.overlays[k];
+                        if (layer.group === groupName) {
+                            layer.visible = visible;
+                        }
+                    }
+                }
+            });
+
+            var div = $element.get(0);
+            if (!L.Browser.touch) {
+                L.DomEvent.disableClickPropagation(div);
+                L.DomEvent.on(div, 'mousewheel', L.DomEvent.stopPropagation);
+            } else {
+                L.DomEvent.on(div, 'click', L.DomEvent.stopPropagation);
+            }
+        }],
+        template: '<div class="angular-leaflet-control-layers" ng-show="overlaysArray.length">' + '<h4 ng-if="title">{{ title }}</h4>' + '<div class="lf-baselayers">' + '<h5 class="lf-title" ng-if="baseTitle">{{ baseTitle }}</h5>' + '<div class="lf-row" ng-repeat="(key, layer) in baselayersArray">' + '<label class="lf-icon-bl" ng-click="changeBaseLayer(key, $event)">' + '<input class="leaflet-control-layers-selector" type="radio" name="lf-radio" ' + 'ng-show="false" ng-checked="baselayer === key" ng-value="key" /> ' + '<i class="lf-icon lf-icon-radio" ng-class="layer.icon"></i>' + '<div class="lf-text">{{layer.name}}</div>' + '</label>' + '</div>' + '</div>' + '<div class="lf-overlays">' + '<h5 class="lf-title" ng-if="overlaysTitle">{{ overlaysTitle }}</h5>' + '<div class="lf-container">' + '<div class="lf-row" ng-repeat="layer in (o = (overlaysArray | orderBy:\'index\':order))" ng-init="initIndex(layer, $index)">' + '<label class="lf-icon-ol-group" ng-if="showGroups &amp;&amp; layer.group &amp;&amp; layer.group != o[$index-1].group">' + '<input class="lf-control-layers-selector" type="checkbox" ng-show="false" ' + 'ng-change="changeGroupVisibility(layer.group)" ng-model="groupProperties[layer.group].visible"/> ' + '<i class="lf-icon lf-icon-check" ng-class="getGroupIcon(groupProperties[layer.group])"></i>' + '<div class="lf-text">{{ layer.group }}</div>' + '</label>' + '<label class="lf-icon-ol">' + '<input class="lf-control-layers-selector" type="checkbox" ng-show="false" ng-model="layer.visible"/> ' + '<i class="lf-icon lf-icon-check" ng-class="layer.icon"></i>' + '<div class="lf-text">{{layer.name}}</div>' + '</label>' + '<div class="lf-icons">' + '<i class="lf-icon lf-up" ng-class="icons.up" ng-click="moveLayer(layer, layer.index - orderNumber, $event)"></i> ' + '<i class="lf-icon lf-down" ng-class="icons.down" ng-click="moveLayer(layer, layer.index + orderNumber, $event)"></i> ' + '<i class="lf-icon lf-toggle-legend" ng-class="icons.toggleLegend" ng-if="layer.legend" ng-click="toggleLegend(layer)"></i> ' + '<i class="lf-icon lf-open" ng-class="getOpacityIcon(layer)" ng-click="toggleOpacity($event, layer)"></i>' + '</div>' + '<div class="lf-legend" ng-if="showLegend(layer)" ng-bind-html="unsafeHTML(layer.legend)"></div>' + '<div class="lf-opacity clearfix" ng-if="layer.visible &amp;&amp; layerProperties[layer.name].opacityControl">' + '<label ng-if="rangeIsSupported" class="pull-left" style="width: 50%">0</label>' + '<label ng-if="rangeIsSupported" class="pull-left text-right" style="width: 50%">100</label>' + '<input ng-if="rangeIsSupported" class="clearfix" type="range" min="0" max="1" step="0.05" ' + 'class="lf-opacity-control" ng-model="layerProperties[layer.name].layerOptions.opacity"/>' + '<h6 ng-if="!rangeIsSupported">Range is not supported in this browser</h6>' + '</div>' + '</div>' + '</div>' + '</div>' + '</div>',
+        link: function link(scope, element, attrs, controller) {
+            var isDefined = leafletHelpers.isDefined,
+                leafletScope = controller.getLeafletScope(),
+                layers = leafletScope.layers;
+
+            scope.$watch('icons', function () {
+                var defaultIcons = {
+                    uncheck: 'fa fa-square-o',
+                    check: 'fa fa-check-square-o',
+                    radio: 'fa fa-dot-circle-o',
+                    unradio: 'fa fa-circle-o',
+                    up: 'fa fa-angle-up',
+                    down: 'fa fa-angle-down',
+                    open: 'fa fa-angle-double-down',
+                    close: 'fa fa-angle-double-up',
+                    toggleLegend: 'fa fa-pencil-square-o'
+                };
+                if (isDefined(scope.icons)) {
+                    angular.extend(defaultIcons, scope.icons);
+                    angular.extend(scope.icons, defaultIcons);
+                } else {
+                    scope.icons = defaultIcons;
+                }
+            });
+
+            // Setting layer stack order.
+            attrs.order = isDefined(attrs.order) && (attrs.order === 'normal' || attrs.order === 'reverse') ? attrs.order : 'normal';
+            scope.order = attrs.order === 'normal';
+            scope.orderNumber = attrs.order === 'normal' ? -1 : 1;
+
+            scope.layers = layers;
+            controller.getMap().then(function (map) {
+                leafletScope.$watch('layers.baselayers', function (newBaseLayers) {
+                    var baselayersArray = {};
+                    leafletData.getLayers().then(function (leafletLayers) {
+                        var key;
+                        for (key in newBaseLayers) {
+                            var layer = newBaseLayers[key];
+                            layer.icon = scope.icons[map.hasLayer(leafletLayers.baselayers[key]) ? 'radio' : 'unradio'];
+                            baselayersArray[key] = layer;
+                        }
+                        scope.baselayersArray = baselayersArray;
+                    });
+                });
+
+                leafletScope.$watch('layers.overlays', function (newOverlayLayers) {
+                    var overlaysArray = [];
+                    var groupVisibleCount = {};
+                    leafletData.getLayers().then(function () {
+                        var key;
+                        for (key in newOverlayLayers) {
+                            var layer = newOverlayLayers[key];
+                            layer.icon = scope.icons[layer.visible ? 'check' : 'uncheck'];
+                            overlaysArray.push(layer);
+
+                            if (!isDefined(scope.layerProperties[layer.name])) {
+                                if (isDefined(layer.layerOptions.opacity)) {
+                                    layer.layerOptions.opacity = 1;
+                                }
+                                scope.layerProperties[layer.name] = {
+                                    opacityControl: false,
+                                    showLegend: true,
+                                    layerOptions: layer.layerOptions
+                                };
+                            }
+                            if (isDefined(layer.group)) {
+                                if (!isDefined(scope.groupProperties[layer.group])) {
+                                    scope.groupProperties[layer.group] = {
+                                        visible: false
+                                    };
+                                }
+                                groupVisibleCount[layer.group] = isDefined(groupVisibleCount[layer.group]) ? groupVisibleCount[layer.group] : {
+                                    count: 0,
+                                    visibles: 0
+                                };
+                                groupVisibleCount[layer.group].count++;
+                                if (layer.visible) {
+                                    groupVisibleCount[layer.group].visibles++;
+                                }
+                            }
+                            /*
+                            if(isDefined(layer.index) && leafletLayers.overlays[key].setZIndex) {
+                                leafletLayers.overlays[key].setZIndex(newOverlayLayers[key].index);
+                            }
+                            */
+                        }
+
+                        for (key in groupVisibleCount) {
+                            scope.groupProperties[key].visible = groupVisibleCount[key].visibles === groupVisibleCount[key].count;
+                        }
+                        scope.overlaysArray = overlaysArray;
+                    });
+                }, true);
+            });
+        }
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').directive('layers', ["leafletLogger", "$q", "leafletData", "leafletHelpers", "leafletLayerHelpers", "leafletControlHelpers", function (leafletLogger, $q, leafletData, leafletHelpers, leafletLayerHelpers, leafletControlHelpers) {
+    // var $log = leafletLogger;
+    return {
+        restrict: "A",
+        scope: false,
+        replace: false,
+        require: 'leaflet',
+        controller: ["$scope", function controller($scope) {
+            $scope._leafletLayers = $q.defer();
+            this.getLayers = function () {
+                return $scope._leafletLayers.promise;
+            };
+        }],
+        link: function link(scope, element, attrs, controller) {
+            var isDefined = leafletHelpers.isDefined,
+                leafletLayers = {},
+                leafletScope = controller.getLeafletScope(),
+                layers = leafletScope.layers,
+                createLayer = leafletLayerHelpers.createLayer,
+                safeAddLayer = leafletLayerHelpers.safeAddLayer,
+                safeRemoveLayer = leafletLayerHelpers.safeRemoveLayer,
+                changeOpacityListener = leafletLayerHelpers.changeOpacityListener,
+                updateLayersControl = leafletControlHelpers.updateLayersControl,
+                isLayersControlVisible = false;
+
+            scope.$on('$destroy', function () {
+                leafletControlHelpers.destroyMapLayersControl(scope.mapId);
+            });
+
+            controller.getMap().then(function (map) {
+
+                // We have baselayers to add to the map
+                scope._leafletLayers.resolve(leafletLayers);
+                leafletData.setLayers(leafletLayers, attrs.id);
+
+                leafletLayers.baselayers = {};
+                leafletLayers.overlays = {};
+
+                var mapId = attrs.id;
+
+                // Setup all baselayers definitions
+                var oneVisibleLayer = false;
+                for (var layerName in layers.baselayers) {
+                    var newBaseLayer = createLayer(layers.baselayers[layerName]);
+                    if (!isDefined(newBaseLayer)) {
+                        delete layers.baselayers[layerName];
+                        continue;
+                    }
+                    leafletLayers.baselayers[layerName] = newBaseLayer;
+                    // Only add the visible layer to the map, layer control manages the addition to the map
+                    // of layers in its control
+                    if (layers.baselayers[layerName].top === true) {
+                        safeAddLayer(map, leafletLayers.baselayers[layerName]);
+                        oneVisibleLayer = true;
+                    }
+                }
+
+                // If there is no visible layer add first to the map
+                if (!oneVisibleLayer && Object.keys(leafletLayers.baselayers).length > 0) {
+                    safeAddLayer(map, leafletLayers.baselayers[Object.keys(layers.baselayers)[0]]);
+                }
+
+                // Setup the Overlays
+                for (layerName in layers.overlays) {
+                    if (layers.overlays[layerName].type === 'cartodb') {}
+                    var newOverlayLayer = createLayer(layers.overlays[layerName]);
+                    if (!isDefined(newOverlayLayer)) {
+                        delete layers.overlays[layerName];
+                        continue;
+                    }
+                    leafletLayers.overlays[layerName] = newOverlayLayer;
+                    // Only add the visible overlays to the map
+                    if (layers.overlays[layerName].visible === true) {
+                        safeAddLayer(map, leafletLayers.overlays[layerName]);
+                    }
+                }
+
+                // Watch for the base layers
+                leafletScope.$watch('layers.baselayers', function (newBaseLayers, oldBaseLayers) {
+                    if (angular.equals(newBaseLayers, oldBaseLayers)) {
+                        isLayersControlVisible = updateLayersControl(map, mapId, isLayersControlVisible, newBaseLayers, layers.overlays, leafletLayers);
+                        return true;
+                    }
+                    // Delete layers from the array
+                    for (var name in leafletLayers.baselayers) {
+                        if (!isDefined(newBaseLayers[name]) || newBaseLayers[name].doRefresh) {
+                            // Remove from the map if it's on it
+                            if (map.hasLayer(leafletLayers.baselayers[name])) {
+                                map.removeLayer(leafletLayers.baselayers[name]);
+                            }
+                            delete leafletLayers.baselayers[name];
+
+                            if (newBaseLayers[name] && newBaseLayers[name].doRefresh) {
+                                newBaseLayers[name].doRefresh = false;
+                            }
+                        }
+                    }
+                    // add new layers
+                    for (var newName in newBaseLayers) {
+                        if (!isDefined(leafletLayers.baselayers[newName])) {
+                            var testBaseLayer = createLayer(newBaseLayers[newName]);
+                            if (isDefined(testBaseLayer)) {
+                                leafletLayers.baselayers[newName] = testBaseLayer;
+                                // Only add the visible layer to the map
+                                if (newBaseLayers[newName].top === true) {
+                                    safeAddLayer(map, leafletLayers.baselayers[newName]);
+                                }
+                            }
+                        } else {
+                            if (newBaseLayers[newName].top === true && !map.hasLayer(leafletLayers.baselayers[newName])) {
+                                safeAddLayer(map, leafletLayers.baselayers[newName]);
+                            } else if (newBaseLayers[newName].top === false && map.hasLayer(leafletLayers.baselayers[newName])) {
+                                map.removeLayer(leafletLayers.baselayers[newName]);
+                            }
+                        }
+                    }
+
+                    //we have layers, so we need to make, at least, one active
+                    var found = false;
+                    // search for an active layer
+                    for (var key in leafletLayers.baselayers) {
+                        if (map.hasLayer(leafletLayers.baselayers[key])) {
+                            found = true;
+                            break;
+                        }
+                    }
+                    // If there is no active layer make one active
+                    if (!found && Object.keys(leafletLayers.baselayers).length > 0) {
+                        safeAddLayer(map, leafletLayers.baselayers[Object.keys(leafletLayers.baselayers)[0]]);
+                    }
+
+                    // Only show the layers switch selector control if we have more than one baselayer + overlay
+                    isLayersControlVisible = updateLayersControl(map, mapId, isLayersControlVisible, newBaseLayers, layers.overlays, leafletLayers);
+                }, true);
+
+                // Watch for the overlay layers
+                leafletScope.$watch('layers.overlays', function (newOverlayLayers, oldOverlayLayers) {
+                    if (angular.equals(newOverlayLayers, oldOverlayLayers)) {
+                        isLayersControlVisible = updateLayersControl(map, mapId, isLayersControlVisible, layers.baselayers, newOverlayLayers, leafletLayers);
+                        return true;
+                    }
+
+                    // Delete layers from the array
+                    for (var name in leafletLayers.overlays) {
+                        if (!isDefined(newOverlayLayers[name]) || newOverlayLayers[name].doRefresh) {
+                            // Remove from the map if it's on it
+                            if (map.hasLayer(leafletLayers.overlays[name])) {
+                                // Safe remove when ArcGIS layers is loading.
+                                var options = isDefined(newOverlayLayers[name]) ? newOverlayLayers[name].layerOptions : null;
+                                safeRemoveLayer(map, leafletLayers.overlays[name], options);
+                            }
+                            // TODO: Depending on the layer type we will have to delete what's included on it
+                            delete leafletLayers.overlays[name];
+
+                            if (newOverlayLayers[name] && newOverlayLayers[name].doRefresh) {
+                                newOverlayLayers[name].doRefresh = false;
+                            }
+                        }
+                    }
+
+                    // add new overlays
+                    for (var newName in newOverlayLayers) {
+                        if (!isDefined(leafletLayers.overlays[newName])) {
+                            var testOverlayLayer = createLayer(newOverlayLayers[newName]);
+                            if (!isDefined(testOverlayLayer)) {
+                                // If the layer creation fails, continue to the next overlay
+                                continue;
+                            }
+                            leafletLayers.overlays[newName] = testOverlayLayer;
+                            if (newOverlayLayers[newName].visible === true) {
+                                safeAddLayer(map, leafletLayers.overlays[newName]);
+                            }
+
+                            if (isDefined(newOverlayLayers[newName].index) && leafletLayers.overlays[newName].setZIndex) {
+                                leafletLayers.overlays[newName].setZIndex(newOverlayLayers[newName].index);
+                            }
+                        } else {
+                            // check for the .visible property to hide/show overLayers
+                            if (newOverlayLayers[newName].visible && !map.hasLayer(leafletLayers.overlays[newName])) {
+                                safeAddLayer(map, leafletLayers.overlays[newName]);
+                            } else if (newOverlayLayers[newName].visible === false && map.hasLayer(leafletLayers.overlays[newName])) {
+                                // Safe remove when ArcGIS layers is loading.
+                                safeRemoveLayer(map, leafletLayers.overlays[newName], newOverlayLayers[newName].layerOptions);
+                            }
+
+                            // check for the .layerOptions.opacity property has changed.
+                            var ly = leafletLayers.overlays[newName];
+                            if (map.hasLayer(leafletLayers.overlays[newName])) {
+                                if (newOverlayLayers[newName].layerOptions.opacity !== oldOverlayLayers[newName].layerOptions.opacity) {
+
+                                    if (isDefined(ly.setOpacity)) {
+                                        ly.setOpacity(newOverlayLayers[newName].layerOptions.opacity);
+                                    }
+                                    if (isDefined(ly.getLayers) && isDefined(ly.eachLayer)) {
+                                        ly.eachLayer(changeOpacityListener(newOverlayLayers[newName].layerOptions.opacity));
+                                    }
+                                }
+
+                                if (isDefined(newOverlayLayers[newName].index) && ly.setZIndex && newOverlayLayers[newName].index !== oldOverlayLayers[newName].index) {
+                                    ly.setZIndex(newOverlayLayers[newName].index);
+                                }
+                            }
+                        }
+
+                        //refresh heatmap data if present
+                        if (newOverlayLayers[newName].visible && map._loaded && newOverlayLayers[newName].data && newOverlayLayers[newName].type === "heatmap") {
+                            leafletLayers.overlays[newName].setData(newOverlayLayers[newName].data);
+                            leafletLayers.overlays[newName].update();
+                        }
+                    }
+
+                    // Only add the layers switch selector control if we have more than one baselayer + overlay
+                    isLayersControlVisible = updateLayersControl(map, mapId, isLayersControlVisible, layers.baselayers, newOverlayLayers, leafletLayers);
+                }, true);
+            });
+        }
+    };
+}]);
+
+'use strict';
+
+angular.module("ui-leaflet").directive('legend', ["leafletLogger", "$http", "$timeout", "leafletHelpers", "leafletLegendHelpers", function (leafletLogger, $http, $timeout, leafletHelpers, leafletLegendHelpers) {
+    var $log = leafletLogger,
+        errorHeader = leafletHelpers.errorHeader + ' [Legend] ';
+    return {
+        restrict: "A",
+        scope: false,
+        replace: false,
+        require: 'leaflet',
+        transclude: false,
+
+        link: function link(scope, element, attrs, controller) {
+
+            var isArray = leafletHelpers.isArray,
+                isString = leafletHelpers.isString,
+                isDefined = leafletHelpers.isDefined,
+                isFunction = leafletHelpers.isFunction,
+                leafletScope = controller.getLeafletScope(),
+                legend = leafletScope.legend;
+
+            var legendClass;
+            var position;
+            var leafletLegend;
+            var type;
+
+            leafletScope.$watch('legend', function (newLegend) {
+
+                if (isDefined(newLegend)) {
+                    legendClass = newLegend.legendClass ? newLegend.legendClass : "legend";
+                    position = newLegend.position || 'bottomright';
+                    // default to arcgis
+                    type = newLegend.type || 'arcgis';
+                }
+            }, true);
+
+            var createLegend = function createLegend(map, legendData, newURL) {
+                if (legendData && legendData.layers && legendData.layers.length > 0) {
+                    if (isDefined(leafletLegend)) {
+                        leafletLegendHelpers.updateLegend(leafletLegend.getContainer(), legendData, type, newURL);
+                    } else {
+                        leafletLegend = L.control({
+                            position: position
+                        });
+                        leafletLegend.onAdd = leafletLegendHelpers.getOnAddLegend(legendData, legendClass, type, newURL);
+                        leafletLegend.addTo(map);
+                    }
+
+                    if (isDefined(legend.loadedData) && isFunction(legend.loadedData)) {
+                        legend.loadedData();
+                    }
+                }
+            };
+
+            controller.getMap().then(function (map) {
+                leafletScope.$watch('legend', function (newLegend) {
+                    if (!isDefined(newLegend)) {
+                        if (isDefined(leafletLegend)) {
+                            leafletLegend.removeFrom(map);
+                            leafletLegend = null;
+                        }
+
+                        return;
+                    }
+
+                    if (!isDefined(newLegend.url) && type === 'arcgis' && (!isArray(newLegend.colors) || !isArray(newLegend.labels) || newLegend.colors.length !== newLegend.labels.length)) {
+                        $log.warn(errorHeader + " legend.colors and legend.labels must be set.");
+                        return;
+                    }
+
+                    if (isDefined(newLegend.url)) {
+                        $log.info(errorHeader + " loading legend service.");
+                        return;
+                    }
+
+                    if (isDefined(leafletLegend)) {
+                        leafletLegend.removeFrom(map);
+                        leafletLegend = null;
+                    }
+
+                    leafletLegend = L.control({
+                        position: position
+                    });
+
+                    if (type === 'arcgis') {
+                        leafletLegend.onAdd = leafletLegendHelpers.getOnAddArrayLegend(newLegend, legendClass);
+                    }
+                    leafletLegend.addTo(map);
+                });
+
+                leafletScope.$watch('legend.url', function (newURL) {
+                    if (!isDefined(newURL)) {
+                        return;
+                    }
+
+                    if (!isArray(newURL) && !isString(newURL)) {
+                        $log.warn(errorHeader + " legend.url must be an array or string.");
+                        return;
+                    }
+
+                    var urls = isString(newURL) ? [newURL] : newURL;
+
+                    var legendData;
+                    var onResult = function onResult(idx, url) {
+                        return function (ld) {
+                            if (isDefined(ld.data.error)) {
+                                $log.warn(errorHeader + 'Error loadin legend from: ' + url, ld.data.error.message);
+                            } else {
+                                if (legendData && legendData.layers && legendData.layers.length > 0) {
+                                    legendData.layers = legendData.layers.concat(ld.data.layers);
+                                } else {
+                                    legendData = ld.data;
+                                }
+                            }
+
+                            if (idx === urls.length - 1) {
+                                createLegend(map, legendData, newURL);
+                            }
+                        };
+                    };
+                    var onError = function onError(err) {
+                        $log.warn(errorHeader + ' legend.url not loaded.', err);
+                    };
+
+                    for (var i = 0; i < urls.length; i++) {
+                        leafletLegendHelpers.addLegendURL(attrs.id, {
+                            url: urls[i],
+                            method: 'GET'
+                        }).then(onResult(i)).catch(onError);
+                    }
+                });
+
+                leafletScope.$watch('legend.legendData', function (legendData) {
+                    $log.debug('legendData', legendData);
+                    if (isDefined(leafletScope.legend.url) || !isDefined(legendData)) {
+                        return;
+                    }
+
+                    createLegend(map, legendData);
+                }, true);
+            });
+        }
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').directive('markers', ["leafletLogger", "$rootScope", "$q", "leafletData", "leafletHelpers", "leafletMapDefaults", "leafletMarkersHelpers", "leafletMarkerEvents", "leafletIterators", "leafletWatchHelpers", "leafletDirectiveControlsHelpers", function (leafletLogger, $rootScope, $q, leafletData, leafletHelpers, leafletMapDefaults, leafletMarkersHelpers, leafletMarkerEvents, leafletIterators, leafletWatchHelpers, leafletDirectiveControlsHelpers) {
+    //less terse vars to helpers
+    var isDefined = leafletHelpers.isDefined,
+        errorHeader = leafletHelpers.errorHeader,
+        Helpers = leafletHelpers,
+        isString = leafletHelpers.isString,
+        addMarkerWatcher = leafletMarkersHelpers.addMarkerWatcher,
+        updateMarker = leafletMarkersHelpers.updateMarker,
+        listenMarkerEvents = leafletMarkersHelpers.listenMarkerEvents,
+        addMarkerToGroup = leafletMarkersHelpers.addMarkerToGroup,
+        createMarker = leafletMarkersHelpers.createMarker,
+        deleteMarker = leafletMarkersHelpers.deleteMarker,
+        getModelFromModels = leafletMarkersHelpers.getModelFromModels,
+        getLayerModels = leafletMarkersHelpers.getLayerModels,
+        resetUnusedMarkerGroups = leafletMarkersHelpers.resetUnusedMarkerGroups,
+        $it = leafletIterators,
+        _defaultWatchOptions = leafletHelpers.watchOptions,
+        maybeWatch = leafletWatchHelpers.maybeWatch,
+        extendDirectiveControls = leafletDirectiveControlsHelpers.extend,
+        $log = leafletLogger,
+        watchTrap = { changeFromDirective: false };
+
+    var _getLMarker = function _getLMarker(leafletMarkers, name, maybeLayerName) {
+        if (!Object.keys(leafletMarkers).length) return;
+        if (maybeLayerName && isString(maybeLayerName)) {
+            if (!leafletMarkers[maybeLayerName] || !Object.keys(leafletMarkers[maybeLayerName]).length) return;
+            return leafletMarkers[maybeLayerName][name];
+        }
+        return leafletMarkers[name];
+    };
+
+    var _setLMarker = function _setLMarker(lObject, leafletMarkers, name, maybeLayerName) {
+        if (maybeLayerName && isString(maybeLayerName)) {
+            if (!isDefined(leafletMarkers[maybeLayerName])) leafletMarkers[maybeLayerName] = {};
+            leafletMarkers[maybeLayerName][name] = lObject;
+        } else leafletMarkers[name] = lObject;
+        return lObject;
+    };
+
+    var _maybeAddMarkerToLayer = function _maybeAddMarkerToLayer(layerName, layers, model, marker, watchType, map) {
+
+        if (!isString(layerName)) {
+            $log.error(errorHeader + ' A layername must be a string');
+            return false;
+        }
+
+        if (!isDefined(layers)) {
+            $log.error(errorHeader + ' You must add layers to the directive if the markers are going to use this functionality.');
+            return false;
+        }
+
+        if (!isDefined(layers.overlays) || !isDefined(layers.overlays[layerName])) {
+            $log.error(errorHeader + ' A marker can only be added to a layer of type "group"');
+            return false;
+        }
+        var layerGroup = layers.overlays[layerName];
+        if (!(layerGroup instanceof L.LayerGroup || layerGroup instanceof L.FeatureGroup)) {
+            $log.error(errorHeader + ' Adding a marker to an overlay needs a overlay of the type "group" or "featureGroup"');
+            return false;
+        }
+
+        // The marker goes to a correct layer group, so first of all we add it
+        layerGroup.addLayer(marker);
+
+        // The marker is automatically added to the map depending on the visibility
+        // of the layer, so we only have to open the popup if the marker is in the map
+        if (watchType === null && map.hasLayer(marker) && model.focus === true) {
+            marker.openPopup();
+        }
+        return true;
+    };
+    //TODO: move to leafletMarkersHelpers??? or make a new class/function file (leafletMarkersHelpers is large already)
+    var _addMarkers = function _addMarkers(mapId, markersToRender, oldModels, map, layers, leafletMarkers, leafletScope, watchOptions, maybeLayerName, skips) {
+        $it.each(markersToRender, function (model, newName) {
+            if (skips[newName]) return;
+
+            if (newName.search("-") !== -1) {
+                $log.error('The marker can\'t use a "-" on his key name: "' + newName + '".');
+                return;
+            }
+
+            var pathToMarker = Helpers.getObjectDotPath(maybeLayerName ? [maybeLayerName, newName] : [newName]);
+            var maybeLMarker = _getLMarker(leafletMarkers, newName, maybeLayerName);
+            Helpers.modelChangeInDirective(watchTrap, "changeFromDirective", function () {
+                if (!isDefined(maybeLMarker)) {
+
+                    var marker = createMarker(model);
+                    var layerName = (model ? model.layer : undefined) || maybeLayerName; //original way takes pref
+                    if (!isDefined(marker)) {
+                        $log.error(errorHeader + ' Received invalid data on the marker ' + newName + '.');
+                        return;
+                    }
+                    _setLMarker(marker, leafletMarkers, newName, maybeLayerName);
+
+                    // Bind message
+                    if (isDefined(model.message)) {
+                        marker.bindPopup(model.message, model.popupOptions);
+                    }
+
+                    // Add the marker to a cluster group if needed
+                    if (isDefined(model.group)) {
+                        var groupOptions = isDefined(model.groupOption) ? model.groupOption : null;
+                        addMarkerToGroup(marker, model.group, groupOptions, map);
+                    }
+
+                    // Show label if defined
+                    if (Helpers.LabelPlugin.isLoaded() && isDefined(model.label) && isDefined(model.label.message)) {
+                        marker.bindLabel(model.label.message, model.label.options);
+                    }
+
+                    // Check if the marker should be added to a layer
+                    if (isDefined(model) && (isDefined(model.layer) || isDefined(maybeLayerName))) {
+
+                        var pass = _maybeAddMarkerToLayer(layerName, layers, model, marker, watchOptions.individual.type, map);
+                        if (!pass) return; //something went wrong move on in the loop
+                    } else if (!isDefined(model.group)) {
+                        // We do not have a layer attr, so the marker goes to the map layer
+                        map.addLayer(marker);
+                        if (watchOptions.individual.type === null && model.focus === true) {
+                            marker.openPopup();
+                        }
+                    }
+
+                    if (watchOptions.individual.type !== null) {
+                        addMarkerWatcher(marker, pathToMarker, leafletScope, layers, map, watchOptions.individual);
+                    }
+
+                    listenMarkerEvents(marker, model, leafletScope, watchOptions.individual.type, map);
+                    leafletMarkerEvents.bindEvents(mapId, marker, pathToMarker, model, leafletScope, layerName);
+                } else {
+                    var oldModel = getModelFromModels(oldModels, newName, maybeLayerName);
+                    updateMarker(model, oldModel, maybeLMarker, pathToMarker, leafletScope, layers, map);
+                }
+            });
+        });
+    };
+    var _seeWhatWeAlreadyHave = function _seeWhatWeAlreadyHave(markerModels, oldMarkerModels, lMarkers, isEqual, cb) {
+        var hasLogged = false,
+            equals = false,
+            oldMarker,
+            newMarker;
+
+        var doCheckOldModel = isDefined(oldMarkerModels);
+        for (var name in lMarkers) {
+            if (!hasLogged) {
+                $log.debug(errorHeader + "[markers] destroy: ");
+                hasLogged = true;
+            }
+
+            if (doCheckOldModel) {
+                //might want to make the option (in watch options) to disable deep checking
+                //ie the options to only check !== (reference check) instead of angular.equals (slow)
+                newMarker = markerModels[name];
+                oldMarker = oldMarkerModels[name];
+                equals = isEqual && angular.equals(newMarker, oldMarker);
+            }
+            if (!isDefined(markerModels) || !Object.keys(markerModels).length || !isDefined(markerModels[name]) || !Object.keys(markerModels[name]).length || equals) {
+                if (cb && Helpers.isFunction(cb)) cb(newMarker, oldMarker, name);
+            }
+        }
+    };
+    var _destroy = function _destroy(markerModels, oldMarkerModels, lMarkers, map, layers) {
+        _seeWhatWeAlreadyHave(markerModels, oldMarkerModels, lMarkers, false, function (newMarker, oldMarker, lMarkerName) {
+            $log.debug(errorHeader + '[marker] is deleting marker: ' + lMarkerName);
+            deleteMarker(lMarkers[lMarkerName], map, layers);
+            delete lMarkers[lMarkerName];
+        });
+    };
+
+    var _getNewModelsToSkipp = function _getNewModelsToSkipp(newModels, oldModels, lMarkers) {
+        var skips = {};
+        _seeWhatWeAlreadyHave(newModels, oldModels, lMarkers, true, function (newMarker, oldMarker, lMarkerName) {
+            $log.debug(errorHeader + '[marker] is already rendered, marker: ' + lMarkerName);
+            skips[lMarkerName] = newMarker;
+        });
+        return skips;
+    };
+
+    return {
+        restrict: "A",
+        scope: false,
+        replace: false,
+        require: ['leaflet', '?layers'],
+
+        link: function link(scope, element, attrs, controller) {
+            var mapController = controller[0],
+                leafletScope = mapController.getLeafletScope();
+
+            mapController.getMap().then(function (map) {
+                var leafletMarkers = {},
+                    getLayers;
+
+                // If the layers attribute is used, we must wait until the layers are created
+                if (isDefined(controller[1])) {
+                    getLayers = controller[1].getLayers;
+                } else {
+                    getLayers = function getLayers() {
+                        var deferred = $q.defer();
+                        deferred.resolve();
+                        return deferred.promise;
+                    };
+                }
+
+                var watchOptions;
+                if (leafletScope.watchOptions && leafletScope.watchOptions.markers) {
+                    watchOptions = leafletScope.watchOptions.markers;
+                } else {
+                    watchOptions = _defaultWatchOptions;
+                }
+
+                var isNested = isDefined(attrs.markersNested) && Helpers.isTruthy(attrs.markersNested);
+
+                getLayers().then(function (layers) {
+                    var _clean = function _clean(models, oldModels) {
+                        resetUnusedMarkerGroups();
+                        if (isNested) {
+                            $it.each(models, function (markerToMaybeDel, layerName) {
+                                var oldLayerModels = getLayerModels(oldModels, layerName);
+                                _destroy(markerToMaybeDel, oldLayerModels, leafletMarkers[layerName], map, layers);
+                            });
+                            return;
+                        }
+                        _destroy(models, oldModels, leafletMarkers, map, layers);
+                    };
+
+                    var _create = function _create(models, oldModels) {
+                        _clean(models, oldModels);
+                        var skips = null;
+                        if (isNested) {
+                            $it.each(models, function (markersToAdd, layerName) {
+                                var oldLayerModels = getLayerModels(oldModels, layerName);
+                                var newlayerModels = getLayerModels(models, layerName);
+                                skips = _getNewModelsToSkipp(newlayerModels, oldLayerModels, leafletMarkers[layerName]);
+                                _addMarkers(attrs.id, markersToAdd, oldModels, map, layers, leafletMarkers, leafletScope, watchOptions, layerName, skips);
+                            });
+                            return;
+                        }
+                        skips = _getNewModelsToSkipp(models, oldModels, leafletMarkers);
+                        _addMarkers(attrs.id, models, oldModels, map, layers, leafletMarkers, leafletScope, watchOptions, undefined, skips);
+                    };
+                    extendDirectiveControls(attrs.id, 'markers', _create, _clean);
+                    leafletData.setMarkers(leafletMarkers, attrs.id);
+
+                    maybeWatch(leafletScope, 'markers', watchOptions, function (newMarkers, oldMarkers) {
+                        if (watchTrap.changeFromDirective) return;
+                        _create(newMarkers, oldMarkers);
+                    });
+                    scope.$on('$destroy', function () {
+                        _destroy(leafletScope.markers, {}, leafletMarkers, map, layers);
+                    });
+                });
+            });
+        }
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').directive('maxbounds', ["leafletLogger", "leafletMapDefaults", "leafletBoundsHelpers", "leafletHelpers", function (leafletLogger, leafletMapDefaults, leafletBoundsHelpers, leafletHelpers) {
+    // var $log = leafletLogger;
+    return {
+        restrict: "A",
+        scope: false,
+        replace: false,
+        require: 'leaflet',
+
+        link: function link(scope, element, attrs, controller) {
+            var leafletScope = controller.getLeafletScope(),
+                isValidBounds = leafletBoundsHelpers.isValidBounds,
+                isNumber = leafletHelpers.isNumber;
+
+            controller.getMap().then(function (map) {
+                leafletScope.$watch("maxbounds", function (maxbounds) {
+                    if (!isValidBounds(maxbounds)) {
+                        // Unset any previous maxbounds
+                        map.setMaxBounds();
+                        return;
+                    }
+
+                    var leafletBounds = leafletBoundsHelpers.createLeafletBounds(maxbounds);
+                    if (isNumber(maxbounds.pad)) {
+                        leafletBounds = leafletBounds.pad(maxbounds.pad);
+                    }
+
+                    map.setMaxBounds(leafletBounds);
+                    if (!attrs.center && !attrs.lfCenter) {
+                        map.fitBounds(leafletBounds);
+                    }
+                });
+            });
+        }
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').directive('paths', ["leafletLogger", "$q", "leafletData", "leafletMapDefaults", "leafletHelpers", "leafletPathsHelpers", "leafletPathEvents", "leafletWatchHelpers", function (leafletLogger, $q, leafletData, leafletMapDefaults, leafletHelpers, leafletPathsHelpers, leafletPathEvents, leafletWatchHelpers) {
+    var $log = leafletLogger;
+    return {
+        restrict: "A",
+        scope: false,
+        replace: false,
+        require: ['leaflet', '?layers'],
+
+        link: function link(scope, element, attrs, controller) {
+            var mapController = controller[0],
+                isDefined = leafletHelpers.isDefined,
+                isString = leafletHelpers.isString,
+                leafletScope = mapController.getLeafletScope(),
+                paths = leafletScope.paths,
+                createPath = leafletPathsHelpers.createPath,
+                bindPathEvents = leafletPathEvents.bindPathEvents,
+                setPathOptions = leafletPathsHelpers.setPathOptions,
+                maybeWatch = leafletWatchHelpers.maybeWatch;
+
+            mapController.getMap().then(function (map) {
+                var defaults = leafletMapDefaults.getDefaults(attrs.id),
+                    getLayers;
+
+                // If the layers attribute is used, we must wait until the layers are created
+                if (isDefined(controller[1])) {
+                    getLayers = controller[1].getLayers;
+                } else {
+                    getLayers = function getLayers() {
+                        var deferred = $q.defer();
+                        deferred.resolve();
+                        return deferred.promise;
+                    };
+                }
+
+                if (!isDefined(paths)) {
+                    return;
+                }
+
+                //legacy behaviour does a watch collection on the paths
+                var _legacyWatchOptions = {
+                    type: 'watchCollection',
+                    individual: {
+                        type: 'watchDeep'
+                    }
+                };
+
+                var watchOptions;
+                if (leafletScope.watchOptions && leafletScope.watchOptions.paths) {
+                    watchOptions = leafletScope.watchOptions.paths;
+                } else {
+                    watchOptions = _legacyWatchOptions;
+                }
+
+                getLayers().then(function (layers) {
+
+                    var leafletPaths = {};
+                    leafletData.setPaths(leafletPaths, attrs.id);
+
+                    // Function for listening every single path once created
+                    var watchPathFn = function watchPathFn(leafletPath, name, watchOptions) {
+                        var pathWatchPath = "paths[\"" + name + "\"]";
+
+                        maybeWatch(leafletScope, pathWatchPath, watchOptions, function (pathData, old, clearWatch) {
+                            if (!isDefined(pathData)) {
+                                if (isDefined(old.layer)) {
+                                    for (var i in layers.overlays) {
+                                        var overlay = layers.overlays[i];
+                                        overlay.removeLayer(leafletPath);
+                                    }
+                                }
+                                map.removeLayer(leafletPath);
+                                clearWatch();
+                                return;
+                            }
+                            setPathOptions(leafletPath, pathData.type, pathData);
+                        });
+                    };
+
+                    var _clean = function _clean(newPaths) {
+                        // Delete paths (by name) from the array
+                        for (var name in leafletPaths) {
+                            if (!isDefined(newPaths[name])) {
+                                map.removeLayer(leafletPaths[name]);
+                                delete leafletPaths[name];
+                            }
+                        }
+                    };
+
+                    var _create = function _create(newPaths) {
+                        _clean(newPaths);
+                        // Create the new paths
+                        for (var newName in newPaths) {
+                            if (newName.search('\\$') === 0) {
+                                continue;
+                            }
+                            if (newName.search("-") !== -1) {
+                                $log.error('[AngularJS - Leaflet] The path name "' + newName + '" is not valid. It must not include "-" and a number.');
+                                continue;
+                            }
+
+                            if (!isDefined(leafletPaths[newName])) {
+                                var pathData = newPaths[newName];
+                                var newPath = createPath(newName, newPaths[newName], defaults);
+
+                                // bind popup if defined
+                                if (isDefined(newPath) && isDefined(pathData.message)) {
+                                    newPath.bindPopup(pathData.message, pathData.popupOptions);
+                                }
+
+                                // Show label if defined
+                                if (leafletHelpers.LabelPlugin.isLoaded() && isDefined(pathData.label) && isDefined(pathData.label.message)) {
+                                    newPath.bindLabel(pathData.label.message, pathData.label.options);
+                                }
+
+                                // Check if the marker should be added to a layer
+                                if (isDefined(pathData) && isDefined(pathData.layer)) {
+
+                                    if (!isString(pathData.layer)) {
+                                        $log.error('[AngularJS - Leaflet] A layername must be a string');
+                                        continue;
+                                    }
+                                    if (!isDefined(layers)) {
+                                        $log.error('[AngularJS - Leaflet] You must add layers to the directive if the markers are going to use this functionality.');
+                                        continue;
+                                    }
+
+                                    if (!isDefined(layers.overlays) || !isDefined(layers.overlays[pathData.layer])) {
+                                        $log.error('[AngularJS - Leaflet] A path can only be added to a layer of type "group"');
+                                        continue;
+                                    }
+                                    var layerGroup = layers.overlays[pathData.layer];
+                                    if (!(layerGroup instanceof L.LayerGroup || layerGroup instanceof L.FeatureGroup)) {
+                                        $log.error('[AngularJS - Leaflet] Adding a path to an overlay needs a overlay of the type "group" or "featureGroup"');
+                                        continue;
+                                    }
+
+                                    // Listen for changes on the new path
+                                    leafletPaths[newName] = newPath;
+                                    // The path goes to a correct layer group, so first of all we add it
+                                    layerGroup.addLayer(newPath);
+
+                                    if (watchOptions.individual.type !== null) {
+                                        watchPathFn(newPath, newName, watchOptions.individual);
+                                    } else {
+                                        setPathOptions(newPath, pathData.type, pathData);
+                                    }
+                                } else if (isDefined(newPath)) {
+                                    // Listen for changes on the new path
+                                    leafletPaths[newName] = newPath;
+                                    map.addLayer(newPath);
+
+                                    if (watchOptions.individual.type !== null) {
+                                        watchPathFn(newPath, newName, watchOptions.individual);
+                                    } else {
+                                        setPathOptions(newPath, pathData.type, pathData);
+                                    }
+                                }
+
+                                bindPathEvents(attrs.id, newPath, newName, pathData, leafletScope);
+                            }
+                        }
+                    };
+
+                    maybeWatch(leafletScope, 'paths', watchOptions, function (newPaths) {
+                        _create(newPaths);
+                    });
+                });
+            });
+        }
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').directive('tiles', ["leafletLogger", "leafletData", "leafletMapDefaults", "leafletHelpers", function (leafletLogger, leafletData, leafletMapDefaults, leafletHelpers) {
+    var $log = leafletLogger;
+    return {
+        restrict: "A",
+        scope: false,
+        replace: false,
+        require: 'leaflet',
+
+        link: function link(scope, element, attrs, controller) {
+            var isDefined = leafletHelpers.isDefined,
+                leafletScope = controller.getLeafletScope(),
+                tiles = leafletScope.tiles;
+
+            if (!isDefined(tiles) || !isDefined(tiles.url)) {
+                $log.warn("[AngularJS - Leaflet] The 'tiles' definition doesn't have the 'url' property.");
+                return;
+            }
+
+            controller.getMap().then(function (map) {
+                var defaults = leafletMapDefaults.getDefaults(attrs.id);
+                var tileLayerObj;
+                leafletScope.$watch("tiles", function (tiles) {
+                    var tileLayerOptions = defaults.tileLayerOptions;
+                    var tileLayerUrl = defaults.tileLayer;
+
+                    // If no valid tiles are in the scope, remove the last layer
+                    if (!isDefined(tiles.url) && isDefined(tileLayerObj)) {
+                        map.removeLayer(tileLayerObj);
+                        return;
+                    }
+
+                    // No leafletTiles object defined yet
+                    if (!isDefined(tileLayerObj)) {
+                        if (isDefined(tiles.options)) {
+                            angular.copy(tiles.options, tileLayerOptions);
+                        }
+
+                        if (isDefined(tiles.url)) {
+                            tileLayerUrl = tiles.url;
+                        }
+
+                        tileLayerObj = L.tileLayer(tileLayerUrl, tileLayerOptions);
+                        tileLayerObj.addTo(map);
+                        leafletData.setTiles(tileLayerObj, attrs.id);
+                        return;
+                    }
+
+                    // If the options of the tilelayer is changed, we need to redraw the layer
+                    if (isDefined(tiles.url) && isDefined(tiles.options) && !angular.equals(tiles.options, tileLayerOptions)) {
+                        map.removeLayer(tileLayerObj);
+                        tileLayerOptions = defaults.tileLayerOptions;
+                        angular.copy(tiles.options, tileLayerOptions);
+                        tileLayerUrl = tiles.url;
+                        tileLayerObj = L.tileLayer(tileLayerUrl, tileLayerOptions);
+                        tileLayerObj.addTo(map);
+                        leafletData.setTiles(tileLayerObj, attrs.id);
+                        return;
+                    }
+
+                    // Only the URL of the layer is changed, update the tiles object
+                    if (isDefined(tiles.url)) {
+                        tileLayerObj.setUrl(tiles.url);
+                    }
+                }, true);
+            });
+        }
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').directive('watchOptions', ['$log', '$rootScope', '$q', 'leafletData', 'leafletHelpers', function (leafletLogger, $rootScope, $q, leafletData, leafletHelpers) {
+
+    var isDefined = leafletHelpers.isDefined,
+        errorHeader = leafletHelpers.errorHeader,
+        isObject = leafletHelpers.isObject,
+        $log = leafletLogger;
+
+    return {
+        restrict: "A",
+        scope: false,
+        replace: false,
+        require: ['leaflet'],
+
+        link: function link(scope, element, attrs, controller) {
+            var mapController = controller[0],
+                leafletScope = mapController.getLeafletScope();
+
+            var _isValidWatchType = function _isValidWatchType(type) {
+                return type === 'watch' || type === 'watchCollection' || type === 'watchDeep' || type === null;
+            };
+
+            if (isDefined(leafletScope.watchOptions) && isObject(leafletScope.watchOptions)) {
+                angular.forEach(['markers', 'geojson', 'paths'], function (name) {
+                    if (isDefined(leafletScope.watchOptions[name])) {
+                        if (!_isValidWatchType(leafletScope.watchOptions[name].type)) {
+                            $log.error(errorHeader + ' watchOptions.' + name + '.type is not a valid type.');
+                        }
+                        if (isDefined(leafletScope.watchOptions[name].individual)) {
+                            if (!_isValidWatchType(leafletScope.watchOptions[name].individual.type)) {
+                                $log.error(errorHeader + ' watchOptions.' + name + '.individual.type is not a valid type.');
+                            }
+                        } else {
+                            $log.error(errorHeader + ' watchOptions.' + name + '.type.individual must be defined.');
+                        }
+                    }
+                });
+            }
+        }
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').factory('leafletEventsHelpersFactory', ["$rootScope", "$q", "leafletLogger", "leafletHelpers", function ($rootScope, $q, leafletLogger, leafletHelpers) {
+    var safeApply = leafletHelpers.safeApply,
+        isDefined = leafletHelpers.isDefined,
+        isObject = leafletHelpers.isObject,
+        isArray = leafletHelpers.isArray,
+        errorHeader = leafletHelpers.errorHeader,
+        $log = leafletLogger;
+
+    var EventsHelper = function EventsHelper(rootBroadcastName, lObjectType) {
+        this.rootBroadcastName = rootBroadcastName;
+        $log.debug("leafletEventsHelpersFactory: lObjectType: " + lObjectType + "rootBroadcastName: " + rootBroadcastName);
+        //used to path/key out certain properties based on the type , "markers", "geojson"
+        this.lObjectType = lObjectType;
+    };
+
+    EventsHelper.prototype.getAvailableEvents = function () {
+        return [];
+    };
+
+    /*
+     argument: name: Note this can be a single string or dot notation
+     Example:
+     markerModel : {
+     m1: { lat:_, lon: _}
+     }
+     //would yield name of
+     name = "m1"
+      If nested:
+     markerModel : {
+     cars: {
+     m1: { lat:_, lon: _}
+     }
+     }
+     //would yield name of
+     name = "cars.m1"
+     */
+    EventsHelper.prototype.genDispatchEvent = function (maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName, extra) {
+        var _this = this;
+
+        maybeMapId = maybeMapId || '';
+        if (maybeMapId) maybeMapId = '.' + maybeMapId;
+
+        return function (e) {
+            var broadcastName = _this.rootBroadcastName + maybeMapId + '.' + eventName;
+            $log.debug(broadcastName);
+            _this.fire(leafletScope, broadcastName, logic, e, e.target || lObject, model, name, layerName, extra);
+        };
+    };
+
+    EventsHelper.prototype.fire = function (scope, broadcastName, logic, event, lObject, model, modelName, layerName, extra) {
+        // Safely broadcast the event
+        safeApply(scope, function () {
+            var toSend = {
+                leafletEvent: event,
+                leafletObject: lObject,
+                modelName: modelName,
+                model: model
+            };
+            if (isDefined(layerName)) angular.extend(toSend, { layerName: layerName });
+
+            if (logic === "emit") {
+                scope.$emit(broadcastName, toSend);
+            } else {
+                $rootScope.$broadcast(broadcastName, toSend);
+            }
+        });
+    };
+
+    EventsHelper.prototype.bindEvents = function (maybeMapId, lObject, name, model, leafletScope, layerName, extra) {
+        var events = [];
+        var logic = 'emit';
+        var _this = this;
+
+        if (!isDefined(leafletScope.eventBroadcast)) {
+            // Backward compatibility, if no event-broadcast attribute, all events are broadcasted
+            events = this.getAvailableEvents();
+        } else if (!isObject(leafletScope.eventBroadcast)) {
+            // Not a valid object
+            $log.error(errorHeader + "event-broadcast must be an object check your model.");
+        } else {
+            // We have a possible valid object
+            if (!isDefined(leafletScope.eventBroadcast[_this.lObjectType])) {
+                // We do not have events enable/disable do we do nothing (all enabled by default)
+                events = this.getAvailableEvents();
+            } else if (!isObject(leafletScope.eventBroadcast[_this.lObjectType])) {
+                // Not a valid object
+                $log.warn(errorHeader + 'event-broadcast.' + [_this.lObjectType] + ' must be an object check your model.');
+            } else {
+                // We have a possible valid map object
+                // Event propadation logic
+                if (isDefined(leafletScope.eventBroadcast[this.lObjectType].logic)) {
+                    // We take care of possible propagation logic
+                    if (leafletScope.eventBroadcast[_this.lObjectType].logic !== "emit" && leafletScope.eventBroadcast[_this.lObjectType].logic !== "broadcast") $log.warn(errorHeader + "Available event propagation logic are: 'emit' or 'broadcast'.");
+                }
+                // Enable / Disable
+                var eventsEnable = false,
+                    eventsDisable = false;
+                if (isDefined(leafletScope.eventBroadcast[_this.lObjectType].enable) && isArray(leafletScope.eventBroadcast[_this.lObjectType].enable)) eventsEnable = true;
+                if (isDefined(leafletScope.eventBroadcast[_this.lObjectType].disable) && isArray(leafletScope.eventBroadcast[_this.lObjectType].disable)) eventsDisable = true;
+
+                if (eventsEnable && eventsDisable) {
+                    // Both are active, this is an error
+                    $log.warn(errorHeader + "can not enable and disable events at the same time");
+                } else if (!eventsEnable && !eventsDisable) {
+                    // Both are inactive, this is an error
+                    $log.warn(errorHeader + "must enable or disable events");
+                } else {
+                    // At this point the object is OK, lets enable or disable events
+                    if (eventsEnable) {
+                        // Enable events
+                        leafletScope.eventBroadcast[this.lObjectType].enable.forEach(function (eventName) {
+                            // Do we have already the event enabled?
+                            if (events.indexOf(eventName) !== -1) {
+                                // Repeated event, this is an error
+                                $log.warn(errorHeader + "This event " + eventName + " is already enabled");
+                            } else {
+                                // Does the event exists?
+                                if (_this.getAvailableEvents().indexOf(eventName) === -1) {
+                                    // The event does not exists, this is an error
+                                    $log.warn(errorHeader + "This event " + eventName + " does not exist");
+                                } else {
+                                    // All ok enable the event
+                                    events.push(eventName);
+                                }
+                            }
+                        });
+                    } else {
+                        // Disable events
+                        events = this.getAvailableEvents();
+                        leafletScope.eventBroadcast[_this.lObjectType].disable.forEach(function (eventName) {
+                            var index = events.indexOf(eventName);
+                            if (index === -1) {
+                                // The event does not exist
+                                $log.warn(errorHeader + "This event " + eventName + " does not exist or has been already disabled");
+                            } else {
+                                events.splice(index, 1);
+                            }
+                        });
+                    }
+                }
+            }
+        }
+
+        events.forEach(function (eventName) {
+            lObject.on(eventName, _this.genDispatchEvent(maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName, extra));
+        });
+        return logic;
+    };
+
+    return EventsHelper;
+}]).service('leafletEventsHelpers', ["leafletEventsHelpersFactory", function (leafletEventsHelpersFactory) {
+    return new leafletEventsHelpersFactory();
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').factory('leafletGeoJsonEvents', ["$rootScope", "$q", "leafletLogger", "leafletHelpers", "leafletEventsHelpersFactory", "leafletData", function ($rootScope, $q, leafletLogger, leafletHelpers, leafletEventsHelpersFactory, leafletData) {
+    var safeApply = leafletHelpers.safeApply,
+        EventsHelper = leafletEventsHelpersFactory;
+    // $log = leafletLogger;
+
+    var GeoJsonEvents = function GeoJsonEvents() {
+        EventsHelper.call(this, 'leafletDirectiveGeoJson', 'geojson');
+    };
+
+    GeoJsonEvents.prototype = new EventsHelper();
+
+    GeoJsonEvents.prototype.genDispatchEvent = function (maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName, extra) {
+        var base = EventsHelper.prototype.genDispatchEvent.call(this, maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName),
+            _this = this;
+
+        return function (e) {
+            if (eventName === 'mouseout') {
+                if (extra.resetStyleOnMouseout) {
+                    leafletData.getGeoJSON(extra.mapId).then(function (leafletGeoJSON) {
+                        //this is broken on nested needs to traverse or user layerName (nested)
+                        var lobj = layerName ? leafletGeoJSON[layerName] : leafletGeoJSON;
+                        lobj.resetStyle(e.target);
+                    });
+                }
+                safeApply(leafletScope, function () {
+                    $rootScope.$broadcast(_this.rootBroadcastName + '.mouseout', e);
+                });
+            }
+            base(e); //common
+        };
+    };
+
+    GeoJsonEvents.prototype.getAvailableEvents = function () {
+        return ['click', 'dblclick', 'mouseover', 'mouseout'];
+    };
+
+    return new GeoJsonEvents();
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').factory('leafletLabelEvents', ["$rootScope", "$q", "leafletLogger", "leafletHelpers", "leafletEventsHelpersFactory", function ($rootScope, $q, leafletLogger, leafletHelpers, leafletEventsHelpersFactory) {
+    var Helpers = leafletHelpers,
+        EventsHelper = leafletEventsHelpersFactory;
+    //$log = leafletLogger;
+
+    var LabelEvents = function LabelEvents() {
+        EventsHelper.call(this, 'leafletDirectiveLabel', 'markers');
+    };
+    LabelEvents.prototype = new EventsHelper();
+
+    LabelEvents.prototype.genDispatchEvent = function (maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName) {
+        var markerName = name.replace('markers.', '');
+        return EventsHelper.prototype.genDispatchEvent.call(this, maybeMapId, eventName, logic, leafletScope, lObject, markerName, model, layerName);
+    };
+
+    LabelEvents.prototype.getAvailableEvents = function () {
+        return ['click', 'dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu'];
+    };
+
+    LabelEvents.prototype.genEvents = function (maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName) {
+        var _this = this;
+        var labelEvents = this.getAvailableEvents();
+        var scopeWatchName = Helpers.getObjectArrayPath("markers." + name);
+        labelEvents.forEach(function (eventName) {
+            lObject.label.on(eventName, _this.genDispatchEvent(maybeMapId, eventName, logic, leafletScope, lObject.label, scopeWatchName, model, layerName));
+        });
+    };
+
+    LabelEvents.prototype.bindEvents = function (maybeMapId, lObject, name, model, leafletScope, layerName) {};
+
+    return new LabelEvents();
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').factory('leafletMapEvents', ["$rootScope", "$q", "leafletLogger", "leafletHelpers", "leafletEventsHelpers", "leafletIterators", function ($rootScope, $q, leafletLogger, leafletHelpers, leafletEventsHelpers, leafletIterators) {
+    var isDefined = leafletHelpers.isDefined,
+        fire = leafletEventsHelpers.fire;
+
+    var _getAvailableMapEvents = function _getAvailableMapEvents() {
+        return ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'mousemove', 'contextmenu', 'focus', 'blur', 'preclick', 'load', 'unload', 'viewreset', 'movestart', 'move', 'moveend', 'dragstart', 'drag', 'dragend', 'zoomstart', 'zoomanim', 'zoomend', 'zoomlevelschange', 'resize', 'autopanstart', 'layeradd', 'layerremove', 'baselayerchange', 'overlayadd', 'overlayremove', 'locationfound', 'locationerror', 'popupopen', 'popupclose', 'draw:created', 'draw:edited', 'draw:deleted', 'draw:drawstart', 'draw:drawstop', 'draw:editstart', 'draw:editstop', 'draw:deletestart', 'draw:deletestop'];
+    };
+
+    var _genDispatchMapEvent = function _genDispatchMapEvent(scope, eventName, logic, maybeMapId) {
+        if (maybeMapId) {
+            maybeMapId = maybeMapId + '.';
+        }
+        return function (e) {
+            // Put together broadcast name
+            var broadcastName = 'leafletDirectiveMap.' + maybeMapId + eventName;
+            leafletLogger.debug(broadcastName);
+            // Safely broadcast the event
+            fire(scope, broadcastName, logic, e, e.target, scope);
+        };
+    };
+
+    var _notifyCenterChangedToBounds = function _notifyCenterChangedToBounds(scope) {
+        scope.$broadcast("boundsChanged");
+    };
+
+    var _notifyCenterUrlHashChanged = function _notifyCenterUrlHashChanged(scope, map, attrs, search) {
+        if (!isDefined(attrs.urlHashCenter)) {
+            return;
+        }
+        var center = map.getCenter();
+        var centerUrlHash = center.lat.toFixed(4) + ":" + center.lng.toFixed(4) + ":" + map.getZoom();
+        if (!isDefined(search.c) || search.c !== centerUrlHash) {
+            //$log.debug("notified new center...");
+            scope.$emit("centerUrlHash", centerUrlHash);
+        }
+    };
+
+    var _addEvents = function _addEvents(map, mapId, mapEvents, contextName, scope, logic) {
+        leafletIterators.each(mapEvents, function (eventName) {
+            var context = {};
+            context[contextName] = eventName;
+            if (!mapId) {
+                mapId = map._container.id || '';
+            }
+
+            map.on(eventName, _genDispatchMapEvent(scope, eventName, logic, mapId), context);
+        });
+    };
+
+    return {
+        getAvailableMapEvents: _getAvailableMapEvents,
+        genDispatchMapEvent: _genDispatchMapEvent,
+        notifyCenterChangedToBounds: _notifyCenterChangedToBounds,
+        notifyCenterUrlHashChanged: _notifyCenterUrlHashChanged,
+        addEvents: _addEvents
+    };
+}]);
+
+'use strict';
+
+angular.module('ui-leaflet').factory('leafletMarkerEvents', ["$rootScope", "$q", "leafletLogger", "leafletHelpers", "leafletEventsHelpersFactory", "leafletLabelEvents", function ($rootScope, $q, leafletLogger, leafletHelpers, leafletEventsHelpersFactory, leafletLabelEvents) {
+    var safeApply = leafletHelpers.safeApply,
+        isDefined = leafletHelpers.isDefined,
+        Helpers = leafletHelpers,
+        lblHelp = leafletLabelEvents,
+        EventsHelper = leafletEventsHelpersFactory,
+        $log = leafletLogger;
+
+    var MarkerEvents = function MarkerEvents() {
+        EventsHelper.call(this, 'leafletDirectiveMarker', 'markers');
+    };
+
+    MarkerEvents.prototype = new EventsHelper();
+
+    MarkerEvents.prototype.genDispatchEvent = function (maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName) {
+        var handle = EventsHelper.prototype.genDispatchEvent.call(this, maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName);
+        return function (e) {
+            // Broadcast old marker click name for backwards compatibility
+            if (eventName === "click") {
+                safeApply(leafletScope, function () {
+                    $rootScope.$broadcast('leafletDirectiveMarkersClick', name);
+                });
+            } else if (eventName === 'dragend') {
+                safeApply(leafletScope, function () {
+                    model.lat = lObject.getLatLng().lat;
+                    model.lng = lObject.getLatLng().lng;
+                });
+                if (model.message && model.focus === true) {
+                    lObject.openPopup();
+                }
+            }
+            handle(e); //common
+        };
+    };
+
+    MarkerEvents.prototype.getAvailableEvents = function () {
+        return ['click', 'dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu', 'dragstart', 'drag', 'dragend', 'move', 'remove', 'popupopen', 'popupclose', 'touchend', 'touchstart', 'touchmove', 'touchcancel', 'touchleave'];
+    };
+
+    MarkerEvents.prototype.bindEvents = function (maybeMapId, lObject, name, model, leafletScope, layerName) {
+        var logic = EventsHelper.prototype.bindEvents.call(this, maybeMapId, lObject, name, model, leafletScope, layerName);
+
+        if (Helpers.LabelPlugin.isLoaded() && isDefined(lObject.label)) {
+            lblHelp.genEvents(maybeMapId, name, logic, leafletScope, lObject, model, layerName);
+        }
+    };
+
+    return new MarkerEvents();
+}]);
+
+'use strict';
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; };
+
+angular.module('ui-leaflet').factory('leafletPathEvents', ["$rootScope", "$q", "leafletLogger", "leafletHelpers", "leafletLabelEvents", "leafletEventsHelpers", function ($rootScope, $q, leafletLogger, leafletHelpers, leafletLabelEvents, leafletEventsHelpers) {
+    var isDefined = leafletHelpers.isDefined,
+        isObject = leafletHelpers.isObject,
+        Helpers = leafletHelpers,
+        errorHeader = leafletHelpers.errorHeader,
+        lblHelp = leafletLabelEvents,
+        fire = leafletEventsHelpers.fire,
+        $log = leafletLogger;
+
+    /*
+    TODO (nmccready) This EventsHelper needs to be derrived from leafletEventsHelpers to elminate copy and paste code.
+    */
+
+    var _genDispatchPathEvent = function _genDispatchPathEvent(maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName) {
+        maybeMapId = maybeMapId || '';
+
+        if (maybeMapId) maybeMapId = '.' + maybeMapId;
+
+        return function (e) {
+            var broadcastName = 'leafletDirectivePath' + maybeMapId + '.' + eventName;
+            $log.debug(broadcastName);
+            fire(leafletScope, broadcastName, logic, e, e.target || lObject, model, name, layerName);
+        };
+    };
+
+    var _bindPathEvents = function _bindPathEvents(maybeMapId, lObject, name, model, leafletScope) {
+        var pathEvents = [],
+            i,
+            eventName,
+            logic = "broadcast";
+
+        if (!isDefined(leafletScope.eventBroadcast)) {
+            // Backward compatibility, if no event-broadcast attribute, all events are broadcasted
+            pathEvents = _getAvailablePathEvents();
+        } else if (!isObject(leafletScope.eventBroadcast)) {
+            // Not a valid object
+            $log.error(errorHeader + "event-broadcast must be an object check your model.");
+        } else {
+            // We have a possible valid object
+            if (!isDefined(leafletScope.eventBroadcast.path)) {
+                // We do not have events enable/disable do we do nothing (all enabled by default)
+                pathEvents = _getAvailablePathEvents();
+            } else if (isObject(leafletScope.eventBroadcast.paths)) {
+                // Not a valid object
+                $log.warn(errorHeader + "event-broadcast.path must be an object check your model.");
+            } else {
+                // We have a possible valid map object
+                // Event propadation logic
+                if (leafletScope.eventBroadcast.path.logic !== undefined && leafletScope.eventBroadcast.path.logic !== null) {
+                    // We take care of possible propagation logic
+                    if (leafletScope.eventBroadcast.path.logic !== "emit" && leafletScope.eventBroadcast.path.logic !== "broadcast") {
+                        // This is an error
+                        $log.warn(errorHeader + "Available event propagation logic are: 'emit' or 'broadcast'.");
+                    } else if (leafletScope.eventBroadcast.path.logic === "emit") {
+                        logic = "emit";
+                    }
+                }
+                // Enable / Disable
+                var pathEventsEnable = false,
+                    pathEventsDisable = false;
+                if (leafletScope.eventBroadcast.path.enable !== undefined && leafletScope.eventBroadcast.path.enable !== null) {
+                    if (_typeof(leafletScope.eventBroadcast.path.enable) === 'object') {
+                        pathEventsEnable = true;
+                    }
+                }
+                if (leafletScope.eventBroadcast.path.disable !== undefined && leafletScope.eventBroadcast.path.disable !== null) {
+                    if (_typeof(leafletScope.eventBroadcast.path.disable) === 'object') {
+                        pathEventsDisable = true;
+                    }
+                }
+                if (pathEventsEnable && pathEventsDisable) {
+                    // Both are active, this is an error
+                    $log.warn(errorHeader + "can not enable and disable events at the same time");
+                } else if (!pathEventsEnable && !pathEventsDisable) {
+                    // Both are inactive, this is an error
+                    $log.warn(errorHeader + "must enable or disable events");
+                } else {
+                    // At this point the path object is OK, lets enable or disable events
+                    if (pathEventsEnable) {
+                        // Enable events
+                        for (i = 0; i < leafletScope.eventBroadcast.path.enable.length; i++) {
+                            eventName = leafletScope.eventBroadcast.path.enable[i];
+                            // Do we have already the event enabled?
+                            if (pathEvents.indexOf(eventName) !== -1) {
+                                // Repeated event, this is an error
+                                $log.warn(errorHeader + "This event " + eventName + " is already enabled");
+                            } else {
+                                // Does the event exists?
+                                if (_getAvailablePathEvents().indexOf(eventName) === -1) {
+                                    // The event does not exists, this is an error
+                                    $log.warn(errorHeader + "This event " + eventName + " does not exist");
+                                } else {
+                                    // All ok enable the event
+                                    pathEvents.push(eventName);
+                                }
+                            }
+                        }
+                    } else {
+                        // Disable events
+                        pathEvents = _getAvailablePathEvents();
+                        for (i = 0; i < leafletScope.eventBroadcast.path.disable.length; i++) {
+                            eventName = leafletScope.eventBroadcast.path.disable[i];
+                            var index = pathEvents.indexOf(eventName);
+                            if (index === -1) {
+                                // The event does not exist
+                                $log.warn(errorHeader + "This event " + eventName + " does not exist or has been already disabled");
+                            } else {
+                                pathEvents.splice(index, 1);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        for (i = 0; i < pathEvents.length; i++) {
+            eventName = pathEvents[i];
+            lObject.on(eventName, _genDispatchPathEvent(maybeMapId, eventName, logic, leafletScope, pathEvents, name));
+        }
+
+        if (Helpers.LabelPlugin.isLoaded() && isDefined(lObject.label)) {
+            lblHelp.genEvents(maybeMapId, name, logic, leafletScope, lObject, model);
+        }
+    };
+
+    var _getAvailablePathEvents = function _getAvailablePathEvents() {
+        return ['click', 'dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu', 'add', 'remove', 'popupopen', 'popupclose'];
+    };
+
+    return {
+        getAvailablePathEvents: _getAvailablePathEvents,
+        bindPathEvents: _bindPathEvents
+    };
+}]);
+
+}(angular));
\ No newline at end of file
diff --git a/www/lib/ionic/js/angular/ui-leaflet.min.js b/www/lib/ionic/js/angular/ui-leaflet.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..3c28209fc121841aee590cf472132daadf7fa5f6
--- /dev/null
+++ b/www/lib/ionic/js/angular/ui-leaflet.min.js
@@ -0,0 +1,39 @@
+/**!
+ * The MIT License
+ *
+ * Copyright (c) the ui-leaflet Team, http://angular-ui.github.io/ui-leaflet
+ *
+ * Original Copyright (c) https://github.com/angular-ui/ui-leaflet
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * ui-leaflet
+ * https://github.com/angular-ui/ui-leaflet
+ *
+ * @authors https://github.com/angular-ui/ui-leaflet/graphs/contributors
+ */
+
+/*!
+*  ui-leaflet 2.0.0 2016-10-04
+*  ui-leaflet - An AngularJS directive to easily interact with Leaflet maps
+*  git: https://github.com/angular-ui/ui-leaflet
+*/
+!function(a){"use strict";a.module("ui-leaflet",["nemLogging"]).directive("leaflet",["$q","leafletData","leafletMapDefaults","leafletHelpers","leafletMapEvents",function(a,b,c,d,e){return{restrict:"EA",replace:!0,scope:{center:"=",lfCenter:"=",defaults:"=",maxbounds:"=",bounds:"=",markers:"=",legend:"=",geojson:"=",paths:"=",tiles:"=",layers:"=",controls:"=",decorations:"=",eventBroadcast:"=",watchOptions:"=",id:"@"},transclude:!0,template:'<div class="angular-leaflet-map"><div ng-transclude></div></div>',controller:["$scope",function(b){this._leafletMap=a.defer(),this.getMap=function(){return this._leafletMap.promise},this.getLeafletScope=function(){return b}}],link:function(a,f,g,h){function i(){isNaN(g.width)?f.css("width",g.width):f.css("width",g.width+"px")}function j(){isNaN(g.height)?f.css("height",g.height):f.css("height",g.height+"px")}var k=d.isDefined,l=c.setDefaults(a.defaults,g.id),m=e.getAvailableMapEvents(),n=e.addEvents;a.mapId=g.id,b.setDirectiveControls({},g.id);var o=new L.Map(f[0],c.getMapCreationDefaults(g.id));if(h._leafletMap.resolve(o),k(g.width)&&(i(),a.$watch(function(){return f[0].getAttribute("width")},function(){i(),o.invalidateSize()})),k(g.height)&&(j(),a.$watch(function(){return f[0].getAttribute("height")},function(){j(),o.invalidateSize()})),k(g.center)||k(g.lfCenter)||o.setView([l.center.lat,l.center.lng],l.center.zoom),!k(g.tiles)&&!k(g.layers)){var p=L.tileLayer(l.tileLayer,l.tileLayerOptions);p.addTo(o),b.setTiles(p,g.id)}if(k(o.zoomControl)&&k(l.zoomControlPosition)&&o.zoomControl.setPosition(l.zoomControlPosition),k(o.zoomControl)&&l.zoomControl===!1&&o.zoomControl.removeFrom(o),k(o.zoomsliderControl)&&k(l.zoomsliderControl)&&l.zoomsliderControl===!1&&o.zoomsliderControl.removeFrom(o),!k(g.eventBroadcast)){var q="broadcast";n(o,g.id,m,"eventName",a,q)}o.whenReady(function(){b.setMap(o,g.id)}),a.$on("$destroy",function(){c.reset(),o.remove(),b.unresolveMap(g.id)}),a.$on("invalidateSize",function(){o.invalidateSize()})}}}]),function(){a.module("ui-leaflet").factory("eventManager",[function(){var a=function(){this.listeners={}};return a.prototype={addEventListener:function(a,b,c){for(var d=[],e=arguments.length,f=0;f<e;f++)d.push(arguments[f]);d=d.length>3?d.splice(3,d.length-1):[],"undefined"!=typeof this.listeners[a]?this.listeners[a].push({scope:c,callback:b,args:d}):this.listeners[a]=[{scope:c,callback:b,args:d}]},removeEventListener:function(a,b,c){if("undefined"!=typeof this.listeners[a]){for(var d=this.listeners[a].length,e=[],f=0;f<d;f++){var g=this.listeners[a][f];g.scope===c&&g.callback===b||e.push(g)}this.listeners[a]=e}},hasEventListener:function(a,b,c){if("undefined"!=typeof this.listeners[a]){var d=this.listeners[a].length;if(void 0===b&&void 0===c)return d>0;for(var e=0;e<d;e++){var f=this.listeners[a][e];if((!c||f.scope===c)&&f.callback===b)return!0}}return!1},dispatch:function(a,b){for(var c=0,d={type:a,target:b},e=[],f=arguments.length,g=0;g<f;g++)e.push(arguments[g]);if(e=e.length>2?e.splice(2,e.length-1):[],e=[d].concat(e),"undefined"!=typeof this.listeners[a])for(var h=this.listeners[a].length,i=0;i<h;i++){var j=this.listeners[a][i];if(j&&j.callback){var k=e.concat(j.args);j.callback.apply(j.scope,k),c+=1}}},getEvents:function(){var a="";for(var b in this.listeners)for(var c=this.listeners[b].length,d=0;d<c;d++){var e=this.listeners[b][d];a+=e.scope&&e.scope.className?e.scope.className:"anonymous",a+=" listen for '"+b+"'\n"}return a}},a}]).service("eventManager",["EventManager",function(a){return new a}])}(),a.module("ui-leaflet").factory("leafletBoundsHelpers",["leafletLogger","leafletHelpers",function(b,c){function d(b){return a.isDefined(b)&&a.isDefined(b.southWest)&&a.isDefined(b.northEast)&&a.isNumber(b.southWest.lat)&&a.isNumber(b.southWest.lng)&&a.isNumber(b.northEast.lat)&&a.isNumber(b.northEast.lng)}var e=c.isArray,f=c.isNumber,g=c.isFunction,h=c.isDefined,i=b;return{createLeafletBounds:function(a){if(d(a))return L.latLngBounds([a.southWest.lat,a.southWest.lng],[a.northEast.lat,a.northEast.lng])},isValidBounds:d,createBoundsFromArray:function(a){return e(a)&&2===a.length&&e(a[0])&&e(a[1])&&2===a[0].length&&2===a[1].length&&f(a[0][0])&&f(a[0][1])&&f(a[1][0])&&f(a[1][1])?{northEast:{lat:a[0][0],lng:a[0][1]},southWest:{lat:a[1][0],lng:a[1][1]}}:void i.error("[AngularJS - Leaflet] The bounds array is not valid.")},createBoundsFromLeaflet:function(a){if(!(h(a)&&g(a.getNorthEast)&&g(a.getSouthWest)))return void i.error("[AngularJS - Leaflet] The leaflet bounds is not valid object.");var b=a.getNorthEast(),c=a.getSouthWest();return{northEast:{lat:b.lat,lng:b.lng},southWest:{lat:c.lat,lng:c.lng}}}}}]),a.module("ui-leaflet").factory("leafletControlHelpers",["$rootScope","leafletLogger","leafletHelpers","leafletLayerHelpers","leafletMapDefaults",function(b,c,d,e,f){var g=d.isDefined,h=d.isObject,i=e.createLayer,j={},k=d.errorHeader+" [Controls] ",l=c,m=function(a,b,c){var d=f.getDefaults(c);if(!d.controls.layers.visible)return!1;var e=!1;return h(a)&&Object.keys(a).forEach(function(b){var c=a[b];g(c.layerOptions)&&c.layerOptions.showOnSelector===!1||(e=!0)}),h(b)&&Object.keys(b).forEach(function(a){var c=b[a];g(c.layerParams)&&c.layerParams.showOnSelector===!1||(e=!0)}),e},n=function(b){var c=f.getDefaults(b),d={collapsed:c.controls.layers.collapsed,position:c.controls.layers.position,autoZIndex:!1};a.extend(d,c.controls.layers.options);var e;return e=c.controls.layers&&g(c.controls.layers.control)?c.controls.layers.control.apply(this,[[],[],d]):new L.control.layers([],[],d)},o={draw:{isPluginLoaded:function(){return!!a.isDefined(L.Control.Draw)||(l.error(k+" Draw plugin is not loaded."),!1)},checkValidParams:function(){return!0},createControl:function(a){return new L.Control.Draw(a)}},scale:{isPluginLoaded:function(){return!0},checkValidParams:function(){return!0},createControl:function(a){return new L.control.scale(a)}},fullscreen:{isPluginLoaded:function(){return!!a.isDefined(L.Control.Fullscreen)||(l.error(k+" Fullscreen plugin is not loaded."),!1)},checkValidParams:function(){return!0},createControl:function(a){return new L.Control.Fullscreen(a)}},search:{isPluginLoaded:function(){return!!a.isDefined(L.Control.Search)||(l.error(k+" Search plugin is not loaded."),!1)},checkValidParams:function(){return!0},createControl:function(a){return new L.Control.Search(a)}},custom:{},minimap:{isPluginLoaded:function(){return!!a.isDefined(L.Control.MiniMap)||(l.error(k+" Minimap plugin is not loaded."),!1)},checkValidParams:function(a){return!!g(a.layer)||(l.warn(k+' minimap "layer" option should be defined.'),!1)},createControl:function(a){var b=i(a.layer);return g(b)?new L.Control.MiniMap(b,a):void l.warn(k+' minimap control "layer" could not be created.')}}};return{layersControlMustBeVisible:m,isValidControlType:function(a){return Object.keys(o).indexOf(a)!==-1},createControl:function(a,b){if(o[a].checkValidParams(b))return o[a].createControl(b)},updateLayersControl:function(a,b,c,d,e,f){var h,i=j[b],k=m(d,e,b);if(g(i)&&c){for(h in f.baselayers)i.removeLayer(f.baselayers[h]);for(h in f.overlays)i.removeLayer(f.overlays[h]);a.removeControl(i),delete j[b]}if(k){i=n(b),j[b]=i;for(h in d){var l=g(d[h].layerOptions)&&d[h].layerOptions.showOnSelector===!1;!l&&g(f.baselayers[h])&&i.addBaseLayer(f.baselayers[h],d[h].name)}for(h in e){var o=g(e[h].layerParams)&&e[h].layerParams.showOnSelector===!1;!o&&g(f.overlays[h])&&i.addOverlay(f.overlays[h],e[h].name)}a.addControl(i)}return k},destroyMapLayersControl:function(a){delete j[a]}}}]),a.module("ui-leaflet").service("leafletData",["leafletLogger","$q","leafletHelpers",function(a,b,c){var d=c.getDefer,e=c.getUnresolvedDefer,f=c.setResolvedDefer,g={},h=this,i=function(a){return a.charAt(0).toUpperCase()+a.slice(1)},j=["map","tiles","layers","paths","markers","geoJSON","UTFGrid","decorations","directiveControls"];j.forEach(function(a){g[a]={}}),this.unresolveMap=function(a){var b=c.obtainEffectiveMapId(g.map,a);j.forEach(function(a){g[a][b]=void 0})},j.forEach(function(a){var b=i(a);h["set"+b]=function(b,c){var d=e(g[a],c);d.resolve(b),f(g[a],c)},h["get"+b]=function(b){var c=d(g[a],b);return c.promise}})}]),a.module("ui-leaflet").service("leafletDirectiveControlsHelpers",["leafletLogger","leafletData","leafletHelpers",function(b,c,d){var e=d.isDefined,f=d.isString,g=d.isObject,h=d.errorHeader,i=b,j=h+"[leafletDirectiveControlsHelpers",k=function(b,d,h,k){var l=j+".extend] ",m={};if(!e(d))return void i.error(l+"thingToAddName cannot be undefined");if(f(d)&&e(h)&&e(k))m[d]={create:h,clean:k};else{if(!g(d)||e(h)||e(k))return void i.error(l+"incorrect arguments");m=d}c.getDirectiveControls().then(function(d){a.extend(d,m),c.setDirectiveControls(d,b)})};return{extend:k}}]),a.module("ui-leaflet").service("leafletGeoJsonHelpers",["leafletHelpers","leafletIterators",function(b,c){var d=b,e=c,f=function(a,b){return this.lat=a,this.lng=b,this},g=function(a){return Array.isArray(a)&&2===a.length?a[1]:d.isDefined(a.type)&&"Point"===a.type?+a.coordinates[1]:+a.lat},h=function(a){return Array.isArray(a)&&2===a.length?a[0]:d.isDefined(a.type)&&"Point"===a.type?+a.coordinates[0]:+a.lng},i=function(a){if(d.isUndefined(a))return!1;if(d.isArray(a)){if(2===a.length&&d.isNumber(a[0])&&d.isNumber(a[1]))return!0}else if(d.isDefined(a.type)&&"Point"===a.type&&d.isArray(a.coordinates)&&2===a.coordinates.length&&d.isNumber(a.coordinates[0])&&d.isNumber(a.coordinates[1]))return!0;var b=e.all(["lat","lng"],function(b){return d.isDefined(a[b])&&d.isNumber(a[b])});return b},j=function(b){if(b&&i(b)){var c=null;if(Array.isArray(b)&&2===b.length)c=new f(b[1],b[0]);else{if(!d.isDefined(b.type)||"Point"!==b.type)return b;c=new f(b.coordinates[1],b.coordinates[0])}return a.extend(b,c)}};return{getLat:g,getLng:h,validateCoords:i,getCoords:j}}]),a.module("ui-leaflet").service("leafletHelpers",["$q","$log","$timeout",function(b,c,d){function e(b,d){var e,f;if(a.isDefined(d))e=d;else if(0===Object.keys(b).length)e="main";else if(Object.keys(b).length>=1)for(f in b)b.hasOwnProperty(f)&&(e=f);else c.error(g+"- You have more than 1 map on the DOM, you must provide the map ID to the leafletData.getXXX call");return e}function f(c,d){var f,g=e(c,d);return a.isDefined(c[g])&&c[g].resolvedDefer!==!0?f=c[g].defer:(f=b.defer(),c[g]={defer:f,resolvedDefer:!1}),f}var g="[ui-leaflet] ",h=a.copy,i=h,j=function(b,c){var d;if(b&&a.isObject(b))return null!==c&&a.isString(c)?(d=b,c.split(".").forEach(function(a){d&&(d=d[a])}),d):c},k=function(a){return a.split(".").reduce(function(a,b){return a+'["'+b+'"]'})},l=function(a){return a.reduce(function(a,b){return a+"."+b})},m=function(b){return a.isDefined(b)&&null!==b},n=function(a){return!m(a)},o=/([\:\-\_]+(.))/g,p=/^moz([A-Z])/,q=/^((?:x|data)[\:\-_])/i,r=function(a){return a.replace(o,function(a,b,c,d){return d?c.toUpperCase():c}).replace(p,"Moz$1")},s=function(a){return r(a.replace(q,""))},t=10,u=function(a,b,c){if(!a)throw new Error(g+"trapObj is undefined");if(!b)throw new Error(g+"trapField is undefined");a[b]=!0;var e=c();return d(function(){a[b]=!1},t),e};return{watchTrapDelayMilliSec:t,modelChangeInDirective:u,camelCase:r,directiveNormalize:s,copy:h,clone:i,errorHeader:g,getObjectValue:j,getObjectArrayPath:k,getObjectDotPath:l,defaultTo:function(a,b){return m(a)?a:b},isTruthy:function(a){return"true"===a||a===!0},isEmpty:function(a){return 0===Object.keys(a).length},isUndefinedOrEmpty:function(b){return a.isUndefined(b)||null===b||0===Object.keys(b).length},isDefined:m,isUndefined:n,isNumber:a.isNumber,isString:a.isString,isArray:a.isArray,isObject:a.isObject,isFunction:a.isFunction,equals:a.equals,isValidCenter:function(b){return a.isDefined(b)&&a.isNumber(b.lat)&&a.isNumber(b.lng)&&a.isNumber(b.zoom)},isValidPoint:function(b){return!!a.isDefined(b)&&(a.isArray(b)?2===b.length&&a.isNumber(b[0])&&a.isNumber(b[1]):a.isNumber(b.lat)&&a.isNumber(b.lng))},isSameCenterOnMap:function(a,b){var c=b.getCenter(),d=b.getZoom();return!(!a.lat||!a.lng||c.lat.toFixed(4)!==a.lat.toFixed(4)||c.lng.toFixed(4)!==a.lng.toFixed(4)||d!==a.zoom)},safeApply:function(a,b){var c=a.$root.$$phase;"$apply"===c||"$digest"===c?a.$eval(b):a.$evalAsync(b)},obtainEffectiveMapId:e,getDefer:function(b,c){var d,g=e(b,c);return d=a.isDefined(b[g])&&b[g].resolvedDefer!==!1?b[g].defer:f(b,c)},getUnresolvedDefer:f,setResolvedDefer:function(a,b){var c=e(a,b);a[c].resolvedDefer=!0},rangeIsSupported:function(){var a=document.createElement("input");return a.setAttribute("type","range"),"range"===a.type},FullScreenControlPlugin:{isLoaded:function(){return a.isDefined(L.Control.Fullscreen)}},MiniMapControlPlugin:{isLoaded:function(){return a.isDefined(L.Control.MiniMap)}},AwesomeMarkersPlugin:{isLoaded:function(){return a.isDefined(L.AwesomeMarkers)&&a.isDefined(L.AwesomeMarkers.Icon)},is:function(a){return!!this.isLoaded()&&a instanceof L.AwesomeMarkers.Icon},equal:function(b,c){return!!this.isLoaded()&&(!!this.is(b)&&a.equals(b,c))}},VectorMarkersPlugin:{isLoaded:function(){return a.isDefined(L.VectorMarkers)&&a.isDefined(L.VectorMarkers.Icon)},is:function(a){return!!this.isLoaded()&&a instanceof L.VectorMarkers.Icon},equal:function(b,c){return!!this.isLoaded()&&(!!this.is(b)&&a.equals(b,c))}},DomMarkersPlugin:{isLoaded:function(){return!(!a.isDefined(L.DomMarkers)||!a.isDefined(L.DomMarkers.Icon))},is:function(a){return!!this.isLoaded()&&a instanceof L.DomMarkers.Icon},equal:function(b,c){return!!this.isLoaded()&&(!!this.is(b)&&a.equals(b,c))}},PolylineDecoratorPlugin:{isLoaded:function(){return!!a.isDefined(L.PolylineDecorator)},is:function(a){return!!this.isLoaded()&&a instanceof L.PolylineDecorator},equal:function(b,c){return!!this.isLoaded()&&(!!this.is(b)&&a.equals(b,c))}},MakiMarkersPlugin:{isLoaded:function(){return!(!a.isDefined(L.MakiMarkers)||!a.isDefined(L.MakiMarkers.Icon))},is:function(a){return!!this.isLoaded()&&a instanceof L.MakiMarkers.Icon},equal:function(b,c){return!!this.isLoaded()&&(!!this.is(b)&&a.equals(b,c))}},ExtraMarkersPlugin:{isLoaded:function(){return!(!a.isDefined(L.ExtraMarkers)||!a.isDefined(L.ExtraMarkers.Icon))},is:function(a){return!!this.isLoaded()&&a instanceof L.ExtraMarkers.Icon},equal:function(b,c){return!!this.isLoaded()&&(!!this.is(b)&&a.equals(b,c))}},LabelPlugin:{isLoaded:function(){return a.isDefined(L.Label)},is:function(a){return!!this.isLoaded()&&a instanceof L.MarkerClusterGroup}},MarkerClusterPlugin:{isLoaded:function(){return a.isDefined(L.MarkerClusterGroup)},is:function(a){return!!this.isLoaded()&&a instanceof L.MarkerClusterGroup}},GeoJSONPlugin:{isLoaded:function(){return a.isDefined(L.TileLayer.GeoJSON)},is:function(a){return!!this.isLoaded()&&a instanceof L.TileLayer.GeoJSON}},CartoDB:{isLoaded:function(){return cartodb},is:function(){return!0}},Leaflet:{DivIcon:{is:function(a){return a instanceof L.DivIcon},equal:function(b,c){return!!this.is(b)&&a.equals(b,c)}},Icon:{is:function(a){return a instanceof L.Icon},equal:function(b,c){return!!this.is(b)&&a.equals(b,c)}}},watchOptions:{type:"watchDeep",individual:{type:"watchDeep"}}}}]),a.module("ui-leaflet").service("leafletIterators",["leafletLogger","leafletHelpers",function(a,b){var c,d=b,e=b.errorHeader+"leafletIterators: ",f=Object.keys,g=d.isFunction,h=d.isObject,i=a,j=Math.pow(2,53)-1,k=function(a){var b=null!==a&&a.length;return d.isNumber(b)&&b>=0&&b<=j},l=function(a){return a},m=function(a){return function(b){return null===b?void 0:b[a]}},n=function(a,b,c){if(void 0===b)return a;switch(null===c?3:c){case 1:return function(c){return a.call(b,c)};case 2:return function(c,d){return a.call(b,c,d)};case 3:return function(c,d,e){return a.call(b,c,d,e)};case 4:return function(c,d,e,f){return a.call(b,c,d,e,f)}}return function(){return a.apply(b,arguments)}},o=function(a,b){return function(c){var d=arguments.length;if(d<2||null===c)return c;for(var e=1;e<d;e++)for(var f=arguments[e],g=a(f),h=g.length,i=0;i<h;i++){var j=g[i];b&&void 0!==c[j]||(c[j]=f[j])}return c}},p=null;c=p=o(f);var q,r=function(a,b){var c=f(b),d=c.length;if(null===a)return!d;for(var e=Object(a),g=0;g<d;g++){var h=c[g];if(b[h]!==e[h]||!(h in e))return!1}return!0},s=null;q=s=function(a){return a=c({},a),function(b){return r(b,a)}};var t,u=function(a,b,c){return null===a?l:g(a)?n(a,b,c):h(a)?q(a):m(a)},v=null;t=v=function(a,b,c){b=u(b,c);for(var d=!k(a)&&f(a),e=(d||a).length,g=0;g<e;g++){var h=d?d[g]:g;if(!b(a[h],h,a))return!1}return!0};var w=function(a,b,c,f){return!(c||d.isDefined(a)&&d.isDefined(b))||!d.isFunction(b)&&(f=d.defaultTo(b,"cb"),i.error(e+f+" is not a function"),!0)},x=function(a,b,c){if(!w(void 0,c,!0,"internalCb")&&!w(a,b))for(var d in a)a.hasOwnProperty(d)&&c(a[d],d)},y=function(a,b){x(a,b,function(a,c){b(a,c)})};return{each:y,forEach:y,every:t,all:v}}]),a.module("ui-leaflet").factory("leafletLayerHelpers",["$rootScope","$q","leafletLogger","leafletHelpers","leafletIterators",function(b,c,d,e,f){function g(a){return l(a.type)?Object.keys(t).indexOf(a.type)===-1?(r.error("[AngularJS - Leaflet] A layer must have a valid type: "+Object.keys(t)),!1):t[a.type].mustHaveUrl&&!l(a.url)?(r.error("[AngularJS - Leaflet] A base layer must have an url"),!1):t[a.type].mustHaveData&&!o(a.data)?(r.error('[AngularJS - Leaflet] The base layer must have a "data" array attribute'),!1):t[a.type].mustHaveLayer&&!o(a.layer)?(r.error("[AngularJS - Leaflet] The type of layer "+a.type+" must have an layer defined"),!1):t[a.type].mustHaveBounds&&!o(a.bounds)?(r.error("[AngularJS - Leaflet] The type of layer "+a.type+" must have bounds defined"),!1):!(t[a.type].mustHaveKey&&!o(a.key))||(r.error("[AngularJS - Leaflet] The type of layer "+a.type+" must have key defined"),!1):(r.error("[AngularJS - Leaflet] A layer must have a valid type defined."),!1)}function h(a){if(g(a)){if(!l(a.name))return void r.error("[AngularJS - Leaflet] A base layer must have a name");m(a.layerParams)||(a.layerParams={}),m(a.layerOptions)||(a.layerOptions={});for(var b in a.layerParams)a.layerOptions[b]=a.layerParams[b];var c={url:a.url,data:a.data,options:a.layerOptions,layer:a.layer,icon:a.icon,type:a.layerType,bounds:a.bounds,key:a.key,apiKey:a.apiKey,pluginOptions:a.pluginOptions,user:a.user,$parent:a};return t[a.type].createLayer(c)}}function i(a,b){b&&"function"==typeof b.addTo?b.addTo(a):a.addLayer(b)}function j(b,c,d){if(o(d)&&o(d.loadedDefer))if(a.isFunction(d.loadedDefer)){var e=d.loadedDefer();r.debug("Loaded Deferred",e);var f=e.length;if(f>0)for(var g=function(){f--,0===f&&b.removeLayer(c)},h=0;h<e.length;h++)e[h].promise.then(g);else b.removeLayer(c)}else d.loadedDefer.promise.then(function(){b.removeLayer(c)});else b.removeLayer(c)}var k=e,l=e.isString,m=e.isObject,n=e.isArray,o=e.isDefined,p=e.errorHeader,q=f,r=d,s=function(c){if(!k.UTFGridPlugin.isLoaded())return void r.error("[AngularJS - Leaflet] The UTFGrid plugin is not loaded.");var d=new L.UtfGrid(c.url,c.pluginOptions),e={model:c.$parent};return d.on("mouseover",function(c){a.extend(e,{leafletEvent:c,leafletObject:c.target}),b.$broadcast("leafletDirectiveMap.utfgridMouseover",e)}),d.on("mouseout",function(c){a.extend(e,{leafletEvent:c,leafletObject:c.target}),b.$broadcast("leafletDirectiveMap.utfgridMouseout",e)}),d.on("click",function(c){a.extend(e,{leafletEvent:c,leafletObject:c.target}),b.$broadcast("leafletDirectiveMap.utfgridClick",e)}),d.on("mousemove",function(c){a.extend(e,{leafletEvent:c,leafletObject:c.target}),b.$broadcast("leafletDirectiveMap.utfgridMousemove",e)}),d},t={xyz:{mustHaveUrl:!0,createLayer:function(a){return L.tileLayer(a.url,a.options)}},geoJSON:{mustHaveUrl:!0,createLayer:function(a){if(k.GeoJSONPlugin.isLoaded())return new L.TileLayer.GeoJSON(a.url,a.pluginOptions,a.options)}},geoJSONShape:{mustHaveUrl:!1,createLayer:function(a){return new L.GeoJSON(a.data,a.options)}},geoJSONAwesomeMarker:{mustHaveUrl:!1,createLayer:function(a){return new L.geoJson(a.data,{pointToLayer:function(b,c){return L.marker(c,{icon:L.AwesomeMarkers.icon(a.icon)})}})}},geoJSONVectorMarker:{mustHaveUrl:!1,createLayer:function(a){return new L.geoJson(a.data,{pointToLayer:function(b,c){return L.marker(c,{icon:L.VectorMarkers.icon(a.icon)})}})}},cartodbTiles:{mustHaveKey:!0,createLayer:function(a){var b=o(a.url)?a.url+"/"+a.user:"//"+a.user+".cartodb.com";return b+="/api/v1/map/"+a.key+"/{z}/{x}/{y}.png",L.tileLayer(b,a.options)}},cartodbUTFGrid:{mustHaveKey:!0,mustHaveLayer:!0,createLayer:function(a){var b=o(a.url)?a.url+"/"+a.user:"//"+a.user+".cartodb.com";return a.url=b+"/api/v1/map/"+a.key+"/"+a.layer+"/{z}/{x}/{y}.grid.json",s(a)}},cartodbInteractive:{mustHaveKey:!0,mustHaveLayer:!0,createLayer:function(b){var c=o(b.url)?b.url+"/"+b.user:"//"+b.user+".cartodb.com",d=c+"/api/v1/map/"+b.key+"/{z}/{x}/{y}.png",e=L.tileLayer(d,b.options),f=[e],g=function(b,d,e){var f=a.copy(d);f.url=c+"/api/v1/map/"+f.key+"/"+e+"/{z}/{x}/{y}.grid.json",b.push(s(f))};if(n(b.layer))for(var h=0;h<b.layer.length;h++)g(f,b,b.layer[h]);else g(f,b,b.layer);return L.layerGroup(f)}},wms:{mustHaveUrl:!0,createLayer:function(a){return L.tileLayer.wms(a.url,a.options)}},wmts:{mustHaveUrl:!0,createLayer:function(a){return L.tileLayer.wmts(a.url,a.options)}},group:{mustHaveUrl:!1,createLayer:function(a){var b=[];return q.each(a.options.layers,function(a){b.push(h(a))}),a.options.loadedDefer=function(){var b=[];if(o(a.options.layers))for(var c=0;c<a.options.layers.length;c++){var d=a.options.layers[c].layerOptions.loadedDefer;o(d)&&b.push(d)}return b},L.layerGroup(b)}},featureGroup:{mustHaveUrl:!1,createLayer:function(){return L.featureGroup()}},markercluster:{mustHaveUrl:!1,createLayer:function(a){return k.MarkerClusterPlugin.isLoaded()?new L.MarkerClusterGroup(a.options):void r.warn(p+" The markercluster plugin is not loaded.")}},imageOverlay:{mustHaveUrl:!0,mustHaveBounds:!0,createLayer:function(a){return L.imageOverlay(a.url,a.bounds,a.options)}},iip:{mustHaveUrl:!0,createLayer:function(a){return L.tileLayer.iip(a.url,a.options)}},custom:{createLayer:function(b){return b.layer instanceof L.Class?a.copy(b.layer):void r.error("[AngularJS - Leaflet] A custom layer must be a leaflet Class")}},cartodb:{mustHaveUrl:!0,createLayer:function(a){return cartodb.createLayer(a.map,a.url)}}},u=function(a){return function(b){o(b.setOpacity)&&b.setOpacity(a)}};return{createLayer:h,layerTypes:t,safeAddLayer:i,safeRemoveLayer:j,changeOpacityListener:u}}]),a.module("ui-leaflet").factory("leafletLegendHelpers",["$http","$q","$log","leafletHelpers",function(a,b,c,d){var e={},f=d.isDefined,g=function b(c){var d=e[c],f=d[0];a(f.c).then(function(a){d.shift(),f.d.resolve(a),d.length>0&&b(c)},function(a){d.shift(),f.d.reject(a),d.length>0&&b(c)})},h=function(a,b,c,d){if(a.innerHTML="",b.error)a.innerHTML+='<div class="info-title alert alert-danger">'+b.error.message+"</div>";else if("arcgis"===c)for(var e=0;e<b.layers.length;e++){var f=b.layers[e];a.innerHTML+='<div class="info-title" data-layerid="'+f.layerId+'">'+f.layerName+"</div>";for(var g=0;g<f.legend.length;g++){var h=f.legend[g];a.innerHTML+='<div class="inline" data-layerid="'+f.layerId+'"><img src="data:'+h.contentType+";base64,"+h.imageData+'" /></div><div class="info-label" data-layerid="'+f.layerId+'">'+h.label+"</div>"}}else"image"===c&&(a.innerHTML='<img src="'+d+'"/>')},i=function(a,b,c,d){return function(){var e=L.DomUtil.create("div",b);return L.Browser.touch?L.DomEvent.on(e,"click",L.DomEvent.stopPropagation):(L.DomEvent.disableClickPropagation(e),L.DomEvent.on(e,"mousewheel",L.DomEvent.stopPropagation)),h(e,a,c,d),e}},j=function(a,b){return function(){for(var c=L.DomUtil.create("div",b),d=0;d<a.colors.length;d++)c.innerHTML+='<div class="outline"><i style="background:'+a.colors[d]+'"></i></div><div class="info-label">'+a.labels[d]+"</div>";return L.Browser.touch?L.DomEvent.on(c,"click",L.DomEvent.stopPropagation):(L.DomEvent.disableClickPropagation(c),L.DomEvent.on(c,"mousewheel",L.DomEvent.stopPropagation)),c}};return{getOnAddLegend:i,getOnAddArrayLegend:j,updateLegend:h,addLegendURL:function(a,c){var d=b.defer();return f(e[a])||(e[a]=[]),e[a].push({c:c,d:d}),1===e[a].length&&g(a),d.promise}}}]),a.module("ui-leaflet").factory("leafletMapDefaults",["$q","leafletHelpers",function(b,c){function d(){return{keyboard:!0,dragging:!0,worldCopyJump:!1,doubleClickZoom:!0,scrollWheelZoom:!0,tap:!0,touchZoom:!0,zoomControl:!0,zoomsliderControl:!1,zoomControlPosition:"topleft",attributionControl:!0,controls:{layers:{visible:!0,position:"topright",collapsed:!0}},nominatim:{server:" http://nominatim.openstreetmap.org/search"},crs:L.CRS.EPSG3857,tileLayer:"//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",tileLayerOptions:{attribution:'&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'},path:{weight:10,opacity:1,color:"#0000ff"},center:{lat:0,lng:0,zoom:1},trackResize:!0}}var e=c.isDefined,f=c.isObject,g=c.obtainEffectiveMapId,h={};return{reset:function(){h={}},getDefaults:function(a){var b=g(h,a);return h[b]},getMapCreationDefaults:function(a){var b=g(h,a),c=h[b],d={maxZoom:c.maxZoom,keyboard:c.keyboard,dragging:c.dragging,zoomControl:c.zoomControl,doubleClickZoom:c.doubleClickZoom,scrollWheelZoom:c.scrollWheelZoom,tap:c.tap,touchZoom:c.touchZoom,attributionControl:c.attributionControl,worldCopyJump:c.worldCopyJump,crs:c.crs,trackResize:c.trackResize};if(e(c.minZoom)&&(d.minZoom=c.minZoom),e(c.zoomAnimation)&&(d.zoomAnimation=c.zoomAnimation),e(c.fadeAnimation)&&(d.fadeAnimation=c.fadeAnimation),e(c.markerZoomAnimation)&&(d.markerZoomAnimation=c.markerZoomAnimation),c.map)for(var f in c.map)d[f]=c.map[f];return d},setDefaults:function(b,c){var i=d();e(b)&&(i.doubleClickZoom=e(b.doubleClickZoom)?b.doubleClickZoom:i.doubleClickZoom,i.scrollWheelZoom=e(b.scrollWheelZoom)?b.scrollWheelZoom:i.doubleClickZoom,i.tap=e(b.tap)?b.tap:i.tap,i.touchZoom=e(b.touchZoom)?b.touchZoom:i.doubleClickZoom,i.zoomControl=e(b.zoomControl)?b.zoomControl:i.zoomControl,i.zoomsliderControl=e(b.zoomsliderControl)?b.zoomsliderControl:i.zoomsliderControl,i.attributionControl=e(b.attributionControl)?b.attributionControl:i.attributionControl,i.tileLayer=e(b.tileLayer)?b.tileLayer:i.tileLayer,i.zoomControlPosition=e(b.zoomControlPosition)?b.zoomControlPosition:i.zoomControlPosition,i.keyboard=e(b.keyboard)?b.keyboard:i.keyboard,i.dragging=e(b.dragging)?b.dragging:i.dragging,i.trackResize=e(b.trackResize)?b.trackResize:i.trackResize,e(b.controls)&&a.extend(i.controls,b.controls),f(b.crs)?i.crs=b.crs:e(L.CRS[b.crs])&&(i.crs=L.CRS[b.crs]),e(b.center)&&a.copy(b.center,i.center),e(b.tileLayerOptions)&&a.copy(b.tileLayerOptions,i.tileLayerOptions),e(b.maxZoom)&&(i.maxZoom=b.maxZoom),e(b.minZoom)&&(i.minZoom=b.minZoom),e(b.zoomAnimation)&&(i.zoomAnimation=b.zoomAnimation),e(b.fadeAnimation)&&(i.fadeAnimation=b.fadeAnimation),e(b.markerZoomAnimation)&&(i.markerZoomAnimation=b.markerZoomAnimation),e(b.worldCopyJump)&&(i.worldCopyJump=b.worldCopyJump),e(b.map)&&(i.map=b.map),e(b.path)&&(i.path=b.path));var j=g(h,c);return h[j]=i,i}}}]),a.module("ui-leaflet").service("leafletMarkersHelpers",["$rootScope","$timeout","leafletHelpers","leafletLogger","$compile","leafletGeoJsonHelpers","leafletWatchHelpers",function(b,c,d,e,f,g,h){var i=d.isDefined,j=d.defaultTo,k=d.MarkerClusterPlugin,l=d.AwesomeMarkersPlugin,m=d.VectorMarkersPlugin,n=d.MakiMarkersPlugin,o=d.ExtraMarkersPlugin,p=d.DomMarkersPlugin,q=d.safeApply,r=d,s=d.isString,t=d.isNumber,u=d.isObject,v={},w=g,x=d.errorHeader,y=h.maybeWatch,z=e,A=function(a){var b="";return["_icon","_latlng","_leaflet_id","_map","_shadow"].forEach(function(c){b+=c+": "+j(a[c],"undefined")+" \n"}),"[leafletMarker] : \n"+b},B=function(a,b){var c=b?console:z;c.debug(A(a))},C=function(b){return a.element(v[b]._map._container).parent().length>0},D=function(c){if(i(c)&&i(c.type)&&"awesomeMarker"===c.type)return l.isLoaded()||z.error(x+" The AwesomeMarkers Plugin is not loaded."),new L.AwesomeMarkers.icon(c);if(i(c)&&i(c.type)&&"vectorMarker"===c.type)return m.isLoaded()||z.error(x+" The VectorMarkers Plugin is not loaded."),new L.VectorMarkers.icon(c);if(i(c)&&i(c.type)&&"makiMarker"===c.type)return n.isLoaded()||z.error(x+"The MakiMarkers Plugin is not loaded."),new L.MakiMarkers.icon(c);if(i(c)&&i(c.type)&&"extraMarker"===c.type)return o.isLoaded()||z.error(x+"The ExtraMarkers Plugin is not loaded."),new L.ExtraMarkers.icon(c);if(i(c)&&i(c.type)&&"div"===c.type)return new L.divIcon(c);if(i(c)&&i(c.type)&&"dom"===c.type){p.isLoaded()||z.error(x+"The DomMarkers Plugin is not loaded.");var d=a.isFunction(c.getMarkerScope)?c.getMarkerScope().$new():b,e=f(c.template)(d),g=a.copy(c);return g.ngElement=e,g.element=e[0],a.isFunction(c.getMarkerScope)&&(g.scope=d),new L.DomMarkers.icon(g)}if(i(c)&&i(c.type)&&"icon"===c.type)return c.icon;var h="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAGmklEQVRYw7VXeUyTZxjvNnfELFuyIzOabermMZEeQC/OclkO49CpOHXOLJl/CAURuYbQi3KLgEhbrhZ1aDwmaoGqKII6odATmH/scDFbdC7LvFqOCc+e95s2VG50X/LLm/f4/Z7neY/ne18aANCmAr5E/xZf1uDOkTcGcWR6hl9247tT5U7Y6SNvWsKT63P58qbfeLJG8M5qcgTknrvvrdDbsT7Ml+tv82X6vVxJE33aRmgSyYtcWVMqX97Yv2JvW39UhRE2HuyBL+t+gK1116ly06EeWFNlAmHxlQE0OMiV6mQCScusKRlhS3QLeVJdl1+23h5dY4FNB3thrbYboqptEFlphTC1hSpJnbRvxP4NWgsE5Jyz86QNNi/5qSUTGuFk1gu54tN9wuK2wc3o+Wc13RCmsoBwEqzGcZsxsvCSy/9wJKf7UWf1mEY8JWfewc67UUoDbDjQC+FqK4QqLVMGGR9d2wurKzqBk3nqIT/9zLxRRjgZ9bqQgub+DdoeCC03Q8j+0QhFhBHR/eP3U/zCln7Uu+hihJ1+bBNffLIvmkyP0gpBZWYXhKussK6mBz5HT6M1Nqpcp+mBCPXosYQfrekGvrjewd59/GvKCE7TbK/04/ZV5QZYVWmDwH1mF3xa2Q3ra3DBC5vBT1oP7PTj4C0+CcL8c7C2CtejqhuCnuIQHaKHzvcRfZpnylFfXsYJx3pNLwhKzRAwAhEqG0SpusBHfAKkxw3w4627MPhoCH798z7s0ZnBJ/MEJbZSbXPhER2ih7p2ok/zSj2cEJDd4CAe+5WYnBCgR2uruyEw6zRoW6/DWJ/OeAP8pd/BGtzOZKpG8oke0SX6GMmRk6GFlyAc59K32OTEinILRJRchah8HQwND8N435Z9Z0FY1EqtxUg+0SO6RJ/mmXz4VuS+DpxXC3gXmZwIL7dBSH4zKE50wESf8qwVgrP1EIlTO5JP9Igu0aexdh28F1lmAEGJGfh7jE6ElyM5Rw/FDcYJjWhbeiBYoYNIpc2FT/SILivp0F1ipDWk4BIEo2VuodEJUifhbiltnNBIXPUFCMpthtAyqws/BPlEF/VbaIxErdxPphsU7rcCp8DohC+GvBIPJS/tW2jtvTmmAeuNO8BNOYQeG8G/2OzCJ3q+soYB5i6NhMaKr17FSal7GIHheuV3uSCY8qYVuEm1cOzqdWr7ku/R0BDoTT+DT+ohCM6/CCvKLKO4RI+dXPeAuaMqksaKrZ7L3FE5FIFbkIceeOZ2OcHO6wIhTkNo0ffgjRGxEqogXHYUPHfWAC/lADpwGcLRY3aeK4/oRGCKYcZXPVoeX/kelVYY8dUGf8V5EBRbgJXT5QIPhP9ePJi428JKOiEYhYXFBqou2Guh+p/mEB1/RfMw6rY7cxcjTrneI1FrDyuzUSRm9miwEJx8E/gUmqlyvHGkneiwErR21F3tNOK5Tf0yXaT+O7DgCvALTUBXdM4YhC/IawPU+2PduqMvuaR6eoxSwUk75ggqsYJ7VicsnwGIkZBSXKOUww73WGXyqP+J2/b9c+gi1YAg/xpwck3gJuucNrh5JvDPvQr0WFXf0piyt8f8/WI0hV4pRxxkQZdJDfDJNOAmM0Ag8jyT6hz0WGXWuP94Yh2jcfjmXAGvHCMslRimDHYuHuDsy2QtHuIavznhbYURq5R57KpzBBRZKPJi8eQg48h4j8SDdowifdIrEVdU+gbO6QNvRRt4ZBthUaZhUnjlYObNagV3keoeru3rU7rcuceqU1mJBxy+BWZYlNEBH+0eH4vRiB+OYybU2hnblYlTvkHinM4m54YnxSyaZYSF6R3jwgP7udKLGIX6r/lbNa9N6y5MFynjWDtrHd75ZvTYAPO/6RgF0k76mQla3FGq7dO+cH8sKn0Vo7nDllwAhqwLPkxrHwWmHJOo+AKJ4rab5OgrM7rVu8eWb2Pu0Dh4eDgXoOfvp7Y7QeqknRmvcTBEyq9m/HQQSCSz6LHq3z0yzsNySRfMS253wl2KyRDbcZPcfJKjZmSEOjcxyi+Y8dUOtsIEH6R2wNykdqrkYJ0RV92H0W58pkfQk7cKevsLK10Py8SdMGfXNXATY+pPbyJR/ET6n9nIfztNtZYRV9XniQu9IA2vOVgy4ir7GCLVmmd+zjkH0eAF9Po6K61pmCXHxU5rHMYd1ftc3owjwRSVRzLjKvqZEty6cRUD7jGqiOdu5HG6MdHjNcNYGqfDm5YRzLBBCCDl/2bk8a8gdbqcfwECu62Fg/HrggAAAABJRU5ErkJggg==",j="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACkAAAApCAYAAACoYAD2AAAC5ElEQVRYw+2YW4/TMBCF45S0S1luXZCABy5CgLQgwf//S4BYBLTdJLax0fFqmB07nnQfEGqkIydpVH85M+NLjPe++dcPc4Q8Qh4hj5D/AaQJx6H/4TMwB0PeBNwU7EGQAmAtsNfAzoZkgIa0ZgLMa4Aj6CxIAsjhjOCoL5z7Glg1JAOkaicgvQBXuncwJAWjksLtBTWZe04CnYRktUGdilALppZBOgHGZcBzL6OClABvMSVIzyBjazOgrvACf1ydC5mguqAVg6RhdkSWQFj2uxfaq/BrIZOLEWgZdALIDvcMcZLD8ZbLC9de4yR1sYMi4G20S4Q/PWeJYxTOZn5zJXANZHIxAd4JWhPIloTJZhzMQduM89WQ3MUVAE/RnhAXpTycqys3NZALOBbB7kFrgLesQl2h45Fcj8L1tTSohUwuxhy8H/Qg6K7gIs+3kkaigQCOcyEXCHN07wyQazhrmIulvKMQAwMcmLNqyCVyMAI+BuxSMeTk3OPikLY2J1uE+VHQk6ANrhds+tNARqBeaGc72cK550FP4WhXmFmcMGhTwAR1ifOe3EvPqIegFmF+C8gVy0OfAaWQPMR7gF1OQKqGoBjq90HPMP01BUjPOqGFksC4emE48tWQAH0YmvOgF3DST6xieJgHAWxPAHMuNhrImIdvoNOKNWIOcE+UXE0pYAnkX6uhWsgVXDxHdTfCmrEEmMB2zMFimLVOtiiajxiGWrbU52EeCdyOwPEQD8LqyPH9Ti2kgYMf4OhSKB7qYILbBv3CuVTJ11Y80oaseiMWOONc/Y7kJYe0xL2f0BaiFTxknHO5HaMGMublKwxFGzYdWsBF174H/QDknhTHmHHN39iWFnkZx8lPyM8WHfYELmlLKtgWNmFNzQcC1b47gJ4hL19i7o65dhH0Negbca8vONZoP7doIeOC9zXm8RjuL0Gf4d4OYaU5ljo3GYiqzrWQHfJxA6ALhDpVKv9qYeZA8eM3EhfPSCmpuD0AAAAASUVORK5CYII=";
+return i(c)&&i(c.iconUrl)?new L.Icon(c):new L.Icon.Default({iconUrl:h,shadowUrl:j,iconSize:[25,41],iconAnchor:[12,41],popupAnchor:[1,-34],shadowSize:[41,41]})},E=function(a){i(v[a])&&delete v[a]},F=function(){v={}},G=function(){for(var a in v)C(a)||E(a)},H=function(a){a.options.icon.options.ngElement&&a.options.icon.options.ngElement.remove(),a.options.icon.options.scope&&a.options.icon.options.scope.$destroy()},I=function(a,b,c){if(a.closePopup(),a.options.icon&&a.options.icon.options&&"dom"===a.options.icon.options.type&&H(a),i(c)&&i(c.overlays))for(var d in c.overlays)if((c.overlays[d]instanceof L.LayerGroup||c.overlays[d]instanceof L.FeatureGroup)&&c.overlays[d].hasLayer(a))return void c.overlays[d].removeLayer(a);if(i(v))for(var e in v)v[e].hasLayer(a)&&v[e].removeLayer(a);b.hasLayer(a)&&b.removeLayer(a)},J=function(a,b){var c=a._popup._container.offsetHeight,d=new L.Point(a._popup._containerLeft,-c-a._popup._containerBottom),e=b.layerPointToContainerPoint(d);null!==e&&a._popup._adjustPan()},K=function(a,b){f(a._popup._contentNode)(b)},M=function a(b,d,e){var f=b._popup._contentNode.innerText||b._popup._contentNode.textContent;f.length<1&&c(function(){a(b,d,e)});var g=b._popup._contentNode.offsetWidth;return b._popup._updateLayout(),b._popup._updatePosition(),b._popup.options.autoPan&&J(b,e),g},N=function(c,d,e){var f=a.isFunction(d.getMessageScope)?d.getMessageScope():b,g=!i(d.compileMessage)||d.compileMessage;if(g){if(!i(c._popup)||!i(c._popup._contentNode))return z.error(x+"Popup is invalid or does not have any content."),!1;K(c,f),M(c,d,e)}},O=function(c,d){var e=a.isFunction(d.getMessageScope)?d.getMessageScope():b,g=a.isFunction(d.getLabelScope)?d.getLabelScope():e,h=!i(d.compileMessage)||d.compileMessage;r.LabelPlugin.isLoaded()&&i(d.label)&&(i(d.label.options)&&d.label.options.noHide===!0&&c.showLabel(),h&&i(c.label)&&f(c.label._container)(g))},P=function(b,c,d,e,f,g,h){if(i(c)){if(!w.validateCoords(b))return z.warn("There are problems with lat-lng data, please verify your marker model"),void I(d,h,g);var j=b===c;if(i(b.iconAngle)&&c.iconAngle!==b.iconAngle&&d.setIconAngle(b.iconAngle),s(b.layer)||s(c.layer)&&(i(g.overlays[c.layer])&&g.overlays[c.layer].hasLayer(d)&&(g.overlays[c.layer].removeLayer(d),d.closePopup()),h.hasLayer(d)||h.addLayer(d)),(t(b.opacity)||t(parseFloat(b.opacity)))&&b.opacity!==c.opacity&&d.setOpacity(b.opacity),s(b.layer)&&c.layer!==b.layer){if(s(c.layer)&&i(g.overlays[c.layer])&&g.overlays[c.layer].hasLayer(d)&&g.overlays[c.layer].removeLayer(d),d.closePopup(),h.hasLayer(d)&&h.removeLayer(d),!i(g.overlays[b.layer]))return void z.error(x+"You must use a name of an existing layer");var k=g.overlays[b.layer];if(!(k instanceof L.LayerGroup||k instanceof L.FeatureGroup))return void z.error(x+'A marker can only be added to a layer of type "group" or "featureGroup"');k.addLayer(d),h.hasLayer(d)&&b.focus===!0&&d.openPopup()}if(b.draggable!==!0&&c.draggable===!0&&i(d.dragging)&&d.dragging.disable(),b.draggable===!0&&c.draggable!==!0&&(d.dragging?d.dragging.enable():L.Handler.MarkerDrag&&(d.dragging=new L.Handler.MarkerDrag(d),d.options.draggable=!0,d.dragging.enable())),u(b.icon)||u(c.icon)&&("dom"===c.icon.type&&H(d),d.setIcon(D()),d.closePopup(),d.unbindPopup(),s(b.message)&&d.bindPopup(b.message,b.popupOptions)),u(b.icon)&&u(c.icon)&&!a.equals(b.icon,c.icon)){var l=!1;d.dragging&&(l=d.dragging.enabled()),"dom"===c.icon.type&&H(d),d.setIcon(D(b.icon)),l&&d.dragging.enable(),d.closePopup(),d.unbindPopup(),s(b.message)&&(d.bindPopup(b.message,b.popupOptions),h.hasLayer(d)&&b.focus===!0&&d.openPopup())}!s(b.message)&&s(c.message)&&(d.closePopup(),d.unbindPopup()),r.LabelPlugin.isLoaded()&&(i(b.label)&&i(b.label.message)?"label"in c&&"message"in c.label&&!a.equals(b.label.message,c.label.message)?d.updateLabelContent(b.label.message):!a.isFunction(d.getLabel)||a.isFunction(d.getLabel)&&!i(d.getLabel())?(d.bindLabel(b.label.message,b.label.options),O(d,b)):O(d,b):"label"in b&&!("message"in b.label)||a.isFunction(d.unbindLabel)&&d.unbindLabel()),s(b.message)&&!s(c.message)&&d.bindPopup(b.message,b.popupOptions),s(b.message)&&s(c.message)&&b.message!==c.message&&d.setPopupContent(b.message);var m=!1;b.focus!==!0&&c.focus===!0&&(d.closePopup(),m=!0),(b.focus===!0&&(!i(c.focus)||c.focus===!1)||j&&b.focus===!0)&&(d.openPopup(),m=!0),c.zIndexOffset!==b.zIndexOffset&&d.setZIndexOffset(b.zIndexOffset);var n=d.getLatLng(),o=s(b.layer)&&r.MarkerClusterPlugin.is(g.overlays[b.layer]);o?m?b.lat===c.lat&&b.lng===c.lng||(g.overlays[b.layer].removeLayer(d),d.setLatLng([b.lat,b.lng]),g.overlays[b.layer].addLayer(d)):n.lat!==b.lat||n.lng!==b.lng?(g.overlays[b.layer].removeLayer(d),d.setLatLng([b.lat,b.lng]),g.overlays[b.layer].addLayer(d)):b.lat!==c.lat||b.lng!==c.lng?(g.overlays[b.layer].removeLayer(d),d.setLatLng([b.lat,b.lng]),g.overlays[b.layer].addLayer(d)):u(b.icon)&&u(c.icon)&&!a.equals(b.icon,c.icon)&&(g.overlays[b.layer].removeLayer(d),g.overlays[b.layer].addLayer(d)):n.lat===b.lat&&n.lng===b.lng||d.setLatLng([b.lat,b.lng])}},Q=function(a,b){if(i(a))return b?a[b]:a},R=function(a,b,c){if(i(a))return b?c?a[c][b]:a[b]:void z.error(x+"marker id missing in getMarker")};return{resetMarkerGroup:E,resetMarkerGroups:F,resetUnusedMarkerGroups:G,deleteMarker:I,manageOpenPopup:N,manageOpenLabel:O,createMarker:function(a){if(!i(a)||!w.validateCoords(a))return void z.error(x+"The marker definition is not valid.");var b=w.getCoords(a);if(!i(b))return void z.error(x+"Unable to get coordinates from markerData.");var c={icon:D(a.icon),title:i(a.title)?a.title:"",draggable:!!i(a.draggable)&&a.draggable,clickable:!i(a.clickable)||a.clickable,riseOnHover:!!i(a.riseOnHover)&&a.riseOnHover,zIndexOffset:i(a.zIndexOffset)?a.zIndexOffset:0,iconAngle:i(a.iconAngle)?a.iconAngle:0};for(var d in a)a.hasOwnProperty(d)&&!c.hasOwnProperty(d)&&(c[d]=a[d]);var e=new L.marker(b,c);return s(a.message)||e.unbindPopup(),e},addMarkerToGroup:function(a,b,c,d){return s(b)?k.isLoaded()?(i(v[b])||(v[b]=new L.MarkerClusterGroup(c),d.addLayer(v[b])),void v[b].addLayer(a)):void z.error(x+"The MarkerCluster plugin is not loaded."):void z.error(x+"The marker group you have specified is invalid.")},listenMarkerEvents:function(a,b,c,d,e){a.on("popupopen",function(){q(c,function(){(i(a._popup)||i(a._popup._contentNode))&&(b.focus=!0,N(a,b,e))})}),a.on("popupclose",function(){q(c,function(){b.focus=!1})}),a.on("add",function(){q(c,function(){"label"in b&&O(a,b)})})},updateMarker:P,addMarkerWatcher:function(a,b,c,d,e,f){var g=r.getObjectArrayPath("markers."+b);y(c,g,f,function(f,g,h){return i(f)?void P(f,g,a,b,c,d,e):(I(a,e,d),void h())})},string:A,log:B,getModelFromModels:R,getLayerModels:Q}}]),a.module("ui-leaflet").factory("leafletPathsHelpers",["$rootScope","leafletLogger","leafletHelpers",function(a,b,c){function d(a){return a.filter(function(a){return k(a)}).map(function(a){return e(a)})}function e(a){return i(a)?new L.LatLng(a[0],a[1]):new L.LatLng(a.lat,a.lng)}function f(a){return a.map(function(a){return d(a)})}function g(a,b){for(var c={},d=0;d<m.length;d++){var e=m[d];h(a[e])?c[e]=a[e]:h(b.path[e])&&(c[e]=b.path[e])}return c}var h=c.isDefined,i=c.isArray,j=c.isNumber,k=c.isValidPoint,l=b,m=["stroke","weight","color","opacity","fill","fillColor","fillOpacity","dashArray","lineCap","lineJoin","clickable","pointerEvents","className","smoothFactor","noClip"],n=function(a,b){for(var c={},d=0;d<m.length;d++){var e=m[d];h(b[e])&&(c[e]=b[e])}a.setStyle(b)},o=function(a){if(!i(a))return!1;for(var b=0;b<a.length;b++){var c=a[b];if(!k(c))return!1}return!0},p={polyline:{isValid:function(a){var b=a.latlngs;return o(b)},createPath:function(a){return new L.Polyline([],a)},setPath:function(a,b){a.setLatLngs(d(b.latlngs)),n(a,b)}},multiPolyline:{isValid:function(a){var b=a.latlngs;if(!i(b))return!1;for(var c in b){var d=b[c];if(!o(d))return!1}return!0},createPath:function(a){return new L.multiPolyline([[[0,0],[1,1]]],a)},setPath:function(a,b){a.setLatLngs(f(b.latlngs)),n(a,b)}},polygon:{isValid:function(a){var b=a.latlngs;return o(b)},createPath:function(a){return new L.Polygon([],a)},setPath:function(a,b){a.setLatLngs(d(b.latlngs)),n(a,b)}},multiPolygon:{isValid:function(a){var b=a.latlngs;if(!i(b))return!1;for(var c in b){var d=b[c];if(!o(d))return!1}return!0},createPath:function(a){return new L.MultiPolygon([[[0,0],[1,1],[0,1]]],a)},setPath:function(a,b){a.setLatLngs(f(b.latlngs)),n(a,b)}},rectangle:{isValid:function(a){var b=a.latlngs;if(!i(b)||2!==b.length)return!1;for(var c in b){var d=b[c];if(!k(d))return!1}return!0},createPath:function(a){return new L.Rectangle([[0,0],[1,1]],a)},setPath:function(a,b){a.setBounds(new L.LatLngBounds(d(b.latlngs))),n(a,b)}},circle:{isValid:function(a){var b=a.latlngs;return k(b)&&j(a.radius)},createPath:function(a){return new L.Circle([0,0],1,a)},setPath:function(a,b){a.setLatLng(e(b.latlngs)),h(b.radius)&&a.setRadius(b.radius),n(a,b)}},circleMarker:{isValid:function(a){var b=a.latlngs;return k(b)&&j(a.radius)},createPath:function(a){return new L.CircleMarker([0,0],a)},setPath:function(a,b){a.setLatLng(e(b.latlngs)),h(b.radius)&&a.setRadius(b.radius),n(a,b)}}},q=function(a){var b={};return a.latlngs&&(b.latlngs=a.latlngs),a.radius&&(b.radius=a.radius),b};return{setPathOptions:function(a,b,c){h(b)||(b="polyline"),p[b].setPath(a,c)},createPath:function(a,b,c){h(b.type)||(b.type="polyline");var d=g(b,c),e=q(b);return p[b.type].isValid(e)?p[b.type].createPath(d):void l.error("[AngularJS - Leaflet] Invalid data passed to the "+b.type+" path")}}}]),a.module("ui-leaflet").service("leafletWatchHelpers",function(){var a=function(a,b,c,d,e){var f=a[b](c,function(a,b){e(a,b,f),null===d.type&&f()},"watchDeep"===d.type);return f},b=function(b,c,d,e){var f;return f="watchCollection"===d.type?"$watchCollection":"$watch",a(b,f,c,d,e)};return{maybeWatch:b}}),a.module("ui-leaflet").service("leafletLogger",["nemSimpleLogger",function(a){return a.spawn()}]),a.module("ui-leaflet").factory("nominatimService",["$q","$http","leafletHelpers","leafletMapDefaults",function(a,b,c,d){var e=c.isDefined;return{query:function(c,f){var g=d.getDefaults(f),h=g.nominatim.server,i=a.defer();return b.get(h,{params:{format:"json",limit:1,q:c}}).success(function(a){a.length>0&&e(a[0].boundingbox)?i.resolve(a[0]):i.reject("[Nominatim] Invalid address")}),i.promise}}}]),a.module("ui-leaflet").directive("bounds",["leafletLogger","$timeout","$http","leafletHelpers","nominatimService","leafletBoundsHelpers",function(b,c,d,e,f,g){var h=b;return{restrict:"A",scope:!1,replace:!1,require:["leaflet"],link:function(b,d,i,j){var k=e.isDefined,l=g.createLeafletBounds,m=j[0].getLeafletScope(),n=j[0],o=e.errorHeader+" [Bounds] ",p=function(a){return 0===a._southWest.lat&&0===a._southWest.lng&&0===a._northEast.lat&&0===a._northEast.lng};n.getMap().then(function(d){m.$on("boundsChanged",function(b){var e=b.currentScope,f=d.getBounds();if(!p(f)&&!e.settingBoundsFromScope){e.settingBoundsFromLeaflet=!0;var g={northEast:{lat:f._northEast.lat,lng:f._northEast.lng},southWest:{lat:f._southWest.lat,lng:f._southWest.lng},options:f.options};a.equals(e.bounds,g)||(e.bounds=g),c(function(){e.settingBoundsFromLeaflet=!1})}});var e;m.$watch("bounds",function(a){if(!b.settingBoundsFromLeaflet){if(k(a.address)&&a.address!==e)return b.settingBoundsFromScope=!0,f.query(a.address,i.id).then(function(a){var b=a.boundingbox,c=[[b[0],b[2]],[b[1],b[3]]];d.fitBounds(c)},function(a){h.error(o+" "+a+".")}),e=a.address,void c(function(){b.settingBoundsFromScope=!1});var g=l(a);g&&!d.getBounds().equals(g)&&(b.settingBoundsFromScope=!0,d.fitBounds(g,a.options),c(function(){b.settingBoundsFromScope=!1}))}},!0)})}}}]);var b=["center","lfCenter"],c={};b.forEach(function(b){c[b]=["leafletLogger","$q","$location","$timeout","leafletMapDefaults","leafletHelpers","leafletBoundsHelpers","leafletMapEvents",function(c,d,e,f,g,h,i,j){var k,l=h.isDefined,m=h.isNumber,n=h.isSameCenterOnMap,o=h.safeApply,p=h.isValidCenter,q=i.isValidBounds,r=h.isUndefinedOrEmpty,s=h.errorHeader,t=c,u=function(a,b){return l(a)&&q(a)&&r(b)};return{restrict:"A",scope:!1,replace:!1,require:"leaflet",controller:function(){k=d.defer(),this.getCenter=function(){return k.promise}},link:function(c,d,h,q){var r=q.getLeafletScope(),v=r[b];q.getMap().then(function(c){var d=g.getDefaults(h.id);if(h[b].search("-")!==-1)return t.error(s+' The "center" variable can\'t use a "-" on its key name: "'+h[b]+'".'),void c.setView([d.center.lat,d.center.lng],d.center.zoom);if(u(r.bounds,v))c.fitBounds(i.createLeafletBounds(r.bounds),r.bounds.options),v=c.getCenter(),o(r,function(d){a.extend(d[b],{lat:c.getCenter().lat,lng:c.getCenter().lng,zoom:c.getZoom(),autoDiscover:!1})}),o(r,function(a){var b=c.getBounds();a.bounds={northEast:{lat:b._northEast.lat,lng:b._northEast.lng},southWest:{lat:b._southWest.lat,lng:b._southWest.lng}}});else{if(!l(v))return t.error(s+' The "center" property is not defined in the main scope'),void c.setView([d.center.lat,d.center.lng],d.center.zoom);l(v.lat)&&l(v.lng)||l(v.autoDiscover)||a.copy(d.center,v)}var q,w;if("yes"===h.urlHashCenter){var x=function(){var a,b=e.search(),c=h.urlHashParam?h.urlHashParam:"c";if(l(b[c])){var d=b[c].split(":");3===d.length&&(a={lat:parseFloat(d[0]),lng:parseFloat(d[1]),zoom:parseInt(d[2],10)})}return a};q=x(),r.$on("$locationChangeSuccess",function(d){var e=d.currentScope,f=x();l(f)&&!n(f,c)&&a.extend(e[b],{lat:f.lat,lng:f.lng,zoom:f.zoom})})}r.$watch(b,function(b){if(!r.settingCenterFromLeaflet)return l(q)&&(a.copy(q,b),q=void 0),p(b)||b.autoDiscover===!0?b.autoDiscover===!0?(m(b.zoom)||c.setView([d.center.lat,d.center.lng],d.center.zoom),void(m(b.zoom)&&b.zoom>d.center.zoom?c.locate({setView:!0,maxZoom:b.zoom}):l(d.maxZoom)?c.locate({setView:!0,maxZoom:d.maxZoom}):c.locate({setView:!0}))):void(w&&n(b,c)||(r.settingCenterFromScope=!0,c.setView([b.lat,b.lng],b.zoom),j.notifyCenterChangedToBounds(r,c),f(function(){r.settingCenterFromScope=!1}))):void t.warn(s+" invalid 'center'")},!0),c.whenReady(function(){w=!0}),c.on("moveend",function(){k.resolve(),j.notifyCenterUrlHashChanged(r,c,h,e.search()),n(v,c)||r.settingCenterFromScope||(r.settingCenterFromLeaflet=!0,o(r,function(d){r.settingCenterFromScope||a.extend(d[b],{lat:c.getCenter().lat,lng:c.getCenter().lng,zoom:c.getZoom(),autoDiscover:!1}),j.notifyCenterChangedToBounds(r,c),f(function(){r.settingCenterFromLeaflet=!1})}))}),v.autoDiscover===!0&&c.on("locationerror",function(){t.warn(s+" The Geolocation API is unauthorized on this page."),p(v)?(c.setView([v.lat,v.lng],v.zoom),j.notifyCenterChangedToBounds(r,c)):(c.setView([d.center.lat,d.center.lng],d.center.zoom),j.notifyCenterChangedToBounds(r,c))})})}}}]}),b.forEach(function(b){a.module("ui-leaflet").directive(b,c[b])}),a.module("ui-leaflet").directive("controls",["leafletLogger","leafletHelpers","leafletControlHelpers",function(a,b,c){var d=a;return{restrict:"A",scope:!1,replace:!1,require:"?^leaflet",link:function(a,e,f,g){if(g){var h=c.createControl,i=c.isValidControlType,j=g.getLeafletScope(),k=b.isDefined,l=b.isArray,m={},n=b.errorHeader+" [Controls] ";a.$on("$destroy",function(){c.destroyMapLayersControl(a.mapId)}),g.getMap().then(function(a){j.$watchCollection("controls",function(b){for(var c in m)k(b[c])||(a.hasControl(m[c])&&a.removeControl(m[c]),delete m[c]);for(var e in b){var f,g=k(b[e].type)?b[e].type:e;if(!i(g))return void d.error(n+" Invalid control type: "+g+".");if("custom"!==g)f=h(g,b[e]),a.addControl(f),m[e]=f;else{var j=b[e];if(l(j))for(var o=0;o<j.length;o++){var p=j[o];a.addControl(p),m[e]=k(m[e])?m[e].concat([p]):[p]}else a.addControl(j),m[e]=j}}})})}}}}]),a.module("ui-leaflet").directive("decorations",["leafletLogger","leafletHelpers",function(b,c){var d=b;return{restrict:"A",scope:!1,replace:!1,require:"leaflet",link:function(b,e,f,g){function h(a){return l(a)&&l(a.coordinates)&&(k.isLoaded()||d.error("[AngularJS - Leaflet] The PolylineDecorator Plugin is not loaded.")),L.polylineDecorator(a.coordinates)}function i(a,b){if(l(a)&&l(b)&&l(b.coordinates)&&l(b.patterns))return a.setPaths(b.coordinates),a.setPatterns(b.patterns),a}var j=g.getLeafletScope(),k=c.PolylineDecoratorPlugin,l=c.isDefined,m={};g.getMap().then(function(b){j.$watch("decorations",function(c){for(var d in m)l(c[d])&&a.equals(c[d],m)||(b.removeLayer(m[d]),delete m[d]);for(var e in c){var f=c[e],g=h(f);l(g)&&(m[e]=g,b.addLayer(g),i(g,f))}},!0)})}}}]),a.module("ui-leaflet").directive("eventBroadcast",["leafletLogger","$rootScope","leafletHelpers","leafletMapEvents","leafletIterators",function(a,b,c,d,e){var f=a;return{restrict:"A",scope:!1,replace:!1,require:"leaflet",link:function(a,b,g,h){var i=c.isObject,j=c.isDefined,k=h.getLeafletScope(),l=k.eventBroadcast,m=d.getAvailableMapEvents(),n=d.addEvents;h.getMap().then(function(a){var b=[],c="broadcast";j(l.map)?i(l.map)?("emit"!==l.map.logic&&"broadcast"!==l.map.logic?f.warn("[AngularJS - Leaflet] Available event propagation logic are: 'emit' or 'broadcast'."):c=l.map.logic,i(l.map.enable)&&l.map.enable.length>=0?e.each(l.map.enable,function(a){b.indexOf(a)===-1&&m.indexOf(a)!==-1&&b.push(a)}):f.warn("[AngularJS - Leaflet] event-broadcast.map.enable must be an object check your model.")):f.warn("[AngularJS - Leaflet] event-broadcast.map must be an object check your model."):b=m,n(a,g.id,b,"eventName",k,c)})}}}]),a.module("ui-leaflet").directive("geojson",["$timeout","leafletLogger","leafletData","leafletHelpers","leafletWatchHelpers","leafletDirectiveControlsHelpers","leafletIterators","leafletGeoJsonEvents",function(b,c,d,e,f,g,h,i){var j=f.maybeWatch,k=e.watchOptions,l=g.extend,m=e,n=h,o={changeFromDirective:!1};return{restrict:"A",scope:!1,replace:!1,require:"leaflet",link:function(b,c,f,g){var h=e.isDefined,p=g.getLeafletScope(),q={},r=!1;g.getMap().then(function(b){var c;c=p.watchOptions&&p.watchOptions.geojson?p.watchOptions.geojson:k;var g=function(b,c){var d;return d=a.isFunction(b.onEachFeature)?b.onEachFeature:function(a,d){e.LabelPlugin.isLoaded()&&h(a.properties.description)&&d.bindLabel(a.properties.description),i.bindEvents(f.id,d,null,a,p,c,{resetStyleOnMouseout:b.resetStyleOnMouseout,mapId:f.id})}},s=m.isDefined(f.geojsonNested)&&m.isTruthy(f.geojsonNested),t=function(){if(q){var a=function(a){h(a)&&b.hasLayer(a)&&b.removeLayer(a)};return s?void n.each(q,function(b){a(b)}):void a(q)}},u=function(a,c){if(h(a)&&h(a.data)){var e=g(a,c);h(a.options)||m.modelChangeInDirective(o,"changeFromDirective",function(){a.options={style:a.style,filter:a.filter,onEachFeature:e,pointToLayer:a.pointToLayer}});var i=L.geoJson(a.data,a.options);c&&m.isString(c)?q[c]=i:q=i,i.addTo(b),r||(r=!0,d.setGeoJSON(q,f.id))}},v=function(a){if(t(),s){if(!a||!Object.keys(a).length)return;return void n.each(a,function(a,b){u(a,b)})}u(a)};l(f.id,"geojson",v,t),j(p,"geojson",c,function(a){o.changeFromDirective||v(a)})})}}}]),a.module("ui-leaflet").directive("layercontrol",["$filter","leafletLogger","leafletData","leafletHelpers",function(b,c,d,e){var f=c;return{restrict:"E",scope:{icons:"=?",autoHideOpacity:"=?",showGroups:"=?",title:"@",baseTitle:"@",overlaysTitle:"@"},replace:!0,transclude:!1,require:"^leaflet",controller:["$scope","$element","$sce",function(b,c,g){f.debug("[Angular Directive - Layers] layers",b,c);var h=e.safeApply,i=e.isDefined;a.extend(b,{baselayer:"",oldGroup:"",layerProperties:{},groupProperties:{},rangeIsSupported:e.rangeIsSupported(),changeBaseLayer:function(a,c){e.safeApply(b,function(c){c.baselayer=a,d.getMap().then(function(e){d.getLayers().then(function(d){if(!e.hasLayer(d.baselayers[a])){for(var f in c.layers.baselayers)c.layers.baselayers[f].icon=c.icons.unradio,e.hasLayer(d.baselayers[f])&&e.removeLayer(d.baselayers[f]);e.addLayer(d.baselayers[a]),c.layers.baselayers[a].icon=b.icons.radio}})})}),c.preventDefault()},moveLayer:function(a,c,d){var e=Object.keys(b.layers.baselayers).length;if(c>=1+e&&c<=b.overlaysArray.length+e){var f;for(var g in b.layers.overlays)if(b.layers.overlays[g].index===c){f=b.layers.overlays[g];break}f&&h(b,function(){f.index=a.index,a.index=c})}d.stopPropagation(),d.preventDefault()},initIndex:function(a,c){var d=Object.keys(b.layers.baselayers).length;a.index=i(a.index)?a.index:c+d+1},initGroup:function(a){b.groupProperties[a]=b.groupProperties[a]?b.groupProperties[a]:{}},toggleOpacity:function(a,c){if(c.visible){if(b.autoHideOpacity&&!b.layerProperties[c.name].opacityControl)for(var d in b.layerProperties)b.layerProperties[d].opacityControl=!1;b.layerProperties[c.name].opacityControl=!b.layerProperties[c.name].opacityControl}a.stopPropagation(),a.preventDefault()},toggleLegend:function(a){b.layerProperties[a.name].showLegend=!b.layerProperties[a.name].showLegend},showLegend:function(a){return a.legend&&b.layerProperties[a.name].showLegend},unsafeHTML:function(a){return g.trustAsHtml(a)},getOpacityIcon:function(a){return a.visible&&b.layerProperties[a.name].opacityControl?b.icons.close:b.icons.open},getGroupIcon:function(a){return a.visible?b.icons.check:b.icons.uncheck},changeGroupVisibility:function(a){if(i(b.groupProperties[a])){var c=b.groupProperties[a].visible;for(var d in b.layers.overlays){var e=b.layers.overlays[d];e.group===a&&(e.visible=c)}}}});var j=c.get(0);L.Browser.touch?L.DomEvent.on(j,"click",L.DomEvent.stopPropagation):(L.DomEvent.disableClickPropagation(j),L.DomEvent.on(j,"mousewheel",L.DomEvent.stopPropagation))}],template:'<div class="angular-leaflet-control-layers" ng-show="overlaysArray.length"><h4 ng-if="title">{{ title }}</h4><div class="lf-baselayers"><h5 class="lf-title" ng-if="baseTitle">{{ baseTitle }}</h5><div class="lf-row" ng-repeat="(key, layer) in baselayersArray"><label class="lf-icon-bl" ng-click="changeBaseLayer(key, $event)"><input class="leaflet-control-layers-selector" type="radio" name="lf-radio" ng-show="false" ng-checked="baselayer === key" ng-value="key" /> <i class="lf-icon lf-icon-radio" ng-class="layer.icon"></i><div class="lf-text">{{layer.name}}</div></label></div></div><div class="lf-overlays"><h5 class="lf-title" ng-if="overlaysTitle">{{ overlaysTitle }}</h5><div class="lf-container"><div class="lf-row" ng-repeat="layer in (o = (overlaysArray | orderBy:\'index\':order))" ng-init="initIndex(layer, $index)"><label class="lf-icon-ol-group" ng-if="showGroups &amp;&amp; layer.group &amp;&amp; layer.group != o[$index-1].group"><input class="lf-control-layers-selector" type="checkbox" ng-show="false" ng-change="changeGroupVisibility(layer.group)" ng-model="groupProperties[layer.group].visible"/> <i class="lf-icon lf-icon-check" ng-class="getGroupIcon(groupProperties[layer.group])"></i><div class="lf-text">{{ layer.group }}</div></label><label class="lf-icon-ol"><input class="lf-control-layers-selector" type="checkbox" ng-show="false" ng-model="layer.visible"/> <i class="lf-icon lf-icon-check" ng-class="layer.icon"></i><div class="lf-text">{{layer.name}}</div></label><div class="lf-icons"><i class="lf-icon lf-up" ng-class="icons.up" ng-click="moveLayer(layer, layer.index - orderNumber, $event)"></i> <i class="lf-icon lf-down" ng-class="icons.down" ng-click="moveLayer(layer, layer.index + orderNumber, $event)"></i> <i class="lf-icon lf-toggle-legend" ng-class="icons.toggleLegend" ng-if="layer.legend" ng-click="toggleLegend(layer)"></i> <i class="lf-icon lf-open" ng-class="getOpacityIcon(layer)" ng-click="toggleOpacity($event, layer)"></i></div><div class="lf-legend" ng-if="showLegend(layer)" ng-bind-html="unsafeHTML(layer.legend)"></div><div class="lf-opacity clearfix" ng-if="layer.visible &amp;&amp; layerProperties[layer.name].opacityControl"><label ng-if="rangeIsSupported" class="pull-left" style="width: 50%">0</label><label ng-if="rangeIsSupported" class="pull-left text-right" style="width: 50%">100</label><input ng-if="rangeIsSupported" class="clearfix" type="range" min="0" max="1" step="0.05" class="lf-opacity-control" ng-model="layerProperties[layer.name].layerOptions.opacity"/><h6 ng-if="!rangeIsSupported">Range is not supported in this browser</h6></div></div></div></div></div>',link:function(b,c,f,g){var h=e.isDefined,i=g.getLeafletScope(),j=i.layers;b.$watch("icons",function(){var c={uncheck:"fa fa-square-o",check:"fa fa-check-square-o",radio:"fa fa-dot-circle-o",unradio:"fa fa-circle-o",up:"fa fa-angle-up",down:"fa fa-angle-down",open:"fa fa-angle-double-down",close:"fa fa-angle-double-up",toggleLegend:"fa fa-pencil-square-o"};h(b.icons)?(a.extend(c,b.icons),a.extend(b.icons,c)):b.icons=c}),f.order=!h(f.order)||"normal"!==f.order&&"reverse"!==f.order?"normal":f.order,b.order="normal"===f.order,b.orderNumber="normal"===f.order?-1:1,b.layers=j,g.getMap().then(function(a){i.$watch("layers.baselayers",function(c){var e={};d.getLayers().then(function(d){var f;for(f in c){var g=c[f];g.icon=b.icons[a.hasLayer(d.baselayers[f])?"radio":"unradio"],e[f]=g}b.baselayersArray=e})}),i.$watch("layers.overlays",function(a){var c=[],e={};d.getLayers().then(function(){var d;for(d in a){var f=a[d];f.icon=b.icons[f.visible?"check":"uncheck"],c.push(f),h(b.layerProperties[f.name])||(h(f.layerOptions.opacity)&&(f.layerOptions.opacity=1),b.layerProperties[f.name]={opacityControl:!1,showLegend:!0,layerOptions:f.layerOptions}),h(f.group)&&(h(b.groupProperties[f.group])||(b.groupProperties[f.group]={visible:!1}),e[f.group]=h(e[f.group])?e[f.group]:{count:0,visibles:0},e[f.group].count++,f.visible&&e[f.group].visibles++)}for(d in e)b.groupProperties[d].visible=e[d].visibles===e[d].count;b.overlaysArray=c})},!0)})}}}]),a.module("ui-leaflet").directive("layers",["leafletLogger","$q","leafletData","leafletHelpers","leafletLayerHelpers","leafletControlHelpers",function(b,c,d,e,f,g){return{restrict:"A",scope:!1,replace:!1,require:"leaflet",controller:["$scope",function(a){a._leafletLayers=c.defer(),this.getLayers=function(){return a._leafletLayers.promise}}],link:function(b,c,h,i){var j=e.isDefined,k={},l=i.getLeafletScope(),m=l.layers,n=f.createLayer,o=f.safeAddLayer,p=f.safeRemoveLayer,q=f.changeOpacityListener,r=g.updateLayersControl,s=!1;b.$on("$destroy",function(){g.destroyMapLayersControl(b.mapId)}),i.getMap().then(function(c){b._leafletLayers.resolve(k),d.setLayers(k,h.id),k.baselayers={},k.overlays={};var e=h.id,f=!1;for(var g in m.baselayers){var i=n(m.baselayers[g]);j(i)?(k.baselayers[g]=i,m.baselayers[g].top===!0&&(o(c,k.baselayers[g]),f=!0)):delete m.baselayers[g]}!f&&Object.keys(k.baselayers).length>0&&o(c,k.baselayers[Object.keys(m.baselayers)[0]]);for(g in m.overlays){"cartodb"===m.overlays[g].type;var t=n(m.overlays[g]);j(t)?(k.overlays[g]=t,m.overlays[g].visible===!0&&o(c,k.overlays[g])):delete m.overlays[g]}l.$watch("layers.baselayers",function(b,d){if(a.equals(b,d))return s=r(c,e,s,b,m.overlays,k),!0;for(var f in k.baselayers)j(b[f])&&!b[f].doRefresh||(c.hasLayer(k.baselayers[f])&&c.removeLayer(k.baselayers[f]),delete k.baselayers[f],b[f]&&b[f].doRefresh&&(b[f].doRefresh=!1));for(var g in b)if(j(k.baselayers[g]))b[g].top!==!0||c.hasLayer(k.baselayers[g])?b[g].top===!1&&c.hasLayer(k.baselayers[g])&&c.removeLayer(k.baselayers[g]):o(c,k.baselayers[g]);else{var h=n(b[g]);j(h)&&(k.baselayers[g]=h,b[g].top===!0&&o(c,k.baselayers[g]))}var i=!1;for(var l in k.baselayers)if(c.hasLayer(k.baselayers[l])){i=!0;break}!i&&Object.keys(k.baselayers).length>0&&o(c,k.baselayers[Object.keys(k.baselayers)[0]]),s=r(c,e,s,b,m.overlays,k)},!0),l.$watch("layers.overlays",function(b,d){if(a.equals(b,d))return s=r(c,e,s,m.baselayers,b,k),!0;for(var f in k.overlays)if(!j(b[f])||b[f].doRefresh){if(c.hasLayer(k.overlays[f])){var g=j(b[f])?b[f].layerOptions:null;p(c,k.overlays[f],g)}delete k.overlays[f],b[f]&&b[f].doRefresh&&(b[f].doRefresh=!1)}for(var h in b){if(j(k.overlays[h])){b[h].visible&&!c.hasLayer(k.overlays[h])?o(c,k.overlays[h]):b[h].visible===!1&&c.hasLayer(k.overlays[h])&&p(c,k.overlays[h],b[h].layerOptions);var i=k.overlays[h];c.hasLayer(k.overlays[h])&&(b[h].layerOptions.opacity!==d[h].layerOptions.opacity&&(j(i.setOpacity)&&i.setOpacity(b[h].layerOptions.opacity),j(i.getLayers)&&j(i.eachLayer)&&i.eachLayer(q(b[h].layerOptions.opacity))),j(b[h].index)&&i.setZIndex&&b[h].index!==d[h].index&&i.setZIndex(b[h].index))}else{var l=n(b[h]);if(!j(l))continue;k.overlays[h]=l,b[h].visible===!0&&o(c,k.overlays[h]),j(b[h].index)&&k.overlays[h].setZIndex&&k.overlays[h].setZIndex(b[h].index)}b[h].visible&&c._loaded&&b[h].data&&"heatmap"===b[h].type&&(k.overlays[h].setData(b[h].data),k.overlays[h].update())}s=r(c,e,s,m.baselayers,b,k)},!0)})}}}]),a.module("ui-leaflet").directive("legend",["leafletLogger","$http","$timeout","leafletHelpers","leafletLegendHelpers",function(a,b,c,d,e){var f=a,g=d.errorHeader+" [Legend] ";return{restrict:"A",scope:!1,replace:!1,require:"leaflet",transclude:!1,link:function(a,b,c,h){var i,j,k,l,m=d.isArray,n=d.isString,o=d.isDefined,p=d.isFunction,q=h.getLeafletScope(),r=q.legend;q.$watch("legend",function(a){o(a)&&(i=a.legendClass?a.legendClass:"legend",j=a.position||"bottomright",l=a.type||"arcgis")},!0);var s=function(a,b,c){b&&b.layers&&b.layers.length>0&&(o(k)?e.updateLegend(k.getContainer(),b,l,c):(k=L.control({position:j}),k.onAdd=e.getOnAddLegend(b,i,l,c),k.addTo(a)),o(r.loadedData)&&p(r.loadedData)&&r.loadedData())};h.getMap().then(function(a){q.$watch("legend",function(b){return o(b)?o(b.url)||"arcgis"!==l||m(b.colors)&&m(b.labels)&&b.colors.length===b.labels.length?o(b.url)?void f.info(g+" loading legend service."):(o(k)&&(k.removeFrom(a),k=null),k=L.control({position:j}),"arcgis"===l&&(k.onAdd=e.getOnAddArrayLegend(b,i)),void k.addTo(a)):void f.warn(g+" legend.colors and legend.labels must be set."):void(o(k)&&(k.removeFrom(a),k=null))}),q.$watch("legend.url",function(b){if(o(b)){if(!m(b)&&!n(b))return void f.warn(g+" legend.url must be an array or string.");for(var d,h=n(b)?[b]:b,i=function(c,e){return function(i){o(i.data.error)?f.warn(g+"Error loadin legend from: "+e,i.data.error.message):d&&d.layers&&d.layers.length>0?d.layers=d.layers.concat(i.data.layers):d=i.data,c===h.length-1&&s(a,d,b)}},j=function(a){f.warn(g+" legend.url not loaded.",a)},k=0;k<h.length;k++)e.addLegendURL(c.id,{url:h[k],method:"GET"}).then(i(k)).catch(j)}}),q.$watch("legend.legendData",function(b){f.debug("legendData",b),!o(q.legend.url)&&o(b)&&s(a,b)},!0)})}}}]),a.module("ui-leaflet").directive("markers",["leafletLogger","$rootScope","$q","leafletData","leafletHelpers","leafletMapDefaults","leafletMarkersHelpers","leafletMarkerEvents","leafletIterators","leafletWatchHelpers","leafletDirectiveControlsHelpers",function(b,c,d,e,f,g,h,i,j,k,l){var m=f.isDefined,n=f.errorHeader,o=f,p=f.isString,q=h.addMarkerWatcher,r=h.updateMarker,s=h.listenMarkerEvents,t=h.addMarkerToGroup,u=h.createMarker,v=h.deleteMarker,w=h.getModelFromModels,x=h.getLayerModels,y=h.resetUnusedMarkerGroups,z=j,A=f.watchOptions,B=k.maybeWatch,C=l.extend,D=b,E={changeFromDirective:!1},F=function(a,b,c){if(Object.keys(a).length){if(c&&p(c)){if(!a[c]||!Object.keys(a[c]).length)return;return a[c][b]}return a[b]}},G=function(a,b,c,d){return d&&p(d)?(m(b[d])||(b[d]={}),b[d][c]=a):b[c]=a,a},H=function(a,b,c,d,e,f){if(!p(a))return D.error(n+" A layername must be a string"),!1;if(!m(b))return D.error(n+" You must add layers to the directive if the markers are going to use this functionality."),!1;if(!m(b.overlays)||!m(b.overlays[a]))return D.error(n+' A marker can only be added to a layer of type "group"'),!1;var g=b.overlays[a];return g instanceof L.LayerGroup||g instanceof L.FeatureGroup?(g.addLayer(d),null===e&&f.hasLayer(d)&&c.focus===!0&&d.openPopup(),!0):(D.error(n+' Adding a marker to an overlay needs a overlay of the type "group" or "featureGroup"'),!1)},I=function(a,b,c,d,e,f,g,h,j,k){z.each(b,function(b,l){if(!k[l]){if(l.search("-")!==-1)return void D.error('The marker can\'t use a "-" on his key name: "'+l+'".');var p=o.getObjectDotPath(j?[j,l]:[l]),v=F(f,l,j);o.modelChangeInDirective(E,"changeFromDirective",function(){if(m(v)){var k=w(c,l,j);r(b,k,v,p,g,e,d)}else{var x=u(b),y=(b?b.layer:void 0)||j;if(!m(x))return void D.error(n+" Received invalid data on the marker "+l+".");if(G(x,f,l,j),
+m(b.message)&&x.bindPopup(b.message,b.popupOptions),m(b.group)){var z=m(b.groupOption)?b.groupOption:null;t(x,b.group,z,d)}if(o.LabelPlugin.isLoaded()&&m(b.label)&&m(b.label.message)&&x.bindLabel(b.label.message,b.label.options),m(b)&&(m(b.layer)||m(j))){var A=H(y,e,b,x,h.individual.type,d);if(!A)return}else m(b.group)||(d.addLayer(x),null===h.individual.type&&b.focus===!0&&x.openPopup());null!==h.individual.type&&q(x,p,g,e,d,h.individual),s(x,b,g,h.individual.type,d),i.bindEvents(a,x,p,b,g,y)}})}})},J=function(b,c,d,e,f){var g,h,i=!1,j=!1,k=m(c);for(var l in d)i||(D.debug(n+"[markers] destroy: "),i=!0),k&&(h=b[l],g=c[l],j=e&&a.equals(h,g)),m(b)&&Object.keys(b).length&&m(b[l])&&Object.keys(b[l]).length&&!j||f&&o.isFunction(f)&&f(h,g,l)},K=function(a,b,c,d,e){J(a,b,c,!1,function(a,b,f){D.debug(n+"[marker] is deleting marker: "+f),v(c[f],d,e),delete c[f]})},M=function(a,b,c){var d={};return J(a,b,c,!0,function(a,b,c){D.debug(n+"[marker] is already rendered, marker: "+c),d[c]=a}),d};return{restrict:"A",scope:!1,replace:!1,require:["leaflet","?layers"],link:function(a,b,c,f){var g=f[0],h=g.getLeafletScope();g.getMap().then(function(b){var g,i={};g=m(f[1])?f[1].getLayers:function(){var a=d.defer();return a.resolve(),a.promise};var j;j=h.watchOptions&&h.watchOptions.markers?h.watchOptions.markers:A;var k=m(c.markersNested)&&o.isTruthy(c.markersNested);g().then(function(d){var f=function(a,c){return y(),k?void z.each(a,function(a,e){var f=x(c,e);K(a,f,i[e],b,d)}):void K(a,c,i,b,d)},g=function(a,e){f(a,e);var g=null;return k?void z.each(a,function(f,k){var l=x(e,k),m=x(a,k);g=M(m,l,i[k]),I(c.id,f,e,b,d,i,h,j,k,g)}):(g=M(a,e,i),void I(c.id,a,e,b,d,i,h,j,void 0,g))};C(c.id,"markers",g,f),e.setMarkers(i,c.id),B(h,"markers",j,function(a,b){E.changeFromDirective||g(a,b)}),a.$on("$destroy",function(){K(h.markers,{},i,b,d)})})})}}}]),a.module("ui-leaflet").directive("maxbounds",["leafletLogger","leafletMapDefaults","leafletBoundsHelpers","leafletHelpers",function(a,b,c,d){return{restrict:"A",scope:!1,replace:!1,require:"leaflet",link:function(a,b,e,f){var g=f.getLeafletScope(),h=c.isValidBounds,i=d.isNumber;f.getMap().then(function(a){g.$watch("maxbounds",function(b){if(!h(b))return void a.setMaxBounds();var d=c.createLeafletBounds(b);i(b.pad)&&(d=d.pad(b.pad)),a.setMaxBounds(d),e.center||e.lfCenter||a.fitBounds(d)})})}}}]),a.module("ui-leaflet").directive("paths",["leafletLogger","$q","leafletData","leafletMapDefaults","leafletHelpers","leafletPathsHelpers","leafletPathEvents","leafletWatchHelpers",function(a,b,c,d,e,f,g,h){var i=a;return{restrict:"A",scope:!1,replace:!1,require:["leaflet","?layers"],link:function(a,j,k,l){var m=l[0],n=e.isDefined,o=e.isString,p=m.getLeafletScope(),q=p.paths,r=f.createPath,s=g.bindPathEvents,t=f.setPathOptions,u=h.maybeWatch;m.getMap().then(function(a){var f,g=d.getDefaults(k.id);if(f=n(l[1])?l[1].getLayers:function(){var a=b.defer();return a.resolve(),a.promise},n(q)){var h,j={type:"watchCollection",individual:{type:"watchDeep"}};h=p.watchOptions&&p.watchOptions.paths?p.watchOptions.paths:j,f().then(function(b){var d={};c.setPaths(d,k.id);var f=function(c,d,e){var f='paths["'+d+'"]';u(p,f,e,function(d,e,f){if(!n(d)){if(n(e.layer))for(var g in b.overlays){var h=b.overlays[g];h.removeLayer(c)}return a.removeLayer(c),void f()}t(c,d.type,d)})},j=function(b){for(var c in d)n(b[c])||(a.removeLayer(d[c]),delete d[c])},l=function(c){j(c);for(var l in c)if(0!==l.search("\\$"))if(l.search("-")===-1){if(!n(d[l])){var m=c[l],q=r(l,c[l],g);if(n(q)&&n(m.message)&&q.bindPopup(m.message,m.popupOptions),e.LabelPlugin.isLoaded()&&n(m.label)&&n(m.label.message)&&q.bindLabel(m.label.message,m.label.options),n(m)&&n(m.layer)){if(!o(m.layer)){i.error("[AngularJS - Leaflet] A layername must be a string");continue}if(!n(b)){i.error("[AngularJS - Leaflet] You must add layers to the directive if the markers are going to use this functionality.");continue}if(!n(b.overlays)||!n(b.overlays[m.layer])){i.error('[AngularJS - Leaflet] A path can only be added to a layer of type "group"');continue}var u=b.overlays[m.layer];if(!(u instanceof L.LayerGroup||u instanceof L.FeatureGroup)){i.error('[AngularJS - Leaflet] Adding a path to an overlay needs a overlay of the type "group" or "featureGroup"');continue}d[l]=q,u.addLayer(q),null!==h.individual.type?f(q,l,h.individual):t(q,m.type,m)}else n(q)&&(d[l]=q,a.addLayer(q),null!==h.individual.type?f(q,l,h.individual):t(q,m.type,m));s(k.id,q,l,m,p)}}else i.error('[AngularJS - Leaflet] The path name "'+l+'" is not valid. It must not include "-" and a number.')};u(p,"paths",h,function(a){l(a)})})}})}}}]),a.module("ui-leaflet").directive("tiles",["leafletLogger","leafletData","leafletMapDefaults","leafletHelpers",function(b,c,d,e){var f=b;return{restrict:"A",scope:!1,replace:!1,require:"leaflet",link:function(b,g,h,i){var j=e.isDefined,k=i.getLeafletScope(),l=k.tiles;return j(l)&&j(l.url)?void i.getMap().then(function(b){var e,f=d.getDefaults(h.id);k.$watch("tiles",function(d){var g=f.tileLayerOptions,i=f.tileLayer;return!j(d.url)&&j(e)?void b.removeLayer(e):j(e)?j(d.url)&&j(d.options)&&!a.equals(d.options,g)?(b.removeLayer(e),g=f.tileLayerOptions,a.copy(d.options,g),i=d.url,e=L.tileLayer(i,g),e.addTo(b),void c.setTiles(e,h.id)):void(j(d.url)&&e.setUrl(d.url)):(j(d.options)&&a.copy(d.options,g),j(d.url)&&(i=d.url),e=L.tileLayer(i,g),e.addTo(b),void c.setTiles(e,h.id))},!0)}):void f.warn("[AngularJS - Leaflet] The 'tiles' definition doesn't have the 'url' property.")}}}]),a.module("ui-leaflet").directive("watchOptions",["$log","$rootScope","$q","leafletData","leafletHelpers",function(b,c,d,e,f){var g=f.isDefined,h=f.errorHeader,i=f.isObject,j=b;return{restrict:"A",scope:!1,replace:!1,require:["leaflet"],link:function(b,c,d,e){var f=e[0],k=f.getLeafletScope(),l=function(a){return"watch"===a||"watchCollection"===a||"watchDeep"===a||null===a};g(k.watchOptions)&&i(k.watchOptions)&&a.forEach(["markers","geojson","paths"],function(a){g(k.watchOptions[a])&&(l(k.watchOptions[a].type)||j.error(h+" watchOptions."+a+".type is not a valid type."),g(k.watchOptions[a].individual)?l(k.watchOptions[a].individual.type)||j.error(h+" watchOptions."+a+".individual.type is not a valid type."):j.error(h+" watchOptions."+a+".type.individual must be defined."))})}}}]),a.module("ui-leaflet").factory("leafletEventsHelpersFactory",["$rootScope","$q","leafletLogger","leafletHelpers",function(b,c,d,e){var f=e.safeApply,g=e.isDefined,h=e.isObject,i=e.isArray,j=e.errorHeader,k=d,l=function(a,b){this.rootBroadcastName=a,k.debug("leafletEventsHelpersFactory: lObjectType: "+b+"rootBroadcastName: "+a),this.lObjectType=b};return l.prototype.getAvailableEvents=function(){return[]},l.prototype.genDispatchEvent=function(a,b,c,d,e,f,g,h,i){var j=this;return a=a||"",a&&(a="."+a),function(l){var m=j.rootBroadcastName+a+"."+b;k.debug(m),j.fire(d,m,c,l,l.target||e,g,f,h,i)}},l.prototype.fire=function(c,d,e,h,i,j,k,l,m){f(c,function(){var f={leafletEvent:h,leafletObject:i,modelName:k,model:j};g(l)&&a.extend(f,{layerName:l}),"emit"===e?c.$emit(d,f):b.$broadcast(d,f)})},l.prototype.bindEvents=function(a,b,c,d,e,f,l){var m=[],n="emit",o=this;if(g(e.eventBroadcast))if(h(e.eventBroadcast))if(g(e.eventBroadcast[o.lObjectType]))if(h(e.eventBroadcast[o.lObjectType])){g(e.eventBroadcast[this.lObjectType].logic)&&"emit"!==e.eventBroadcast[o.lObjectType].logic&&"broadcast"!==e.eventBroadcast[o.lObjectType].logic&&k.warn(j+"Available event propagation logic are: 'emit' or 'broadcast'.");var p=!1,q=!1;g(e.eventBroadcast[o.lObjectType].enable)&&i(e.eventBroadcast[o.lObjectType].enable)&&(p=!0),g(e.eventBroadcast[o.lObjectType].disable)&&i(e.eventBroadcast[o.lObjectType].disable)&&(q=!0),p&&q?k.warn(j+"can not enable and disable events at the same time"):p||q?p?e.eventBroadcast[this.lObjectType].enable.forEach(function(a){m.indexOf(a)!==-1?k.warn(j+"This event "+a+" is already enabled"):o.getAvailableEvents().indexOf(a)===-1?k.warn(j+"This event "+a+" does not exist"):m.push(a)}):(m=this.getAvailableEvents(),e.eventBroadcast[o.lObjectType].disable.forEach(function(a){var b=m.indexOf(a);b===-1?k.warn(j+"This event "+a+" does not exist or has been already disabled"):m.splice(b,1)})):k.warn(j+"must enable or disable events")}else k.warn(j+"event-broadcast."+[o.lObjectType]+" must be an object check your model.");else m=this.getAvailableEvents();else k.error(j+"event-broadcast must be an object check your model.");else m=this.getAvailableEvents();return m.forEach(function(g){b.on(g,o.genDispatchEvent(a,g,n,e,b,c,d,f,l))}),n},l}]).service("leafletEventsHelpers",["leafletEventsHelpersFactory",function(a){return new a}]),a.module("ui-leaflet").factory("leafletGeoJsonEvents",["$rootScope","$q","leafletLogger","leafletHelpers","leafletEventsHelpersFactory","leafletData",function(a,b,c,d,e,f){var g=d.safeApply,h=e,i=function(){h.call(this,"leafletDirectiveGeoJson","geojson")};return i.prototype=new h,i.prototype.genDispatchEvent=function(b,c,d,e,i,j,k,l,m){var n=h.prototype.genDispatchEvent.call(this,b,c,d,e,i,j,k,l),o=this;return function(b){"mouseout"===c&&(m.resetStyleOnMouseout&&f.getGeoJSON(m.mapId).then(function(a){var c=l?a[l]:a;c.resetStyle(b.target)}),g(e,function(){a.$broadcast(o.rootBroadcastName+".mouseout",b)})),n(b)}},i.prototype.getAvailableEvents=function(){return["click","dblclick","mouseover","mouseout"]},new i}]),a.module("ui-leaflet").factory("leafletLabelEvents",["$rootScope","$q","leafletLogger","leafletHelpers","leafletEventsHelpersFactory",function(a,b,c,d,e){var f=d,g=e,h=function(){g.call(this,"leafletDirectiveLabel","markers")};return h.prototype=new g,h.prototype.genDispatchEvent=function(a,b,c,d,e,f,h,i){var j=f.replace("markers.","");return g.prototype.genDispatchEvent.call(this,a,b,c,d,e,j,h,i)},h.prototype.getAvailableEvents=function(){return["click","dblclick","mousedown","mouseover","mouseout","contextmenu"]},h.prototype.genEvents=function(a,b,c,d,e,g,h,i){var j=this,k=this.getAvailableEvents(),l=f.getObjectArrayPath("markers."+g);k.forEach(function(b){e.label.on(b,j.genDispatchEvent(a,b,c,d,e.label,l,h,i))})},h.prototype.bindEvents=function(a,b,c,d,e,f){},new h}]),a.module("ui-leaflet").factory("leafletMapEvents",["$rootScope","$q","leafletLogger","leafletHelpers","leafletEventsHelpers","leafletIterators",function(a,b,c,d,e,f){var g=d.isDefined,h=e.fire,i=function(){return["click","dblclick","mousedown","mouseup","mouseover","mouseout","mousemove","contextmenu","focus","blur","preclick","load","unload","viewreset","movestart","move","moveend","dragstart","drag","dragend","zoomstart","zoomanim","zoomend","zoomlevelschange","resize","autopanstart","layeradd","layerremove","baselayerchange","overlayadd","overlayremove","locationfound","locationerror","popupopen","popupclose","draw:created","draw:edited","draw:deleted","draw:drawstart","draw:drawstop","draw:editstart","draw:editstop","draw:deletestart","draw:deletestop"]},j=function(a,b,d,e){return e&&(e+="."),function(f){var g="leafletDirectiveMap."+e+b;c.debug(g),h(a,g,d,f,f.target,a)}},k=function(a){a.$broadcast("boundsChanged")},l=function(a,b,c,d){if(g(c.urlHashCenter)){var e=b.getCenter(),f=e.lat.toFixed(4)+":"+e.lng.toFixed(4)+":"+b.getZoom();g(d.c)&&d.c===f||a.$emit("centerUrlHash",f)}},m=function(a,b,c,d,e,g){f.each(c,function(c){var f={};f[d]=c,b||(b=a._container.id||""),a.on(c,j(e,c,g,b),f)})};return{getAvailableMapEvents:i,genDispatchMapEvent:j,notifyCenterChangedToBounds:k,notifyCenterUrlHashChanged:l,addEvents:m}}]),a.module("ui-leaflet").factory("leafletMarkerEvents",["$rootScope","$q","leafletLogger","leafletHelpers","leafletEventsHelpersFactory","leafletLabelEvents",function(a,b,c,d,e,f){var g=d.safeApply,h=d.isDefined,i=d,j=f,k=e,l=function(){k.call(this,"leafletDirectiveMarker","markers")};return l.prototype=new k,l.prototype.genDispatchEvent=function(b,c,d,e,f,h,i,j){var l=k.prototype.genDispatchEvent.call(this,b,c,d,e,f,h,i,j);return function(b){"click"===c?g(e,function(){a.$broadcast("leafletDirectiveMarkersClick",h)}):"dragend"===c&&(g(e,function(){i.lat=f.getLatLng().lat,i.lng=f.getLatLng().lng}),i.message&&i.focus===!0&&f.openPopup()),l(b)}},l.prototype.getAvailableEvents=function(){return["click","dblclick","mousedown","mouseover","mouseout","contextmenu","dragstart","drag","dragend","move","remove","popupopen","popupclose","touchend","touchstart","touchmove","touchcancel","touchleave"]},l.prototype.bindEvents=function(a,b,c,d,e,f){var g=k.prototype.bindEvents.call(this,a,b,c,d,e,f);i.LabelPlugin.isLoaded()&&h(b.label)&&j.genEvents(a,c,g,e,b,d,f)},new l}]);var d="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol?"symbol":typeof a};a.module("ui-leaflet").factory("leafletPathEvents",["$rootScope","$q","leafletLogger","leafletHelpers","leafletLabelEvents","leafletEventsHelpers",function(a,b,c,e,f,g){var h=e.isDefined,i=e.isObject,j=e,k=e.errorHeader,l=f,m=g.fire,n=c,o=function(a,b,c,d,e,f,g,h){return a=a||"",a&&(a="."+a),function(i){var j="leafletDirectivePath"+a+"."+b;n.debug(j),m(d,j,c,i,i.target||e,g,f,h)}},p=function(a,b,c,e,f){var g,m,p=[],r="broadcast";if(h(f.eventBroadcast))if(i(f.eventBroadcast))if(h(f.eventBroadcast.path))if(i(f.eventBroadcast.paths))n.warn(k+"event-broadcast.path must be an object check your model.");else{void 0!==f.eventBroadcast.path.logic&&null!==f.eventBroadcast.path.logic&&("emit"!==f.eventBroadcast.path.logic&&"broadcast"!==f.eventBroadcast.path.logic?n.warn(k+"Available event propagation logic are: 'emit' or 'broadcast'."):"emit"===f.eventBroadcast.path.logic&&(r="emit"));var s=!1,t=!1;if(void 0!==f.eventBroadcast.path.enable&&null!==f.eventBroadcast.path.enable&&"object"===d(f.eventBroadcast.path.enable)&&(s=!0),void 0!==f.eventBroadcast.path.disable&&null!==f.eventBroadcast.path.disable&&"object"===d(f.eventBroadcast.path.disable)&&(t=!0),s&&t)n.warn(k+"can not enable and disable events at the same time");else if(s||t)if(s)for(g=0;g<f.eventBroadcast.path.enable.length;g++)m=f.eventBroadcast.path.enable[g],p.indexOf(m)!==-1?n.warn(k+"This event "+m+" is already enabled"):q().indexOf(m)===-1?n.warn(k+"This event "+m+" does not exist"):p.push(m);else for(p=q(),g=0;g<f.eventBroadcast.path.disable.length;g++){m=f.eventBroadcast.path.disable[g];var u=p.indexOf(m);u===-1?n.warn(k+"This event "+m+" does not exist or has been already disabled"):p.splice(u,1)}else n.warn(k+"must enable or disable events")}else p=q();else n.error(k+"event-broadcast must be an object check your model.");else p=q();for(g=0;g<p.length;g++)m=p[g],b.on(m,o(a,m,r,f,p,c));j.LabelPlugin.isLoaded()&&h(b.label)&&l.genEvents(a,c,r,f,b,e)},q=function(){return["click","dblclick","mousedown","mouseover","mouseout","contextmenu","add","remove","popupopen","popupclose"]};return{getAvailablePathEvents:q,bindPathEvents:p}}])}(angular);
\ No newline at end of file
diff --git a/www/plugins/es/js/controllers/blockchain-controllers.js b/www/plugins/es/js/controllers/blockchain-controllers.js
index 5d483ae872b64f6966c4a3b445b17c65b02042ab..4ec8ff4f0199f50fce5c853032fd216fa8c06a42 100644
--- a/www/plugins/es/js/controllers/blockchain-controllers.js
+++ b/www/plugins/es/js/controllers/blockchain-controllers.js
@@ -23,6 +23,9 @@ angular.module('cesium.es.blockchain.controllers', ['cesium.es.services'])
             templateUrl: "plugins/es/templates/blockchain/lookup.html",
             controller: 'ESBlockLookupCtrl'
           }
+        },
+        data: {
+          silentLocationChange: true
         }
       })
     ;
@@ -32,7 +35,7 @@ angular.module('cesium.es.blockchain.controllers', ['cesium.es.services'])
 ;
 
 
-function ESBlockLookupController($scope, $state, $controller, $ionicPopover, UIUtils, esBlockchain, $ionicHistory) {
+function ESBlockLookupController($scope, $controller, $ionicPopover, $location, UIUtils, esBlockchain) {
   'ngInject';
 
   // Initialize the super class and extend it.
@@ -56,15 +59,8 @@ function ESBlockLookupController($scope, $state, $controller, $ionicPopover, UIU
 
     $scope.doSearch();
 
-    $ionicHistory.nextViewOptions({
-      disableAnimate: true,
-      disableBack: true,
-      historyRoot: true
-    });
-    $state.go('app.blockchain_search', {q: $scope.search.query}, {
-      reload: false,
-      inherit: true,
-      notify: false});
+    // Update location href
+    $location.search({q: $scope.search.query}).replace();
   };
 
   $scope.doSearchLast = function() {
@@ -74,15 +70,7 @@ function ESBlockLookupController($scope, $state, $controller, $ionicPopover, UIU
     $scope.search.sort = undefined;
     $scope.doSearch();
 
-    $ionicHistory.nextViewOptions({
-      disableAnimate: true,
-      disableBack: true,
-      historyRoot: true
-    });
-    $state.go('app.blockchain_search', {q: undefined}, {
-      reload: false,
-      inherit: true,
-      notify: false});
+    $location.search({q: undefined}).replace();
   };
 
 
diff --git a/www/plugins/map/js/controllers/network-controllers.js b/www/plugins/map/js/controllers/network-controllers.js
index c058c7825357c10de0d0851cb65608781cbcc41f..a0df7e4d104177222a67b423ed7ce4e1a30ca60a 100644
--- a/www/plugins/map/js/controllers/network-controllers.js
+++ b/www/plugins/map/js/controllers/network-controllers.js
@@ -21,21 +21,22 @@ angular.module('cesium.map.network.controllers', ['cesium.services', 'cesium.map
       // [NEW] Ajout d'une nouvelle page #/app/wot/map
       $stateProvider
         .state('app.view_network_map', {
-          url: "/network/map?lat&lng&zoom",
+          url: "/network/map?c",
           views: {
             'menuContent': {
               templateUrl: "plugins/map/templates/network/view_map.html",
               controller: 'MapNetworkViewCtrl'
             }
+          },
+          data: {
+            silentLocationChange: true
           }
         });
     }
-
-    L.AwesomeMarkers.Icon.prototype.options.prefix = 'ion';
   })
 
   // [NEW] Manage events from the page #/app/wot/map
-  .controller('MapNetworkViewCtrl', function($scope, $controller, $q, $interpolate, $translate, $state, $filter, $templateCache, $timeout, $ionicHistory,
+  .controller('MapNetworkViewCtrl', function($scope, $controller, $q, $interpolate, $translate, $filter, $templateCache, $timeout, $location,
                                          esGeo, UIUtils, csNetwork, MapUtils, leafletData) {
     'ngInject';
 
@@ -103,64 +104,66 @@ angular.module('cesium.map.network.controllers', ['cesium.services', 'cesium.map
             }
           }
         },
+      loading: true,
         markers: {}
       });
 
     var inheritedEnter = $scope.enter;
     $scope.enter = function(e, state) {
-      if ($scope.networkStarted) return;
-
-      // remember state, to be able to refresh location
-      $scope.stateName = state && state.stateName;
-      $scope.stateParams = angular.copy(state && state.stateParams||{});
+      if ($scope.map.loading) {
 
-      // Read center from state params
-      $scope.stateCenter = MapUtils.center(state.stateParams);
-
-      // inherited
-      return inheritedEnter(e, state);
-    };
-    $scope.$on('$ionicView.enter', $scope.enter);
+        // Load the map (and init if need)
+        $scope.loadMap().then(function(map){
 
-    // Update the browser location, to be able to refresh the page
-    $scope.updateLocation = function() {
+          // Load indicator
+          map.fire('dataloading');
 
-      $ionicHistory.nextViewOptions({
-        disableAnimate: true,
-        disableBack: true,
-        historyRoot: false
-      });
+          // inherited
+          return inheritedEnter(e, state); // will call inherited load()
+        });
+      }
 
-      $scope.stateParams = $scope.stateParams || {};
-      $scope.stateParams.lat = ($scope.map.center.lat != MapUtils.constants.DEFAULT_CENTER.lat) ? $scope.map.center.lat : undefined;
-      $scope.stateParams.lng = ($scope.map.center.lng != MapUtils.constants.DEFAULT_CENTER.lng) ? $scope.map.center.lng : undefined;
-      $scope.stateParams.zoom = ($scope.map.center.zoom != MapUtils.constants.DEFAULT_CENTER.zoom) ? $scope.map.center.zoom : undefined;
+      else {
+        // Make sur to have previous center coordinate defined in the location URL
+        $scope.updateLocationHref();
 
-      $state.go($scope.stateName, $scope.stateParams, {
-        reload: false,
-        inherit: true,
-        notify: false}
-      );
+        // inherited
+        return inheritedEnter(e, state);
+      }
     };
+    $scope.$on('$ionicView.enter', $scope.enter);
 
+    $scope.loadMap = function() {
+      return leafletData.getMap($scope.mapId).then(function(map) {
+        if (!$scope.map.loading) return map; // already loaded
 
-    // removeIf(device)
-    $scope.onMapCenterChanged = function() {
-      if (!$scope.loading) {
-        $timeout($scope.updateLocation, 500);
-      }
-    };
-    $scope.$watch('map.center', $scope.onMapCenterChanged, true);
-    // endRemoveIf(device)
+        // Add loading control
+        loadingControl = L.Control.loading({
+          position: 'topright',
+          separate: true
+        });
+        loadingControl.addTo(map);
 
-    $scope.showHelpTip = function() {
-      // override subclass
+        // Add search control
+        var searchTip = $interpolate($templateCache.get('plugins/map/templates/network/item_search_tooltip.html'));
+        searchControl = MapUtils.control.search({
+          layer: markersSearchLayer,
+          propertyName: 'title',
+          zoom: 10,
+          buildTip: function (text, val) {
+            return searchTip(val.layer.options);
+          }
+        });
+        searchControl.addTo(map);
+
+        $scope.map.loading = false;
+        return map;
+      });
     };
 
     $scope.updateView = function(data) {
       console.debug("[map] [peers] Updating UI");
 
-      $scope.search.memberPeersCount = data.memberPeersCount;
       // Always tru if network not started (e.g. after leave+renter the view)
       $scope.search.loading = !$scope.networkStarted || csNetwork.isBusy();
 
@@ -185,7 +188,7 @@ angular.module('cesium.map.network.controllers', ['cesium.services', 'cesium.map
         var address = peer.hasValid4(bma) ? bma.ipv4 : (bma.dns || bma.ipv6);
         esGeo.point.searchByIP(address)
 
-          // Create the marker
+        // Create the marker
           .then(function(position){
             var marker = $scope.updateMarker({
               position: position,
@@ -238,48 +241,6 @@ angular.module('cesium.map.network.controllers', ['cesium.services', 'cesium.map
           });
       });
 
-      leafletData.getMap($scope.mapId).then(function(map) {
-
-        // Add loading control
-        if (!loadingControl) {
-          loadingControl = L.Control.loading({
-            position: 'topright',
-            separate: true
-          });
-          loadingControl.addTo(map);
-          if ($scope.search.loading) {
-            map.fire('dataloading');
-          }
-        }
-
-        else if (!$scope.search.loading) {
-          $timeout(function() {
-            map.fire('dataload');
-          }, 500);
-        }
-
-        // Add search control
-        if (!searchControl) {
-          var searchTip = $interpolate($templateCache.get('plugins/map/templates/network/item_search_tooltip.html'));
-          searchControl = MapUtils.control.search({
-            layer: markersSearchLayer,
-            propertyName: 'title',
-            buildTip: function (text, val) {
-              return searchTip(val.layer.options);
-            }
-          });
-          searchControl.addTo(map);
-        }
-
-        // Recenter map// Update map center (if need)
-        var needCenterUpdate = $scope.stateCenter && !angular.equals($scope.map.center, $scope.stateCenter);
-        if (needCenterUpdate) {
-          MapUtils.updateCenter(map, $scope.stateCenter);
-          delete $scope.stateCenter;
-        }
-
-      });
-
       // Remove old markers not found in the new result
       _.forEach(_.keys(markerIdByPeerIdToRemove), function(peerId) {
         delete markerIdByPeerId[peerId];
@@ -288,7 +249,12 @@ angular.module('cesium.map.network.controllers', ['cesium.services', 'cesium.map
         delete $scope.map.markers[markerId];
       });
 
-
+      // Hide loading indicator, when finished
+      if (!$scope.search.loading) {
+        leafletData.getMap($scope.mapId).then(function (map) {
+            map.fire('dataload');
+          });
+      }
     };
 
     $scope.updateMarker = function(marker, peer) {
@@ -308,4 +274,31 @@ angular.module('cesium.map.network.controllers', ['cesium.services', 'cesium.map
       return marker;
     };
 
+    $scope.showHelpTip = function() {
+      // override subclass
+    };
+
+    // Update the browser location, to be able to refresh the page
+    $scope.updateLocationHref = function(centerHash) {
+      // removeIf(device)
+      var params = $location.search() || {};
+      if (!params.c || !MapUtils.center.isDefault($scope.map.center)) {
+        centerHash = centerHash || '{0}:{1}:{2}'.format($scope.map.center.lat.toFixed(4), $scope.map.center.lng.toFixed(4), $scope.map.center.zoom);
+        $location.search({ c:  centerHash}).replace();
+      }
+      // endRemoveIf(device)
+    };
+
+    // removeIf(device)
+    // Update the browser location, to be able to refresh the page
+    $scope.$on("centerUrlHash", function(event, centerHash) {
+      if (!$scope.loading) {
+        return $timeout(function() {
+          $scope.updateLocationHref(centerHash);
+        }, 300);
+      }
+    });
+    // endRemoveIf(device)
+
+
   });
diff --git a/www/plugins/map/js/controllers/wot-controllers.js b/www/plugins/map/js/controllers/wot-controllers.js
index 1894d7c5bab6a6af1c97b11fda35f12d12ed22c2..d7dbe85130a0e8f63f025b2ed2db7d4a20becace 100644
--- a/www/plugins/map/js/controllers/wot-controllers.js
+++ b/www/plugins/map/js/controllers/wot-controllers.js
@@ -18,31 +18,32 @@ angular.module('cesium.map.wot.controllers', ['cesium.services', 'cesium.map.ser
           }
         });
 
-      // [NEW] Ajout d'une nouvelle page #/app/wot/map
+      // Wot map (default position)
       $stateProvider
         .state('app.view_wot_map', {
-          url: "/wot/map?lat&lng&zoom",
+          url: "/wot/map?c&center",
           views: {
             'menuContent': {
               templateUrl: "plugins/map/templates/wot/view_map.html",
               controller: 'MapWotViewCtrl'
             }
+          },
+          data: {
+            silentLocationChange: true
           }
         });
     }
-
-    L.AwesomeMarkers.Icon.prototype.options.prefix = 'ion';
   })
 
   // [NEW] Manage events from the page #/app/wot/map
-  .controller('MapWotViewCtrl', function($scope, $q, $translate, $state, $filter, $templateCache, $interpolate, $timeout, $ionicHistory,
-                                         esGeo, UIUtils, MapUtils, mapWot, leafletData) {
+  .controller('MapWotViewCtrl', function($scope, $rootScope, $q, $translate, $state, $stateParams, $filter, $templateCache, $interpolate, $timeout, $ionicHistory,
+                                         esGeo, UIUtils, MapUtils, mapWot, leafletData, $location) {
     'ngInject';
 
     var
       // Create a  hidden layer, to hold search markers
       markersSearchLayer = L.layerGroup({visible: false}),
-      loadingControl, searchControl,
+      loadingControl, searchControl, localizeMe,
       icons= {
         member: {
           type: 'awesomeMarker',
@@ -83,163 +84,150 @@ angular.module('cesium.map.wot.controllers', ['cesium.services', 'cesium.map.ser
           }
         }
       },
+      loading: true,
       markers: {}
     });
 
     // [NEW] When opening the view
     $scope.enter = function(e, state) {
       if ($scope.loading) {
-        console.log("[map] Opening the view... (first time)");
 
-        // remember state, to be able to refresh location
-        $scope.stateName = state && state.stateName;
-        $scope.stateParams = angular.copy(state && state.stateParams||{});
+        // Load the map (and init if need)
+        $scope.loadMap().then(function(map) {
 
-        // Read center from state params
-        var center = MapUtils.center(state.stateParams);
+          // Load indicator
+          map.fire('dataloading');
 
-        // Load data
-        return $scope.load(center);
+          // Load data
+          return $scope.load()
+
+            // Hide loading indicator
+            .then(function() {
+              map.fire('dataload');
+            });
+        });
+      }
+      else {
+        // Make sur to have previous center coordinate defined in the location URL
+        $scope.updateLocationHref();
       }
     };
     $scope.$on('$ionicView.enter', $scope.enter);
 
-    // Load markers data
-    $scope.load = function(center) {
-      $scope.loading = true;
+    $scope.loadMap = function() {
+      return leafletData.getMap($scope.mapId).then(function(map) {
+        if (!$scope.map.loading) return map; // already loaded
 
-      var map;
-      return leafletData.getMap($scope.mapId)
-        .then(function(res) {
-          map = res;
-
-          // Add localize me control
-          MapUtils.control.localizeMe().addTo(map);
-
-          // Add search control
-          if (!searchControl) {
-            var searchTip = $interpolate($templateCache.get('plugins/map/templates/wot/item_search_tooltip.html'));
-            searchControl = MapUtils.control.search({
-              layer: markersSearchLayer,
-              propertyName: 'title',
-              buildTip: function (text, val) {
-                return searchTip(val.layer.options);
-              }
-            });
-            searchControl.addTo(map);
+        // Add loading control
+        loadingControl = L.Control.loading({
+          position: 'topright',
+          separate: true
+        });
+        loadingControl.addTo(map);
+
+        // Add localize me control
+        localizeMe = MapUtils.control.localizeMe();
+        localizeMe.addTo(map);
+
+        // Add search control
+        var searchTip = $interpolate($templateCache.get('plugins/map/templates/wot/item_search_tooltip.html'));
+        searchControl = MapUtils.control.search({
+          layer: markersSearchLayer,
+          propertyName: 'title',
+          buildTip: function (text, val) {
+            return searchTip(val.layer.options);
           }
+        });
+        searchControl.addTo(map);
 
-          // Add loading control
-          if (!loadingControl) {
-            loadingControl = L.Control.loading({
-              position: 'topright',
-              separate: true
-            });
-            loadingControl.addTo(map);
-          }
+        $scope.map.loading = false;
+        return map;
+      });
+    };
 
-          // Enable loading UI
-          map.fire('dataloading');
+    // Load markers data
+    $scope.load = function() {
+      $scope.loading = true;
 
-          // Load wot data
-          return mapWot.load();
-        })
-        .then(function(res) {
-          if (!res || !res.length) {
-            map.fire('dataload');
-            $scope.loading = false;
-            return;
-          }
+      // Load wot data
+      return mapWot.load()
 
-          var formatPubkey = $filter('formatPubkey');
-          var markerTemplate = $templateCache.get('plugins/map/templates/wot/popup_marker.html');
-
-          // Sort with member first
-          /*res = _.sortBy(res, function(hit) {
-            var score = 0;
-            score += (!hit.uid) ? 100 : 0;
-            return -score;
-          });*/
-
-          _.forEach(res, function(hit) {
-            var type = hit.uid ? 'member' : 'wallet';
-            var shortPubkey = formatPubkey(hit.issuer);
-            var marker = {
-              layer: type,
-              icon: icons[type],
-              opacity: 0.8,
-              title: hit.title + ' | ' + shortPubkey,
-              lat: hit.geoPoint.lat,
-              lng: hit.geoPoint.lon,
-              getMessageScope: function() {
-                var scope = $scope.$new();
-                scope.hit = hit;
-                return scope;
-              },
-              focus: false,
-              message: markerTemplate
-            };
-            var id = hit.uid ? (hit.uid + ':' + hit.issuer) : hit.issuer;
-            $scope.map.markers[id] = marker;
-
-            // Create a search marker (will be hide)
-            var searchText = hit.title + ((hit.uid && hit.uid != hit.title) ? (' | ' + hit.uid) : '') + ' | ' + shortPubkey;
-            var searchMarker = angular.merge({
-              type: type,
-              opacity: 0,
-              icon: L.divIcon({
-                className: type + ' ng-hide',
-                iconSize: L.point(0, 0)
-              })
-            }, {title: searchText, issuer: hit.issuer, uid: hit.uid, name: hit.title});
-            markersSearchLayer.addLayer(new L.Marker({
+        .then(function(res) {
+          if (res && res.length) {
+
+            var formatPubkey = $filter('formatPubkey');
+            var markerTemplate = $templateCache.get('plugins/map/templates/wot/popup_marker.html');
+
+            // Sort with member first
+            /*res = _.sortBy(res, function(hit) {
+             var score = 0;
+             score += (!hit.uid) ? 100 : 0;
+             return -score;
+             });*/
+
+            _.forEach(res, function (hit) {
+              var type = hit.uid ? 'member' : 'wallet';
+              var shortPubkey = formatPubkey(hit.issuer);
+              var marker = {
+                layer: type,
+                icon: icons[type],
+                opacity: 0.8,
+                title: hit.title + ' | ' + shortPubkey,
                 lat: hit.geoPoint.lat,
-                lng: hit.geoPoint.lon
-              },
-              searchMarker));
-          });
-
-          // end loading
-          $timeout(function() {
-            map.fire('dataload');
-          }, 1000);
-
-          // Update map center (if need)
-          var needCenterUpdate = center && !angular.equals($scope.map.center, center);
-          if (needCenterUpdate) {
-            MapUtils.updateCenter(map, center);
+                lng: hit.geoPoint.lon,
+                getMessageScope: function () {
+                  var scope = $scope.$new();
+                  scope.hit = hit;
+                  return scope;
+                },
+                focus: false,
+                message: markerTemplate
+              };
+              var id = hit.uid ? (hit.uid + ':' + hit.issuer) : hit.issuer;
+              $scope.map.markers[id] = marker;
+
+              // Create a search marker (will be hide)
+              var searchText = hit.title + ((hit.uid && hit.uid != hit.title) ? (' | ' + hit.uid) : '') + ' | ' + shortPubkey;
+              var searchMarker = angular.merge({
+                type: type,
+                opacity: 0,
+                icon: L.divIcon({
+                  className: type + ' ng-hide',
+                  iconSize: L.point(0, 0)
+                })
+              }, {title: searchText, issuer: hit.issuer, uid: hit.uid, name: hit.title});
+              markersSearchLayer.addLayer(new L.Marker({
+                  lat: hit.geoPoint.lat,
+                  lng: hit.geoPoint.lon
+                },
+                searchMarker));
+            });
           }
+
+          $scope.loading = false;
         });
     };
 
-    // Update the browser location, to be able to refresh the page
-    $scope.updateLocation = function() {
-
-      $ionicHistory.nextViewOptions({
-        disableAnimate: true,
-        disableBack: true,
-        historyRoot: false
-      });
-
-      $scope.stateParams = $scope.stateParams || {};
-      $scope.stateParams.lat = ($scope.map.center.lat != MapUtils.constants.DEFAULT_CENTER.lat) ? $scope.map.center.lat : undefined;
-      $scope.stateParams.lng = ($scope.map.center.lng != MapUtils.constants.DEFAULT_CENTER.lng) ? $scope.map.center.lng : undefined;
-      $scope.stateParams.zoom = ($scope.map.center.zoom != MapUtils.constants.DEFAULT_CENTER.zoom) ? $scope.map.center.zoom : undefined;
 
-      $state.go($scope.stateName, $scope.stateParams, {
-        reload: false,
-        inherit: true,
-        notify: false}
-      );
+    $scope.updateLocationHref = function(centerHash) {
+      // removeIf(device)
+      var params = $location.search() || {};
+      if (!params.c || !MapUtils.center.isDefault($scope.map.center)) {
+        centerHash = centerHash || '{0}:{1}:{2}'.format($scope.map.center.lat.toFixed(4), $scope.map.center.lng.toFixed(4), $scope.map.center.zoom);
+        $location.search({ c:  centerHash}).replace();
+      }
+      // endRemoveIf(device)
     };
 
     // removeIf(device)
-    $scope.onMapCenterChanged = function() {
+    // Update the browser location, to be able to refresh the page
+    $scope.$on("centerUrlHash", function(event, centerHash) {
       if (!$scope.loading) {
-        $timeout($scope.updateLocation, 500);
+        return $timeout(function() {
+          $scope.updateLocationHref(centerHash);
+        }, 300);
       }
-    };
-    $scope.$watch('map.center', $scope.onMapCenterChanged, true);
+    });
     // endRemoveIf(device)
 
   });
diff --git a/www/plugins/map/js/plugin.js b/www/plugins/map/js/plugin.js
index 8f02458a5c5b1784e5d8c2c0c88c95b9e8c8752f..f3f2437335276cd885aed6b5554f675ce5991873 100644
--- a/www/plugins/map/js/plugin.js
+++ b/www/plugins/map/js/plugin.js
@@ -1,6 +1,6 @@
 
 angular.module('cesium.map.plugin', [
-    'leaflet-directive',
+    'ui-leaflet',
     // Services
     'cesium.map.services',
     // Controllers
diff --git a/www/plugins/map/js/services/utils-services.js b/www/plugins/map/js/services/utils-services.js
index 0197b2ba4d037d8c3ba7eda901bb0c10793859a1..37ce44f95b6c96372a4f5f8b5254564e130d03fd 100644
--- a/www/plugins/map/js/services/utils-services.js
+++ b/www/plugins/map/js/services/utils-services.js
@@ -1,7 +1,7 @@
 
-angular.module('cesium.map.utils.services', ['cesium.services', 'leaflet-directive'])
+angular.module('cesium.map.utils.services', ['cesium.services', 'ui-leaflet'])
 
-.factory('MapUtils', function($timeout, $q, $translate, leafletData, csSettings, esGeo, UIUtils) {
+.factory('MapUtils', function($timeout, $q, $translate, leafletData, csSettings, esGeo, UIUtils, leafletHelpers) {
   'ngInject';
 
   var constants = {
@@ -53,13 +53,15 @@ angular.module('cesium.map.utils.services', ['cesium.services', 'leaflet-directi
   }
 
   function updateMapCenter(map, center) {
+    if (isSameCenter(center, map)) return $q.when();
+
     return $timeout(function () {
       map.invalidateSize();
       map._resetView(center, center.zoom, true);
     }, 300);
   }
 
-  function initCenter(options) {
+  function getCenter(options) {
     if (!options) return;
     var center;
     if (options.lat) {
@@ -75,7 +77,24 @@ angular.module('cesium.map.utils.services', ['cesium.services', 'leaflet-directi
       center.zoom = parseFloat(options.zoom);
     }
     if (!center) return;
-    return angular.merge({}, constants.DEFAULT_CENTER, center);
+
+    // If missing some properties, complete with defaults
+    if (!leafletHelpers.isValidCenter(center)) {
+      center = angular.merge({}, constants.DEFAULT_CENTER, center);
+    }
+    return center;
+  }
+
+  function isSameCenter(center, map) {
+    return leafletHelpers.isSameCenterOnMap(center, map);
+  }
+
+  function isDefaultCenter(centerModel) {
+    var mapCenter = constants.DEFAULT_CENTER;
+    if (centerModel.lat && centerModel.lng && mapCenter.lat.toFixed(4) === centerModel.lat.toFixed(4) && mapCenter.lng.toFixed(4) === centerModel.lng.toFixed(4) && mapCenter.zoom === centerModel.zoom) {
+      return true;
+    }
+    return false;
   }
 
   // Create a default serach control, with default options
@@ -120,7 +139,11 @@ angular.module('cesium.map.utils.services', ['cesium.services', 'leaflet-directi
 
   return {
     map: initMap,
-    center: initCenter,
+    center: {
+      get: getCenter,
+      isSame: isSameCenter,
+      isDefault: isDefaultCenter
+    },
     updateCenter: updateMapCenter,
     control: {
       search: initSearchControl,
diff --git a/www/plugins/map/templates/network/view_map.html b/www/plugins/map/templates/network/view_map.html
index 7fcf70841496471e9c1127cd8ebaa0df8b6bb830..ff9063496b27a401683acf36a644aab11289e024 100644
--- a/www/plugins/map/templates/network/view_map.html
+++ b/www/plugins/map/templates/network/view_map.html
@@ -8,6 +8,7 @@
   <ion-content data-tap-disabled="true">
     <leaflet id="{{mapId}}"
              height="100%"
+             url-hash-center="yes"
              center="map.center"
              markers="map.markers"
              layers="map.layers">
diff --git a/www/plugins/map/templates/wot/view_map.html b/www/plugins/map/templates/wot/view_map.html
index e60fb9807d9c46ff404882f7fa67077805c9de37..0e90bf019d296364149991ee5139de3f95fcf4d1 100644
--- a/www/plugins/map/templates/wot/view_map.html
+++ b/www/plugins/map/templates/wot/view_map.html
@@ -1,16 +1,15 @@
-<ion-view left-buttons="leftButtons">
+<ion-view left-buttons="leftButtons" class="view-map-wot">
   <ion-nav-title>
-
   </ion-nav-title>
 
   <ion-nav-buttons side="secondary">
-
   </ion-nav-buttons>
 
   <ion-content data-tap-disabled="true">
     <leaflet id="{{mapId}}"
              height="100%"
-             center="map.center"
+             url-hash-center="yes"
+             lf-center="map.center"
              markers="map.markers"
              layers="map.layers">
     </leaflet>