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 = '×'; + 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: '© <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 && layer.group && 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 && 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:'© <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 && layer.group && 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 && 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: '© <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 && layer.group && 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 && 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:'© <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 && layer.group && 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 && 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¢er", 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>