/**!
* The MIT License
*
* Copyright (c) 2013 the angular-leaflet-directive Team, http://tombatossals.github.io/angular-leaflet-directive
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* angular-leaflet-directive
* https://github.com/tombatossals/angular-leaflet-directive
*
* @authors https://github.com/tombatossals/angular-leaflet-directive/graphs/contributors
*/
/*!
* angular-leaflet-directive 2015-11-06
* angular-leaflet-directive - An AngularJS directive to easily interact with Leaflet maps
* git: https://github.com/tombatossals/angular-leaflet-directive
*/
(function(angular){
'use strict';
angular.module('leaflet-directive', []).directive('leaflet', ["$q", "leafletData", "leafletMapDefaults", "leafletHelpers", "leafletMapEvents", function($q, leafletData, leafletMapDefaults, leafletHelpers, leafletMapEvents) {
return {
restrict: 'EA',
replace: true,
scope: {
center: '=',
lfCenter: '=',
defaults: '=',
maxbounds: '=',
bounds: '=',
markers: '=',
legend: '=',
geojson: '=',
paths: '=',
tiles: '=',
layers: '=',
controls: '=',
decorations: '=',
eventBroadcast: '=',
markersWatchOptions: '=',
geojsonWatchOptions: '=',
},
transclude: true,
template: '
',
controller: ["$scope", function($scope) {
this._leafletMap = $q.defer();
this.getMap = function() {
return this._leafletMap.promise;
};
this.getLeafletScope = function() {
return $scope;
};
}],
link: function(scope, element, attrs, ctrl) {
var isDefined = leafletHelpers.isDefined;
var defaults = leafletMapDefaults.setDefaults(scope.defaults, attrs.id);
var mapEvents = leafletMapEvents.getAvailableMapEvents();
var addEvents = leafletMapEvents.addEvents;
scope.mapId = attrs.id;
leafletData.setDirectiveControls({}, attrs.id);
// Set width and height utility functions
function updateWidth() {
if (isNaN(attrs.width)) {
element.css('width', attrs.width);
} else {
element.css('width', attrs.width + 'px');
}
}
function updateHeight() {
if (isNaN(attrs.height)) {
element.css('height', attrs.height);
} else {
element.css('height', attrs.height + 'px');
}
}
// If the width attribute defined update css
// Then watch if bound property changes and update css
if (isDefined(attrs.width)) {
updateWidth();
scope.$watch(
function() {
return element[0].getAttribute('width');
},
function() {
updateWidth();
map.invalidateSize();
});
}
// If the height attribute defined update css
// Then watch if bound property changes and update css
if (isDefined(attrs.height)) {
updateHeight();
scope.$watch(
function() {
return element[0].getAttribute('height');
},
function() {
updateHeight();
map.invalidateSize();
});
}
// Create the Leaflet Map Object with the options
var map = new L.Map(element[0], leafletMapDefaults.getMapCreationDefaults(attrs.id));
ctrl._leafletMap.resolve(map);
if (!isDefined(attrs.center) && !isDefined(attrs.lfCenter)) {
map.setView([defaults.center.lat, defaults.center.lng], defaults.center.zoom);
}
// If no layers nor tiles defined, set the default tileLayer
if (!isDefined(attrs.tiles) && (!isDefined(attrs.layers))) {
var tileLayerObj = L.tileLayer(defaults.tileLayer, defaults.tileLayerOptions);
tileLayerObj.addTo(map);
leafletData.setTiles(tileLayerObj, attrs.id);
}
// Set zoom control configuration
if (isDefined(map.zoomControl) &&
isDefined(defaults.zoomControlPosition)) {
map.zoomControl.setPosition(defaults.zoomControlPosition);
}
if (isDefined(map.zoomControl) && defaults.zoomControl === false) {
map.zoomControl.removeFrom(map);
}
if (isDefined(map.zoomsliderControl) &&
isDefined(defaults.zoomsliderControl) &&
defaults.zoomsliderControl === false) {
map.zoomsliderControl.removeFrom(map);
}
// if no event-broadcast attribute, all events are broadcasted
if (!isDefined(attrs.eventBroadcast)) {
var logic = 'broadcast';
addEvents(map, mapEvents, 'eventName', scope, logic);
}
// Resolve the map object to the promises
map.whenReady(function() {
leafletData.setMap(map, attrs.id);
});
scope.$on('$destroy', function() {
leafletMapDefaults.reset();
map.remove();
leafletData.unresolveMap(attrs.id);
});
//Handle request to invalidate the map size
//Up scope using $scope.$emit('invalidateSize')
//Down scope using $scope.$broadcast('invalidateSize')
scope.$on('invalidateSize', function() {
map.invalidateSize();
});
},
};
}]);
angular.module('leaflet-directive').factory('leafletBoundsHelpers', ["$log", "leafletHelpers", function($log, leafletHelpers) {
var isArray = leafletHelpers.isArray;
var isNumber = leafletHelpers.isNumber;
var isFunction = leafletHelpers.isFunction;
var isDefined = leafletHelpers.isDefined;
function _isValidBounds(bounds) {
return angular.isDefined(bounds) && angular.isDefined(bounds.southWest) &&
angular.isDefined(bounds.northEast) && angular.isNumber(bounds.southWest.lat) &&
angular.isNumber(bounds.southWest.lng) && angular.isNumber(bounds.northEast.lat) &&
angular.isNumber(bounds.northEast.lng);
}
return {
createLeafletBounds: function(bounds) {
if (_isValidBounds(bounds)) {
return L.latLngBounds([bounds.southWest.lat, bounds.southWest.lng],
[bounds.northEast.lat, bounds.northEast.lng]);
}
},
isValidBounds: _isValidBounds,
createBoundsFromArray: function(boundsArray) {
if (!(isArray(boundsArray) && boundsArray.length === 2 &&
isArray(boundsArray[0]) && isArray(boundsArray[1]) &&
boundsArray[0].length === 2 && boundsArray[1].length === 2 &&
isNumber(boundsArray[0][0]) && isNumber(boundsArray[0][1]) &&
isNumber(boundsArray[1][0]) && isNumber(boundsArray[1][1]))) {
$log.error('[AngularJS - Leaflet] The bounds array is not valid.');
return;
}
return {
northEast: {
lat: boundsArray[0][0],
lng: boundsArray[0][1],
},
southWest: {
lat: boundsArray[1][0],
lng: boundsArray[1][1],
},
};
},
createBoundsFromLeaflet: function(lfBounds) {
if (!(isDefined(lfBounds) && isFunction(lfBounds.getNorthEast) && isFunction(lfBounds.getSouthWest))) {
$log.error('[AngularJS - Leaflet] The leaflet bounds is not valid object.');
return;
}
var northEast = lfBounds.getNorthEast();
var southWest = lfBounds.getSouthWest();
return {
northEast: {
lat: northEast.lat,
lng: northEast.lng,
},
southWest: {
lat: southWest.lat,
lng: southWest.lng,
},
};
},
};
}]);
angular.module('leaflet-directive').factory('leafletControlHelpers', ["$rootScope", "$log", "leafletHelpers", "leafletLayerHelpers", "leafletMapDefaults", function($rootScope, $log, leafletHelpers, leafletLayerHelpers, leafletMapDefaults) {
var isDefined = leafletHelpers.isDefined;
var isObject = leafletHelpers.isObject;
var createLayer = leafletLayerHelpers.createLayer;
var _controls = {};
var errorHeader = leafletHelpers.errorHeader + ' [Controls] ';
var _controlLayersMustBeVisible = function(baselayers, overlays, mapId) {
var defaults = leafletMapDefaults.getDefaults(mapId);
if (!defaults.controls.layers.visible) {
return false;
}
var atLeastOneControlItemMustBeShown = false;
if (isObject(baselayers)) {
Object.keys(baselayers).forEach(function(key) {
var layer = baselayers[key];
if (!isDefined(layer.layerOptions) || layer.layerOptions.showOnSelector !== false) {
atLeastOneControlItemMustBeShown = true;
}
});
}
if (isObject(overlays)) {
Object.keys(overlays).forEach(function(key) {
var layer = overlays[key];
if (!isDefined(layer.layerParams) || layer.layerParams.showOnSelector !== false) {
atLeastOneControlItemMustBeShown = true;
}
});
}
return atLeastOneControlItemMustBeShown;
};
var _createLayersControl = function(mapId) {
var defaults = leafletMapDefaults.getDefaults(mapId);
var controlOptions = {
collapsed: defaults.controls.layers.collapsed,
position: defaults.controls.layers.position,
autoZIndex: false,
};
angular.extend(controlOptions, defaults.controls.layers.options);
var control;
if (defaults.controls.layers && isDefined(defaults.controls.layers.control)) {
control = defaults.controls.layers.control.apply(this, [[], [], controlOptions]);
} else {
control = new L.control.layers([], [], controlOptions);
}
return control;
};
var controlTypes = {
draw: {
isPluginLoaded: function() {
if (!angular.isDefined(L.Control.Draw)) {
$log.error(errorHeader + ' Draw plugin is not loaded.');
return false;
}
return true;
},
checkValidParams: function(/* params */) {
return true;
},
createControl: function(params) {
return new L.Control.Draw(params);
},
},
scale: {
isPluginLoaded: function() {
return true;
},
checkValidParams: function(/* params */) {
return true;
},
createControl: function(params) {
return new L.control.scale(params);
},
},
fullscreen: {
isPluginLoaded: function() {
if (!angular.isDefined(L.Control.Fullscreen)) {
$log.error(errorHeader + ' Fullscreen plugin is not loaded.');
return false;
}
return true;
},
checkValidParams: function(/* params */) {
return true;
},
createControl: function(params) {
return new L.Control.Fullscreen(params);
},
},
search: {
isPluginLoaded: function() {
if (!angular.isDefined(L.Control.Search)) {
$log.error(errorHeader + ' Search plugin is not loaded.');
return false;
}
return true;
},
checkValidParams: function(/* params */) {
return true;
},
createControl: function(params) {
return new L.Control.Search(params);
},
},
custom: {},
minimap: {
isPluginLoaded: function() {
if (!angular.isDefined(L.Control.MiniMap)) {
$log.error(errorHeader + ' Minimap plugin is not loaded.');
return false;
}
return true;
},
checkValidParams: function(params) {
if (!isDefined(params.layer)) {
$log.warn(errorHeader + ' minimap "layer" option should be defined.');
return false;
}
return true;
},
createControl: function(params) {
var layer = createLayer(params.layer);
if (!isDefined(layer)) {
$log.warn(errorHeader + ' minimap control "layer" could not be created.');
return;
}
return new L.Control.MiniMap(layer, params);
},
},
};
return {
layersControlMustBeVisible: _controlLayersMustBeVisible,
isValidControlType: function(type) {
return Object.keys(controlTypes).indexOf(type) !== -1;
},
createControl: function(type, params) {
if (!controlTypes[type].checkValidParams(params)) {
return;
}
return controlTypes[type].createControl(params);
},
updateLayersControl: function(map, mapId, loaded, baselayers, overlays, leafletLayers) {
var i;
var _layersControl = _controls[mapId];
var mustBeLoaded = _controlLayersMustBeVisible(baselayers, overlays, mapId);
if (isDefined(_layersControl) && loaded) {
for (i in leafletLayers.baselayers) {
_layersControl.removeLayer(leafletLayers.baselayers[i]);
}
for (i in leafletLayers.overlays) {
_layersControl.removeLayer(leafletLayers.overlays[i]);
}
map.removeControl(_layersControl);
delete _controls[mapId];
}
if (mustBeLoaded) {
_layersControl = _createLayersControl(mapId);
_controls[mapId] = _layersControl;
for (i in baselayers) {
var hideOnSelector = isDefined(baselayers[i].layerOptions) &&
baselayers[i].layerOptions.showOnSelector === false;
if (!hideOnSelector && isDefined(leafletLayers.baselayers[i])) {
_layersControl.addBaseLayer(leafletLayers.baselayers[i], baselayers[i].name);
}
}
for (i in overlays) {
var hideOverlayOnSelector = isDefined(overlays[i].layerParams) &&
overlays[i].layerParams.showOnSelector === false;
if (!hideOverlayOnSelector && isDefined(leafletLayers.overlays[i])) {
_layersControl.addOverlay(leafletLayers.overlays[i], overlays[i].name);
}
}
map.addControl(_layersControl);
}
return mustBeLoaded;
},
};
}]);
angular.module('leaflet-directive').service('leafletData', ["$log", "$q", "leafletHelpers", function($log, $q, leafletHelpers) {
var getDefer = leafletHelpers.getDefer,
getUnresolvedDefer = leafletHelpers.getUnresolvedDefer,
setResolvedDefer = leafletHelpers.setResolvedDefer;
var _private = {};
var self = this;
var upperFirst = function(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
};
var _privateItems = [
'map',
'tiles',
'layers',
'paths',
'markers',
'geoJSON',
'UTFGrid', //odd ball on naming convention keeping to not break
'decorations',
'directiveControls',];
//init
_privateItems.forEach(function(itemName) {
_private[itemName] = {};
});
this.unresolveMap = function(scopeId) {
var id = leafletHelpers.obtainEffectiveMapId(_private.map, scopeId);
_privateItems.forEach(function(itemName) {
_private[itemName][id] = undefined;
});
};
//int repetitive stuff (get and sets)
_privateItems.forEach(function(itemName) {
var name = upperFirst(itemName);
self['set' + name] = function(lObject, scopeId) {
var defer = getUnresolvedDefer(_private[itemName], scopeId);
defer.resolve(lObject);
setResolvedDefer(_private[itemName], scopeId);
};
self['get' + name] = function(scopeId) {
var defer = getDefer(_private[itemName], scopeId);
return defer.promise;
};
});
}]);
angular.module('leaflet-directive')
.service('leafletDirectiveControlsHelpers', ["$log", "leafletData", "leafletHelpers", function($log, leafletData, leafletHelpers) {
var _isDefined = leafletHelpers.isDefined;
var _isString = leafletHelpers.isString;
var _isObject = leafletHelpers.isObject;
var _mainErrorHeader = leafletHelpers.errorHeader;
var _errorHeader = _mainErrorHeader + '[leafletDirectiveControlsHelpers';
var _extend = function(id, thingToAddName, createFn, cleanFn) {
var _fnHeader = _errorHeader + '.extend] ';
var extender = {};
if (!_isDefined(thingToAddName)) {
$log.error(_fnHeader + 'thingToAddName cannot be undefined');
return;
}
if (_isString(thingToAddName) && _isDefined(createFn) && _isDefined(cleanFn)) {
extender[thingToAddName] = {
create: createFn,
clean: cleanFn,
};
} else if (_isObject(thingToAddName) && !_isDefined(createFn) && !_isDefined(cleanFn)) {
extender = thingToAddName;
} else {
$log.error(_fnHeader + 'incorrect arguments');
return;
}
//add external control to create / destroy markers without a watch
leafletData.getDirectiveControls().then(function(controls) {
angular.extend(controls, extender);
leafletData.setDirectiveControls(controls, id);
});
};
return {
extend: _extend,
};
}]);
angular.module('leaflet-directive')
.service('leafletGeoJsonHelpers', ["leafletHelpers", "leafletIterators", function(leafletHelpers, leafletIterators) {
var lHlp = leafletHelpers;
var lIt = leafletIterators;
var Point = function(lat, lng) {
this.lat = lat;
this.lng = lng;
return this;
};
var _getLat = function(value) {
if (Array.isArray(value) && value.length === 2) {
return value[1];
} else if (lHlp.isDefined(value.type) && value.type === 'Point') {
return +value.coordinates[1];
} else {
return +value.lat;
}
};
var _getLng = function(value) {
if (Array.isArray(value) && value.length === 2) {
return value[0];
} else if (lHlp.isDefined(value.type) && value.type === 'Point') {
return +value.coordinates[0];
} else {
return +value.lng;
}
};
var _validateCoords = function(coords) {
if (lHlp.isUndefined(coords)) {
return false;
}
if (lHlp.isArray(coords)) {
if (coords.length === 2 && lHlp.isNumber(coords[0]) && lHlp.isNumber(coords[1])) {
return true;
}
} else if (lHlp.isDefined(coords.type)) {
if (
coords.type === 'Point' && lHlp.isArray(coords.coordinates) &&
coords.coordinates.length === 2 &&
lHlp.isNumber(coords.coordinates[0]) &&
lHlp.isNumber(coords.coordinates[1])) {
return true;
}
}
var ret = lIt.all(['lat', 'lng'], function(pos) {
return lHlp.isDefined(coords[pos]) && lHlp.isNumber(coords[pos]);
});
return ret;
};
var _getCoords = function(value) {
if (!value || !_validateCoords(value)) {
return;
}
var p = null;
if (Array.isArray(value) && value.length === 2) {
p = new Point(value[1], value[0]);
} else if (lHlp.isDefined(value.type) && value.type === 'Point') {
p = new Point(value.coordinates[1], value.coordinates[0]);
} else {
return value;
}
//note angular.merge is avail in angular 1.4.X we might want to fill it here
return angular.extend(value, p);//tap on lat, lng if it doesnt exist
};
return {
getLat: _getLat,
getLng: _getLng,
validateCoords: _validateCoords,
getCoords: _getCoords,
};
}]);
angular.module('leaflet-directive').service('leafletHelpers', ["$q", "$log", function($q, $log) {
var _errorHeader = '[AngularJS - Leaflet] ';
var _copy = angular.copy;
var _clone = _copy;
/*
For parsing paths to a field in an object
Example:
var obj = {
bike:{
1: 'hi'
2: 'foo'
}
};
_getObjectValue(obj,"bike.1") returns 'hi'
this is getPath in ui-gmap
*/
var _getObjectValue = function(object, pathStr) {
var obj;
if (!object || !angular.isObject(object))
return;
//if the key is not a sting then we already have the value
if ((pathStr === null) || !angular.isString(pathStr)) {
return pathStr;
}
obj = object;
pathStr.split('.').forEach(function(value) {
if (obj) {
obj = obj[value];
}
});
return obj;
};
/*
Object Array Notation
_getObjectArrayPath("bike.one.two")
returns:
'bike["one"]["two"]'
*/
var _getObjectArrayPath = function(pathStr) {
return pathStr.split('.').reduce(function(previous, current) {
return previous + '["' + current + '"]';
});
};
/* Object Dot Notation
_getObjectPath(["bike","one","two"])
returns:
"bike.one.two"
*/
var _getObjectDotPath = function(arrayOfStrings) {
return arrayOfStrings.reduce(function(previous, current) {
return previous + '.' + current;
});
};
function _obtainEffectiveMapId(d, mapId) {
var id;
var i;
if (!angular.isDefined(mapId)) {
if (Object.keys(d).length === 0) {
id = 'main';
} else if (Object.keys(d).length >= 1) {
for (i in d) {
if (d.hasOwnProperty(i)) {
id = i;
}
}
} else {
$log.error(_errorHeader + '- You have more than 1 map on the DOM, you must provide the map ID to the leafletData.getXXX call');
}
} else {
id = mapId;
}
return id;
}
function _getUnresolvedDefer(d, mapId) {
var id = _obtainEffectiveMapId(d, mapId);
var defer;
if (!angular.isDefined(d[id]) || d[id].resolvedDefer === true) {
defer = $q.defer();
d[id] = {
defer: defer,
resolvedDefer: false,
};
} else {
defer = d[id].defer;
}
return defer;
}
var _isDefined = function(value) {
return angular.isDefined(value) && value !== null;
};
var _isUndefined = function(value) {
return !_isDefined(value);
};
// BEGIN DIRECT PORT FROM AngularJS code base
var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g;
var MOZ_HACK_REGEXP = /^moz([A-Z])/;
var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
/**
Converts snake_case to camelCase.
Also there is special case for Moz prefix starting with upper case letter.
@param name Name to normalize
*/
var camelCase = function(name) {
return name.replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
if (offset) {
return letter.toUpperCase();
} else {
return letter;
}
}).replace(MOZ_HACK_REGEXP, 'Moz$1');
};
/**
Converts all accepted directives format into proper directive name.
@param name Name to normalize
*/
var directiveNormalize = function(name) {
return camelCase(name.replace(PREFIX_REGEXP, ''));
};
// END AngularJS port
return {
camelCase: camelCase,
directiveNormalize: directiveNormalize,
copy:_copy,
clone:_clone,
errorHeader: _errorHeader,
getObjectValue: _getObjectValue,
getObjectArrayPath:_getObjectArrayPath,
getObjectDotPath: _getObjectDotPath,
defaultTo: function(val, _default) {
return _isDefined(val) ? val : _default;
},
//mainly for checking attributes of directives lets keep this minimal (on what we accept)
isTruthy: function(val) {
return val === 'true' || val === true;
},
//Determine if a reference is {}
isEmpty: function(value) {
return Object.keys(value).length === 0;
},
//Determine if a reference is undefined or {}
isUndefinedOrEmpty: function(value) {
return (angular.isUndefined(value) || value === null) || Object.keys(value).length === 0;
},
// Determine if a reference is defined
isDefined: _isDefined,
isUndefined:_isUndefined,
isNumber: angular.isNumber,
isString: angular.isString,
isArray: angular.isArray,
isObject: angular.isObject,
isFunction: angular.isFunction,
equals: angular.equals,
isValidCenter: function(center) {
return angular.isDefined(center) && angular.isNumber(center.lat) &&
angular.isNumber(center.lng) && angular.isNumber(center.zoom);
},
isValidPoint: function(point) {
if (!angular.isDefined(point)) {
return false;
}
if (angular.isArray(point)) {
return point.length === 2 && angular.isNumber(point[0]) && angular.isNumber(point[1]);
}
return angular.isNumber(point.lat) && angular.isNumber(point.lng);
},
isSameCenterOnMap: function(centerModel, map) {
var mapCenter = map.getCenter();
var zoom = map.getZoom();
if (centerModel.lat && centerModel.lng &&
mapCenter.lat.toFixed(4) === centerModel.lat.toFixed(4) &&
mapCenter.lng.toFixed(4) === centerModel.lng.toFixed(4) &&
zoom === centerModel.zoom) {
return true;
}
return false;
},
safeApply: function($scope, fn) {
var phase = $scope.$root.$$phase;
if (phase === '$apply' || phase === '$digest') {
$scope.$eval(fn);
} else {
$scope.$evalAsync(fn);
}
},
obtainEffectiveMapId: _obtainEffectiveMapId,
getDefer: function(d, mapId) {
var id = _obtainEffectiveMapId(d, mapId);
var defer;
if (!angular.isDefined(d[id]) || d[id].resolvedDefer === false) {
defer = _getUnresolvedDefer(d, mapId);
} else {
defer = d[id].defer;
}
return defer;
},
getUnresolvedDefer: _getUnresolvedDefer,
setResolvedDefer: function(d, mapId) {
var id = _obtainEffectiveMapId(d, mapId);
d[id].resolvedDefer = true;
},
rangeIsSupported: function() {
var testrange = document.createElement('input');
testrange.setAttribute('type', 'range');
return testrange.type === 'range';
},
FullScreenControlPlugin: {
isLoaded: function() {
return angular.isDefined(L.Control.Fullscreen);
},
},
MiniMapControlPlugin: {
isLoaded: function() {
return angular.isDefined(L.Control.MiniMap);
},
},
AwesomeMarkersPlugin: {
isLoaded: function() {
return angular.isDefined(L.AwesomeMarkers) && angular.isDefined(L.AwesomeMarkers.Icon);
},
is: function(icon) {
if (this.isLoaded()) {
return icon instanceof L.AwesomeMarkers.Icon;
} else {
return false;
}
},
equal: function(iconA, iconB) {
if (!this.isLoaded()) {
return false;
}
if (this.is(iconA)) {
return angular.equals(iconA, iconB);
} else {
return false;
}
},
},
VectorMarkersPlugin: {
isLoaded: function() {
return angular.isDefined(L.VectorMarkers) && angular.isDefined(L.VectorMarkers.Icon);
},
is: function(icon) {
if (this.isLoaded()) {
return icon instanceof L.VectorMarkers.Icon;
} else {
return false;
}
},
equal: function(iconA, iconB) {
if (!this.isLoaded()) {
return false;
}
if (this.is(iconA)) {
return angular.equals(iconA, iconB);
} else {
return false;
}
},
},
DomMarkersPlugin: {
isLoaded: function() {
if (angular.isDefined(L.DomMarkers) && angular.isDefined(L.DomMarkers.Icon)) {
return true;
} else {
return false;
}
},
is: function(icon) {
if (this.isLoaded()) {
return icon instanceof L.DomMarkers.Icon;
} else {
return false;
}
},
equal: function(iconA, iconB) {
if (!this.isLoaded()) {
return false;
}
if (this.is(iconA)) {
return angular.equals(iconA, iconB);
} else {
return false;
}
},
},
PolylineDecoratorPlugin: {
isLoaded: function() {
if (angular.isDefined(L.PolylineDecorator)) {
return true;
} else {
return false;
}
},
is: function(decoration) {
if (this.isLoaded()) {
return decoration instanceof L.PolylineDecorator;
} else {
return false;
}
},
equal: function(decorationA, decorationB) {
if (!this.isLoaded()) {
return false;
}
if (this.is(decorationA)) {
return angular.equals(decorationA, decorationB);
} else {
return false;
}
},
},
MakiMarkersPlugin: {
isLoaded: function() {
if (angular.isDefined(L.MakiMarkers) && angular.isDefined(L.MakiMarkers.Icon)) {
return true;
} else {
return false;
}
},
is: function(icon) {
if (this.isLoaded()) {
return icon instanceof L.MakiMarkers.Icon;
} else {
return false;
}
},
equal: function(iconA, iconB) {
if (!this.isLoaded()) {
return false;
}
if (this.is(iconA)) {
return angular.equals(iconA, iconB);
} else {
return false;
}
},
},
ExtraMarkersPlugin: {
isLoaded: function() {
if (angular.isDefined(L.ExtraMarkers) && angular.isDefined(L.ExtraMarkers.Icon)) {
return true;
} else {
return false;
}
},
is: function(icon) {
if (this.isLoaded()) {
return icon instanceof L.ExtraMarkers.Icon;
} else {
return false;
}
},
equal: function(iconA, iconB) {
if (!this.isLoaded()) {
return false;
}
if (this.is(iconA)) {
return angular.equals(iconA, iconB);
} else {
return false;
}
},
},
LabelPlugin: {
isLoaded: function() {
return angular.isDefined(L.Label);
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof L.MarkerClusterGroup;
} else {
return false;
}
},
},
MarkerClusterPlugin: {
isLoaded: function() {
return angular.isDefined(L.MarkerClusterGroup);
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof L.MarkerClusterGroup;
} else {
return false;
}
},
},
GoogleLayerPlugin: {
isLoaded: function() {
return angular.isDefined(L.Google);
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof L.Google;
} else {
return false;
}
},
},
LeafletProviderPlugin: {
isLoaded: function() {
return angular.isDefined(L.TileLayer.Provider);
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof L.TileLayer.Provider;
} else {
return false;
}
},
},
ChinaLayerPlugin: {
isLoaded: function() {
return angular.isDefined(L.tileLayer.chinaProvider);
},
},
HeatLayerPlugin: {
isLoaded: function() {
return angular.isDefined(L.heatLayer);
},
},
WebGLHeatMapLayerPlugin: {
isLoaded: function() {
return angular.isDefined(L.TileLayer.WebGLHeatMap);
},
},
BingLayerPlugin: {
isLoaded: function() {
return angular.isDefined(L.BingLayer);
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof L.BingLayer;
} else {
return false;
}
},
},
WFSLayerPlugin: {
isLoaded: function() {
return L.GeoJSON.WFS !== undefined;
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof L.GeoJSON.WFS;
} else {
return false;
}
},
},
AGSBaseLayerPlugin: {
isLoaded: function() {
return L.esri !== undefined && L.esri.basemapLayer !== undefined;
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof L.esri.basemapLayer;
} else {
return false;
}
},
},
AGSLayerPlugin: {
isLoaded: function() {
return lvector !== undefined && lvector.AGS !== undefined;
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof lvector.AGS;
} else {
return false;
}
},
},
AGSFeatureLayerPlugin: {
isLoaded: function() {
return L.esri !== undefined && L.esri.featureLayer !== undefined;
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof L.esri.featureLayer;
} else {
return false;
}
},
},
AGSTiledMapLayerPlugin: {
isLoaded: function() {
return L.esri !== undefined && L.esri.tiledMapLayer !== undefined;
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof L.esri.tiledMapLayer;
} else {
return false;
}
},
},
AGSDynamicMapLayerPlugin: {
isLoaded: function() {
return L.esri !== undefined && L.esri.dynamicMapLayer !== undefined;
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof L.esri.dynamicMapLayer;
} else {
return false;
}
},
},
AGSImageMapLayerPlugin: {
isLoaded: function() {
return L.esri !== undefined && L.esri.imageMapLayer !== undefined;
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof L.esri.imageMapLayer;
} else {
return false;
}
},
},
AGSClusteredLayerPlugin: {
isLoaded: function() {
return L.esri !== undefined && L.esri.clusteredFeatureLayer !== undefined;
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof L.esri.clusteredFeatureLayer;
} else {
return false;
}
},
},
AGSHeatmapLayerPlugin: {
isLoaded: function() {
return L.esri !== undefined && L.esri.heatmapFeatureLayer !== undefined;
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof L.esri.heatmapFeatureLayer;
} else {
return false;
}
},
},
YandexLayerPlugin: {
isLoaded: function() {
return angular.isDefined(L.Yandex);
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof L.Yandex;
} else {
return false;
}
},
},
GeoJSONPlugin: {
isLoaded: function() {
return angular.isDefined(L.TileLayer.GeoJSON);
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof L.TileLayer.GeoJSON;
} else {
return false;
}
},
},
UTFGridPlugin: {
isLoaded: function() {
return angular.isDefined(L.UtfGrid);
},
is: function(layer) {
if (this.isLoaded()) {
return layer instanceof L.UtfGrid;
} else {
$log.error('[AngularJS - Leaflet] No UtfGrid plugin found.');
return false;
}
},
},
CartoDB: {
isLoaded: function() {
return cartodb;
},
is: function(/*layer*/) {
return true;
/*
if (this.isLoaded()) {
return layer instanceof L.TileLayer.GeoJSON;
} else {
return false;
}*/
},
},
Leaflet: {
DivIcon: {
is: function(icon) {
return icon instanceof L.DivIcon;
},
equal: function(iconA, iconB) {
if (this.is(iconA)) {
return angular.equals(iconA, iconB);
} else {
return false;
}
},
},
Icon: {
is: function(icon) {
return icon instanceof L.Icon;
},
equal: function(iconA, iconB) {
if (this.is(iconA)) {
return angular.equals(iconA, iconB);
} else {
return false;
}
},
},
},
/*
watchOptions - object to set deep nested watches and turn off watches all together
(rely on control / functional updates)
watchOptions - Object
doWatch:boolean
isDeep:boolean (sets $watch(function,isDeep))
individual
doWatch:boolean
isDeep:boolean
*/
//legacy defaults
watchOptions: {
doWatch:true,
isDeep: true,
individual:{
doWatch:true,
isDeep: true,
},
},
};
}]);
angular.module('leaflet-directive').service('leafletIterators', ["$log", "leafletHelpers", function($log, leafletHelpers) {
var lHlp = leafletHelpers;
var errorHeader = leafletHelpers.errorHeader + 'leafletIterators: ';
//BEGIN COPY from underscore
var _keys = Object.keys;
var _isFunction = lHlp.isFunction;
var _isObject = lHlp.isObject;
// Helper for collection methods to determine whether a collection
// should be iterated as an array or as an object
// Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
var _isArrayLike = function(collection) {
var length = collection !== null && collection.length;
return lHlp.isNumber(length) && length >= 0 && length <= MAX_ARRAY_INDEX;
};
// Keep the identity function around for default iteratees.
var _identity = function(value) {
return value;
};
var _property = function(key) {
return function(obj) {
return obj === null ? void 0 : obj[key];
};
};
// Internal function that returns an efficient (for current engines) version
// of the passed-in callback, to be repeatedly applied in other Underscore
// functions.
var optimizeCb = function(func, context, argCount) {
if (context === void 0) return func;
switch (argCount === null ? 3 : argCount) {
case 1: return function(value) {
return func.call(context, value);
};
case 2: return function(value, other) {
return func.call(context, value, other);
};
case 3: return function(value, index, collection) {
return func.call(context, value, index, collection);
};
case 4: return function(accumulator, value, index, collection) {
return func.call(context, accumulator, value, index, collection);
};
}
return function() {
return func.apply(context, arguments);
};
};
// An internal function for creating assigner functions.
var createAssigner = function(keysFunc, undefinedOnly) {
return function(obj) {
var length = arguments.length;
if (length < 2 || obj === null) return obj;
for (var index = 1; index < length; index++) {
var source = arguments[index];
var keys = keysFunc(source);
var l = keys.length;
for (var i = 0; i < l; i++) {
var key = keys[i];
if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
}
}
return obj;
};
};
// Assigns a given object with all the own properties in the passed-in object(s)
// (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
var _extendOwn;
var _assign = null;
_extendOwn = _assign = createAssigner(_keys);
// Returns whether an object has a given set of `key:value` pairs.
var _isMatch = function(object, attrs) {
var keys = _keys(attrs);
var length = keys.length;
if (object === null) return !length;
var obj = Object(object);
for (var i = 0; i < length; i++) {
var key = keys[i];
if (attrs[key] !== obj[key] || !(key in obj)) return false;
}
return true;
};
// Returns a predicate for checking whether an object has a given set of
// `key:value` pairs.
var _matcher;
var _matches = null;
_matcher = _matches = function(attrs) {
attrs = _extendOwn({}, attrs);
return function(obj) {
return _isMatch(obj, attrs);
};
};
// A mostly-internal function to generate callbacks that can be applied
// to each element in a collection, returning the desired result — either
// identity, an arbitrary callback, a property matcher, or a property accessor.
var cb = function(value, context, argCount) {
if (value === null) return _identity;
if (_isFunction(value)) return optimizeCb(value, context, argCount);
if (_isObject(value)) return _matcher(value);
return _property(value);
};
var _every;
var _all = null;
_every = _all = function(obj, predicate, context) {
predicate = cb(predicate, context);
var keys = !_isArrayLike(obj) && _keys(obj);
var length = (keys || obj).length;
for (var index = 0; index < length; index++) {
var currentKey = keys ? keys[index] : index;
if (!predicate(obj[currentKey], currentKey, obj)) return false;
}
return true;
};
//END COPY fron underscore
var _hasErrors = function(collection, cb, ignoreCollection, cbName) {
if (!ignoreCollection) {
if (!lHlp.isDefined(collection) || !lHlp.isDefined(cb)) {
return true;
}
}
if (!lHlp.isFunction(cb)) {
cbName = lHlp.defaultTo(cb, 'cb');
$log.error(errorHeader + cbName + ' is not a function');
return true;
}
return false;
};
var _iterate = function(collection, externalCb, internalCb) {
if (_hasErrors(undefined, internalCb, true, 'internalCb')) {
return;
}
if (!_hasErrors(collection, externalCb)) {
for (var key in collection) {
if (collection.hasOwnProperty(key)) {
internalCb(collection[key], key);
}
}
}
};
//see http://jsperf.com/iterators/3
//utilizing for in is way faster
var _each = function(collection, cb) {
_iterate(collection, cb, function(val, key) {
cb(val, key);
});
};
return {
each:_each,
forEach: _each,
every: _every,
all: _all,
};
}]);
angular.module('leaflet-directive')
.factory('leafletLayerHelpers', ["$rootScope", "$log", "$q", "leafletHelpers", "leafletIterators", function($rootScope, $log, $q, leafletHelpers, leafletIterators) {
var Helpers = leafletHelpers;
var isString = leafletHelpers.isString;
var isObject = leafletHelpers.isObject;
var isArray = leafletHelpers.isArray;
var isDefined = leafletHelpers.isDefined;
var errorHeader = leafletHelpers.errorHeader;
var $it = leafletIterators;
var utfGridCreateLayer = function(params) {
if (!Helpers.UTFGridPlugin.isLoaded()) {
$log.error('[AngularJS - Leaflet] The UTFGrid plugin is not loaded.');
return;
}
var utfgrid = new L.UtfGrid(params.url, params.pluginOptions);
utfgrid.on('mouseover', function(e) {
$rootScope.$broadcast('leafletDirectiveMap.utfgridMouseover', e);
});
utfgrid.on('mouseout', function(e) {
$rootScope.$broadcast('leafletDirectiveMap.utfgridMouseout', e);
});
utfgrid.on('click', function(e) {
$rootScope.$broadcast('leafletDirectiveMap.utfgridClick', e);
});
utfgrid.on('mousemove', function(e) {
$rootScope.$broadcast('leafletDirectiveMap.utfgridMousemove', e);
});
return utfgrid;
};
var layerTypes = {
xyz: {
mustHaveUrl: true,
createLayer: function(params) {
return L.tileLayer(params.url, params.options);
},
},
mapbox: {
mustHaveKey: true,
createLayer: function(params) {
var version = 3;
if (isDefined(params.options.version) && params.options.version === 4) {
version = params.options.version;
}
var url = version === 3 ?
'//{s}.tiles.mapbox.com/v3/' + params.key + '/{z}/{x}/{y}.png' :
'//api.tiles.mapbox.com/v4/' + params.key + '/{z}/{x}/{y}.png?access_token=' + params.apiKey;
return L.tileLayer(url, params.options);
},
},
geoJSON: {
mustHaveUrl: true,
createLayer: function(params) {
if (!Helpers.GeoJSONPlugin.isLoaded()) {
return;
}
return new L.TileLayer.GeoJSON(params.url, params.pluginOptions, params.options);
},
},
geoJSONShape: {
mustHaveUrl: false,
createLayer: function(params) {
return new L.GeoJSON(params.data,
params.options);
},
},
geoJSONAwesomeMarker: {
mustHaveUrl: false,
createLayer: function(params) {
return new L.geoJson(params.data, {
pointToLayer: function(feature, latlng) {
return L.marker(latlng, {icon: L.AwesomeMarkers.icon(params.icon)});
},
});
},
},
geoJSONVectorMarker: {
mustHaveUrl: false,
createLayer: function(params) {
return new L.geoJson(params.data, {
pointToLayer: function(feature, latlng) {
return L.marker(latlng, {icon: L.VectorMarkers.icon(params.icon)});
},
});
},
},
utfGrid: {
mustHaveUrl: true,
createLayer: utfGridCreateLayer,
},
cartodbTiles: {
mustHaveKey: true,
createLayer: function(params) {
var url = '//' + params.user + '.cartodb.com/api/v1/map/' + params.key + '/{z}/{x}/{y}.png';
return L.tileLayer(url, params.options);
},
},
cartodbUTFGrid: {
mustHaveKey: true,
mustHaveLayer: true,
createLayer: function(params) {
params.url = '//' + params.user + '.cartodb.com/api/v1/map/' + params.key + '/' + params.layer + '/{z}/{x}/{y}.grid.json';
return utfGridCreateLayer(params);
},
},
cartodbInteractive: {
mustHaveKey: true,
mustHaveLayer: true,
createLayer: function(params) {
var tilesURL = '//' + params.user + '.cartodb.com/api/v1/map/' + params.key + '/{z}/{x}/{y}.png';
var tileLayer = L.tileLayer(tilesURL, params.options);
params.url = '//' + params.user + '.cartodb.com/api/v1/map/' + params.key + '/' + params.layer + '/{z}/{x}/{y}.grid.json';
var utfLayer = utfGridCreateLayer(params);
return L.layerGroup([tileLayer, utfLayer]);
},
},
wms: {
mustHaveUrl: true,
createLayer: function(params) {
return L.tileLayer.wms(params.url, params.options);
},
},
wmts: {
mustHaveUrl: true,
createLayer: function(params) {
return L.tileLayer.wmts(params.url, params.options);
},
},
wfs: {
mustHaveUrl: true,
mustHaveLayer: true,
createLayer: function(params) {
if (!Helpers.WFSLayerPlugin.isLoaded()) {
return;
}
var options = angular.copy(params.options);
if (options.crs && typeof options.crs === 'string') {
/*jshint -W061 */
options.crs = eval(options.crs);
}
return new L.GeoJSON.WFS(params.url, params.layer, options);
},
},
group: {
mustHaveUrl: false,
createLayer: function(params) {
var lyrs = [];
$it.each(params.options.layers, function(l) {
lyrs.push(createLayer(l));
});
params.options.loadedDefer = function() {
var defers = [];
if (isDefined(params.options.layers)) {
for (var i = 0; i < params.options.layers.length; i++) {
var d = params.options.layers[i].layerOptions.loadedDefer;
if (isDefined(d)) {
defers.push(d);
}
}
}
return defers;
};
return L.layerGroup(lyrs);
},
},
featureGroup: {
mustHaveUrl: false,
createLayer: function() {
return L.featureGroup();
},
},
google: {
mustHaveUrl: false,
createLayer: function(params) {
var type = params.type || 'SATELLITE';
if (!Helpers.GoogleLayerPlugin.isLoaded()) {
return;
}
return new L.Google(type, params.options);
},
},
here: {
mustHaveUrl: false,
createLayer: function(params) {
var provider = params.provider || 'HERE.terrainDay';
if (!Helpers.LeafletProviderPlugin.isLoaded()) {
return;
}
return new L.TileLayer.Provider(provider, params.options);
},
},
china:{
mustHaveUrl:false,
createLayer:function(params) {
var type = params.type || '';
if (!Helpers.ChinaLayerPlugin.isLoaded()) {
return;
}
return L.tileLayer.chinaProvider(type, params.options);
},
},
agsBase: {
mustHaveLayer: true,
createLayer: function(params) {
if (!Helpers.AGSBaseLayerPlugin.isLoaded()) {
return;
}
return L.esri.basemapLayer(params.layer, params.options);
},
},
ags: {
mustHaveUrl: true,
createLayer: function(params) {
if (!Helpers.AGSLayerPlugin.isLoaded()) {
return;
}
var options = angular.copy(params.options);
angular.extend(options, {
url: params.url,
});
var layer = new lvector.AGS(options);
layer.onAdd = function(map) {
this.setMap(map);
};
layer.onRemove = function() {
this.setMap(null);
};
return layer;
},
},
agsFeature: {
mustHaveUrl: true,
createLayer: function(params) {
if (!Helpers.AGSFeatureLayerPlugin.isLoaded()) {
$log.warn(errorHeader + ' The esri plugin is not loaded.');
return;
}
params.options.url = params.url;
var layer = L.esri.featureLayer(params.options);
var load = function() {
if (isDefined(params.options.loadedDefer)) {
params.options.loadedDefer.resolve();
}
};
layer.on('loading', function() {
params.options.loadedDefer = $q.defer();
layer.off('load', load);
layer.on('load', load);
});
return layer;
},
},
agsTiled: {
mustHaveUrl: true,
createLayer: function(params) {
if (!Helpers.AGSTiledMapLayerPlugin.isLoaded()) {
$log.warn(errorHeader + ' The esri plugin is not loaded.');
return;
}
params.options.url = params.url;
return L.esri.tiledMapLayer(params.options);
},
},
agsDynamic: {
mustHaveUrl: true,
createLayer: function(params) {
if (!Helpers.AGSDynamicMapLayerPlugin.isLoaded()) {
$log.warn(errorHeader + ' The esri plugin is not loaded.');
return;
}
params.options.url = params.url;
return L.esri.dynamicMapLayer(params.options);
},
},
agsImage: {
mustHaveUrl: true,
createLayer: function(params) {
if (!Helpers.AGSImageMapLayerPlugin.isLoaded()) {
$log.warn(errorHeader + ' The esri plugin is not loaded.');
return;
}
params.options.url = params.url;
return L.esri.imageMapLayer(params.options);
},
},
agsClustered: {
mustHaveUrl: true,
createLayer: function(params) {
if (!Helpers.AGSClusteredLayerPlugin.isLoaded()) {
$log.warn(errorHeader + ' The esri clustered layer plugin is not loaded.');
return;
}
if (!Helpers.MarkerClusterPlugin.isLoaded()) {
$log.warn(errorHeader + ' The markercluster plugin is not loaded.');
return;
}
return L.esri.clusteredFeatureLayer(params.url, params.options);
},
},
agsHeatmap: {
mustHaveUrl: true,
createLayer: function(params) {
if (!Helpers.AGSHeatmapLayerPlugin.isLoaded()) {
$log.warn(errorHeader + ' The esri heatmap layer plugin is not loaded.');
return;
}
if (!Helpers.HeatLayerPlugin.isLoaded()) {
$log.warn(errorHeader + ' The heatlayer plugin is not loaded.');
return;
}
return L.esri.heatmapFeatureLayer(params.url, params.options);
},
},
markercluster: {
mustHaveUrl: false,
createLayer: function(params) {
if (!Helpers.MarkerClusterPlugin.isLoaded()) {
$log.warn(errorHeader + ' The markercluster plugin is not loaded.');
return;
}
return new L.MarkerClusterGroup(params.options);
},
},
bing: {
mustHaveUrl: false,
createLayer: function(params) {
if (!Helpers.BingLayerPlugin.isLoaded()) {
return;
}
return new L.BingLayer(params.key, params.options);
},
},
webGLHeatmap: {
mustHaveUrl: false,
mustHaveData: true,
createLayer: function(params) {
if (!Helpers.WebGLHeatMapLayerPlugin.isLoaded()) {
return;
}
var layer = new L.TileLayer.WebGLHeatMap(params.options);
if (isDefined(params.data)) {
layer.setData(params.data);
}
return layer;
},
},
heat: {
mustHaveUrl: false,
mustHaveData: true,
createLayer: function(params) {
if (!Helpers.HeatLayerPlugin.isLoaded()) {
return;
}
var layer = new L.heatLayer();
if (isArray(params.data)) {
layer.setLatLngs(params.data);
}
if (isObject(params.options)) {
layer.setOptions(params.options);
}
return layer;
},
},
yandex: {
mustHaveUrl: false,
createLayer: function(params) {
var type = params.type || 'map';
if (!Helpers.YandexLayerPlugin.isLoaded()) {
return;
}
return new L.Yandex(type, params.options);
},
},
imageOverlay: {
mustHaveUrl: true,
mustHaveBounds: true,
createLayer: function(params) {
return L.imageOverlay(params.url, params.bounds, params.options);
},
},
iip: {
mustHaveUrl: true,
createLayer: function(params) {
return L.tileLayer.iip(params.url, params.options);
},
},
// This "custom" type is used to accept every layer that user want to define himself.
// We can wrap these custom layers like heatmap or yandex, but it means a lot of work/code to wrap the world,
// so we let user to define their own layer outside the directive,
// and pass it on "createLayer" result for next processes
custom: {
createLayer: function(params) {
if (params.layer instanceof L.Class) {
return angular.copy(params.layer);
} else {
$log.error('[AngularJS - Leaflet] A custom layer must be a leaflet Class');
}
},
},
cartodb: {
mustHaveUrl: true,
createLayer: function(params) {
return cartodb.createLayer(params.map, params.url);
},
},
};
function isValidLayerType(layerDefinition) {
// Check if the baselayer has a valid type
if (!isString(layerDefinition.type)) {
$log.error('[AngularJS - Leaflet] A layer must have a valid type defined.');
return false;
}
if (Object.keys(layerTypes).indexOf(layerDefinition.type) === -1) {
$log.error('[AngularJS - Leaflet] A layer must have a valid type: ' + Object.keys(layerTypes));
return false;
}
// Check if the layer must have an URL
if (layerTypes[layerDefinition.type].mustHaveUrl && !isString(layerDefinition.url)) {
$log.error('[AngularJS - Leaflet] A base layer must have an url');
return false;
}
if (layerTypes[layerDefinition.type].mustHaveData && !isDefined(layerDefinition.data)) {
$log.error('[AngularJS - Leaflet] The base layer must have a "data" array attribute');
return false;
}
if (layerTypes[layerDefinition.type].mustHaveLayer && !isDefined(layerDefinition.layer)) {
$log.error('[AngularJS - Leaflet] The type of layer ' + layerDefinition.type + ' must have an layer defined');
return false;
}
if (layerTypes[layerDefinition.type].mustHaveBounds && !isDefined(layerDefinition.bounds)) {
$log.error('[AngularJS - Leaflet] The type of layer ' + layerDefinition.type + ' must have bounds defined');
return false;
}
if (layerTypes[layerDefinition.type].mustHaveKey && !isDefined(layerDefinition.key)) {
$log.error('[AngularJS - Leaflet] The type of layer ' + layerDefinition.type + ' must have key defined');
return false;
}
return true;
}
function createLayer(layerDefinition) {
if (!isValidLayerType(layerDefinition)) {
return;
}
if (!isString(layerDefinition.name)) {
$log.error('[AngularJS - Leaflet] A base layer must have a name');
return;
}
if (!isObject(layerDefinition.layerParams)) {
layerDefinition.layerParams = {};
}
if (!isObject(layerDefinition.layerOptions)) {
layerDefinition.layerOptions = {};
}
// Mix the layer specific parameters with the general Leaflet options. Although this is an overhead
// the definition of a base layers is more 'clean' if the two types of parameters are differentiated
for (var attrname in layerDefinition.layerParams) {
layerDefinition.layerOptions[attrname] = layerDefinition.layerParams[attrname];
}
var params = {
url: layerDefinition.url,
data: layerDefinition.data,
options: layerDefinition.layerOptions,
layer: layerDefinition.layer,
icon: layerDefinition.icon,
type: layerDefinition.layerType,
bounds: layerDefinition.bounds,
key: layerDefinition.key,
apiKey: layerDefinition.apiKey,
pluginOptions: layerDefinition.pluginOptions,
user: layerDefinition.user,
};
//TODO Add $watch to the layer properties
return layerTypes[layerDefinition.type].createLayer(params);
}
function safeAddLayer(map, layer) {
if (layer && typeof layer.addTo === 'function') {
layer.addTo(map);
} else {
map.addLayer(layer);
}
}
function safeRemoveLayer(map, layer, layerOptions) {
if (isDefined(layerOptions) && isDefined(layerOptions.loadedDefer)) {
if (angular.isFunction(layerOptions.loadedDefer)) {
var defers = layerOptions.loadedDefer();
$log.debug('Loaded Deferred', defers);
var count = defers.length;
if (count > 0) {
var resolve = function() {
count--;
if (count === 0) {
map.removeLayer(layer);
}
};
for (var i = 0; i < defers.length; i++) {
defers[i].promise.then(resolve);
}
} else {
map.removeLayer(layer);
}
} else {
layerOptions.loadedDefer.promise.then(function() {
map.removeLayer(layer);
});
}
} else {
map.removeLayer(layer);
}
}
return {
createLayer: createLayer,
safeAddLayer: safeAddLayer,
safeRemoveLayer: safeRemoveLayer,
};
}]);
angular.module('leaflet-directive').factory('leafletLegendHelpers', function() {
var _updateLegend = function(div, legendData, type, url) {
div.innerHTML = '';
if (legendData.error) {
div.innerHTML += '
' + 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 +=
'
',
link: function(scope, element, attrs, controller) {
var isDefined = leafletHelpers.isDefined;
var leafletScope = controller.getLeafletScope();
var layers = leafletScope.layers;
scope.$watch('icons', function() {
var defaultIcons = {
uncheck: 'fa fa-square-o',
check: 'fa fa-check-square-o',
radio: 'fa fa-dot-circle-o',
unradio: 'fa fa-circle-o',
up: 'fa fa-angle-up',
down: 'fa fa-angle-down',
open: 'fa fa-angle-double-down',
close: 'fa fa-angle-double-up',
toggleLegend: 'fa fa-pencil-square-o',
};
if (isDefined(scope.icons)) {
angular.extend(defaultIcons, scope.icons);
angular.extend(scope.icons, defaultIcons);
} else {
scope.icons = defaultIcons;
}
});
// Setting layer stack order.
attrs.order = (isDefined(attrs.order) && (attrs.order === 'normal' || attrs.order === 'reverse')) ? attrs.order : 'normal';
scope.order = attrs.order === 'normal';
scope.orderNumber = attrs.order === 'normal' ? -1 : 1;
scope.layers = layers;
controller.getMap().then(function(map) {
leafletScope.$watch('layers.baselayers', function(newBaseLayers) {
var baselayersArray = {};
leafletData.getLayers().then(function(leafletLayers) {
var key;
for (key in newBaseLayers) {
var layer = newBaseLayers[key];
layer.icon = scope.icons[map.hasLayer(leafletLayers.baselayers[key]) ? 'radio' : 'unradio'];
baselayersArray[key] = layer;
}
scope.baselayersArray = baselayersArray;
});
});
leafletScope.$watch('layers.overlays', function(newOverlayLayers) {
var overlaysArray = [];
var groupVisibleCount = {};
leafletData.getLayers().then(function(leafletLayers) {
var key;
for (key in newOverlayLayers) {
var layer = newOverlayLayers[key];
layer.icon = scope.icons[(layer.visible ? 'check' : 'uncheck')];
overlaysArray.push(layer);
if (!isDefined(scope.layerProperties[layer.name])) {
scope.layerProperties[layer.name] = {
opacity: isDefined(layer.layerOptions.opacity) ? layer.layerOptions.opacity * 100 : 100,
opacityControl: false,
showLegend: true,
};
}
if (isDefined(layer.group)) {
if (!isDefined(scope.groupProperties[layer.group])) {
scope.groupProperties[layer.group] = {
visible: false,
};
}
groupVisibleCount[layer.group] = isDefined(groupVisibleCount[layer.group]) ? groupVisibleCount[layer.group] : {
count: 0,
visibles: 0,
};
groupVisibleCount[layer.group].count++;
if (layer.visible) {
groupVisibleCount[layer.group].visibles++;
}
}
if (isDefined(layer.index) && leafletLayers.overlays[key].setZIndex) {
leafletLayers.overlays[key].setZIndex(newOverlayLayers[key].index);
}
}
for (key in groupVisibleCount) {
scope.groupProperties[key].visible = groupVisibleCount[key].visibles === groupVisibleCount[key].count;
}
scope.overlaysArray = overlaysArray;
});
}, true);
});
},
};
}]);
angular.module('leaflet-directive').directive('layers', ["$log", "$q", "leafletData", "leafletHelpers", "leafletLayerHelpers", "leafletControlHelpers", function($log, $q, leafletData, leafletHelpers, leafletLayerHelpers, leafletControlHelpers) {
return {
restrict: 'A',
scope: false,
replace: false,
require: 'leaflet',
controller: ["$scope", function($scope) {
$scope._leafletLayers = $q.defer();
this.getLayers = function() {
return $scope._leafletLayers.promise;
};
}],
link: function(scope, element, attrs, controller) {
var isDefined = leafletHelpers.isDefined;
var leafletLayers = {};
var leafletScope = controller.getLeafletScope();
var layers = leafletScope.layers;
var createLayer = leafletLayerHelpers.createLayer;
var safeAddLayer = leafletLayerHelpers.safeAddLayer;
var safeRemoveLayer = leafletLayerHelpers.safeRemoveLayer;
var updateLayersControl = leafletControlHelpers.updateLayersControl;
var isLayersControlVisible = false;
controller.getMap().then(function(map) {
// We have baselayers to add to the map
scope._leafletLayers.resolve(leafletLayers);
leafletData.setLayers(leafletLayers, attrs.id);
leafletLayers.baselayers = {};
leafletLayers.overlays = {};
var mapId = attrs.id;
// Setup all baselayers definitions
var oneVisibleLayer = false;
for (var layerName in layers.baselayers) {
var newBaseLayer = createLayer(layers.baselayers[layerName]);
if (!isDefined(newBaseLayer)) {
delete layers.baselayers[layerName];
continue;
}
leafletLayers.baselayers[layerName] = newBaseLayer;
// Only add the visible layer to the map, layer control manages the addition to the map
// of layers in its control
if (layers.baselayers[layerName].top === true) {
safeAddLayer(map, leafletLayers.baselayers[layerName]);
oneVisibleLayer = true;
}
}
// If there is no visible layer add first to the map
if (!oneVisibleLayer && Object.keys(leafletLayers.baselayers).length > 0) {
safeAddLayer(map, leafletLayers.baselayers[Object.keys(layers.baselayers)[0]]);
}
// Setup the Overlays
for (layerName in layers.overlays) {
//if (layers.overlays[layerName].type === 'cartodb') {
//
//}
var newOverlayLayer = createLayer(layers.overlays[layerName]);
if (!isDefined(newOverlayLayer)) {
delete layers.overlays[layerName];
continue;
}
leafletLayers.overlays[layerName] = newOverlayLayer;
// Only add the visible overlays to the map
if (layers.overlays[layerName].visible === true) {
safeAddLayer(map, leafletLayers.overlays[layerName]);
}
}
// Watch for the base layers
leafletScope.$watch('layers.baselayers', function(newBaseLayers, oldBaseLayers) {
if (angular.equals(newBaseLayers, oldBaseLayers)) {
isLayersControlVisible = updateLayersControl(map, mapId, isLayersControlVisible, newBaseLayers, layers.overlays, leafletLayers);
return true;
}
// Delete layers from the array
for (var name in leafletLayers.baselayers) {
if (!isDefined(newBaseLayers[name]) || newBaseLayers[name].doRefresh) {
// Remove from the map if it's on it
if (map.hasLayer(leafletLayers.baselayers[name])) {
map.removeLayer(leafletLayers.baselayers[name]);
}
delete leafletLayers.baselayers[name];
if (newBaseLayers[name] && newBaseLayers[name].doRefresh) {
newBaseLayers[name].doRefresh = false;
}
}
}
// add new layers
for (var newName in newBaseLayers) {
if (!isDefined(leafletLayers.baselayers[newName])) {
var testBaseLayer = createLayer(newBaseLayers[newName]);
if (isDefined(testBaseLayer)) {
leafletLayers.baselayers[newName] = testBaseLayer;
// Only add the visible layer to the map
if (newBaseLayers[newName].top === true) {
safeAddLayer(map, leafletLayers.baselayers[newName]);
}
}
} else {
if (newBaseLayers[newName].top === true && !map.hasLayer(leafletLayers.baselayers[newName])) {
safeAddLayer(map, leafletLayers.baselayers[newName]);
} else if (newBaseLayers[newName].top === false && map.hasLayer(leafletLayers.baselayers[newName])) {
map.removeLayer(leafletLayers.baselayers[newName]);
}
}
}
//we have layers, so we need to make, at least, one active
var found = false;
// search for an active layer
for (var key in leafletLayers.baselayers) {
if (map.hasLayer(leafletLayers.baselayers[key])) {
found = true;
break;
}
}
// If there is no active layer make one active
if (!found && Object.keys(leafletLayers.baselayers).length > 0) {
safeAddLayer(map, leafletLayers.baselayers[Object.keys(leafletLayers.baselayers)[0]]);
}
// Only show the layers switch selector control if we have more than one baselayer + overlay
isLayersControlVisible = updateLayersControl(map, mapId, isLayersControlVisible, newBaseLayers, layers.overlays, leafletLayers);
}, true);
// Watch for the overlay layers
leafletScope.$watch('layers.overlays', function(newOverlayLayers, oldOverlayLayers) {
if (angular.equals(newOverlayLayers, oldOverlayLayers)) {
isLayersControlVisible = updateLayersControl(map, mapId, isLayersControlVisible, layers.baselayers, newOverlayLayers, leafletLayers);
return true;
}
// Delete layers from the array
for (var name in leafletLayers.overlays) {
if (!isDefined(newOverlayLayers[name]) || newOverlayLayers[name].doRefresh) {
// Remove from the map if it's on it
if (map.hasLayer(leafletLayers.overlays[name])) {
// Safe remove when ArcGIS layers is loading.
var options = isDefined(newOverlayLayers[name]) ?
newOverlayLayers[name].layerOptions : null;
safeRemoveLayer(map, leafletLayers.overlays[name], options);
}
// TODO: Depending on the layer type we will have to delete what's included on it
delete leafletLayers.overlays[name];
if (newOverlayLayers[name] && newOverlayLayers[name].doRefresh) {
newOverlayLayers[name].doRefresh = false;
}
}
}
// add new overlays
for (var newName in newOverlayLayers) {
if (!isDefined(leafletLayers.overlays[newName])) {
var testOverlayLayer = createLayer(newOverlayLayers[newName]);
if (!isDefined(testOverlayLayer)) {
// If the layer creation fails, continue to the next overlay
continue;
}
leafletLayers.overlays[newName] = testOverlayLayer;
if (newOverlayLayers[newName].visible === true) {
safeAddLayer(map, leafletLayers.overlays[newName]);
}
} else {
// check for the .visible property to hide/show overLayers
if (newOverlayLayers[newName].visible && !map.hasLayer(leafletLayers.overlays[newName])) {
safeAddLayer(map, leafletLayers.overlays[newName]);
} else if (newOverlayLayers[newName].visible === false && map.hasLayer(leafletLayers.overlays[newName])) {
// Safe remove when ArcGIS layers is loading.
safeRemoveLayer(map, leafletLayers.overlays[newName], newOverlayLayers[newName].layerOptions);
}
}
//refresh heatmap data if present
if (newOverlayLayers[newName].visible && map._loaded && newOverlayLayers[newName].data && newOverlayLayers[newName].type === 'heatmap') {
leafletLayers.overlays[newName].setData(newOverlayLayers[newName].data);
leafletLayers.overlays[newName].update();
}
}
// Only add the layers switch selector control if we have more than one baselayer + overlay
isLayersControlVisible = updateLayersControl(map, mapId, isLayersControlVisible, layers.baselayers, newOverlayLayers, leafletLayers);
}, true);
});
},
};
}]);
angular.module('leaflet-directive').directive('legend', ["$log", "$http", "leafletHelpers", "leafletLegendHelpers", function($log, $http, leafletHelpers, leafletLegendHelpers) {
return {
restrict: 'A',
scope: false,
replace: false,
require: 'leaflet',
link: function(scope, element, attrs, controller) {
var isArray = leafletHelpers.isArray;
var isDefined = leafletHelpers.isDefined;
var isFunction = leafletHelpers.isFunction;
var leafletScope = controller.getLeafletScope();
var legend = leafletScope.legend;
var legendClass;
var position;
var leafletLegend;
var type;
leafletScope.$watch('legend', function(newLegend) {
if (isDefined(newLegend)) {
legendClass = newLegend.legendClass ? newLegend.legendClass : 'legend';
position = newLegend.position || 'bottomright';
// default to arcgis
type = newLegend.type || 'arcgis';
}
}, true);
controller.getMap().then(function(map) {
leafletScope.$watch('legend', function(newLegend) {
if (!isDefined(newLegend)) {
if (isDefined(leafletLegend)) {
leafletLegend.removeFrom(map);
leafletLegend = null;
}
return;
}
if (!isDefined(newLegend.url) && (type === 'arcgis') && (!isArray(newLegend.colors) || !isArray(newLegend.labels) || newLegend.colors.length !== newLegend.labels.length)) {
$log.warn('[AngularJS - Leaflet] legend.colors and legend.labels must be set.');
return;
}
if (isDefined(newLegend.url)) {
$log.info('[AngularJS - Leaflet] loading legend service.');
return;
}
if (isDefined(leafletLegend)) {
leafletLegend.removeFrom(map);
leafletLegend = null;
}
leafletLegend = L.control({
position: position,
});
if (type === 'arcgis') {
leafletLegend.onAdd = leafletLegendHelpers.getOnAddArrayLegend(newLegend, legendClass);
}
leafletLegend.addTo(map);
});
leafletScope.$watch('legend.url', function(newURL) {
if (!isDefined(newURL)) {
return;
}
$http.get(newURL)
.success(function(legendData) {
if (isDefined(leafletLegend)) {
leafletLegendHelpers.updateLegend(leafletLegend.getContainer(), legendData, type, newURL);
} else {
leafletLegend = L.control({
position: position,
});
leafletLegend.onAdd = leafletLegendHelpers.getOnAddLegend(legendData, legendClass, type, newURL);
leafletLegend.addTo(map);
}
if (isDefined(legend.loadedData) && isFunction(legend.loadedData)) {
legend.loadedData();
}
})
.error(function() {
$log.warn('[AngularJS - Leaflet] legend.url not loaded.');
});
});
});
},
};
}]);
angular.module('leaflet-directive').directive('markers',
["$log", "$rootScope", "$q", "leafletData", "leafletHelpers", "leafletMapDefaults", "leafletMarkersHelpers", "leafletMarkerEvents", "leafletIterators", "leafletWatchHelpers", "leafletDirectiveControlsHelpers", function($log, $rootScope, $q, leafletData, leafletHelpers, leafletMapDefaults,
leafletMarkersHelpers, leafletMarkerEvents, leafletIterators, leafletWatchHelpers,
leafletDirectiveControlsHelpers) {
//less terse vars to helpers
var isDefined = leafletHelpers.isDefined;
var errorHeader = leafletHelpers.errorHeader;
var Helpers = leafletHelpers;
var isString = leafletHelpers.isString;
var addMarkerWatcher = leafletMarkersHelpers.addMarkerWatcher;
var updateMarker = leafletMarkersHelpers.updateMarker;
var listenMarkerEvents = leafletMarkersHelpers.listenMarkerEvents;
var addMarkerToGroup = leafletMarkersHelpers.addMarkerToGroup;
var createMarker = leafletMarkersHelpers.createMarker;
var deleteMarker = leafletMarkersHelpers.deleteMarker;
var $it = leafletIterators;
var _markersWatchOptions = leafletHelpers.watchOptions;
var maybeWatch = leafletWatchHelpers.maybeWatch;
var extendDirectiveControls = leafletDirectiveControlsHelpers.extend;
var _getLMarker = function(leafletMarkers, name, maybeLayerName) {
if (!Object.keys(leafletMarkers).length) return;
if (maybeLayerName && isString(maybeLayerName)) {
if (!leafletMarkers[maybeLayerName] || !Object.keys(leafletMarkers[maybeLayerName]).length)
return;
return leafletMarkers[maybeLayerName][name];
}
return leafletMarkers[name];
};
var _setLMarker = function(lObject, leafletMarkers, name, maybeLayerName) {
if (maybeLayerName && isString(maybeLayerName)) {
if (!isDefined(leafletMarkers[maybeLayerName]))
leafletMarkers[maybeLayerName] = {};
leafletMarkers[maybeLayerName][name] = lObject;
} else
leafletMarkers[name] = lObject;
return lObject;
};
var _maybeAddMarkerToLayer = function(layerName, layers, model, marker, doIndividualWatch, map) {
if (!isString(layerName)) {
$log.error(errorHeader + ' A layername must be a string');
return false;
}
if (!isDefined(layers)) {
$log.error(errorHeader + ' You must add layers to the directive if the markers are going to use this functionality.');
return false;
}
if (!isDefined(layers.overlays) || !isDefined(layers.overlays[layerName])) {
$log.error(errorHeader + ' A marker can only be added to a layer of type "group"');
return false;
}
var layerGroup = layers.overlays[layerName];
if (!(layerGroup instanceof L.LayerGroup || layerGroup instanceof L.FeatureGroup)) {
$log.error(errorHeader + ' Adding a marker to an overlay needs a overlay of the type "group" or "featureGroup"');
return false;
}
// The marker goes to a correct layer group, so first of all we add it
layerGroup.addLayer(marker);
// The marker is automatically added to the map depending on the visibility
// of the layer, so we only have to open the popup if the marker is in the map
if (!doIndividualWatch && map.hasLayer(marker) && model.focus === true) {
marker.openPopup();
}
return true;
};
//TODO: move to leafletMarkersHelpers??? or make a new class/function file (leafletMarkersHelpers is large already)
var _addMarkers = function(mapId, markersToRender, oldModels, map, layers, leafletMarkers, leafletScope,
watchOptions, maybeLayerName, skips) {
for (var newName in markersToRender) {
if (skips[newName])
continue;
if (newName.search('-') !== -1) {
$log.error('The marker can\'t use a "-" on his key name: "' + newName + '".');
continue;
}
var model = Helpers.copy(markersToRender[newName]);
var pathToMarker = Helpers.getObjectDotPath(maybeLayerName ? [maybeLayerName, newName] : [newName]);
var maybeLMarker = _getLMarker(leafletMarkers, newName, maybeLayerName);
if (!isDefined(maybeLMarker)) {
//(nmccready) very important to not have model changes when lObject is changed
//this might be desirable in some cases but it causes two-way binding to lObject which is not ideal
//if it is left as the reference then all changes from oldModel vs newModel are ignored
//see _destroy (where modelDiff becomes meaningless if we do not copy here)
var marker = createMarker(model);
var layerName = (model ? model.layer : undefined) || maybeLayerName; //original way takes pref
if (!isDefined(marker)) {
$log.error(errorHeader + ' Received invalid data on the marker ' + newName + '.');
continue;
}
_setLMarker(marker, leafletMarkers, newName, maybeLayerName);
// Bind message
if (isDefined(model.message)) {
marker.bindPopup(model.message, model.popupOptions);
}
// Add the marker to a cluster group if needed
if (isDefined(model.group)) {
var groupOptions = isDefined(model.groupOption) ? model.groupOption : null;
addMarkerToGroup(marker, model.group, groupOptions, map);
}
// Show label if defined
if (Helpers.LabelPlugin.isLoaded() && isDefined(model.label) && isDefined(model.label.message)) {
marker.bindLabel(model.label.message, model.label.options);
}
// Check if the marker should be added to a layer
if (isDefined(model) && (isDefined(model.layer) || isDefined(maybeLayerName))) {
var pass = _maybeAddMarkerToLayer(layerName, layers, model, marker,
watchOptions.individual.doWatch, map);
if (!pass)
continue; //something went wrong move on in the loop
} else if (!isDefined(model.group)) {
// We do not have a layer attr, so the marker goes to the map layer
map.addLayer(marker);
if (!watchOptions.individual.doWatch && model.focus === true) {
marker.openPopup();
}
}
if (watchOptions.individual.doWatch) {
addMarkerWatcher(marker, pathToMarker, leafletScope, layers, map,
watchOptions.individual.isDeep);
}
listenMarkerEvents(marker, model, leafletScope, watchOptions.individual.doWatch, map);
leafletMarkerEvents.bindEvents(mapId, marker, pathToMarker, model, leafletScope, layerName);
} else {
var oldModel = isDefined(oldModel) ? oldModels[newName] : undefined;
updateMarker(model, oldModel, maybeLMarker, pathToMarker, leafletScope, layers, map);
}
}
};
var _seeWhatWeAlreadyHave = function(markerModels, oldMarkerModels, lMarkers, isEqual, cb) {
var hasLogged = false;
var equals = false;
var oldMarker;
var newMarker;
var doCheckOldModel = isDefined(oldMarkerModels);
for (var name in lMarkers) {
if (!hasLogged) {
$log.debug(errorHeader + '[markers] destroy: ');
hasLogged = true;
}
if (doCheckOldModel) {
//might want to make the option (in watch options) to disable deep checking
//ie the options to only check !== (reference check) instead of angular.equals (slow)
newMarker = markerModels[name];
oldMarker = oldMarkerModels[name];
equals = angular.equals(newMarker, oldMarker) && isEqual;
}
if (!isDefined(markerModels) ||
!Object.keys(markerModels).length ||
!isDefined(markerModels[name]) ||
!Object.keys(markerModels[name]).length ||
equals) {
if (cb && Helpers.isFunction(cb))
cb(newMarker, oldMarker, name);
}
}
};
var _destroy = function(markerModels, oldMarkerModels, lMarkers, map, layers) {
_seeWhatWeAlreadyHave(markerModels, oldMarkerModels, lMarkers, false,
function(newMarker, oldMarker, lMarkerName) {
$log.debug(errorHeader + '[marker] is deleting marker: ' + lMarkerName);
deleteMarker(lMarkers[lMarkerName], map, layers);
delete lMarkers[lMarkerName];
});
};
var _getNewModelsToSkipp = function(newModels, oldModels, lMarkers) {
var skips = {};
_seeWhatWeAlreadyHave(newModels, oldModels, lMarkers, true,
function(newMarker, oldMarker, lMarkerName) {
$log.debug(errorHeader + '[marker] is already rendered, marker: ' + lMarkerName);
skips[lMarkerName] = newMarker;
});
return skips;
};
return {
restrict: 'A',
scope: false,
replace: false,
require: ['leaflet', '?layers'],
link: function(scope, element, attrs, controller) {
var mapController = controller[0];
var leafletScope = mapController.getLeafletScope();
mapController.getMap().then(function(map) {
var leafletMarkers = {};
var getLayers;
// If the layers attribute is used, we must wait until the layers are created
if (isDefined(controller[1])) {
getLayers = controller[1].getLayers;
} else {
getLayers = function() {
var deferred = $q.defer();
deferred.resolve();
return deferred.promise;
};
}
var watchOptions = leafletScope.markersWatchOptions || _markersWatchOptions;
// backwards compat
if (isDefined(attrs.watchMarkers))
watchOptions.doWatch = watchOptions.individual.doWatch =
(!isDefined(attrs.watchMarkers) || Helpers.isTruthy(attrs.watchMarkers));
var isNested = (isDefined(attrs.markersNested) && Helpers.isTruthy(attrs.markersNested));
getLayers().then(function(layers) {
var _clean = function(models, oldModels) {
if (isNested) {
$it.each(models, function(markerToMaybeDel, layerName) {
var oldModel = isDefined(oldModel) ? oldModels[layerName] : undefined;
_destroy(markerToMaybeDel, oldModel, leafletMarkers[layerName], map, layers);
});
return;
}
_destroy(models, oldModels, leafletMarkers, map, layers);
};
var _create = function(models, oldModels) {
_clean(models, oldModels);
var skips = null;
if (isNested) {
$it.each(models, function(markersToAdd, layerName) {
var oldModel = isDefined(oldModel) ? oldModels[layerName] : undefined;
skips = _getNewModelsToSkipp(models[layerName], oldModel, leafletMarkers[layerName]);
_addMarkers(attrs.id, markersToAdd, oldModels, map, layers, leafletMarkers, leafletScope,
watchOptions, layerName, skips);
});
return;
}
skips = _getNewModelsToSkipp(models, oldModels, leafletMarkers);
_addMarkers(attrs.id, models, oldModels, map, layers, leafletMarkers, leafletScope,
watchOptions, undefined, skips);
};
extendDirectiveControls(attrs.id, 'markers', _create, _clean);
leafletData.setMarkers(leafletMarkers, attrs.id);
maybeWatch(leafletScope, 'markers', watchOptions, function(newMarkers, oldMarkers) {
_create(newMarkers, oldMarkers);
});
});
});
},
};
}]);
angular.module('leaflet-directive').directive('maxbounds', ["$log", "leafletMapDefaults", "leafletBoundsHelpers", "leafletHelpers", function($log, leafletMapDefaults, leafletBoundsHelpers, leafletHelpers) {
return {
restrict: 'A',
scope: false,
replace: false,
require: 'leaflet',
link: function(scope, element, attrs, controller) {
var leafletScope = controller.getLeafletScope();
var isValidBounds = leafletBoundsHelpers.isValidBounds;
var isNumber = leafletHelpers.isNumber;
controller.getMap().then(function(map) {
leafletScope.$watch('maxbounds', function(maxbounds) {
if (!isValidBounds(maxbounds)) {
// Unset any previous maxbounds
map.setMaxBounds();
return;
}
var leafletBounds = leafletBoundsHelpers.createLeafletBounds(maxbounds);
if (isNumber(maxbounds.pad)) {
leafletBounds = leafletBounds.pad(maxbounds.pad);
}
map.setMaxBounds(leafletBounds);
if (!attrs.center && !attrs.lfCenter) {
map.fitBounds(leafletBounds);
}
});
});
},
};
}]);
angular.module('leaflet-directive').directive('paths', ["$log", "$q", "leafletData", "leafletMapDefaults", "leafletHelpers", "leafletPathsHelpers", "leafletPathEvents", function($log, $q, leafletData, leafletMapDefaults, leafletHelpers, leafletPathsHelpers, leafletPathEvents) {
return {
restrict: 'A',
scope: false,
replace: false,
require: ['leaflet', '?layers'],
link: function(scope, element, attrs, controller) {
var mapController = controller[0];
var isDefined = leafletHelpers.isDefined;
var isString = leafletHelpers.isString;
var leafletScope = mapController.getLeafletScope();
var paths = leafletScope.paths;
var createPath = leafletPathsHelpers.createPath;
var bindPathEvents = leafletPathEvents.bindPathEvents;
var setPathOptions = leafletPathsHelpers.setPathOptions;
mapController.getMap().then(function(map) {
var defaults = leafletMapDefaults.getDefaults(attrs.id);
var getLayers;
// If the layers attribute is used, we must wait until the layers are created
if (isDefined(controller[1])) {
getLayers = controller[1].getLayers;
} else {
getLayers = function() {
var deferred = $q.defer();
deferred.resolve();
return deferred.promise;
};
}
if (!isDefined(paths)) {
return;
}
getLayers().then(function(layers) {
var leafletPaths = {};
leafletData.setPaths(leafletPaths, attrs.id);
// Should we watch for every specific marker on the map?
var shouldWatch = (!isDefined(attrs.watchPaths) || attrs.watchPaths === 'true');
// Function for listening every single path once created
var watchPathFn = function(leafletPath, name) {
var clearWatch = leafletScope.$watch('paths["' + name + '"]', function(pathData, old) {
if (!isDefined(pathData)) {
if (isDefined(old.layer)) {
for (var i in layers.overlays) {
var overlay = layers.overlays[i];
overlay.removeLayer(leafletPath);
}
}
map.removeLayer(leafletPath);
clearWatch();
return;
}
setPathOptions(leafletPath, pathData.type, pathData);
}, true);
};
leafletScope.$watchCollection('paths', function(newPaths) {
// Delete paths (by name) from the array
for (var name in leafletPaths) {
if (!isDefined(newPaths[name])) {
map.removeLayer(leafletPaths[name]);
delete leafletPaths[name];
}
}
// Create the new paths
for (var newName in newPaths) {
if (newName.search('\\$') === 0) {
continue;
}
if (newName.search('-') !== -1) {
$log.error('[AngularJS - Leaflet] The path name "' + newName + '" is not valid. It must not include "-" and a number.');
continue;
}
if (!isDefined(leafletPaths[newName])) {
var pathData = newPaths[newName];
var newPath = createPath(newName, newPaths[newName], defaults);
// bind popup if defined
if (isDefined(newPath) && isDefined(pathData.message)) {
newPath.bindPopup(pathData.message, pathData.popupOptions);
}
// Show label if defined
if (leafletHelpers.LabelPlugin.isLoaded() && isDefined(pathData.label) && isDefined(pathData.label.message)) {
newPath.bindLabel(pathData.label.message, pathData.label.options);
}
// Check if the marker should be added to a layer
if (isDefined(pathData) && isDefined(pathData.layer)) {
if (!isString(pathData.layer)) {
$log.error('[AngularJS - Leaflet] A layername must be a string');
continue;
}
if (!isDefined(layers)) {
$log.error('[AngularJS - Leaflet] You must add layers to the directive if the markers are going to use this functionality.');
continue;
}
if (!isDefined(layers.overlays) || !isDefined(layers.overlays[pathData.layer])) {
$log.error('[AngularJS - Leaflet] A path can only be added to a layer of type "group"');
continue;
}
var layerGroup = layers.overlays[pathData.layer];
if (!(layerGroup instanceof L.LayerGroup || layerGroup instanceof L.FeatureGroup)) {
$log.error('[AngularJS - Leaflet] Adding a path to an overlay needs a overlay of the type "group" or "featureGroup"');
continue;
}
// Listen for changes on the new path
leafletPaths[newName] = newPath;
// The path goes to a correct layer group, so first of all we add it
layerGroup.addLayer(newPath);
if (shouldWatch) {
watchPathFn(newPath, newName);
} else {
setPathOptions(newPath, pathData.type, pathData);
}
} else if (isDefined(newPath)) {
// Listen for changes on the new path
leafletPaths[newName] = newPath;
map.addLayer(newPath);
if (shouldWatch) {
watchPathFn(newPath, newName);
} else {
setPathOptions(newPath, pathData.type, pathData);
}
}
bindPathEvents(attrs.id, newPath, newName, pathData, leafletScope);
}
}
});
});
});
},
};
}]);
angular.module('leaflet-directive').directive('tiles', ["$log", "leafletData", "leafletMapDefaults", "leafletHelpers", function($log, leafletData, leafletMapDefaults, leafletHelpers) {
return {
restrict: 'A',
scope: false,
replace: false,
require: 'leaflet',
link: function(scope, element, attrs, controller) {
var isDefined = leafletHelpers.isDefined;
var leafletScope = controller.getLeafletScope();
var tiles = leafletScope.tiles;
if (!isDefined(tiles) || !isDefined(tiles.url)) {
$log.warn('[AngularJS - Leaflet] The \'tiles\' definition doesn\'t have the \'url\' property.');
return;
}
controller.getMap().then(function(map) {
var defaults = leafletMapDefaults.getDefaults(attrs.id);
var tileLayerObj;
leafletScope.$watch('tiles', function(tiles, oldtiles) {
var tileLayerOptions = defaults.tileLayerOptions;
var tileLayerUrl = defaults.tileLayer;
// If no valid tiles are in the scope, remove the last layer
if (!isDefined(tiles.url) && isDefined(tileLayerObj)) {
map.removeLayer(tileLayerObj);
return;
}
// No leafletTiles object defined yet
if (!isDefined(tileLayerObj)) {
if (isDefined(tiles.options)) {
angular.copy(tiles.options, tileLayerOptions);
}
if (isDefined(tiles.url)) {
tileLayerUrl = tiles.url;
}
if (tiles.type === 'wms') {
tileLayerObj = L.tileLayer.wms(tileLayerUrl, tileLayerOptions);
} else {
tileLayerObj = L.tileLayer(tileLayerUrl, tileLayerOptions);
}
tileLayerObj.addTo(map);
leafletData.setTiles(tileLayerObj, attrs.id);
return;
}
// If the options of the tilelayer is changed, we need to redraw the layer
if (isDefined(tiles.url) && isDefined(tiles.options) &&
(tiles.type !== oldtiles.type || !angular.equals(tiles.options, tileLayerOptions))) {
map.removeLayer(tileLayerObj);
tileLayerOptions = defaults.tileLayerOptions;
angular.copy(tiles.options, tileLayerOptions);
tileLayerUrl = tiles.url;
if (tiles.type === 'wms') {
tileLayerObj = L.tileLayer.wms(tileLayerUrl, tileLayerOptions);
} else {
tileLayerObj = L.tileLayer(tileLayerUrl, tileLayerOptions);
}
tileLayerObj.addTo(map);
leafletData.setTiles(tileLayerObj, attrs.id);
return;
}
// Only the URL of the layer is changed, update the tiles object
if (isDefined(tiles.url)) {
tileLayerObj.setUrl(tiles.url);
}
}, true);
});
},
};
}]);
/*
Create multiple similar directives for watchOptions to support directiveControl
instead. (when watches are disabled)
NgAnnotate does not work here due to the functional creation
*/
['markers', 'geojson'].forEach(function(name) {
angular.module('leaflet-directive').directive(name + 'WatchOptions', [
'$log', '$rootScope', '$q', 'leafletData', 'leafletHelpers',
function($log, $rootScope, $q, leafletData, leafletHelpers) {
var isDefined = leafletHelpers.isDefined,
errorHeader = leafletHelpers.errorHeader,
isObject = leafletHelpers.isObject,
_watchOptions = leafletHelpers.watchOptions;
return {
restrict: 'A',
scope: false,
replace: false,
require: ['leaflet'],
link: function(scope, element, attrs, controller) {
var mapController = controller[0],
leafletScope = mapController.getLeafletScope();
mapController.getMap().then(function() {
if (isDefined(scope[name + 'WatchOptions'])) {
if (isObject(scope[name + 'WatchOptions']))
angular.extend(_watchOptions, scope[name + 'WatchOptions']);
else
$log.error(errorHeader + '[' + name + 'WatchOptions] is not an object');
leafletScope[name + 'WatchOptions'] = _watchOptions;
}
});
},
};
},]);
});
angular.module('leaflet-directive')
.factory('LeafletEventsHelpersFactory', ["$rootScope", "$q", "$log", "leafletHelpers", function($rootScope, $q, $log, leafletHelpers) {
var safeApply = leafletHelpers.safeApply;
var isDefined = leafletHelpers.isDefined;
var isObject = leafletHelpers.isObject;
var isArray = leafletHelpers.isArray;
var errorHeader = leafletHelpers.errorHeader;
var EventsHelper = function(rootBroadcastName, lObjectType) {
this.rootBroadcastName = rootBroadcastName;
$log.debug('LeafletEventsHelpersFactory: lObjectType: ' + lObjectType + 'rootBroadcastName: ' + rootBroadcastName);
//used to path/key out certain properties based on the type , "markers", "geojson"
this.lObjectType = lObjectType;
};
EventsHelper.prototype.getAvailableEvents = function() {return [];};
/*
argument: name: Note this can be a single string or dot notation
Example:
markerModel : {
m1: { lat:_, lon: _}
}
//would yield name of
name = "m1"
If nested:
markerModel : {
cars: {
m1: { lat:_, lon: _}
}
}
//would yield name of
name = "cars.m1"
*/
EventsHelper.prototype.genDispatchEvent = function(maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName, extra) {
var _this = this;
maybeMapId = maybeMapId || '';
if (maybeMapId)
maybeMapId = '.' + maybeMapId;
return function(e) {
var broadcastName = _this.rootBroadcastName + maybeMapId + '.' + eventName;
$log.debug(broadcastName);
_this.fire(leafletScope, broadcastName, logic, e, e.target || lObject, model, name, layerName, extra);
};
};
EventsHelper.prototype.fire = function(scope, broadcastName, logic, event, lObject, model, modelName, layerName) {
// Safely broadcast the event
safeApply(scope, function() {
var toSend = {
leafletEvent: event,
leafletObject: lObject,
modelName: modelName,
model: model,
};
if (isDefined(layerName))
angular.extend(toSend, {layerName: layerName});
if (logic === 'emit') {
scope.$emit(broadcastName, toSend);
} else {
$rootScope.$broadcast(broadcastName, toSend);
}
});
};
EventsHelper.prototype.bindEvents = function(maybeMapId, lObject, name, model, leafletScope, layerName, extra) {
var events = [];
var logic = 'emit';
var _this = this;
if (!isDefined(leafletScope.eventBroadcast)) {
// Backward compatibility, if no event-broadcast attribute, all events are broadcasted
events = this.getAvailableEvents();
} else if (!isObject(leafletScope.eventBroadcast)) {
// Not a valid object
$log.error(errorHeader + 'event-broadcast must be an object check your model.');
} else {
// We have a possible valid object
if (!isDefined(leafletScope.eventBroadcast[_this.lObjectType])) {
// We do not have events enable/disable do we do nothing (all enabled by default)
events = this.getAvailableEvents();
} else if (!isObject(leafletScope.eventBroadcast[_this.lObjectType])) {
// Not a valid object
$log.warn(errorHeader + 'event-broadcast.' + [_this.lObjectType] + ' must be an object check your model.');
} else {
// We have a possible valid map object
// Event propadation logic
if (isDefined(leafletScope.eventBroadcast[this.lObjectType].logic)) {
// We take care of possible propagation logic
if (leafletScope.eventBroadcast[_this.lObjectType].logic !== 'emit' &&
leafletScope.eventBroadcast[_this.lObjectType].logic !== 'broadcast')
$log.warn(errorHeader + 'Available event propagation logic are: \'emit\' or \'broadcast\'.');
}
// Enable / Disable
var eventsEnable = false;
var eventsDisable = false;
if (isDefined(leafletScope.eventBroadcast[_this.lObjectType].enable) &&
isArray(leafletScope.eventBroadcast[_this.lObjectType].enable))
eventsEnable = true;
if (isDefined(leafletScope.eventBroadcast[_this.lObjectType].disable) &&
isArray(leafletScope.eventBroadcast[_this.lObjectType].disable))
eventsDisable = true;
if (eventsEnable && eventsDisable) {
// Both are active, this is an error
$log.warn(errorHeader + 'can not enable and disable events at the same time');
} else if (!eventsEnable && !eventsDisable) {
// Both are inactive, this is an error
$log.warn(errorHeader + 'must enable or disable events');
} else {
// At this point the object is OK, lets enable or disable events
if (eventsEnable) {
// Enable events
leafletScope.eventBroadcast[this.lObjectType].enable.forEach(function(eventName) {
// Do we have already the event enabled?
if (events.indexOf(eventName) !== -1) {
// Repeated event, this is an error
$log.warn(errorHeader + 'This event ' + eventName + ' is already enabled');
} else {
// Does the event exists?
if (_this.getAvailableEvents().indexOf(eventName) === -1) {
// The event does not exists, this is an error
$log.warn(errorHeader + 'This event ' + eventName + ' does not exist');
} else {
// All ok enable the event
events.push(eventName);
}
}
});
} else {
// Disable events
events = this.getAvailableEvents();
leafletScope.eventBroadcast[_this.lObjectType].disable.forEach(function(eventName) {
var index = events.indexOf(eventName);
if (index === -1) {
// The event does not exist
$log.warn(errorHeader + 'This event ' + eventName + ' does not exist or has been already disabled');
} else {
events.splice(index, 1);
}
});
}
}
}
}
events.forEach(function(eventName) {
lObject.on(eventName, _this.genDispatchEvent(maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName, extra));
});
return logic;
};
return EventsHelper;
}])
.service('leafletEventsHelpers', ["LeafletEventsHelpersFactory", function(LeafletEventsHelpersFactory) {
return new LeafletEventsHelpersFactory();
}]);
angular.module('leaflet-directive')
.factory('leafletGeoJsonEvents', ["$rootScope", "$q", "$log", "leafletHelpers", "LeafletEventsHelpersFactory", "leafletData", function($rootScope, $q, $log, leafletHelpers,
LeafletEventsHelpersFactory, leafletData) {
var safeApply = leafletHelpers.safeApply;
var EventsHelper = LeafletEventsHelpersFactory;
var GeoJsonEvents = function() {
EventsHelper.call(this, 'leafletDirectiveGeoJson', 'geojson');
};
GeoJsonEvents.prototype = new EventsHelper();
GeoJsonEvents.prototype.genDispatchEvent = function(maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName, extra) {
var base = EventsHelper.prototype.genDispatchEvent.call(this, maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName);
var _this = this;
return function(e) {
if (eventName === 'mouseout') {
if (extra.resetStyleOnMouseout) {
leafletData.getGeoJSON(extra.mapId)
.then(function(leafletGeoJSON) {
//this is broken on nested needs to traverse or user layerName (nested)
var lobj = layerName ? leafletGeoJSON[layerName] : leafletGeoJSON;
lobj.resetStyle(e.target);
});
}
safeApply(leafletScope, function() {
$rootScope.$broadcast(_this.rootBroadcastName + '.mouseout', e);
});
}
base(e); //common
};
};
GeoJsonEvents.prototype.getAvailableEvents = function() { return [
'click',
'dblclick',
'mouseover',
'mouseout',
];
};
return new GeoJsonEvents();
}]);
angular.module('leaflet-directive')
.factory('leafletLabelEvents', ["$rootScope", "$q", "$log", "leafletHelpers", "LeafletEventsHelpersFactory", function($rootScope, $q, $log, leafletHelpers, LeafletEventsHelpersFactory) {
var Helpers = leafletHelpers;
var EventsHelper = LeafletEventsHelpersFactory;
var LabelEvents = function() {
EventsHelper.call(this, 'leafletDirectiveLabel', 'markers');
};
LabelEvents.prototype = new EventsHelper();
LabelEvents.prototype.genDispatchEvent = function(maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName) {
var markerName = name.replace('markers.', '');
return EventsHelper.prototype
.genDispatchEvent.call(this, maybeMapId, eventName, logic, leafletScope, lObject, markerName, model, layerName);
};
LabelEvents.prototype.getAvailableEvents = function() {
return [
'click',
'dblclick',
'mousedown',
'mouseover',
'mouseout',
'contextmenu',
];
};
LabelEvents.prototype.genEvents = function(maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName) {
var _this = this;
var labelEvents = this.getAvailableEvents();
var scopeWatchName = Helpers.getObjectArrayPath('markers.' + name);
labelEvents.forEach(function(eventName) {
lObject.label.on(eventName, _this.genDispatchEvent(
maybeMapId, eventName, logic, leafletScope, lObject.label, scopeWatchName, model, layerName));
});
};
LabelEvents.prototype.bindEvents = function() {};
return new LabelEvents();
}]);
angular.module('leaflet-directive')
.factory('leafletMapEvents', ["$rootScope", "$q", "$log", "leafletHelpers", "leafletEventsHelpers", "leafletIterators", function($rootScope, $q, $log, leafletHelpers, leafletEventsHelpers, leafletIterators) {
var isDefined = leafletHelpers.isDefined;
var fire = leafletEventsHelpers.fire;
var _getAvailableMapEvents = function() {
return [
'click',
'dblclick',
'mousedown',
'mouseup',
'mouseover',
'mouseout',
'mousemove',
'contextmenu',
'focus',
'blur',
'preclick',
'load',
'unload',
'viewreset',
'movestart',
'move',
'moveend',
'dragstart',
'drag',
'dragend',
'zoomstart',
'zoomanim',
'zoomend',
'zoomlevelschange',
'resize',
'autopanstart',
'layeradd',
'layerremove',
'baselayerchange',
'overlayadd',
'overlayremove',
'locationfound',
'locationerror',
'popupopen',
'popupclose',
'draw:created',
'draw:edited',
'draw:deleted',
'draw:drawstart',
'draw:drawstop',
'draw:editstart',
'draw:editstop',
'draw:deletestart',
'draw:deletestop',
];
};
var _genDispatchMapEvent = function(scope, eventName, logic, maybeMapId) {
if (maybeMapId)
maybeMapId = maybeMapId + '.';
return function(e) {
// Put together broadcast name
var broadcastName = 'leafletDirectiveMap.' + maybeMapId + eventName;
$log.debug(broadcastName);
// Safely broadcast the event
fire(scope, broadcastName, logic, e, e.target, scope);
};
};
var _notifyCenterChangedToBounds = function(scope) {
scope.$broadcast('boundsChanged');
};
var _notifyCenterUrlHashChanged = function(scope, map, attrs, search) {
if (!isDefined(attrs.urlHashCenter)) {
return;
}
var center = map.getCenter();
var centerUrlHash = (center.lat).toFixed(4) + ':' + (center.lng).toFixed(4) + ':' + map.getZoom();
if (!isDefined(search.c) || search.c !== centerUrlHash) {
//$log.debug("notified new center...");
scope.$emit('centerUrlHash', centerUrlHash);
}
};
var _addEvents = function(map, mapEvents, contextName, scope, logic) {
leafletIterators.each(mapEvents, function(eventName) {
var context = {};
context[contextName] = eventName;
map.on(eventName, _genDispatchMapEvent(scope, eventName, logic, map._container.id || ''), context);
});
};
return {
getAvailableMapEvents: _getAvailableMapEvents,
genDispatchMapEvent: _genDispatchMapEvent,
notifyCenterChangedToBounds: _notifyCenterChangedToBounds,
notifyCenterUrlHashChanged: _notifyCenterUrlHashChanged,
addEvents: _addEvents,
};
}]);
angular.module('leaflet-directive')
.factory('leafletMarkerEvents', ["$rootScope", "$q", "$log", "leafletHelpers", "LeafletEventsHelpersFactory", "leafletLabelEvents", function($rootScope, $q, $log, leafletHelpers, LeafletEventsHelpersFactory, leafletLabelEvents) {
var safeApply = leafletHelpers.safeApply;
var isDefined = leafletHelpers.isDefined;
var Helpers = leafletHelpers;
var lblHelp = leafletLabelEvents;
var EventsHelper = LeafletEventsHelpersFactory;
var MarkerEvents = function() {
EventsHelper.call(this, 'leafletDirectiveMarker', 'markers');
};
MarkerEvents.prototype = new EventsHelper();
MarkerEvents.prototype.genDispatchEvent = function(maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName) {
var handle = EventsHelper.prototype
.genDispatchEvent.call(this, maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName);
return function(e) {
// Broadcast old marker click name for backwards compatibility
if (eventName === 'click') {
safeApply(leafletScope, function() {
$rootScope.$broadcast('leafletDirectiveMarkersClick', name);
});
} else if (eventName === 'dragend') {
safeApply(leafletScope, function() {
model.lat = lObject.getLatLng().lat;
model.lng = lObject.getLatLng().lng;
});
if (model.message && model.focus === true) {
lObject.openPopup();
}
}
handle(e); //common
};
};
MarkerEvents.prototype.getAvailableEvents = function() { return [
'click',
'dblclick',
'mousedown',
'mouseover',
'mouseout',
'contextmenu',
'dragstart',
'drag',
'dragend',
'move',
'remove',
'popupopen',
'popupclose',
'touchend',
'touchstart',
'touchmove',
'touchcancel',
'touchleave',
];
};
MarkerEvents.prototype.bindEvents = function(maybeMapId, lObject, name, model, leafletScope, layerName) {
var logic = EventsHelper.prototype.bindEvents.call(this, maybeMapId, lObject, name, model, leafletScope, layerName);
if (Helpers.LabelPlugin.isLoaded() && isDefined(lObject.label)) {
lblHelp.genEvents(maybeMapId, name, logic, leafletScope, lObject, model, layerName);
}
};
return new MarkerEvents();
}]);
angular.module('leaflet-directive')
.factory('leafletPathEvents', ["$rootScope", "$q", "$log", "leafletHelpers", "leafletLabelEvents", "leafletEventsHelpers", function($rootScope, $q, $log, leafletHelpers, leafletLabelEvents, leafletEventsHelpers) {
var isDefined = leafletHelpers.isDefined;
var isObject = leafletHelpers.isObject;
var Helpers = leafletHelpers;
var errorHeader = leafletHelpers.errorHeader;
var lblHelp = leafletLabelEvents;
var fire = leafletEventsHelpers.fire;
/*
TODO (nmccready) This EventsHelper needs to be derrived from leafletEventsHelpers to elminate copy and paste code.
*/
var _genDispatchPathEvent = function(maybeMapId, eventName, logic, leafletScope, lObject, name, model, layerName) {
maybeMapId = maybeMapId || '';
if (maybeMapId)
maybeMapId = '.' + maybeMapId;
return function(e) {
var broadcastName = 'leafletDirectivePath' + maybeMapId + '.' + eventName;
$log.debug(broadcastName);
fire(leafletScope, broadcastName, logic, e, e.target || lObject, model, name, layerName);
};
};
var _bindPathEvents = function(maybeMapId, lObject, name, model, leafletScope) {
var pathEvents = [];
var i;
var eventName;
var logic = 'broadcast';
if (!isDefined(leafletScope.eventBroadcast)) {
// Backward compatibility, if no event-broadcast attribute, all events are broadcasted
pathEvents = _getAvailablePathEvents();
} else if (!isObject(leafletScope.eventBroadcast)) {
// Not a valid object
$log.error(errorHeader + 'event-broadcast must be an object check your model.');
} else {
// We have a possible valid object
if (!isDefined(leafletScope.eventBroadcast.path)) {
// We do not have events enable/disable do we do nothing (all enabled by default)
pathEvents = _getAvailablePathEvents();
} else if (isObject(leafletScope.eventBroadcast.paths)) {
// Not a valid object
$log.warn(errorHeader + 'event-broadcast.path must be an object check your model.');
} else {
// We have a possible valid map object
// Event propadation logic
if (leafletScope.eventBroadcast.path.logic !== undefined && leafletScope.eventBroadcast.path.logic !== null) {
// We take care of possible propagation logic
if (leafletScope.eventBroadcast.path.logic !== 'emit' && leafletScope.eventBroadcast.path.logic !== 'broadcast') {
// This is an error
$log.warn(errorHeader + 'Available event propagation logic are: \'emit\' or \'broadcast\'.');
} else if (leafletScope.eventBroadcast.path.logic === 'emit') {
logic = 'emit';
}
}
// Enable / Disable
var pathEventsEnable = false;
var pathEventsDisable = false;
if (leafletScope.eventBroadcast.path.enable !== undefined && leafletScope.eventBroadcast.path.enable !== null) {
if (typeof leafletScope.eventBroadcast.path.enable === 'object') {
pathEventsEnable = true;
}
}
if (leafletScope.eventBroadcast.path.disable !== undefined && leafletScope.eventBroadcast.path.disable !== null) {
if (typeof leafletScope.eventBroadcast.path.disable === 'object') {
pathEventsDisable = true;
}
}
if (pathEventsEnable && pathEventsDisable) {
// Both are active, this is an error
$log.warn(errorHeader + 'can not enable and disable events at the same time');
} else if (!pathEventsEnable && !pathEventsDisable) {
// Both are inactive, this is an error
$log.warn(errorHeader + 'must enable or disable events');
} else {
// At this point the path object is OK, lets enable or disable events
if (pathEventsEnable) {
// Enable events
for (i = 0; i < leafletScope.eventBroadcast.path.enable.length; i++) {
eventName = leafletScope.eventBroadcast.path.enable[i];
// Do we have already the event enabled?
if (pathEvents.indexOf(eventName) !== -1) {
// Repeated event, this is an error
$log.warn(errorHeader + 'This event ' + eventName + ' is already enabled');
} else {
// Does the event exists?
if (_getAvailablePathEvents().indexOf(eventName) === -1) {
// The event does not exists, this is an error
$log.warn(errorHeader + 'This event ' + eventName + ' does not exist');
} else {
// All ok enable the event
pathEvents.push(eventName);
}
}
}
} else {
// Disable events
pathEvents = _getAvailablePathEvents();
for (i = 0; i < leafletScope.eventBroadcast.path.disable.length; i++) {
eventName = leafletScope.eventBroadcast.path.disable[i];
var index = pathEvents.indexOf(eventName);
if (index === -1) {
// The event does not exist
$log.warn(errorHeader + 'This event ' + eventName + ' does not exist or has been already disabled');
} else {
pathEvents.splice(index, 1);
}
}
}
}
}
}
for (i = 0; i < pathEvents.length; i++) {
eventName = pathEvents[i];
lObject.on(eventName, _genDispatchPathEvent(maybeMapId, eventName, logic, leafletScope, pathEvents, name));
}
if (Helpers.LabelPlugin.isLoaded() && isDefined(lObject.label)) {
lblHelp.genEvents(maybeMapId, name, logic, leafletScope, lObject, model);
}
};
var _getAvailablePathEvents = function() {
return [
'click',
'dblclick',
'mousedown',
'mouseover',
'mouseout',
'contextmenu',
'add',
'remove',
'popupopen',
'popupclose',
];
};
return {
getAvailablePathEvents: _getAvailablePathEvents,
bindPathEvents: _bindPathEvents,
};
}]);
}(angular));