diff --git a/dist/angular-leaflet-directive.js b/dist/angular-leaflet-directive.js index 5f94e7a2..05e83b62 100644 --- a/dist/angular-leaflet-directive.js +++ b/dist/angular-leaflet-directive.js @@ -1,5 +1,34 @@ +/**! + * 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 0.9.1 2015-11-04 +* 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 */ diff --git a/dist/angular-leaflet-directive.min.js b/dist/angular-leaflet-directive.min.js index 2d491c57..aab6e085 100644 --- a/dist/angular-leaflet-directive.min.js +++ b/dist/angular-leaflet-directive.min.js @@ -28,7 +28,7 @@ */ /*! -* angular-leaflet-directive 0.9.1 2015-11-04 +* 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 */ @@ -36,4 +36,5 @@ '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:'
',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'+b.error.message+"";else if("arcgis"===c)for(var e=0;e'+f.layerName+"";for(var g=0;g
'+h.label+"
"}}else"image"===c&&(a.innerHTML='')},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.labels[d]+"
";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:'© OpenStreetMap 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;d0&&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:'

{{ title }}

{{ baseTitle }}
{{ overlaysTitle }}
Range is not supported in this browser
',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){"cartodb"===l.overlays[f].type;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,l){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(a,b,c,d,e,f){},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
', + 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, + 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'); + } + } + + // 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, + isNumber = leafletHelpers.isNumber, + isFunction = leafletHelpers.isFunction, + 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(), + 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, + isObject = leafletHelpers.isObject, + createLayer = leafletLayerHelpers.createLayer, + _controls = {}, + 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, + _isString = leafletHelpers.isString, + _isObject = leafletHelpers.isObject, + _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, + 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, 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(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), + 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, + 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], + 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(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(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, _all = null; + _every = _all = function(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(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 && 'string' === typeof options.crs) { + /*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 += '
' + legendData.error.message + '
'; + } else { + if (type === 'arcgis') { + for (var i = 0; i < legendData.layers.length; i++) { + var layer = legendData.layers[i]; + div.innerHTML += '
' + layer.layerName + '
'; + for(var j = 0; j < layer.legend.length; j++) { + var leg = layer.legend[j]; + div.innerHTML += + '
' + + '
' + leg.label + '
'; + } + } + } + else if (type === 'image') { + div.innerHTML = ''; + } + } + }; + + 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 += + '
' + + '
' + legend.labels[i] + '
'; + } + 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: '© OpenStreetMap contributors' + }, + path: { + weight: 10, + opacity: 1, + color: '#0000ff' + }, + center: { + lat: 0, + lng: 0, + zoom: 1 + } + }; + } + + 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 () { + 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, + 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; + + 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, + template = $compile(iconData.template)(markerScope), + 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, + layerPos = new L.Point(marker._popup._containerLeft, -containerHeight - marker._popup._containerBottom), + 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, + 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, + 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 (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, + isArray = leafletHelpers.isArray, + isNumber = leafletHelpers.isNumber, + 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'], + 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, + isNumber = leafletHelpers.isNumber, + isSameCenterOnMap = leafletHelpers.isSameCenterOnMap, + safeApply = leafletHelpers.safeApply, + isValidCenter = leafletHelpers.isValidCenter, + isValidBounds = leafletBoundsHelpers.isValidBounds, + isUndefinedOrEmpty = leafletHelpers.isUndefinedOrEmpty, + 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(), + 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() { + 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(), + 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); + }); + } + }; +}]); + +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, + 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, 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, + _watchOptions = leafletHelpers.watchOptions, + _extendDirectiveControls = leafletDirectiveControlsHelpers.extend, + hlp = leafletHelpers, + $it = leafletIterators; + + return { + restrict: "A", + scope: false, + replace: false, + require: 'leaflet', + + link: function(scope, element, attrs, controller) { + var isDefined = leafletHelpers.isDefined, + leafletScope = controller.getLeafletScope(), + leafletGeoJSON = {}, + _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, + 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: + '
' + + '

{{ title }}

' + + '
' + + '
{{ baseTitle }}
' + + '
' + + '' + + '
' + + '
' + + '
' + + '
{{ overlaysTitle }}
' + + '
' + + '
' + + ''+ + ''+ + '
' + + ' ' + + ' ' + + ' ' + + '' + + '
' + + '
' + + '
' + + '' + + '' + + '' + + '
Range is not supported in this browser
' + + '
' + + '
' + + '
' + + '
' + + '
', + link: function(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(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, + leafletLayers = {}, + leafletScope = controller.getLeafletScope(), + layers = leafletScope.layers, + createLayer = leafletLayerHelpers.createLayer, + safeAddLayer = leafletLayerHelpers.safeAddLayer, + safeRemoveLayer = leafletLayerHelpers.safeRemoveLayer, + updateLayersControl = leafletControlHelpers.updateLayersControl, + 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, + 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); + + 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, + 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, + $it = leafletIterators, + _markersWatchOptions = leafletHelpers.watchOptions, + maybeWatch = leafletWatchHelpers.maybeWatch, + 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, + 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 = 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], + 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() { + 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(), + 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); + } + }); + }); + } + }; +}]); + +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], + isDefined = leafletHelpers.isDefined, + isString = leafletHelpers.isString, + leafletScope = mapController.getLeafletScope(), + paths = leafletScope.paths, + createPath = leafletPathsHelpers.createPath, + bindPathEvents = leafletPathEvents.bindPathEvents, + setPathOptions = leafletPathsHelpers.setPathOptions; + + 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() { + 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, + 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, 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, + isDefined = leafletHelpers.isDefined, + isObject = leafletHelpers.isObject, + isArray = leafletHelpers.isArray, + 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, 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(); +}]); + +angular.module("leaflet-directive") +.factory('leafletGeoJsonEvents', ["$rootScope", "$q", "$log", "leafletHelpers", "leafletEventsHelpersFactory", "leafletData", function ($rootScope, $q, $log, leafletHelpers, + leafletEventsHelpersFactory, leafletData) { + var safeApply = leafletHelpers.safeApply, + 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), + _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, + 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 (maybeMapId, lObject, name, model, leafletScope, layerName) {}; + + 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, + 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, + isDefined = leafletHelpers.isDefined, + Helpers = leafletHelpers, + lblHelp = leafletLabelEvents, + 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, + isObject = leafletHelpers.isObject, + Helpers = leafletHelpers, + errorHeader = leafletHelpers.errorHeader, + lblHelp = leafletLabelEvents, + 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 = [], + 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 () { + 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/grunt/aliases.yaml b/grunt/aliases.yaml index 0c368458..3872906c 100644 --- a/grunt/aliases.yaml +++ b/grunt/aliases.yaml @@ -35,12 +35,12 @@ fast-build: - 'concat:dist' - 'ngAnnotate' - 'uglify' + - 'concat:license' + - 'concat:license-minified' build: - 'fast-build' - - 'uglify' - 'test-unit' - - 'concat:license' - 'clean:pre' travis: diff --git a/grunt/concat.json b/grunt/concat.json index 7dece7b3..cbb62244 100644 --- a/grunt/concat.json +++ b/grunt/concat.json @@ -8,11 +8,17 @@ "src/directives/leaflet.js", "src/services/*.js", "src/**/*.js" - ], "dest": "dist/angular-leaflet-directive.pre.js" }, "license": { + "src": [ + "src/header-MIT-license.txt", + "dist/angular-leaflet-directive.no-header.js" + ], + "dest": "dist/angular-leaflet-directive.js" + }, + "license-minified": { "src": [ "src/header-MIT-license.txt", "dist/angular-leaflet-directive.min.no-header.js" diff --git a/grunt/ngAnnotate.json b/grunt/ngAnnotate.json index f9fc860d..2522a73d 100644 --- a/grunt/ngAnnotate.json +++ b/grunt/ngAnnotate.json @@ -2,7 +2,7 @@ "options": {}, "dist": { "files": { - "dist/angular-leaflet-directive.js": [ "dist/angular-leaflet-directive.pre.js" ] + "dist/angular-leaflet-directive.no-header.js": [ "dist/angular-leaflet-directive.pre.js" ] } } } diff --git a/grunt/uglify.json b/grunt/uglify.json index f257af49..f53aa71c 100644 --- a/grunt/uglify.json +++ b/grunt/uglify.json @@ -1,10 +1,11 @@ { - "options": { - "banner": "/*!\n* <%= pkg.name %> <%= pkg.version %> <%= grunt.template.today(\"yyyy-mm-dd\") %>\n* <%= pkg.description %>\n* <%= pkg.repository.type %>: <%= pkg.repository.url %>\n*/\n(function(angular){\n'use strict';\n" - }, "dist": { + "options": { + "banner": "/*!\n* <%= pkg.name %> <%= pkg.version %> <%= grunt.template.today(\"yyyy-mm-dd\") %>\n* <%= pkg.description %>\n* <%= pkg.repository.type %>: <%= pkg.repository.url %>\n*/\n(function(angular){\n'use strict';\n", + "footer": "\n}(angular));" + }, "files": { - "dist/<%= pkg.name %>.min.no-header.js": ["dist/angular-leaflet-directive.js"] + "dist/<%= pkg.name %>.min.no-header.js": ["dist/<%= pkg.name %>.no-header.js"] } } }