diff --git a/dist/angular-openlayers-directive.css b/dist/angular-openlayers-directive.css
new file mode 100644
index 00000000..fd9933e0
--- /dev/null
+++ b/dist/angular-openlayers-directive.css
@@ -0,0 +1,58 @@
+.popup-label {
+ background-color: #fff;
+ border: 2px #444 solid;
+ border-radius: 7px;
+ -webkit-box-shadow: 4px 4px 5px 0px rgba(50, 50, 50, 0.75);
+ -moz-box-shadow: 4px 4px 5px 0px rgba(50, 50, 50, 0.75);
+ box-shadow: 4px 4px 5px 0px rgba(50, 50, 50, 0.75);
+ color: #111;
+ font: 12px/20px "Helvetica Neue", Arial, Helvetica, sans-serif;
+ font-weight: bold;
+ padding: 3px 6px;
+ position: absolute;
+ white-space: nowrap;
+ top: -35px;
+ left: 20px;
+ display: none;
+}
+
+.popup-label img {
+ vertical-align: middle;
+}
+
+.popup-label.marker:before {
+ border-top: 6px solid transparent;
+ border-bottom: 6px solid transparent;
+ content: "";
+ border-right: 6px solid black;
+ border-right-color: inherit;
+ position: absolute;
+ left: -8px;
+ top: 5px;
+}
+
+.angular-openlayers-map:-moz-full-screen {
+ height: 100%;
+}
+.angular-openlayers-map:-webkit-full-screen {
+ height: 100%;
+}
+.angular-openlayers-map:full-screen {
+ height: 100%;
+}
+
+.angular-openlayers-map:not(-moz-full-screen) {
+ height: 400px;
+}
+
+.angular-openlayers-map:not(-webkit-full-screen) {
+ height: 400px;
+}
+
+.angular-openlayers-map:not(full-screen) {
+ height: 400px;
+}
+.ol-full-screen {
+ position: absolute;
+ top: 50%;
+}
diff --git a/dist/angular-openlayers-directive.js b/dist/angular-openlayers-directive.js
new file mode 100644
index 00000000..eea46b35
--- /dev/null
+++ b/dist/angular-openlayers-directive.js
@@ -0,0 +1,2583 @@
+(function (root, factory) {
+ if (typeof require === 'function' && typeof exports === 'object') {
+ // CommonJS
+ var ol = require('openlayers');
+ exports.angularOpenlayersDirective = factory(ol);
+ } else if (typeof define === 'function' && define.amd) {
+ // AMD.
+ define(['ol'], function (ol) {
+ return root.angularOpenlayersDirective = factory(ol);
+ });
+ } else {
+ // Browser globals
+ root.angularOpenlayersDirective = factory(root.ol);
+ }
+}(this, function (ol) {
+angular.module('openlayers-directive', ['ngSanitize']).directive('openlayers', ["$log", "$q", "$compile", "olHelpers", "olMapDefaults", "olData", function($log, $q, $compile, olHelpers,
+ olMapDefaults, olData) {
+ return {
+ restrict: 'EA',
+ transclude: true,
+ replace: true,
+ scope: {
+ center: '=olCenter',
+ defaults: '=olDefaults',
+ view: '=olView',
+ events: '=olEvents'
+ },
+ template: '
',
+ controller: ["$scope", function($scope) {
+ var _map = $q.defer();
+ $scope.getMap = function() {
+ return _map.promise;
+ };
+
+ $scope.setMap = function(map) {
+ _map.resolve(map);
+ };
+
+ this.getOpenlayersScope = function() {
+ return $scope;
+ };
+ }],
+ link: function(scope, element, attrs) {
+ var isDefined = olHelpers.isDefined;
+ var createLayer = olHelpers.createLayer;
+ var setMapEvents = olHelpers.setMapEvents;
+ var setViewEvents = olHelpers.setViewEvents;
+ var createView = olHelpers.createView;
+ var defaults = olMapDefaults.setDefaults(scope);
+
+ // Set width and height if they are defined
+ if (isDefined(attrs.width)) {
+ if (isNaN(attrs.width)) {
+ element.css('width', attrs.width);
+ } else {
+ element.css('width', attrs.width + 'px');
+ }
+ }
+
+ if (isDefined(attrs.height)) {
+ if (isNaN(attrs.height)) {
+ element.css('height', attrs.height);
+ } else {
+ element.css('height', attrs.height + 'px');
+ }
+ }
+
+ if (isDefined(attrs.lat)) {
+ defaults.center.lat = parseFloat(attrs.lat);
+ }
+
+ if (isDefined(attrs.lon)) {
+ defaults.center.lon = parseFloat(attrs.lon);
+ }
+
+ if (isDefined(attrs.zoom)) {
+ defaults.center.zoom = parseFloat(attrs.zoom);
+ }
+
+ var controls = ol.control.defaults(defaults.controls);
+ var interactions = ol.interaction.defaults(defaults.interactions);
+ var view = createView(defaults.view);
+
+ // Create the Openlayers Map Object with the options
+ var map = new ol.Map({
+ target: element[0],
+ controls: controls,
+ interactions: interactions,
+ renderer: defaults.renderer,
+ view: view,
+ loadTilesWhileAnimating: defaults.loadTilesWhileAnimating,
+ loadTilesWhileInteracting: defaults.loadTilesWhileInteracting
+ });
+
+ scope.$on('$destroy', function() {
+ olData.resetMap(attrs.id);
+ map.setTarget(null);
+ map = null;
+ });
+
+ // If no layer is defined, set the default tileLayer
+ if (!attrs.customLayers) {
+ var l = {
+ type: 'Tile',
+ source: {
+ type: 'OSM'
+ }
+ };
+ var layer = createLayer(l, view.getProjection(), 'default');
+ map.addLayer(layer);
+ map.set('default', true);
+ }
+
+ if (!isDefined(attrs.olCenter)) {
+ var c = ol.proj.transform([defaults.center.lon,
+ defaults.center.lat
+ ],
+ defaults.center.projection, view.getProjection()
+ );
+ view.setCenter(c);
+ view.setZoom(defaults.center.zoom);
+ }
+
+ // Set the Default events for the map
+ setMapEvents(defaults.events, map, scope);
+
+ //Set the Default events for the map view
+ setViewEvents(defaults.events, map, scope);
+
+ // Resolve the map object to the promises
+ scope.setMap(map);
+ olData.setMap(map, attrs.id);
+
+ }
+ };
+ }]);
+
+angular.module('openlayers-directive').directive('olCenter', ["$log", "$location", "olMapDefaults", "olHelpers", function($log, $location, olMapDefaults, olHelpers) {
+
+ return {
+ restrict: 'A',
+ scope: false,
+ replace: false,
+ require: 'openlayers',
+
+ link: function(scope, element, attrs, controller) {
+ var safeApply = olHelpers.safeApply;
+ var isValidCenter = olHelpers.isValidCenter;
+ var isDefined = olHelpers.isDefined;
+ var isArray = olHelpers.isArray;
+ var isNumber = olHelpers.isNumber;
+ var isSameCenterOnMap = olHelpers.isSameCenterOnMap;
+ var setCenter = olHelpers.setCenter;
+ var setZoom = olHelpers.setZoom;
+ var olScope = controller.getOpenlayersScope();
+
+ olScope.getMap().then(function(map) {
+ var defaults = olMapDefaults.getDefaults(olScope);
+ var view = map.getView();
+ var center = olScope.center;
+
+ if (attrs.olCenter.search('-') !== -1) {
+ $log.error('[AngularJS - Openlayers] The "center" variable can\'t use ' +
+ 'a "-" on his key name: "' + attrs.center + '".');
+ setCenter(view, defaults.view.projection, defaults.center, map);
+ return;
+ }
+
+ if (!isDefined(center)) {
+ center = {};
+ }
+
+ if (!isValidCenter(center)) {
+ $log.warn('[AngularJS - Openlayers] invalid \'center\'');
+ center.lat = defaults.center.lat;
+ center.lon = defaults.center.lon;
+ center.zoom = defaults.center.zoom;
+ center.projection = defaults.center.projection;
+ }
+
+ if (!center.projection) {
+ if (defaults.view.projection !== 'pixel') {
+ center.projection = defaults.center.projection;
+ } else {
+ center.projection = 'pixel';
+ }
+ }
+
+ if (!isNumber(center.zoom)) {
+ center.zoom = 1;
+ }
+
+ setCenter(view, defaults.view.projection, center, map);
+ view.setZoom(center.zoom);
+
+ var centerUrlHash;
+ if (center.centerUrlHash === true) {
+ var extractCenterFromUrl = function() {
+ var search = $location.search();
+ var centerParam;
+ if (isDefined(search.c)) {
+ var cParam = search.c.split(':');
+ if (cParam.length === 3) {
+ centerParam = {
+ lat: parseFloat(cParam[0]),
+ lon: parseFloat(cParam[1]),
+ zoom: parseInt(cParam[2], 10)
+ };
+ }
+ }
+ return centerParam;
+ };
+ centerUrlHash = extractCenterFromUrl();
+
+ olScope.$on('$locationChangeSuccess', function() {
+ var urlCenter = extractCenterFromUrl();
+ if (urlCenter && !isSameCenterOnMap(urlCenter, map)) {
+ safeApply(olScope, function(scope) {
+ scope.center.lat = urlCenter.lat;
+ scope.center.lon = urlCenter.lon;
+ scope.center.zoom = urlCenter.zoom;
+ });
+ }
+ });
+ }
+
+ var geolocation;
+ olScope.$watchCollection('center', function(center) {
+
+ if (!center) {
+ return;
+ }
+
+ if (!center.projection) {
+ center.projection = defaults.center.projection;
+ }
+
+ if (center.autodiscover) {
+ if (!geolocation) {
+ geolocation = new ol.Geolocation({
+ projection: ol.proj.get(center.projection)
+ });
+
+ geolocation.on('change', function() {
+ if (center.autodiscover) {
+ var location = geolocation.getPosition();
+ safeApply(olScope, function(scope) {
+ scope.center.lat = location[1];
+ scope.center.lon = location[0];
+ scope.center.zoom = 12;
+ scope.center.autodiscover = false;
+ geolocation.setTracking(false);
+ });
+ }
+ });
+ }
+ geolocation.setTracking(true);
+ return;
+ }
+
+ if (!isValidCenter(center)) {
+ $log.warn('[AngularJS - Openlayers] invalid \'center\'');
+ center = defaults.center;
+ }
+
+ var viewCenter = view.getCenter();
+ if (viewCenter) {
+ if (defaults.view.projection === 'pixel' || center.projection === 'pixel') {
+ view.setCenter(center.coord);
+ } else {
+ var actualCenter =
+ ol.proj.transform(viewCenter, defaults.view.projection, center.projection);
+ if (!(actualCenter[1] === center.lat && actualCenter[0] === center.lon)) {
+ setCenter(view, defaults.view.projection, center, map);
+ }
+ }
+ }
+
+ if (view.getZoom() !== center.zoom) {
+ setZoom(view, center.zoom, map);
+ }
+ });
+
+ var moveEndEventKey = map.on('moveend', function() {
+ safeApply(olScope, function(scope) {
+
+ if (!isDefined(scope.center)) {
+ return;
+ }
+
+ var center = map.getView().getCenter();
+ scope.center.zoom = view.getZoom();
+
+ if (defaults.view.projection === 'pixel' || scope.center.projection === 'pixel') {
+ scope.center.coord = center;
+ return;
+ }
+
+ if (scope.center) {
+ var proj = ol.proj.transform(center, defaults.view.projection, scope.center.projection);
+ scope.center.lat = proj[1];
+ scope.center.lon = proj[0];
+
+ // Notify the controller about a change in the center position
+ olHelpers.notifyCenterUrlHashChanged(olScope, scope.center, $location.search());
+
+ // Calculate the bounds if needed
+ if (isArray(scope.center.bounds)) {
+ var extent = view.calculateExtent(map.getSize());
+ var centerProjection = scope.center.projection;
+ var viewProjection = defaults.view.projection;
+ scope.center.bounds = ol.proj.transformExtent(extent, viewProjection, centerProjection);
+ }
+ }
+ });
+ });
+
+ olScope.$on('$destroy', function() {
+ ol.Observable.unByKey(moveEndEventKey);
+ });
+ });
+ }
+ };
+}]);
+
+angular.module('openlayers-directive').directive('olLayer', ["$log", "$q", "olMapDefaults", "olHelpers", function($log, $q, olMapDefaults, olHelpers) {
+
+ return {
+ restrict: 'E',
+ scope: {
+ properties: '=olLayerProperties',
+ onLayerCreated: '&'
+ },
+ replace: false,
+ require: '^openlayers',
+ link: function(scope, element, attrs, controller) {
+ var isDefined = olHelpers.isDefined;
+ var equals = olHelpers.equals;
+ var olScope = controller.getOpenlayersScope();
+ var createLayer = olHelpers.createLayer;
+ var setVectorLayerEvents = olHelpers.setVectorLayerEvents;
+ var detectLayerType = olHelpers.detectLayerType;
+ var createStyle = olHelpers.createStyle;
+ var isBoolean = olHelpers.isBoolean;
+ var addLayerBeforeMarkers = olHelpers.addLayerBeforeMarkers;
+ var isNumber = olHelpers.isNumber;
+ var insertLayer = olHelpers.insertLayer;
+ var removeLayer = olHelpers.removeLayer;
+ var addLayerToGroup = olHelpers.addLayerToGroup;
+ var removeLayerFromGroup = olHelpers.removeLayerFromGroup;
+ var getGroup = olHelpers.getGroup;
+
+ olScope.getMap().then(function(map) {
+ var projection = map.getView().getProjection();
+ var defaults = olMapDefaults.setDefaults(olScope);
+ var layerCollection = map.getLayers();
+ var olLayer;
+
+ scope.$on('$destroy', function() {
+ if (scope.properties.group) {
+ removeLayerFromGroup(layerCollection, olLayer, scope.properties.group);
+ } else {
+ removeLayer(layerCollection, olLayer.index);
+ }
+
+ map.removeLayer(olLayer);
+ });
+
+ if (!isDefined(scope.properties)) {
+ if (isDefined(attrs.sourceType) && isDefined(attrs.sourceUrl)) {
+ var l = {
+ source: {
+ url: attrs.sourceUrl,
+ type: attrs.sourceType
+ }
+ };
+
+ olLayer = createLayer(l, projection, attrs.layerName, scope.onLayerCreated);
+ if (detectLayerType(l) === 'Vector') {
+ setVectorLayerEvents(defaults.events, map, scope, attrs.name);
+ }
+ addLayerBeforeMarkers(layerCollection, olLayer);
+ }
+ return;
+ }
+
+ scope.$watch('properties', function(properties, oldProperties) {
+ if (!isDefined(properties.source) || !isDefined(properties.source.type)) {
+ return;
+ }
+
+ if (!isDefined(properties.visible)) {
+ properties.visible = true;
+ return;
+ }
+
+ if (!isDefined(properties.opacity)) {
+ properties.opacity = 1;
+ return;
+ }
+
+ var style;
+ var group;
+ var collection;
+ if (!isDefined(olLayer)) {
+ olLayer = createLayer(properties, projection, scope.onLayerCreated);
+ if (isDefined(properties.group)) {
+ addLayerToGroup(layerCollection, olLayer, properties.group);
+ } else if (isDefined(properties.index)) {
+ insertLayer(layerCollection, properties.index, olLayer);
+ } else {
+ addLayerBeforeMarkers(layerCollection, olLayer);
+ }
+
+ if (detectLayerType(properties) === 'Vector') {
+ setVectorLayerEvents(defaults.events, map, scope, properties.name);
+ }
+
+ if (isBoolean(properties.visible)) {
+ olLayer.setVisible(properties.visible);
+ }
+
+ if (properties.opacity) {
+ olLayer.setOpacity(properties.opacity);
+ }
+
+ if (angular.isArray(properties.extent)) {
+ olLayer.setExtent(properties.extent);
+ }
+
+ if (properties.style) {
+ if (!angular.isFunction(properties.style)) {
+ style = createStyle(properties.style);
+ } else {
+ style = properties.style;
+ }
+ // not every layer has a setStyle method
+ if (olLayer.setStyle && angular.isFunction(olLayer.setStyle)) {
+ olLayer.setStyle(style);
+ }
+ }
+
+ if (properties.minResolution) {
+ olLayer.setMinResolution(properties.minResolution);
+ }
+
+ if (properties.maxResolution) {
+ olLayer.setMaxResolution(properties.maxResolution);
+ }
+
+ } else {
+ var isNewLayer = (function(olLayer) {
+ // this function can be used to verify whether a new layer instance has
+ // been created. This is needed in order to re-assign styles, opacity
+ // etc...
+ return function(layer) {
+ return layer !== olLayer;
+ };
+ })(olLayer);
+
+ // set source properties
+ if (isDefined(oldProperties) && !equals(properties.source, oldProperties.source)) {
+ var idx = olLayer.index;
+ collection = layerCollection;
+ group = olLayer.get('group');
+
+ if (group) {
+ collection = getGroup(layerCollection, group).getLayers();
+ }
+
+ collection.removeAt(idx);
+
+ olLayer = createLayer(properties, projection, scope.onLayerCreated);
+ olLayer.set('group', group);
+
+ if (isDefined(olLayer)) {
+ insertLayer(collection, idx, olLayer);
+
+ if (detectLayerType(properties) === 'Vector') {
+ setVectorLayerEvents(defaults.events, map, scope, properties.name);
+ }
+ }
+ }
+
+ // set opacity
+ if (isDefined(oldProperties) &&
+ properties.opacity !== oldProperties.opacity || isNewLayer(olLayer)) {
+ if (isNumber(properties.opacity) || isNumber(parseFloat(properties.opacity))) {
+ olLayer.setOpacity(properties.opacity);
+ }
+ }
+
+ // set index
+ if (isDefined(properties.index) && properties.index !== olLayer.index) {
+ collection = layerCollection;
+ group = olLayer.get('group');
+
+ if (group) {
+ collection = getGroup(layerCollection, group).getLayers();
+ }
+
+ removeLayer(collection, olLayer.index);
+ insertLayer(collection, properties.index, olLayer);
+ }
+
+ // set group
+ if (isDefined(properties.group) && properties.group !== oldProperties.group) {
+ removeLayerFromGroup(layerCollection, olLayer, oldProperties.group);
+ addLayerToGroup(layerCollection, olLayer, properties.group);
+ }
+
+ // set visibility
+ if (isDefined(oldProperties) &&
+ isBoolean(properties.visible) &&
+ (
+ properties.visible !== oldProperties.visible ||
+ isNewLayer(olLayer) ||
+ // to make sure the underlying ol3 object is always synched
+ olLayer.getVisible() !== properties.visible
+ )
+ ) {
+ olLayer.setVisible(properties.visible);
+ }
+
+ // set style
+ if (isDefined(properties.style) &&
+ !equals(properties.style, oldProperties.style) || isNewLayer(olLayer)) {
+ if (!angular.isFunction(properties.style)) {
+ style = createStyle(properties.style);
+ } else {
+ style = properties.style;
+ }
+ // not every layer has a setStyle method
+ if (olLayer.setStyle && angular.isFunction(olLayer.setStyle)) {
+ olLayer.setStyle(style);
+ }
+ }
+
+ //set min resolution
+ if (!equals(properties.minResolution, oldProperties.minResolution) || isNewLayer(olLayer)) {
+ if (isDefined(properties.minResolution)) {
+ olLayer.setMinResolution(properties.minResolution);
+ }
+ }
+
+ //set max resolution
+ if (!equals(properties.maxResolution, oldProperties.maxResolution) || isNewLayer(olLayer)) {
+ if (isDefined(properties.maxResolution)) {
+ olLayer.setMaxResolution(properties.maxResolution);
+ }
+ }
+ }
+ }, true);
+ });
+ }
+ };
+}]);
+
+angular.module('openlayers-directive').directive('olPath', ["$log", "$q", "olMapDefaults", "olHelpers", function($log, $q, olMapDefaults, olHelpers) {
+
+ return {
+ restrict: 'E',
+ scope: {
+ properties: '=olGeomProperties',
+ style: '=olStyle'
+ },
+ require: '^openlayers',
+ replace: true,
+ template: '',
+
+ link: function(scope, element, attrs, controller) {
+ var isDefined = olHelpers.isDefined;
+ var createFeature = olHelpers.createFeature;
+ var createOverlay = olHelpers.createOverlay;
+ var createVectorLayer = olHelpers.createVectorLayer;
+ var insertLayer = olHelpers.insertLayer;
+ var removeLayer = olHelpers.removeLayer;
+ var olScope = controller.getOpenlayersScope();
+
+ olScope.getMap().then(function(map) {
+ var mapDefaults = olMapDefaults.getDefaults(olScope);
+ var viewProjection = mapDefaults.view.projection;
+
+ var layer = createVectorLayer();
+ var layerCollection = map.getLayers();
+
+ insertLayer(layerCollection, layerCollection.getLength(), layer);
+
+ scope.$on('$destroy', function() {
+ removeLayer(layerCollection, layer.index);
+ });
+
+ if (isDefined(attrs.coords)) {
+ var proj = attrs.proj || 'EPSG:4326';
+ var coords = JSON.parse(attrs.coords);
+ var data = {
+ type: 'Polygon',
+ coords: coords,
+ projection: proj,
+ style: scope.style ? scope.style : mapDefaults.styles.path
+ };
+ var feature = createFeature(data, viewProjection);
+ layer.getSource().addFeature(feature);
+
+ if (attrs.message) {
+ scope.message = attrs.message;
+ var extent = feature.getGeometry().getExtent();
+ var label = createOverlay(element, extent);
+ map.addOverlay(label);
+ }
+ return;
+ }
+ });
+ }
+ };
+}]);
+
+angular.module('openlayers-directive').directive('olView', ["$log", "$q", "olData", "olMapDefaults", "olHelpers", function($log, $q, olData, olMapDefaults, olHelpers) {
+ return {
+ restrict: 'A',
+ scope: false,
+ replace: false,
+ require: 'openlayers',
+ link: function(scope, element, attrs, controller) {
+ var olScope = controller.getOpenlayersScope();
+ var isNumber = olHelpers.isNumber;
+ var safeApply = olHelpers.safeApply;
+ var createView = olHelpers.createView;
+
+ olScope.getMap().then(function(map) {
+ var defaults = olMapDefaults.getDefaults(olScope);
+ var view = olScope.view;
+
+ if (!view.projection) {
+ view.projection = defaults.view.projection;
+ }
+
+ if (!view.maxZoom) {
+ view.maxZoom = defaults.view.maxZoom;
+ }
+
+ if (!view.minZoom) {
+ view.minZoom = defaults.view.minZoom;
+ }
+
+ if (!view.rotation) {
+ view.rotation = defaults.view.rotation;
+ }
+
+ var mapView = createView(view);
+ map.setView(mapView);
+
+ olScope.$watchCollection('view', function(view) {
+ if (isNumber(view.rotation)) {
+ mapView.setRotation(view.rotation);
+ }
+ });
+
+ var rotationEventKey = mapView.on('change:rotation', function() {
+ safeApply(olScope, function(scope) {
+ scope.view.rotation = map.getView().getRotation();
+ });
+ });
+
+ olScope.$on('$destroy', function() {
+ ol.Observable.unByKey(rotationEventKey);
+ });
+
+ });
+ }
+ };
+}]);
+
+angular.module('openlayers-directive')
+.directive('olControl', ["$log", "$q", "olData", "olMapDefaults", "olHelpers", function($log, $q, olData, olMapDefaults, olHelpers) {
+ return {
+ restrict: 'E',
+ scope: {
+ properties: '=olControlProperties'
+ },
+ replace: false,
+ require: '^openlayers',
+ link: function(scope, element, attrs, controller) {
+ var isDefined = olHelpers.isDefined;
+ var olScope = controller.getOpenlayersScope();
+ var olControl;
+ var olControlOps;
+ var getControlClasses = olHelpers.getControlClasses;
+ var controlClasses = getControlClasses();
+
+ olScope.getMap().then(function(map) {
+
+ scope.$on('$destroy', function() {
+ map.removeControl(olControl);
+ });
+
+ scope.$watch('properties', function(properties) {
+ if (!isDefined(properties)) {
+ return;
+ }
+
+ initCtrls(properties);
+ });
+
+ function initCtrls(properties) {
+ if (properties && properties.control) {
+ // the control instance is already defined,
+ // so simply use it and go ahead
+
+ // is there already a control, so destroy and recreate it?
+ if (olControl) {
+ map.removeControl(olControl);
+ }
+
+ olControl = properties.control;
+ map.addControl(olControl);
+ } else {
+
+ // the name is the key to instantiate an ol3 control
+ if (attrs.name) {
+ if (isDefined(properties)) {
+ olControlOps = properties;
+ }
+
+ // is there already a control, so destroy and recreate it?
+ if (olControl) {
+ map.removeControl(olControl);
+ }
+
+ olControl = new controlClasses[attrs.name](olControlOps);
+ map.addControl(olControl);
+ }
+ }
+ }
+
+ initCtrls(scope.properties);
+
+ });
+
+ }
+ };
+}]);
+
+angular.module('openlayers-directive').directive('olMarker', ["$log", "$q", "olMapDefaults", "olHelpers", function($log, $q, olMapDefaults, olHelpers) {
+
+ var getMarkerDefaults = function() {
+ return {
+ projection: 'EPSG:4326',
+ lat: 0,
+ lon: 0,
+ coord: [],
+ show: true,
+ showOnMouseOver: false,
+ showOnMouseClick: false,
+ keepOneOverlayVisible: false
+ };
+ };
+
+ var markerLayerManager = (function() {
+ var mapDict = [];
+
+ function getMapIndex(map) {
+ return mapDict.map(function(record) {
+ return record.map;
+ }).indexOf(map);
+ }
+
+ return {
+ getInst: function getMarkerLayerInst(scope, map) {
+ var mapIndex = getMapIndex(map);
+
+ if (mapIndex === -1) {
+ var markerLayer = olHelpers.createVectorLayer();
+ markerLayer.set('markers', true);
+ map.addLayer(markerLayer);
+ mapDict.push({
+ map: map,
+ markerLayer: markerLayer,
+ instScopes: []
+ });
+ mapIndex = mapDict.length - 1;
+ }
+
+ mapDict[mapIndex].instScopes.push(scope);
+
+ return mapDict[mapIndex].markerLayer;
+ },
+ deregisterScope: function deregisterScope(scope, map) {
+ var mapIndex = getMapIndex(map);
+ if (mapIndex === -1) {
+ throw Error('This map has no markers');
+ }
+
+ var scopes = mapDict[mapIndex].instScopes;
+ var scopeIndex = scopes.indexOf(scope);
+ if (scopeIndex === -1) {
+ throw Error('Scope wan\'t registered');
+ }
+
+ scopes.splice(scopeIndex, 1);
+
+ if (!scopes.length) {
+ map.removeLayer(mapDict[mapIndex].markerLayer);
+ delete mapDict[mapIndex].markerLayer;
+ delete mapDict[mapIndex];
+ }
+ }
+ };
+ })();
+ return {
+ restrict: 'E',
+ scope: {
+ lat: '=lat',
+ lon: '=lon',
+ label: '=label',
+ properties: '=olMarkerProperties',
+ style: '=olStyle'
+ },
+ transclude: true,
+ require: '^openlayers',
+ replace: true,
+ template:
+ '',
+
+ link: function(scope, element, attrs, controller) {
+ var isDefined = olHelpers.isDefined;
+ var olScope = controller.getOpenlayersScope();
+ var createFeature = olHelpers.createFeature;
+ var createOverlay = olHelpers.createOverlay;
+
+ var hasTranscluded = element.find('ng-transclude').children().length > 0;
+
+ olScope.getMap().then(function(map) {
+ var markerLayer = markerLayerManager.getInst(scope, map);
+ var data = getMarkerDefaults();
+
+ var mapDefaults = olMapDefaults.getDefaults(olScope);
+ var viewProjection = mapDefaults.view.projection;
+ var label;
+ var pos;
+ var marker;
+
+ // This function handles dragging a marker
+ var pickOffset = null;
+ var pickProperties = null;
+ scope.handleDrag = function(evt) {
+ var coord = evt.coordinate;
+ var proj = map.getView().getProjection().getCode();
+ if (proj === 'pixel') {
+ coord = coord.map(function(v) {
+ return parseInt(v, 10);
+ });
+ } else {
+ coord = ol.proj.transform(coord, proj, 'EPSG:4326');
+ }
+
+ if (evt.type === 'pointerdown') {
+ // Get feature under mouse if any
+ var feature = map.forEachFeatureAtPixel(evt.pixel, function(feature) {
+ return feature;
+ });
+ // Get associated marker properties
+ pickProperties = (feature ? feature.get('marker') : null);
+ if (!pickProperties || !pickProperties.draggable) {
+ pickProperties = null;
+ return;
+ }
+ map.getTarget().style.cursor = 'pointer';
+ if (proj === 'pixel') {
+ pickOffset = [coord[0] - pickProperties.coord[0], coord[1] - pickProperties.coord[1]];
+ } else {
+ pickOffset = [coord[0] - pickProperties.lon, coord[1] - pickProperties.lat];
+ }
+ evt.preventDefault();
+ } else if (pickOffset && pickProperties) {
+ if (evt.type === 'pointerup') {
+ map.getTarget().style.cursor = '';
+ pickOffset = null;
+ pickProperties = null;
+ evt.preventDefault();
+ } else if (evt.type === 'pointerdrag') {
+ evt.preventDefault();
+ scope.$apply(function() {
+ // Add current delta to marker initial position
+ if (proj === 'pixel') {
+ pickProperties.coord[0] = coord[0] - pickOffset[0];
+ pickProperties.coord[1] = coord[1] - pickOffset[1];
+ } else {
+ pickProperties.lon = coord[0] - pickOffset[0];
+ pickProperties.lat = coord[1] - pickOffset[1];
+ }
+ });
+ }
+ }
+ };
+
+ function unregisterHandlers() {
+ if (!scope.properties) { return ; }
+ // Remove previous listeners if any
+ map.getViewport().removeEventListener('mousemove', scope.properties.handleInteraction);
+ map.getViewport().removeEventListener('click', scope.properties.handleTapInteraction);
+ map.getViewport().querySelector('canvas.ol-unselectable').removeEventListener(
+ 'touchend', scope.properties.handleTapInteraction);
+ map.getViewport().removeEventListener('mousemove', scope.properties.showAtLeastOneOverlay);
+ map.getViewport().removeEventListener('click', scope.properties.removeAllOverlays);
+ map.getViewport().querySelector('canvas.ol-unselectable').removeEventListener(
+ 'touchmove', scope.properties.activateCooldown);
+ }
+
+ // Setup generic handlers for marker drag
+ map.on('pointerdown', scope.handleDrag);
+ map.on('pointerup', scope.handleDrag);
+ map.on('pointerdrag', scope.handleDrag);
+
+ scope.$on('$destroy', function() {
+ markerLayer.getSource().removeFeature(marker);
+ if (isDefined(label)) {
+ map.removeOverlay(label);
+ }
+ markerLayerManager.deregisterScope(scope, map);
+ map.un('pointerdown', scope.handleDrag);
+ map.un('pointerup', scope.handleDrag);
+ map.un('pointerdrag', scope.handleDrag);
+ unregisterHandlers();
+ });
+
+ if (!isDefined(scope.properties)) {
+ data.lat = scope.lat ? scope.lat : data.lat;
+ data.lon = scope.lon ? scope.lon : data.lon;
+ data.message = attrs.message;
+ data.style = scope.style ? scope.style : mapDefaults.styles.marker;
+
+ marker = createFeature(data, viewProjection);
+ if (!isDefined(marker)) {
+ $log.error('[AngularJS - Openlayers] Received invalid data on ' +
+ 'the marker.');
+ }
+ // Add a link between the feature and the marker properties
+ marker.set('marker', scope);
+ markerLayer.getSource().addFeature(marker);
+
+ if (data.message || hasTranscluded) {
+ scope.message = attrs.message;
+ pos = ol.proj.transform([data.lon, data.lat], data.projection,
+ viewProjection);
+ label = createOverlay(element, pos);
+ map.addOverlay(label);
+ }
+ return;
+ }
+
+ scope.$watch('properties', function(properties) {
+
+ unregisterHandlers();
+
+ // This function handles popup on mouse over/click
+ properties.handleInteraction = function(evt) {
+ var ngClick = false;
+ if (attrs.hasOwnProperty('ngClick')) {
+ ngClick = true;
+ }
+
+ if (properties.label.show && !ngClick) {
+ return;
+ }
+ var found = false;
+ var pixel = map.getEventPixel(evt);
+ var feature = map.forEachFeatureAtPixel(pixel, function(feature) {
+ return feature;
+ });
+
+ var actionTaken = false;
+ if (feature === marker) {
+ actionTaken = true;
+ found = true;
+ if (ngClick && (evt.type === 'click' || evt.type === 'touchend')) {
+ element.triggerHandler('click');
+ evt.preventDefault();
+ evt.stopPropagation();
+ return;
+ }
+ if (!isDefined(label)) {
+ if (data.projection === 'pixel') {
+ pos = properties.coord;
+ } else {
+ pos = ol.proj.transform([properties.lon, properties.lat],
+ data.projection, viewProjection);
+ }
+ label = createOverlay(element, pos);
+ map.addOverlay(label);
+ }
+ map.getTarget().style.cursor = 'pointer';
+ }
+
+ if (!found && label) {
+ actionTaken = true;
+ map.removeOverlay(label);
+ label = undefined;
+ map.getTarget().style.cursor = '';
+ }
+
+ if (actionTaken) {
+ evt.preventDefault();
+ }
+ };
+
+ // Made to filter out click/tap events if both are being triggered on this platform
+ properties.handleTapInteraction = (function() {
+ var cooldownActive = false;
+ var prevTimeout;
+
+ // Sets the cooldown flag to filter out any subsequent events within 500 ms
+ properties.activateCooldown = function() {
+ cooldownActive = true;
+ if (prevTimeout) {
+ clearTimeout(prevTimeout);
+ }
+ prevTimeout = setTimeout(function() {
+ cooldownActive = false;
+ prevTimeout = null;
+ }, 500);
+ };
+
+ // Preventing from 'touchend' to be considered a tap, if fired immediately after 'touchmove'
+ if (properties.activateCooldown) {
+ map.getViewport().querySelector('canvas.ol-unselectable').removeEventListener(
+ 'touchmove', properties.activateCooldown);
+ }
+ map.getViewport().querySelector('canvas.ol-unselectable').addEventListener(
+ 'touchmove', properties.activateCooldown);
+
+ return function() {
+ if (!cooldownActive) {
+ properties.handleInteraction.apply(null, arguments);
+ properties.activateCooldown();
+ }
+ };
+ })();
+
+ properties.showAtLeastOneOverlay = function(evt) {
+ if (properties.label.show) {
+ return;
+ }
+ var found = false;
+ var pixel = map.getEventPixel(evt);
+ var feature = map.forEachFeatureAtPixel(pixel, function(feature) {
+ return feature;
+ });
+
+ var actionTaken = false;
+ if (feature === marker) {
+ actionTaken = true;
+ found = true;
+ if (!isDefined(label)) {
+ if (data.projection === 'pixel') {
+ pos = data.coord;
+ } else {
+ pos = ol.proj.transform([data.lon, data.lat],
+ data.projection, viewProjection);
+ }
+ label = createOverlay(element, pos);
+ angular.forEach(map.getOverlays(), function(value) {
+ map.removeOverlay(value);
+ });
+ map.addOverlay(label);
+ }
+ map.getTarget().style.cursor = 'pointer';
+ }
+
+ if (!found && label) {
+ actionTaken = true;
+ label = undefined;
+ map.getTarget().style.cursor = '';
+ }
+
+ if (actionTaken) {
+ evt.preventDefault();
+ }
+ };
+
+ properties.removeAllOverlays = function(evt) {
+ angular.forEach(map.getOverlays(), function(value) {
+ map.removeOverlay(value);
+ });
+ evt.preventDefault();
+ };
+
+ if (!isDefined(marker)) {
+ data.projection = properties.projection ? properties.projection :
+ data.projection;
+ data.coord = properties.coord ? properties.coord : data.coord;
+ data.lat = properties.lat ? properties.lat : data.lat;
+ data.lon = properties.lon ? properties.lon : data.lon;
+
+ if (isDefined(properties.style)) {
+ data.style = properties.style;
+ } else {
+ data.style = mapDefaults.styles.marker;
+ }
+
+ marker = createFeature(data, viewProjection);
+ if (!isDefined(marker)) {
+ $log.error('[AngularJS - Openlayers] Received invalid data on ' +
+ 'the marker.');
+ }
+ // Add a link between the feature and the marker properties
+ marker.set('marker', properties);
+ markerLayer.getSource().addFeature(marker);
+ } else {
+ var requestedPosition;
+ if (properties.projection === 'pixel') {
+ requestedPosition = properties.coord;
+ } else {
+ requestedPosition = ol.proj.transform([properties.lon, properties.lat], data.projection,
+ map.getView().getProjection());
+ }
+
+ if (!angular.equals(marker.getGeometry().getCoordinates(), requestedPosition)) {
+ var geometry = new ol.geom.Point(requestedPosition);
+ marker.setGeometry(geometry);
+ }
+ }
+
+ if (isDefined(label)) {
+ map.removeOverlay(label);
+ }
+
+ if (!isDefined(properties.label)) {
+ return;
+ }
+
+ scope.message = properties.label.message;
+ if (!hasTranscluded && (!isDefined(scope.message) || scope.message.length === 0)) {
+ return;
+ }
+
+ if (properties.label && properties.label.show === true) {
+ if (data.projection === 'pixel') {
+ pos = data.coord;
+ } else {
+ pos = ol.proj.transform([properties.lon, properties.lat], data.projection,
+ viewProjection);
+ }
+ label = createOverlay(element, pos);
+ map.addOverlay(label);
+ }
+
+ if (label && properties.label && properties.label.show === false) {
+ map.removeOverlay(label);
+ label = undefined;
+ }
+
+ // Then setup new ones according to properties
+ if (properties.label && properties.label.show === false &&
+ properties.label.showOnMouseOver) {
+ map.getViewport().addEventListener('mousemove', properties.handleInteraction);
+ }
+
+ if ((properties.label && properties.label.show === false &&
+ properties.label.showOnMouseClick) ||
+ attrs.hasOwnProperty('ngClick')) {
+ map.getViewport().addEventListener('click', properties.handleTapInteraction);
+ map.getViewport().querySelector('canvas.ol-unselectable').addEventListener(
+ 'touchend', properties.handleTapInteraction);
+ }
+
+ if ((properties.label && properties.label.show === false &&
+ properties.label.keepOneOverlayVisible)) {
+ map.getViewport().addEventListener('mousemove', properties.showAtLeastOneOverlay);
+ map.getViewport().addEventListener('click', properties.removeAllOverlays);
+ }
+ }, true);
+ });
+ }
+ };
+}]);
+
+angular.module('openlayers-directive').service('olData', ["$log", "$q", function($log, $q) {
+
+ var maps = {};
+
+ var setResolvedDefer = function(d, mapId) {
+ var id = obtainEffectiveMapId(d, mapId);
+ d[id].resolvedDefer = true;
+ };
+
+ var getUnresolvedDefer = function(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 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;
+ };
+
+ this.setMap = function(olMap, scopeId) {
+ var defer = getUnresolvedDefer(maps, scopeId);
+ defer.resolve(olMap);
+ setResolvedDefer(maps, scopeId);
+ };
+
+ this.getMap = function(scopeId) {
+ var defer = getDefer(maps, scopeId);
+ return defer.promise;
+ };
+
+ function obtainEffectiveMapId(d, mapId) {
+ var id;
+ var i;
+ if (!angular.isDefined(mapId)) {
+ if (Object.keys(d).length === 1) {
+ for (i in d) {
+ if (d.hasOwnProperty(i)) {
+ id = i;
+ }
+ }
+ } else if (Object.keys(d).length === 0) {
+ id = 'main';
+ } else {
+ $log.error('[AngularJS - Openlayers] - You have more than 1 map on the DOM, ' +
+ 'you must provide the map ID to the olData.getXXX call');
+ }
+ } else {
+ id = mapId;
+ }
+ return id;
+ }
+
+ this.resetMap = function(scopeId) {
+ if (angular.isDefined(maps[scopeId])) {
+ delete maps[scopeId];
+ }
+ };
+
+}]);
+
+angular.module('openlayers-directive').factory('olHelpers', ["$q", "$log", "$http", function($q, $log, $http) {
+
+ var isDefined = function(value) {
+ return angular.isDefined(value);
+ };
+
+ var isDefinedAndNotNull = function(value) {
+ return angular.isDefined(value) && value !== null;
+ };
+
+ var setEvent = function(map, eventType, scope) {
+ map.on(eventType, function(event) {
+ var coord = event.coordinate;
+ var proj = map.getView().getProjection().getCode();
+ if (proj === 'pixel') {
+ coord = coord.map(function(v) {
+ return parseInt(v, 10);
+ });
+ }
+ scope.$emit('openlayers.map.' + eventType, {
+ 'coord': coord,
+ 'projection': proj,
+ 'event': event
+ });
+ });
+ };
+
+ var bingImagerySets = [
+ 'Road',
+ 'Aerial',
+ 'AerialWithLabels',
+ 'collinsBart',
+ 'ordnanceSurvey'
+ ];
+
+ var getControlClasses = function() {
+ return {
+ attribution: ol.control.Attribution,
+ fullscreen: ol.control.FullScreen,
+ mouseposition: ol.control.MousePosition,
+ overviewmap: ol.control.OverviewMap,
+ rotate: ol.control.Rotate,
+ scaleline: ol.control.ScaleLine,
+ zoom: ol.control.Zoom,
+ zoomslider: ol.control.ZoomSlider,
+ zoomtoextent: ol.control.ZoomToExtent
+ };
+ };
+
+ var mapQuestLayers = ['osm', 'sat', 'hyb'];
+
+ var esriBaseLayers = ['World_Imagery', 'World_Street_Map', 'World_Topo_Map',
+ 'World_Physical_Map', 'World_Terrain_Base',
+ 'Ocean_Basemap', 'NatGeo_World_Map'];
+
+ var styleMap = {
+ 'style': ol.style.Style,
+ 'fill': ol.style.Fill,
+ 'stroke': ol.style.Stroke,
+ 'circle': ol.style.Circle,
+ 'icon': ol.style.Icon,
+ 'image': ol.style.Image,
+ 'regularshape': ol.style.RegularShape,
+ 'text': ol.style.Text
+ };
+
+ var optionalFactory = function(style, Constructor) {
+ if (Constructor && style instanceof Constructor) {
+ return style;
+ } else if (Constructor) {
+ return new Constructor(style);
+ } else {
+ return style;
+ }
+ };
+
+ //Parse the style tree calling the appropriate constructors.
+ //The keys in styleMap can be used and the OpenLayers constructors can be
+ //used directly.
+ var createStyle = function recursiveStyle(data, styleName) {
+ var style;
+ if (!styleName) {
+ styleName = 'style';
+ style = data;
+ } else {
+ style = data[styleName];
+ }
+ //Instead of defining one style for the layer, we've been given a style function
+ //to apply to each feature.
+ if (styleName === 'style' && data instanceof Function) {
+ return data;
+ }
+
+ if (!(style instanceof Object)) {
+ return style;
+ }
+
+ var styleObject;
+ if (Object.prototype.toString.call(style) === '[object Object]') {
+ styleObject = {};
+ var styleConstructor = styleMap[styleName];
+ if (styleConstructor && style instanceof styleConstructor) {
+ return style;
+ }
+ Object.getOwnPropertyNames(style).forEach(function(val, idx, array) {
+ //Consider the case
+ //image: {
+ // circle: {
+ // fill: {
+ // color: 'red'
+ // }
+ // }
+ //
+ //An ol.style.Circle is an instance of ol.style.Image, so we do not want to construct
+ //an Image and then construct a Circle. We assume that if we have an instanceof
+ //relationship, that the JSON parent has exactly one child.
+ //We check to see if an inheritance relationship exists.
+ //If it does, then for the parent we create an instance of the child.
+ var valConstructor = styleMap[val];
+ if (styleConstructor && valConstructor &&
+ valConstructor.prototype instanceof styleMap[styleName]) {
+ console.assert(array.length === 1, 'Extra parameters for ' + styleName);
+ styleObject = recursiveStyle(style, val);
+ return optionalFactory(styleObject, valConstructor);
+ } else {
+ styleObject[val] = recursiveStyle(style, val);
+
+ // if the value is 'text' and it contains a String, then it should be interpreted
+ // as such, 'cause the text style might effectively contain a text to display
+ if (val !== 'text' && typeof styleObject[val] !== 'string') {
+ styleObject[val] = optionalFactory(styleObject[val], styleMap[val]);
+ }
+ }
+ });
+ } else {
+ styleObject = style;
+ }
+ return optionalFactory(styleObject, styleMap[styleName]);
+ };
+
+ var detectLayerType = function(layer) {
+ if (layer.type) {
+ return layer.type;
+ } else {
+ switch (layer.source.type) {
+ case 'ImageWMS':
+ return 'Image';
+ case 'ImageStatic':
+ return 'Image';
+ case 'GeoJSON':
+ case 'JSONP':
+ case 'TopoJSON':
+ case 'KML':
+ case 'WKT':
+ return 'Vector';
+ case 'TileVector':
+ case 'MVT':
+ return 'TileVector';
+ default:
+ return 'Tile';
+ }
+ }
+ };
+
+ var createProjection = function(view) {
+ var oProjection;
+
+ switch (view.projection) {
+ case 'pixel':
+ if (!isDefined(view.extent)) {
+ $log.error('[AngularJS - Openlayers] - You must provide the extent of the image ' +
+ 'if using pixel projection');
+ return;
+ }
+ oProjection = new ol.proj.Projection({
+ code: 'pixel',
+ units: 'pixels',
+ extent: view.extent
+ });
+ break;
+ default:
+ oProjection = new ol.proj.get(view.projection);
+ break;
+ }
+
+ return oProjection;
+ };
+
+ var isValidStamenLayer = function(layer) {
+ return ['watercolor', 'terrain', 'toner'].indexOf(layer) !== -1;
+ };
+
+ var createSource = function(source, projection) {
+ var oSource;
+ var pixelRatio;
+ var url;
+ var geojsonFormat = new ol.format.GeoJSON(); // used in various switch stmnts below
+
+ switch (source.type) {
+ case 'MapBox':
+ if (!source.mapId || !source.accessToken) {
+ $log.error('[AngularJS - Openlayers] - MapBox layer requires the map id and the access token');
+ return;
+ }
+ url = 'https://api.tiles.mapbox.com/v4/' + source.mapId + '/{z}/{x}/{y}.png?access_token=' +
+ source.accessToken;
+
+ pixelRatio = window.devicePixelRatio;
+
+ if (pixelRatio > 1) {
+ url = url.replace('.png', '@2x.png');
+ }
+
+ oSource = new ol.source.XYZ({
+ url: url,
+ tileLoadFunction: source.tileLoadFunction,
+ attributions: createAttribution(source),
+ tilePixelRatio: pixelRatio > 1 ? 2 : 1,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+ case 'MapBoxStudio':
+ if (!source.mapId || !source.accessToken || !source.userId) {
+ $log.error('[AngularJS - Openlayers] - MapBox Studio layer requires the map id' +
+ ', user id and the access token');
+ return;
+ }
+ url = 'https://api.mapbox.com/styles/v1/' + source.userId +
+ '/' + source.mapId + '/tiles/{z}/{x}/{y}?access_token=' +
+ source.accessToken;
+
+ pixelRatio = window.devicePixelRatio;
+
+ if (pixelRatio > 1) {
+ url = url.replace('{y}?access_token', '{y}@2x?access_token');
+ }
+
+ oSource = new ol.source.XYZ({
+ url: url,
+ tileLoadFunction: source.tileLoadFunction,
+ attributions: createAttribution(source),
+ tilePixelRatio: pixelRatio > 1 ? 2 : 1,
+ tileSize: source.tileSize || [512, 512],
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+ case 'MVT':
+ if (!source.url) {
+ $log.error('[AngularJS - Openlayers] - MVT layer requires the source url');
+ return;
+ }
+ oSource = new ol.source.VectorTile({
+ attributions: source.attributions || '',
+ format: new ol.format.MVT(),
+ tileGrid: ol.tilegrid.createXYZ({maxZoom: source.maxZoom || 22}),
+ tilePixelRatio: source.tilePixelRatio || 16,
+ url: source.url
+ });
+ break;
+ case 'ImageWMS':
+ if (!source.url || !source.params) {
+ $log.error('[AngularJS - Openlayers] - ImageWMS Layer needs ' +
+ 'valid server url and params properties');
+ }
+ oSource = new ol.source.ImageWMS({
+ url: source.url,
+ imageLoadFunction: source.imageLoadFunction,
+ attributions: createAttribution(source),
+ crossOrigin: (typeof source.crossOrigin === 'undefined') ? 'anonymous' : source.crossOrigin,
+ params: deepCopy(source.params),
+ ratio: source.ratio
+ });
+ break;
+
+ case 'TileWMS':
+ if ((!source.url && !source.urls) || !source.params) {
+ $log.error('[AngularJS - Openlayers] - TileWMS Layer needs ' +
+ 'valid url (or urls) and params properties');
+ }
+
+ var wmsConfiguration = {
+ tileLoadFunction: source.tileLoadFunction,
+ crossOrigin: (typeof source.crossOrigin === 'undefined') ? 'anonymous' : source.crossOrigin,
+ params: deepCopy(source.params),
+ attributions: createAttribution(source),
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ };
+
+ if (source.serverType) {
+ wmsConfiguration.serverType = source.serverType;
+ }
+
+ if (source.url) {
+ wmsConfiguration.url = source.url;
+ }
+
+ if (source.urls) {
+ wmsConfiguration.urls = source.urls;
+ }
+
+ oSource = new ol.source.TileWMS(wmsConfiguration);
+ break;
+
+ case 'WMTS':
+ if ((!source.url && !source.urls) || !source.tileGrid) {
+ $log.error('[AngularJS - Openlayers] - WMTS Layer needs valid url ' +
+ '(or urls) and tileGrid properties');
+ }
+
+ var wmtsConfiguration = {
+ tileLoadFunction: source.tileLoadFunction,
+ projection: projection,
+ layer: source.layer,
+ attributions: createAttribution(source),
+ matrixSet: (source.matrixSet === 'undefined') ? projection : source.matrixSet,
+ format: (source.format === 'undefined') ? 'image/jpeg' : source.format,
+ requestEncoding: (source.requestEncoding === 'undefined') ?
+ 'KVP' : source.requestEncoding,
+ tileGrid: new ol.tilegrid.WMTS({
+ origin: source.tileGrid.origin,
+ resolutions: source.tileGrid.resolutions,
+ matrixIds: source.tileGrid.matrixIds
+ }),
+ style: (source.style === 'undefined') ? 'normal' : source.style,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ };
+
+ if (isDefined(source.url)) {
+ wmtsConfiguration.url = source.url;
+ }
+
+ if (isDefined(source.urls)) {
+ wmtsConfiguration.urls = source.urls;
+ }
+
+ oSource = new ol.source.WMTS(wmtsConfiguration);
+ break;
+
+ case 'OSM':
+ oSource = new ol.source.OSM({
+ tileLoadFunction: source.tileLoadFunction,
+ attributions: createAttribution(source),
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+
+ if (source.url) {
+ oSource.setUrl(source.url);
+ }
+
+ break;
+ case 'BingMaps':
+ if (!source.key) {
+ $log.error('[AngularJS - Openlayers] - You need an API key to show the Bing Maps.');
+ return;
+ }
+
+ var bingConfiguration = {
+ key: source.key,
+ tileLoadFunction: source.tileLoadFunction,
+ attributions: createAttribution(source),
+ imagerySet: source.imagerySet ? source.imagerySet : bingImagerySets[0],
+ culture: source.culture,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ };
+
+ if (source.maxZoom) {
+ bingConfiguration.maxZoom = source.maxZoom;
+ }
+
+ oSource = new ol.source.BingMaps(bingConfiguration);
+ break;
+
+ case 'MapQuest':
+ if (!source.layer || mapQuestLayers.indexOf(source.layer) === -1) {
+ $log.error('[AngularJS - Openlayers] - MapQuest layers needs a valid \'layer\' property.');
+ return;
+ }
+
+ oSource = new ol.source.MapQuest({
+ attributions: createAttribution(source),
+ layer: source.layer,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+
+ break;
+
+ case 'EsriBaseMaps':
+ if (!source.layer || esriBaseLayers.indexOf(source.layer) === -1) {
+ $log.error('[AngularJS - Openlayers] - ESRI layers needs a valid \'layer\' property.');
+ return;
+ }
+
+ var _urlBase = 'https://services.arcgisonline.com/ArcGIS/rest/services/';
+ var _url = _urlBase + source.layer + '/MapServer/tile/{z}/{y}/{x}';
+
+ oSource = new ol.source.XYZ({
+ attributions: createAttribution(source),
+ tileLoadFunction: source.tileLoadFunction,
+ url: _url,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+
+ break;
+
+ case 'TileArcGISRest':
+ if (!source.url) {
+ $log.error('[AngularJS - Openlayers] - TileArcGISRest Layer needs valid url');
+ }
+
+ oSource = new ol.source.TileArcGISRest({
+ attributions: createAttribution(source),
+ tileLoadFunction: source.tileLoadFunction,
+ url: source.url,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+
+ break;
+
+ case 'GeoJSON':
+ if (!(source.geojson || source.url)) {
+ $log.error('[AngularJS - Openlayers] - You need a geojson ' +
+ 'property to add a GeoJSON layer.');
+ return;
+ }
+
+ if (isDefined(source.url)) {
+ oSource = new ol.source.Vector({
+ format: new ol.format.GeoJSON(),
+ url: source.url
+ });
+ } else {
+ oSource = new ol.source.Vector();
+
+ var projectionToUse = projection;
+ var dataProjection; // Projection of geojson data
+ if (isDefined(source.geojson.projection)) {
+ dataProjection = new ol.proj.get(source.geojson.projection);
+ } else {
+ dataProjection = projection; // If not defined, features will not be reprojected.
+ }
+
+ var features = geojsonFormat.readFeatures(
+ source.geojson.object, {
+ featureProjection: projectionToUse.getCode(),
+ dataProjection: dataProjection.getCode()
+ });
+
+ oSource.addFeatures(features);
+ }
+
+ break;
+
+ case 'WKT':
+ if (!(source.wkt) && !(source.wkt.data)) {
+ $log.error('[AngularJS - Openlayers] - You need a WKT ' +
+ 'property to add a WKT format vector layer.');
+ return;
+ }
+
+ oSource = new ol.source.Vector();
+ var wktFormatter = new ol.format.WKT();
+ var wktProjection; // Projection of wkt data
+ if (isDefined(source.wkt.projection)) {
+ wktProjection = new ol.proj.get(source.wkt.projection);
+ } else {
+ wktProjection = projection; // If not defined, features will not be reprojected.
+ }
+
+ var wktFeatures = wktFormatter.readFeatures(
+ source.wkt.data, {
+ featureProjection: projection.getCode(),
+ dataProjection: wktProjection.getCode()
+ });
+
+ oSource.addFeatures(wktFeatures);
+ break;
+
+ case 'JSONP':
+ if (!(source.url)) {
+ $log.error('[AngularJS - Openlayers] - You need an url properly configured to add a JSONP layer.');
+ return;
+ }
+
+ if (isDefined(source.url)) {
+ oSource = new ol.source.ServerVector({
+ format: geojsonFormat,
+ loader: function(/*extent, resolution, projection*/) {
+ var url = source.url +
+ '&outputFormat=text/javascript&format_options=callback:JSON_CALLBACK';
+ $http.jsonp(url, { cache: source.cache})
+ .success(function(response) {
+ oSource.addFeatures(geojsonFormat.readFeatures(response));
+ })
+ .error(function(response) {
+ $log(response);
+ });
+ },
+ projection: projection
+ });
+ }
+ break;
+ case 'TopoJSON':
+ if (!(source.topojson || source.url)) {
+ $log.error('[AngularJS - Openlayers] - You need a topojson ' +
+ 'property to add a TopoJSON layer.');
+ return;
+ }
+
+ if (source.url) {
+ oSource = new ol.source.Vector({
+ format: new ol.format.TopoJSON(),
+ url: source.url
+ });
+ } else {
+ oSource = new ol.source.Vector(angular.extend(source.topojson, {
+ format: new ol.format.TopoJSON()
+ }));
+ }
+ break;
+ case 'TileJSON':
+ oSource = new ol.source.TileJSON({
+ url: source.url,
+ attributions: createAttribution(source),
+ tileLoadFunction: source.tileLoadFunction,
+ crossOrigin: 'anonymous',
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+
+ case 'TileVector':
+ if (!source.url || !source.format) {
+ $log.error('[AngularJS - Openlayers] - TileVector Layer needs valid url and format properties');
+ }
+ oSource = new ol.source.VectorTile({
+ url: source.url,
+ projection: projection,
+ attributions: createAttribution(source),
+ tileLoadFunction: source.tileLoadFunction,
+ format: source.format,
+ tileGrid: new ol.tilegrid.createXYZ({
+ maxZoom: source.maxZoom || 19
+ }),
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+
+ case 'TileTMS':
+ if (!source.url || !source.tileGrid) {
+ $log.error('[AngularJS - Openlayers] - TileTMS Layer needs valid url and tileGrid properties');
+ }
+ oSource = new ol.source.TileImage({
+ url: source.url,
+ maxExtent: source.maxExtent,
+ attributions: createAttribution(source),
+ tileLoadFunction: source.tileLoadFunction,
+ tileGrid: new ol.tilegrid.TileGrid({
+ origin: source.tileGrid.origin,
+ resolutions: source.tileGrid.resolutions
+ }),
+ tileUrlFunction: function(tileCoord) {
+
+ var z = tileCoord[0];
+ var x = tileCoord[1];
+ var y = tileCoord[2]; //(1 << z) - tileCoord[2] - 1;
+
+ if (x < 0 || y < 0) {
+ return '';
+ }
+
+ var url = source.url + z + '/' + x + '/' + y + '.png';
+
+ return url;
+ },
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+ case 'TileImage':
+ oSource = new ol.source.TileImage({
+ url: source.url,
+ attributions: createAttribution(source),
+ tileLoadFunction: source.tileLoadFunction,
+ tileGrid: new ol.tilegrid.TileGrid({
+ origin: source.tileGrid.origin, // top left corner of the pixel projection's extent
+ resolutions: source.tileGrid.resolutions
+ }),
+ tileUrlFunction: function(tileCoord/*, pixelRatio, projection*/) {
+ var z = tileCoord[0];
+ var x = tileCoord[1];
+ var y = -tileCoord[2] - 1;
+ var url = source.url
+ .replace('{z}', z.toString())
+ .replace('{x}', x.toString())
+ .replace('{y}', y.toString());
+ return url;
+ },
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+ case 'KML':
+ var extractStyles = source.extractStyles || false;
+ oSource = new ol.source.Vector({
+ url: source.url,
+ format: new ol.format.KML(),
+ radius: source.radius,
+ extractStyles: extractStyles
+ });
+ break;
+ case 'Stamen':
+ if (!source.layer || !isValidStamenLayer(source.layer)) {
+ $log.error('[AngularJS - Openlayers] - You need a valid Stamen layer.');
+ return;
+ }
+ oSource = new ol.source.Stamen({
+ tileLoadFunction: source.tileLoadFunction,
+ layer: source.layer,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+ case 'ImageStatic':
+ if (!source.url || !angular.isArray(source.imageSize) || source.imageSize.length !== 2) {
+ $log.error('[AngularJS - Openlayers] - You need a image URL to create a ImageStatic layer.');
+ return;
+ }
+
+ oSource = new ol.source.ImageStatic({
+ url: source.url,
+ attributions: createAttribution(source),
+ imageSize: source.imageSize,
+ projection: projection,
+ imageExtent: source.imageExtent ? source.imageExtent : projection.getExtent(),
+ imageLoadFunction: source.imageLoadFunction
+ });
+ break;
+ case 'XYZ':
+ if (!source.url && !source.urls && !source.tileUrlFunction) {
+ $log.error('[AngularJS - Openlayers] - XYZ Layer needs valid url(s) or tileUrlFunction properties');
+ }
+ oSource = new ol.source.XYZ({
+ url: source.url,
+ urls: source.urls,
+ attributions: createAttribution(source),
+ minZoom: source.minZoom,
+ maxZoom: source.maxZoom,
+ projection: source.projection,
+ tileUrlFunction: source.tileUrlFunction,
+ tileLoadFunction: source.tileLoadFunction,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+ case 'Zoomify':
+ if (!source.url || !angular.isArray(source.imageSize) || source.imageSize.length !== 2) {
+ $log.error('[AngularJS - Openlayers] - Zoomify Layer needs valid url and imageSize properties');
+ }
+ oSource = new ol.source.Zoomify({
+ url: source.url,
+ size: source.imageSize,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+ }
+
+ // log a warning when no source could be created for the given type
+ if (!oSource) {
+ $log.warn('[AngularJS - Openlayers] - No source could be found for type "' + source.type + '"');
+ }
+
+ return oSource;
+ };
+
+ var deepCopy = function(oldObj) {
+ var newObj = oldObj;
+ if (oldObj && typeof oldObj === 'object') {
+ newObj = Object.prototype.toString.call(oldObj) === '[object Array]' ? [] : {};
+ for (var i in oldObj) {
+ newObj[i] = deepCopy(oldObj[i]);
+ }
+ }
+ return newObj;
+ };
+
+ var createAttribution = function(source) {
+ var attributions = [];
+ if (isDefined(source.attribution)) {
+ // opt-out -> default tries to show an attribution
+ if (!(source.attribution === false)) { // jshint ignore:line
+ // we got some HTML so display that as the attribution
+ attributions.unshift(new ol.Attribution({html: source.attribution}));
+ }
+ } else {
+ // try to infer automatically
+ var attrib = extractAttributionFromSource(source);
+ if (attrib) {
+ attributions.unshift(attrib);
+ }
+ }
+
+ return attributions;
+ };
+
+ var extractAttributionFromSource = function(source) {
+ if (source && source.type) {
+ var ol3SourceInstance = ol.source[source.type];
+ if (ol3SourceInstance) {
+ // iterate over the object's props and try
+ // to find the attribution one as it differs
+ for (var prop in ol3SourceInstance) {
+ if (ol3SourceInstance.hasOwnProperty(prop)) {
+ if (prop.toLowerCase().indexOf('attribution') > -1) {
+ return ol.source[source.type][prop];
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ };
+
+ var createGroup = function(name) {
+ var olGroup = new ol.layer.Group();
+ olGroup.set('name', name);
+
+ return olGroup;
+ };
+
+ var getGroup = function(layers, name) {
+ var layer;
+
+ angular.forEach(layers, function(l) {
+ if (l instanceof ol.layer.Group && l.get('name') === name) {
+ layer = l;
+ return;
+ }
+ });
+
+ return layer;
+ };
+
+ var addLayerBeforeMarkers = function(layers, layer) {
+ var markersIndex;
+ for (var i = 0; i < layers.getLength(); i++) {
+ var l = layers.item(i);
+
+ if (l.get('markers')) {
+ markersIndex = i;
+ break;
+ }
+ }
+
+ if (isDefined(markersIndex)) {
+ var markers = layers.item(markersIndex);
+ layer.index = markersIndex;
+ layers.setAt(markersIndex, layer);
+ markers.index = layers.getLength();
+ layers.push(markers);
+ } else {
+ layer.index = layers.getLength();
+ layers.push(layer);
+ }
+
+ };
+
+ var removeLayer = function(layers, index) {
+ layers.removeAt(index);
+ for (var i = index; i < layers.getLength(); i++) {
+ var l = layers.item(i);
+ if (l === null) {
+ layers.insertAt(i, null);
+ break;
+ } else {
+ l.index = i;
+ }
+ }
+ };
+
+ return {
+ // Determine if a reference is defined
+ isDefined: isDefined,
+
+ // Determine if a reference is a number
+ isNumber: function(value) {
+ return angular.isNumber(value);
+ },
+
+ createView: function(view) {
+ var projection = createProjection(view);
+
+ var viewConfig = {
+ projection: projection,
+ maxZoom: view.maxZoom,
+ minZoom: view.minZoom
+ };
+
+ if (view.center) {
+ viewConfig.center = view.center;
+ }
+ if (view.extent) {
+ viewConfig.extent = view.extent;
+ }
+ if (view.zoom) {
+ viewConfig.zoom = view.zoom;
+ }
+ if (view.resolutions) {
+ viewConfig.resolutions = view.resolutions;
+ }
+
+ return new ol.View(viewConfig);
+ },
+
+ // Determine if a reference is defined and not null
+ isDefinedAndNotNull: isDefinedAndNotNull,
+
+ // Determine if a reference is a string
+ isString: function(value) {
+ return angular.isString(value);
+ },
+
+ // Determine if a reference is an array
+ isArray: function(value) {
+ return angular.isArray(value);
+ },
+
+ // Determine if a reference is an object
+ isObject: function(value) {
+ return angular.isObject(value);
+ },
+
+ // Determine if two objects have the same properties
+ equals: function(o1, o2) {
+ return angular.equals(o1, o2);
+ },
+
+ isValidCenter: function(center) {
+ return angular.isDefined(center) &&
+ (typeof center.autodiscover === 'boolean' ||
+ angular.isNumber(center.lat) && angular.isNumber(center.lon) ||
+ (angular.isArray(center.coord) && center.coord.length === 2 &&
+ angular.isNumber(center.coord[0]) && angular.isNumber(center.coord[1])) ||
+ (angular.isArray(center.bounds) && center.bounds.length === 4 &&
+ angular.isNumber(center.bounds[0]) && angular.isNumber(center.bounds[1]) &&
+ angular.isNumber(center.bounds[1]) && angular.isNumber(center.bounds[2])));
+ },
+
+ safeApply: function($scope, fn) {
+ var phase = $scope.$root.$$phase;
+ if (phase === '$apply' || phase === '$digest') {
+ $scope.$eval(fn);
+ } else {
+ $scope.$apply(fn);
+ }
+ },
+
+ isSameCenterOnMap: function(center, map) {
+ var urlProj = center.projection || 'EPSG:4326';
+ var urlCenter = [center.lon, center.lat];
+ var mapProj = map.getView().getProjection();
+ var mapCenter = ol.proj.transform(map.getView().getCenter(), mapProj, urlProj);
+ var zoom = map.getView().getZoom();
+ if (mapCenter[1].toFixed(4) === urlCenter[1].toFixed(4) &&
+ mapCenter[0].toFixed(4) === urlCenter[0].toFixed(4) &&
+ zoom === center.zoom) {
+ return true;
+ }
+ return false;
+ },
+
+ setCenter: function(view, projection, newCenter, map) {
+
+ if (map && view.getCenter()) {
+ view.animate({
+ duration: 150,
+ center: view.getCenter()
+ });
+ }
+
+ if (newCenter.projection === projection) {
+ view.setCenter([newCenter.lon, newCenter.lat]);
+ } else {
+ var coord = [newCenter.lon, newCenter.lat];
+ view.setCenter(ol.proj.transform(coord, newCenter.projection, projection));
+ }
+ },
+
+ setZoom: function(view, zoom, map) {
+ view.animate({
+ duration: 150,
+ resolution: map.getView().getResolution(),
+ zoom: zoom
+ });
+ view.setZoom(zoom);
+ },
+
+ isBoolean: function(value) {
+ return typeof value === 'boolean';
+ },
+
+ createStyle: createStyle,
+
+ setMapEvents: function(events, map, scope) {
+ if (isDefined(events) && angular.isArray(events.map)) {
+ for (var i in events.map) {
+ var event = events.map[i];
+ setEvent(map, event, scope);
+ }
+ }
+ },
+
+ setVectorLayerEvents: function(events, map, scope, layerName) {
+ if (isDefined(events) && angular.isArray(events.layers)) {
+ angular.forEach(events.layers, function(eventType) {
+ angular.element(map.getViewport()).on(eventType, function(evt) {
+ var pixel = map.getEventPixel(evt);
+ var feature = map.forEachFeatureAtPixel(pixel, function(feature, olLayer) {
+ // only return the feature if it is in this layer (based on the name)
+ return (isDefinedAndNotNull(olLayer) && olLayer.get('name') === layerName) ? feature : null;
+ });
+ if (isDefinedAndNotNull(feature)) {
+ scope.$emit('openlayers.layers.' + layerName + '.' + eventType, feature, evt);
+ }
+ });
+ });
+ }
+ },
+
+ setViewEvents: function(events, map, scope) {
+ if (isDefined(events) && angular.isArray(events.view)) {
+ var view = map.getView();
+ angular.forEach(events.view, function(eventType) {
+ view.on(eventType, function(event) {
+ scope.$emit('openlayers.view.' + eventType, view, event);
+ });
+ });
+ }
+ },
+
+ detectLayerType: detectLayerType,
+
+ createLayer: function(layer, projection, name, onLayerCreatedFn) {
+ var oLayer;
+ var type = detectLayerType(layer);
+ var oSource = createSource(layer.source, projection);
+ if (!oSource) {
+ return;
+ }
+
+ // handle function overloading. 'name' argument may be
+ // our onLayerCreateFn since name is optional
+ if (typeof(name) === 'function' && !onLayerCreatedFn) {
+ onLayerCreatedFn = name;
+ name = undefined; // reset, otherwise it'll be used later on
+ }
+
+ // Manage clustering
+ if ((type === 'Vector') && layer.clustering) {
+ oSource = new ol.source.Cluster({
+ source: oSource,
+ distance: layer.clusteringDistance
+ });
+ }
+
+ var layerConfig = {};
+
+ // copy over eventual properties set on the passed layerconfig which
+ // can later be retrieved via layer.get('propName');
+ for (var property in layer) {
+ if (layer.hasOwnProperty(property) &&
+ // ignore props like source or those angular might add (starting with $)
+ // don't use startsWith as it is not supported in IE
+ property.indexOf('$', 0) !== 0 &&
+ property.indexOf('source', 0) !== 0 &&
+ property.indexOf('style', 0) !== 0
+ ) {
+ layerConfig[property] = layer[property];
+ }
+ }
+
+ layerConfig.source = oSource;
+
+ // ol.layer.Layer configuration options
+ if (isDefinedAndNotNull(layer.opacity)) {
+ layerConfig.opacity = layer.opacity;
+ }
+ if (isDefinedAndNotNull(layer.visible)) {
+ layerConfig.visible = layer.visible;
+ }
+ if (isDefinedAndNotNull(layer.extent)) {
+ layerConfig.extent = layer.extent;
+ }
+ if (isDefinedAndNotNull(layer.zIndex)) {
+ layerConfig.zIndex = layer.zIndex;
+ }
+ if (isDefinedAndNotNull(layer.minResolution)) {
+ layerConfig.minResolution = layer.minResolution;
+ }
+ if (isDefinedAndNotNull(layer.maxResolution)) {
+ layerConfig.maxResolution = layer.maxResolution;
+ }
+ if (isDefinedAndNotNull(layer.style) && type === 'TileVector') {
+ layerConfig.style = layer.style;
+ }
+
+ switch (type) {
+ case 'Image':
+ oLayer = new ol.layer.Image(layerConfig);
+ break;
+ case 'Tile':
+ oLayer = new ol.layer.Tile(layerConfig);
+ break;
+ case 'Heatmap':
+ oLayer = new ol.layer.Heatmap(layerConfig);
+ break;
+ case 'Vector':
+ oLayer = new ol.layer.Vector(layerConfig);
+ break;
+ case 'TileVector':
+ oLayer = new ol.layer.VectorTile(layerConfig);
+ break;
+ }
+
+ // set a layer name if given
+ if (isDefined(name)) {
+ oLayer.set('name', name);
+ } else if (isDefined(layer.name)) {
+ oLayer.set('name', layer.name);
+ }
+
+ // set custom layer properties if given
+ if (isDefined(layer.customAttributes)) {
+ for (var key in layer.customAttributes) {
+ oLayer.set(key, layer.customAttributes[key]);
+ }
+ }
+
+ // invoke the onSourceCreated callback
+ if (onLayerCreatedFn) {
+ onLayerCreatedFn({
+ oLayer: oLayer
+ });
+ }
+
+ return oLayer;
+ },
+
+ createVectorLayer: function() {
+ return new ol.layer.Vector({
+ source: new ol.source.Vector()
+ });
+ },
+
+ notifyCenterUrlHashChanged: function(scope, center, search) {
+ if (center.centerUrlHash) {
+ var centerUrlHash = center.lat.toFixed(4) + ':' + center.lon.toFixed(4) + ':' + center.zoom;
+ if (!isDefined(search.c) || search.c !== centerUrlHash) {
+ scope.$emit('centerUrlHash', centerUrlHash);
+ }
+ }
+ },
+
+ getControlClasses: getControlClasses,
+
+ detectControls: function(controls) {
+ var actualControls = {};
+ var controlClasses = getControlClasses();
+
+ controls.forEach(function(control) {
+ for (var i in controlClasses) {
+ if (control instanceof controlClasses[i]) {
+ actualControls[i] = control;
+ }
+ }
+ });
+
+ return actualControls;
+ },
+
+ createFeature: function(data, viewProjection) {
+ var geometry;
+
+ switch (data.type) {
+ case 'Polygon':
+ geometry = new ol.geom.Polygon(data.coords);
+ break;
+ default:
+ if (isDefined(data.coord) && data.projection === 'pixel') {
+ geometry = new ol.geom.Point(data.coord);
+ } else {
+ geometry = new ol.geom.Point([data.lon, data.lat]);
+ }
+ break;
+ }
+
+ if (isDefined(data.projection) && data.projection !== 'pixel') {
+ geometry = geometry.transform(data.projection, viewProjection);
+ }
+
+ var feature = new ol.Feature({
+ geometry: geometry
+ });
+
+ if (isDefined(data.style)) {
+ var style = createStyle(data.style);
+ feature.setStyle(style);
+ }
+ return feature;
+ },
+
+ addLayerBeforeMarkers: addLayerBeforeMarkers,
+
+ getGroup: getGroup,
+
+ addLayerToGroup: function(layers, layer, name) {
+ var groupLayer = getGroup(layers, name);
+
+ if (!isDefined(groupLayer)) {
+ groupLayer = createGroup(name);
+ addLayerBeforeMarkers(layers, groupLayer);
+ }
+
+ layer.set('group', name);
+ addLayerBeforeMarkers(groupLayer.getLayers(), layer);
+ },
+
+ removeLayerFromGroup: function(layers, layer, name) {
+ var groupLayer = getGroup(layers, name);
+ layer.set('group');
+ removeLayer(groupLayer.getLayers(), layer.index);
+ },
+
+ removeLayer: removeLayer,
+
+ insertLayer: function(layers, index, layer) {
+ if (layers.getLength() < index) {
+ // fill up with "null layers" till we get to the desired index
+ while (layers.getLength() < index) {
+ var nullLayer = new ol.layer.Image();
+ nullLayer.index = layers.getLength(); // add index which will be equal to the length in this case
+ nullLayer.name = '(null-layer)'; // we need a marker somehow
+ layers.push(nullLayer);
+ }
+ layer.index = index;
+ layers.push(layer);
+ } else {
+ layer.index = index;
+ layers.insertAt(layer.index, layer);
+
+ // remove eventual null layers
+ for (var i = index + 1; i < layers.getLength(); i++) {
+ var l = layers.item(i);
+ if (l.name === '(null-layer)') {
+ layers.removeAt(i);
+ break;
+ } else {
+ l.index = i;
+ }
+ }
+ }
+ },
+
+ createOverlay: function(element, pos) {
+ element.css('display', 'block');
+ var ov = new ol.Overlay({
+ position: pos,
+ element: element[0],
+ positioning: 'center-left'
+ });
+
+ return ov;
+ }
+ };
+}]);
+
+angular.module('openlayers-directive').factory('olMapDefaults', ["$q", "olHelpers", function($q, olHelpers) {
+
+ var base64icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAGmklEQVRYw' +
+ '7VXeUyTZxjvNnfELFuyIzOabermMZEeQC/OclkO49CpOHXOLJl/CAURuYbQi3KLgEhbrhZ1aDwmaoGq' +
+ 'KII6odATmH/scDFbdC7LvFqOCc+e95s2VG50X/LLm/f4/Z7neY/ne18aANCmAr5E/xZf1uDOkTcGcWR' +
+ '6hl9247tT5U7Y6SNvWsKT63P58qbfeLJG8M5qcgTknrvvrdDbsT7Ml+tv82X6vVxJE33aRmgSyYtcWV' +
+ 'MqX97Yv2JvW39UhRE2HuyBL+t+gK1116ly06EeWFNlAmHxlQE0OMiV6mQCScusKRlhS3QLeVJdl1+23' +
+ 'h5dY4FNB3thrbYboqptEFlphTC1hSpJnbRvxP4NWgsE5Jyz86QNNi/5qSUTGuFk1gu54tN9wuK2wc3o' +
+ '+Wc13RCmsoBwEqzGcZsxsvCSy/9wJKf7UWf1mEY8JWfewc67UUoDbDjQC+FqK4QqLVMGGR9d2wurKzq' +
+ 'Bk3nqIT/9zLxRRjgZ9bqQgub+DdoeCC03Q8j+0QhFhBHR/eP3U/zCln7Uu+hihJ1+bBNffLIvmkyP0g' +
+ 'pBZWYXhKussK6mBz5HT6M1Nqpcp+mBCPXosYQfrekGvrjewd59/GvKCE7TbK/04/ZV5QZYVWmDwH1mF' +
+ '3xa2Q3ra3DBC5vBT1oP7PTj4C0+CcL8c7C2CtejqhuCnuIQHaKHzvcRfZpnylFfXsYJx3pNLwhKzRAw' +
+ 'AhEqG0SpusBHfAKkxw3w4627MPhoCH798z7s0ZnBJ/MEJbZSbXPhER2ih7p2ok/zSj2cEJDd4CAe+5W' +
+ 'YnBCgR2uruyEw6zRoW6/DWJ/OeAP8pd/BGtzOZKpG8oke0SX6GMmRk6GFlyAc59K32OTEinILRJRcha' +
+ 'h8HQwND8N435Z9Z0FY1EqtxUg+0SO6RJ/mmXz4VuS+DpxXC3gXmZwIL7dBSH4zKE50wESf8qwVgrP1E' +
+ 'IlTO5JP9Igu0aexdh28F1lmAEGJGfh7jE6ElyM5Rw/FDcYJjWhbeiBYoYNIpc2FT/SILivp0F1ipDWk' +
+ '4BIEo2VuodEJUifhbiltnNBIXPUFCMpthtAyqws/BPlEF/VbaIxErdxPphsU7rcCp8DohC+GvBIPJS/' +
+ 'tW2jtvTmmAeuNO8BNOYQeG8G/2OzCJ3q+soYB5i6NhMaKr17FSal7GIHheuV3uSCY8qYVuEm1cOzqdW' +
+ 'r7ku/R0BDoTT+DT+ohCM6/CCvKLKO4RI+dXPeAuaMqksaKrZ7L3FE5FIFbkIceeOZ2OcHO6wIhTkNo0' +
+ 'ffgjRGxEqogXHYUPHfWAC/lADpwGcLRY3aeK4/oRGCKYcZXPVoeX/kelVYY8dUGf8V5EBRbgJXT5QIP' +
+ 'hP9ePJi428JKOiEYhYXFBqou2Guh+p/mEB1/RfMw6rY7cxcjTrneI1FrDyuzUSRm9miwEJx8E/gUmql' +
+ 'yvHGkneiwErR21F3tNOK5Tf0yXaT+O7DgCvALTUBXdM4YhC/IawPU+2PduqMvuaR6eoxSwUk75ggqsY' +
+ 'J7VicsnwGIkZBSXKOUww73WGXyqP+J2/b9c+gi1YAg/xpwck3gJuucNrh5JvDPvQr0WFXf0piyt8f8/' +
+ 'WI0hV4pRxxkQZdJDfDJNOAmM0Ag8jyT6hz0WGXWuP94Yh2jcfjmXAGvHCMslRimDHYuHuDsy2QtHuIa' +
+ 'vznhbYURq5R57KpzBBRZKPJi8eQg48h4j8SDdowifdIrEVdU+gbO6QNvRRt4ZBthUaZhUnjlYObNagV' +
+ '3keoeru3rU7rcuceqU1mJBxy+BWZYlNEBH+0eH4vRiB+OYybU2hnblYlTvkHinM4m54YnxSyaZYSF6R' +
+ '3jwgP7udKLGIX6r/lbNa9N6y5MFynjWDtrHd75ZvTYAPO/6RgF0k76mQla3FGq7dO+cH8sKn0Vo7nDl' +
+ 'lwAhqwLPkxrHwWmHJOo+AKJ4rab5OgrM7rVu8eWb2Pu0Dh4eDgXoOfvp7Y7QeqknRmvcTBEyq9m/HQQ' +
+ 'SCSz6LHq3z0yzsNySRfMS253wl2KyRDbcZPcfJKjZmSEOjcxyi+Y8dUOtsIEH6R2wNykdqrkYJ0RV92' +
+ 'H0W58pkfQk7cKevsLK10Py8SdMGfXNXATY+pPbyJR/ET6n9nIfztNtZYRV9XniQu9IA2vOVgy4ir7GC' +
+ 'LVmmd+zjkH0eAF9Po6K61pmCXHxU5rHMYd1ftc3owjwRSVRzLjKvqZEty6cRUD7jGqiOdu5HG6MdHjN' +
+ 'cNYGqfDm5YRzLBBCCDl/2bk8a8gdbqcfwECu62Fg/HrggAAAABJRU5ErkJggg==';
+
+ var _getDefaults = function() {
+ return {
+ view: {
+ projection: 'EPSG:3857',
+ minZoom: undefined,
+ maxZoom: undefined,
+ rotation: 0,
+ extent: undefined
+ },
+ center: {
+ lat: 0,
+ lon: 0,
+ zoom: 1,
+ autodiscover: false,
+ bounds: [],
+ centerUrlHash: false,
+ projection: 'EPSG:4326'
+ },
+ styles: {
+ path: {
+ stroke: {
+ color: 'blue',
+ width: 8
+ }
+ },
+ marker: {
+ image: new ol.style.Icon({
+ anchor: [0.5, 1],
+ anchorXUnits: 'fraction',
+ anchorYUnits: 'fraction',
+ opacity: 0.90,
+ src: base64icon
+ })
+ }
+ },
+ events: {
+ map: [],
+ markers: [],
+ layers: []
+ },
+ controls: {
+ attribution: true,
+ rotate: false,
+ zoom: true
+ },
+ interactions: {
+ mouseWheelZoom: false
+ },
+ renderer: 'canvas'
+ };
+ };
+
+ var isDefined = olHelpers.isDefined;
+ var defaults = {};
+
+ // Get the _defaults dictionary, and override the properties defined by the user
+ return {
+ getDefaults: function(scope) {
+ if (!isDefined(scope)) {
+ for (var i in defaults) {
+ return defaults[i];
+ }
+ }
+ return defaults[scope.$id];
+ },
+
+ setDefaults: function(scope) {
+ var userDefaults = scope.defaults;
+ var scopeId = scope.$id;
+ var newDefaults = _getDefaults();
+
+ if (isDefined(userDefaults)) {
+
+ if (isDefined(userDefaults.layers)) {
+ newDefaults.layers = angular.copy(userDefaults.layers);
+ }
+
+ if (isDefined(userDefaults.controls)) {
+ newDefaults.controls = angular.copy(userDefaults.controls);
+ }
+
+ if (isDefined(userDefaults.events)) {
+ newDefaults.events = angular.copy(userDefaults.events);
+ }
+
+ if (isDefined(userDefaults.interactions)) {
+ newDefaults.interactions = angular.copy(userDefaults.interactions);
+ }
+
+ if (isDefined(userDefaults.renderer)) {
+ newDefaults.renderer = userDefaults.renderer;
+ }
+
+ if (isDefined(userDefaults.view)) {
+ newDefaults.view.maxZoom = userDefaults.view.maxZoom || newDefaults.view.maxZoom;
+ newDefaults.view.minZoom = userDefaults.view.minZoom || newDefaults.view.minZoom;
+ newDefaults.view.projection = userDefaults.view.projection || newDefaults.view.projection;
+ newDefaults.view.extent = userDefaults.view.extent || newDefaults.view.extent;
+ newDefaults.view.resolutions = userDefaults.view.resolutions || newDefaults.view.resolutions;
+ }
+
+ if (isDefined(userDefaults.styles)) {
+ newDefaults.styles = angular.extend(newDefaults.styles, userDefaults.styles);
+ }
+
+ if (isDefined(userDefaults.loadTilesWhileAnimating)) {
+ newDefaults.loadTilesWhileAnimating = userDefaults.loadTilesWhileAnimating;
+ }
+
+ if (isDefined(userDefaults.loadTilesWhileInteracting)) {
+ newDefaults.loadTilesWhileInteracting = userDefaults.loadTilesWhileInteracting;
+ }
+ }
+
+ defaults[scopeId] = newDefaults;
+ return newDefaults;
+ }
+ };
+}]);
+
+}));
\ No newline at end of file
diff --git a/dist/angular-openlayers-directive.min.js b/dist/angular-openlayers-directive.min.js
new file mode 100644
index 00000000..069ce9c0
--- /dev/null
+++ b/dist/angular-openlayers-directive.min.js
@@ -0,0 +1,32 @@
+/**!
+ * The MIT License
+ *
+ * Copyright (c) 2013 the angular-openlayers-directive Team, http://tombatossals.github.io/angular-openlayers-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-google-maps
+ * https://github.com/tombatossals/angular-openlayers-directive
+ *
+ * @authors https://github.com/tombatossals/angular-openlayers-directive/graphs/contributors
+ */
+
+/*! angular-openlayers-directive 06-03-2018 */
+!function(a,b){if("function"==typeof require&&"object"==typeof exports){var c=require("openlayers");exports.angularOpenlayersDirective=b(c)}else"function"==typeof define&&define.amd?define(["ol"],function(c){return a.angularOpenlayersDirective=b(c)}):a.angularOpenlayersDirective=b(a.ol)}(this,function(a){angular.module("openlayers-directive",["ngSanitize"]).directive("openlayers",["$log","$q","$compile","olHelpers","olMapDefaults","olData",function(b,c,d,e,f,g){return{restrict:"EA",transclude:!0,replace:!0,scope:{center:"=olCenter",defaults:"=olDefaults",view:"=olView",events:"=olEvents"},template:'',controller:["$scope",function(a){var b=c.defer();a.getMap=function(){return b.promise},a.setMap=function(a){b.resolve(a)},this.getOpenlayersScope=function(){return a}}],link:function(b,c,d){var h=e.isDefined,i=e.createLayer,j=e.setMapEvents,k=e.setViewEvents,l=e.createView,m=f.setDefaults(b);h(d.width)&&(isNaN(d.width)?c.css("width",d.width):c.css("width",d.width+"px")),h(d.height)&&(isNaN(d.height)?c.css("height",d.height):c.css("height",d.height+"px")),h(d.lat)&&(m.center.lat=parseFloat(d.lat)),h(d.lon)&&(m.center.lon=parseFloat(d.lon)),h(d.zoom)&&(m.center.zoom=parseFloat(d.zoom));var n=a.control.defaults(m.controls),o=a.interaction.defaults(m.interactions),p=l(m.view),q=new a.Map({target:c[0],controls:n,interactions:o,renderer:m.renderer,view:p,loadTilesWhileAnimating:m.loadTilesWhileAnimating,loadTilesWhileInteracting:m.loadTilesWhileInteracting});if(b.$on("$destroy",function(){g.resetMap(d.id),q.setTarget(null),q=null}),!d.customLayers){var r={type:"Tile",source:{type:"OSM"}},s=i(r,p.getProjection(),"default");q.addLayer(s),q.set("default",!0)}if(!h(d.olCenter)){var t=a.proj.transform([m.center.lon,m.center.lat],m.center.projection,p.getProjection());p.setCenter(t),p.setZoom(m.center.zoom)}j(m.events,q,b),k(m.events,q,b),b.setMap(q),g.setMap(q,d.id)}}}]),angular.module("openlayers-directive").directive("olCenter",["$log","$location","olMapDefaults","olHelpers",function(b,c,d,e){return{restrict:"A",scope:!1,replace:!1,require:"openlayers",link:function(f,g,h,i){var j=e.safeApply,k=e.isValidCenter,l=e.isDefined,m=e.isArray,n=e.isNumber,o=e.isSameCenterOnMap,p=e.setCenter,q=e.setZoom,r=i.getOpenlayersScope();r.getMap().then(function(f){var g=d.getDefaults(r),i=f.getView(),s=r.center;if(-1!==h.olCenter.search("-"))return b.error('[AngularJS - Openlayers] The "center" variable can\'t use a "-" on his key name: "'+h.center+'".'),void p(i,g.view.projection,g.center,f);l(s)||(s={}),k(s)||(b.warn("[AngularJS - Openlayers] invalid 'center'"),s.lat=g.center.lat,s.lon=g.center.lon,s.zoom=g.center.zoom,s.projection=g.center.projection),s.projection||("pixel"!==g.view.projection?s.projection=g.center.projection:s.projection="pixel"),n(s.zoom)||(s.zoom=1),p(i,g.view.projection,s,f),i.setZoom(s.zoom);if(!0===s.centerUrlHash){var t=function(){var a,b=c.search();if(l(b.c)){var d=b.c.split(":");3===d.length&&(a={lat:parseFloat(d[0]),lon:parseFloat(d[1]),zoom:parseInt(d[2],10)})}return a};t(),r.$on("$locationChangeSuccess",function(){var a=t();a&&!o(a,f)&&j(r,function(b){b.center.lat=a.lat,b.center.lon=a.lon,b.center.zoom=a.zoom})})}var u;r.$watchCollection("center",function(c){if(c){if(c.projection||(c.projection=g.center.projection),c.autodiscover)return u||(u=new a.Geolocation({projection:a.proj.get(c.projection)}),u.on("change",function(){if(c.autodiscover){var a=u.getPosition();j(r,function(b){b.center.lat=a[1],b.center.lon=a[0],b.center.zoom=12,b.center.autodiscover=!1,u.setTracking(!1)})}})),void u.setTracking(!0);k(c)||(b.warn("[AngularJS - Openlayers] invalid 'center'"),c=g.center);var d=i.getCenter();if(d)if("pixel"===g.view.projection||"pixel"===c.projection)i.setCenter(c.coord);else{var e=a.proj.transform(d,g.view.projection,c.projection);e[1]===c.lat&&e[0]===c.lon||p(i,g.view.projection,c,f)}i.getZoom()!==c.zoom&&q(i,c.zoom,f)}});var v=f.on("moveend",function(){j(r,function(b){if(l(b.center)){var d=f.getView().getCenter();if(b.center.zoom=i.getZoom(),"pixel"===g.view.projection||"pixel"===b.center.projection)return void(b.center.coord=d);if(b.center){var h=a.proj.transform(d,g.view.projection,b.center.projection);if(b.center.lat=h[1],b.center.lon=h[0],e.notifyCenterUrlHashChanged(r,b.center,c.search()),m(b.center.bounds)){var j=i.calculateExtent(f.getSize()),k=b.center.projection,n=g.view.projection;b.center.bounds=a.proj.transformExtent(j,n,k)}}}})});r.$on("$destroy",function(){a.Observable.unByKey(v)})})}}}]),angular.module("openlayers-directive").directive("olLayer",["$log","$q","olMapDefaults","olHelpers",function(a,b,c,d){return{restrict:"E",scope:{properties:"=olLayerProperties",onLayerCreated:"&"},replace:!1,require:"^openlayers",link:function(a,b,e,f){var g=d.isDefined,h=d.equals,i=f.getOpenlayersScope(),j=d.createLayer,k=d.setVectorLayerEvents,l=d.detectLayerType,m=d.createStyle,n=d.isBoolean,o=d.addLayerBeforeMarkers,p=d.isNumber,q=d.insertLayer,r=d.removeLayer,s=d.addLayerToGroup,t=d.removeLayerFromGroup,u=d.getGroup;i.getMap().then(function(b){var d,f=b.getView().getProjection(),v=c.setDefaults(i),w=b.getLayers();if(a.$on("$destroy",function(){a.properties.group?t(w,d,a.properties.group):r(w,d.index),b.removeLayer(d)}),g(a.properties))a.$watch("properties",function(c,e){if(g(c.source)&&g(c.source.type)){if(!g(c.visible))return void(c.visible=!0);if(!g(c.opacity))return void(c.opacity=1);var i,x,y;if(g(d)){var z=function(a){return function(b){return b!==a}}(d);if(g(e)&&!h(c.source,e.source)){var A=d.index;y=w,x=d.get("group"),x&&(y=u(w,x).getLayers()),y.removeAt(A),d=j(c,f,a.onLayerCreated),d.set("group",x),g(d)&&(q(y,A,d),"Vector"===l(c)&&k(v.events,b,a,c.name))}(g(e)&&c.opacity!==e.opacity||z(d))&&(p(c.opacity)||p(parseFloat(c.opacity)))&&d.setOpacity(c.opacity),g(c.index)&&c.index!==d.index&&(y=w,x=d.get("group"),x&&(y=u(w,x).getLayers()),r(y,d.index),q(y,c.index,d)),g(c.group)&&c.group!==e.group&&(t(w,d,e.group),s(w,d,c.group)),g(e)&&n(c.visible)&&(c.visible!==e.visible||z(d)||d.getVisible()!==c.visible)&&d.setVisible(c.visible),(g(c.style)&&!h(c.style,e.style)||z(d))&&(i=angular.isFunction(c.style)?c.style:m(c.style),d.setStyle&&angular.isFunction(d.setStyle)&&d.setStyle(i)),h(c.minResolution,e.minResolution)&&!z(d)||g(c.minResolution)&&d.setMinResolution(c.minResolution),h(c.maxResolution,e.maxResolution)&&!z(d)||g(c.maxResolution)&&d.setMaxResolution(c.maxResolution)}else d=j(c,f,a.onLayerCreated),g(c.group)?s(w,d,c.group):g(c.index)?q(w,c.index,d):o(w,d),"Vector"===l(c)&&k(v.events,b,a,c.name),n(c.visible)&&d.setVisible(c.visible),c.opacity&&d.setOpacity(c.opacity),angular.isArray(c.extent)&&d.setExtent(c.extent),c.style&&(i=angular.isFunction(c.style)?c.style:m(c.style),d.setStyle&&angular.isFunction(d.setStyle)&&d.setStyle(i)),c.minResolution&&d.setMinResolution(c.minResolution),c.maxResolution&&d.setMaxResolution(c.maxResolution)}},!0);else if(g(e.sourceType)&&g(e.sourceUrl)){var x={source:{url:e.sourceUrl,type:e.sourceType}};d=j(x,f,e.layerName,a.onLayerCreated),"Vector"===l(x)&&k(v.events,b,a,e.name),o(w,d)}})}}}]),angular.module("openlayers-directive").directive("olPath",["$log","$q","olMapDefaults","olHelpers",function(a,b,c,d){return{restrict:"E",scope:{properties:"=olGeomProperties",style:"=olStyle"},require:"^openlayers",replace:!0,template:'',link:function(a,b,e,f){var g=d.isDefined,h=d.createFeature,i=d.createOverlay,j=d.createVectorLayer,k=d.insertLayer,l=d.removeLayer,m=f.getOpenlayersScope();m.getMap().then(function(d){var f=c.getDefaults(m),n=f.view.projection,o=j(),p=d.getLayers();if(k(p,p.getLength(),o),a.$on("$destroy",function(){l(p,o.index)}),g(e.coords)){var q=e.proj||"EPSG:4326",r=JSON.parse(e.coords),s={type:"Polygon",coords:r,projection:q,style:a.style?a.style:f.styles.path},t=h(s,n);if(o.getSource().addFeature(t),e.message){a.message=e.message;var u=t.getGeometry().getExtent(),v=i(b,u);d.addOverlay(v)}}else;})}}}]),angular.module("openlayers-directive").directive("olView",["$log","$q","olData","olMapDefaults","olHelpers",function(b,c,d,e,f){return{restrict:"A",scope:!1,replace:!1,require:"openlayers",link:function(b,c,d,g){var h=g.getOpenlayersScope(),i=f.isNumber,j=f.safeApply,k=f.createView;h.getMap().then(function(b){var c=e.getDefaults(h),d=h.view;d.projection||(d.projection=c.view.projection),d.maxZoom||(d.maxZoom=c.view.maxZoom),d.minZoom||(d.minZoom=c.view.minZoom),d.rotation||(d.rotation=c.view.rotation);var f=k(d);b.setView(f),h.$watchCollection("view",function(a){i(a.rotation)&&f.setRotation(a.rotation)});var g=f.on("change:rotation",function(){j(h,function(a){a.view.rotation=b.getView().getRotation()})});h.$on("$destroy",function(){a.Observable.unByKey(g)})})}}}]),angular.module("openlayers-directive").directive("olControl",["$log","$q","olData","olMapDefaults","olHelpers",function(a,b,c,d,e){return{restrict:"E",scope:{properties:"=olControlProperties"},replace:!1,require:"^openlayers",link:function(a,b,c,d){var f,g,h=e.isDefined,i=d.getOpenlayersScope(),j=e.getControlClasses,k=j();i.getMap().then(function(b){function d(a){a&&a.control?(f&&b.removeControl(f),f=a.control,b.addControl(f)):c.name&&(h(a)&&(g=a),f&&b.removeControl(f),f=new k[c.name](g),b.addControl(f))}a.$on("$destroy",function(){b.removeControl(f)}),a.$watch("properties",function(a){h(a)&&d(a)}),d(a.properties)})}}}]),angular.module("openlayers-directive").directive("olMarker",["$log","$q","olMapDefaults","olHelpers",function(b,c,d,e){var f=function(){return{projection:"EPSG:4326",lat:0,lon:0,coord:[],show:!0,showOnMouseOver:!1,showOnMouseClick:!1,keepOneOverlayVisible:!1}},g=function(){function a(a){return b.map(function(a){return a.map}).indexOf(a)}var b=[];return{getInst:function(c,d){var f=a(d);if(-1===f){var g=e.createVectorLayer();g.set("markers",!0),d.addLayer(g),b.push({map:d,markerLayer:g,instScopes:[]}),f=b.length-1}return b[f].instScopes.push(c),b[f].markerLayer},deregisterScope:function(c,d){var e=a(d);if(-1===e)throw Error("This map has no markers");var f=b[e].instScopes,g=f.indexOf(c);if(-1===g)throw Error("Scope wan't registered");f.splice(g,1),f.length||(d.removeLayer(b[e].markerLayer),delete b[e].markerLayer,delete b[e])}}}();return{restrict:"E",scope:{lat:"=lat",lon:"=lon",label:"=label",properties:"=olMarkerProperties",style:"=olStyle"},transclude:!0,require:"^openlayers",replace:!0,template:'',link:function(c,h,i,j){var k=e.isDefined,l=j.getOpenlayersScope(),m=e.createFeature,n=e.createOverlay,o=h.find("ng-transclude").children().length>0;l.getMap().then(function(e){function j(){c.properties&&(e.getViewport().removeEventListener("mousemove",c.properties.handleInteraction),e.getViewport().removeEventListener("click",c.properties.handleTapInteraction),e.getViewport().querySelector("canvas.ol-unselectable").removeEventListener("touchend",c.properties.handleTapInteraction),e.getViewport().removeEventListener("mousemove",c.properties.showAtLeastOneOverlay),e.getViewport().removeEventListener("click",c.properties.removeAllOverlays),e.getViewport().querySelector("canvas.ol-unselectable").removeEventListener("touchmove",c.properties.activateCooldown))}var p,q,r,s=g.getInst(c,e),t=f(),u=d.getDefaults(l),v=u.view.projection,w=null,x=null;if(c.handleDrag=function(b){var d=b.coordinate,f=e.getView().getProjection().getCode();if(d="pixel"===f?d.map(function(a){return parseInt(a,10)}):a.proj.transform(d,f,"EPSG:4326"),"pointerdown"===b.type){var g=e.forEachFeatureAtPixel(b.pixel,function(a){return a});if(!(x=g?g.get("marker"):null)||!x.draggable)return void(x=null);e.getTarget().style.cursor="pointer",w="pixel"===f?[d[0]-x.coord[0],d[1]-x.coord[1]]:[d[0]-x.lon,d[1]-x.lat],b.preventDefault()}else w&&x&&("pointerup"===b.type?(e.getTarget().style.cursor="",w=null,x=null,b.preventDefault()):"pointerdrag"===b.type&&(b.preventDefault(),c.$apply(function(){"pixel"===f?(x.coord[0]=d[0]-w[0],x.coord[1]=d[1]-w[1]):(x.lon=d[0]-w[0],x.lat=d[1]-w[1])})))},e.on("pointerdown",c.handleDrag),e.on("pointerup",c.handleDrag),e.on("pointerdrag",c.handleDrag),c.$on("$destroy",function(){s.getSource().removeFeature(r),k(p)&&e.removeOverlay(p),g.deregisterScope(c,e),e.un("pointerdown",c.handleDrag),e.un("pointerup",c.handleDrag),e.un("pointerdrag",c.handleDrag),j()}),!k(c.properties))return t.lat=c.lat?c.lat:t.lat,t.lon=c.lon?c.lon:t.lon,t.message=i.message,t.style=c.style?c.style:u.styles.marker,r=m(t,v),k(r)||b.error("[AngularJS - Openlayers] Received invalid data on the marker."),r.set("marker",c),s.getSource().addFeature(r),void((t.message||o)&&(c.message=i.message,q=a.proj.transform([t.lon,t.lat],t.projection,v),p=n(h,q),e.addOverlay(p)));c.$watch("properties",function(d){if(j(),d.handleInteraction=function(b){var c=!1;if(i.hasOwnProperty("ngClick")&&(c=!0),!d.label.show||c){var f=!1,g=e.getEventPixel(b),j=e.forEachFeatureAtPixel(g,function(a){return a}),l=!1;if(j===r){if(l=!0,f=!0,c&&("click"===b.type||"touchend"===b.type))return h.triggerHandler("click"),b.preventDefault(),void b.stopPropagation();k(p)||(q="pixel"===t.projection?d.coord:a.proj.transform([d.lon,d.lat],t.projection,v),p=n(h,q),e.addOverlay(p)),e.getTarget().style.cursor="pointer"}!f&&p&&(l=!0,e.removeOverlay(p),p=void 0,e.getTarget().style.cursor=""),l&&b.preventDefault()}},d.handleTapInteraction=function(){var a,b=!1;return d.activateCooldown=function(){b=!0,a&&clearTimeout(a),a=setTimeout(function(){b=!1,a=null},500)},d.activateCooldown&&e.getViewport().querySelector("canvas.ol-unselectable").removeEventListener("touchmove",d.activateCooldown),e.getViewport().querySelector("canvas.ol-unselectable").addEventListener("touchmove",d.activateCooldown),function(){b||(d.handleInteraction.apply(null,arguments),d.activateCooldown())}}(),d.showAtLeastOneOverlay=function(b){if(!d.label.show){var c=!1,f=e.getEventPixel(b),g=e.forEachFeatureAtPixel(f,function(a){return a}),i=!1;g===r&&(i=!0,c=!0,k(p)||(q="pixel"===t.projection?t.coord:a.proj.transform([t.lon,t.lat],t.projection,v),p=n(h,q),angular.forEach(e.getOverlays(),function(a){e.removeOverlay(a)}),e.addOverlay(p)),e.getTarget().style.cursor="pointer"),!c&&p&&(i=!0,p=void 0,e.getTarget().style.cursor=""),i&&b.preventDefault()}},d.removeAllOverlays=function(a){angular.forEach(e.getOverlays(),function(a){e.removeOverlay(a)}),a.preventDefault()},k(r)){var f;if(f="pixel"===d.projection?d.coord:a.proj.transform([d.lon,d.lat],t.projection,e.getView().getProjection()),!angular.equals(r.getGeometry().getCoordinates(),f)){var g=new a.geom.Point(f);r.setGeometry(g)}}else t.projection=d.projection?d.projection:t.projection,t.coord=d.coord?d.coord:t.coord,t.lat=d.lat?d.lat:t.lat,t.lon=d.lon?d.lon:t.lon,k(d.style)?t.style=d.style:t.style=u.styles.marker,r=m(t,v),k(r)||b.error("[AngularJS - Openlayers] Received invalid data on the marker."),r.set("marker",d),s.getSource().addFeature(r);k(p)&&e.removeOverlay(p),k(d.label)&&(c.message=d.label.message,(o||k(c.message)&&0!==c.message.length)&&(d.label&&!0===d.label.show&&(q="pixel"===t.projection?t.coord:a.proj.transform([d.lon,d.lat],t.projection,v),p=n(h,q),e.addOverlay(p)),p&&d.label&&!1===d.label.show&&(e.removeOverlay(p),p=void 0),d.label&&!1===d.label.show&&d.label.showOnMouseOver&&e.getViewport().addEventListener("mousemove",d.handleInteraction),(d.label&&!1===d.label.show&&d.label.showOnMouseClick||i.hasOwnProperty("ngClick"))&&(e.getViewport().addEventListener("click",d.handleTapInteraction),e.getViewport().querySelector("canvas.ol-unselectable").addEventListener("touchend",d.handleTapInteraction)),d.label&&!1===d.label.show&&d.label.keepOneOverlayVisible&&(e.getViewport().addEventListener("mousemove",d.showAtLeastOneOverlay),e.getViewport().addEventListener("click",d.removeAllOverlays))))},!0)})}}}]),angular.module("openlayers-directive").service("olData",["$log","$q",function(a,b){function c(b,c){var d,e;if(angular.isDefined(c))d=c;else if(1===Object.keys(b).length)for(e in b)b.hasOwnProperty(e)&&(d=e);else 0===Object.keys(b).length?d="main":a.error("[AngularJS - Openlayers] - You have more than 1 map on the DOM, you must provide the map ID to the olData.getXXX call");return d}var d={},e=function(a,b){a[c(a,b)].resolvedDefer=!0},f=function(a,d){var e,f=c(a,d);return angular.isDefined(a[f])&&!0!==a[f].resolvedDefer?e=a[f].defer:(e=b.defer(),a[f]={defer:e,resolvedDefer:!1}),e},g=function(a,b){var d=c(a,b);return angular.isDefined(a[d])&&!1!==a[d].resolvedDefer?a[d].defer:f(a,b)};this.setMap=function(a,b){f(d,b).resolve(a),e(d,b)},this.getMap=function(a){return g(d,a).promise},this.resetMap=function(a){angular.isDefined(d[a])&&delete d[a]}}]),angular.module("openlayers-directive").factory("olHelpers",["$q","$log","$http",function(b,c,d){var e=function(a){return angular.isDefined(a)},f=function(a){return angular.isDefined(a)&&null!==a},g=function(a,b,c){a.on(b,function(d){var e=d.coordinate,f=a.getView().getProjection().getCode();"pixel"===f&&(e=e.map(function(a){return parseInt(a,10)})),c.$emit("openlayers.map."+b,{coord:e,projection:f,event:d})})},h=["Road","Aerial","AerialWithLabels","collinsBart","ordnanceSurvey"],i=function(){return{attribution:a.control.Attribution,fullscreen:a.control.FullScreen,mouseposition:a.control.MousePosition,overviewmap:a.control.OverviewMap,rotate:a.control.Rotate,scaleline:a.control.ScaleLine,zoom:a.control.Zoom,zoomslider:a.control.ZoomSlider,zoomtoextent:a.control.ZoomToExtent}},j=["osm","sat","hyb"],k=["World_Imagery","World_Street_Map","World_Topo_Map","World_Physical_Map","World_Terrain_Base","Ocean_Basemap","NatGeo_World_Map"],l={style:a.style.Style,fill:a.style.Fill,stroke:a.style.Stroke,circle:a.style.Circle,icon:a.style.Icon,image:a.style.Image,regularshape:a.style.RegularShape,text:a.style.Text},m=function(a,b){return b&&a instanceof b?a:b?new b(a):a},n=function a(b,c){var d;if(c?d=b[c]:(c="style",d=b),"style"===c&&b instanceof Function)return b;if(!(d instanceof Object))return d;var e;if("[object Object]"===Object.prototype.toString.call(d)){e={};var f=l[c];if(f&&d instanceof f)return d;Object.getOwnPropertyNames(d).forEach(function(b,g,h){var i=l[b];if(f&&i&&i.prototype instanceof l[c])return console.assert(1===h.length,"Extra parameters for "+c),e=a(d,b),m(e,i);e[b]=a(d,b),"text"!==b&&"string"!=typeof e[b]&&(e[b]=m(e[b],l[b]))})}else e=d;return m(e,l[c])},o=function(a){if(a.type)return a.type;switch(a.source.type){case"ImageWMS":case"ImageStatic":return"Image";case"GeoJSON":case"JSONP":case"TopoJSON":case"KML":case"WKT":return"Vector";case"TileVector":case"MVT":return"TileVector";default:return"Tile"}},p=function(b){var d;switch(b.projection){case"pixel":if(!e(b.extent))return void c.error("[AngularJS - Openlayers] - You must provide the extent of the image if using pixel projection");d=new a.proj.Projection({code:"pixel",units:"pixels",extent:b.extent});break;default:d=new a.proj.get(b.projection)}return d},q=function(a){return-1!==["watercolor","terrain","toner"].indexOf(a)},r=function(b,f){var g,i,l,m=new a.format.GeoJSON;switch(b.type){case"MapBox":if(!b.mapId||!b.accessToken)return void c.error("[AngularJS - Openlayers] - MapBox layer requires the map id and the access token");l="https://api.tiles.mapbox.com/v4/"+b.mapId+"/{z}/{x}/{y}.png?access_token="+b.accessToken,i=window.devicePixelRatio,i>1&&(l=l.replace(".png","@2x.png")),g=new a.source.XYZ({url:l,tileLoadFunction:b.tileLoadFunction,attributions:t(b),tilePixelRatio:i>1?2:1,wrapX:void 0===b.wrapX||b.wrapX});break;case"MapBoxStudio":if(!b.mapId||!b.accessToken||!b.userId)return void c.error("[AngularJS - Openlayers] - MapBox Studio layer requires the map id, user id and the access token");l="https://api.mapbox.com/styles/v1/"+b.userId+"/"+b.mapId+"/tiles/{z}/{x}/{y}?access_token="+b.accessToken,i=window.devicePixelRatio,i>1&&(l=l.replace("{y}?access_token","{y}@2x?access_token")),g=new a.source.XYZ({url:l,tileLoadFunction:b.tileLoadFunction,attributions:t(b),tilePixelRatio:i>1?2:1,tileSize:b.tileSize||[512,512],wrapX:void 0===b.wrapX||b.wrapX});break;case"MVT":if(!b.url)return void c.error("[AngularJS - Openlayers] - MVT layer requires the source url");g=new a.source.VectorTile({attributions:b.attributions||"",format:new a.format.MVT,tileGrid:a.tilegrid.createXYZ({maxZoom:b.maxZoom||22}),tilePixelRatio:b.tilePixelRatio||16,url:b.url});break;case"ImageWMS":b.url&&b.params||c.error("[AngularJS - Openlayers] - ImageWMS Layer needs valid server url and params properties"),g=new a.source.ImageWMS({url:b.url,imageLoadFunction:b.imageLoadFunction,attributions:t(b),crossOrigin:void 0===b.crossOrigin?"anonymous":b.crossOrigin,params:s(b.params),ratio:b.ratio});break;case"TileWMS":(b.url||b.urls)&&b.params||c.error("[AngularJS - Openlayers] - TileWMS Layer needs valid url (or urls) and params properties");var n={tileLoadFunction:b.tileLoadFunction,crossOrigin:void 0===b.crossOrigin?"anonymous":b.crossOrigin,params:s(b.params),attributions:t(b),wrapX:void 0===b.wrapX||b.wrapX};b.serverType&&(n.serverType=b.serverType),b.url&&(n.url=b.url),b.urls&&(n.urls=b.urls),g=new a.source.TileWMS(n);break;case"WMTS":(b.url||b.urls)&&b.tileGrid||c.error("[AngularJS - Openlayers] - WMTS Layer needs valid url (or urls) and tileGrid properties");var o={tileLoadFunction:b.tileLoadFunction,projection:f,layer:b.layer,attributions:t(b),matrixSet:"undefined"===b.matrixSet?f:b.matrixSet,format:"undefined"===b.format?"image/jpeg":b.format,requestEncoding:"undefined"===b.requestEncoding?"KVP":b.requestEncoding,tileGrid:new a.tilegrid.WMTS({origin:b.tileGrid.origin,resolutions:b.tileGrid.resolutions,matrixIds:b.tileGrid.matrixIds}),style:"undefined"===b.style?"normal":b.style,wrapX:void 0===b.wrapX||b.wrapX};e(b.url)&&(o.url=b.url),e(b.urls)&&(o.urls=b.urls),g=new a.source.WMTS(o);break;case"OSM":g=new a.source.OSM({tileLoadFunction:b.tileLoadFunction,attributions:t(b),wrapX:void 0===b.wrapX||b.wrapX}),b.url&&g.setUrl(b.url);break;case"BingMaps":if(!b.key)return void c.error("[AngularJS - Openlayers] - You need an API key to show the Bing Maps.");var p={key:b.key,tileLoadFunction:b.tileLoadFunction,attributions:t(b),imagerySet:b.imagerySet?b.imagerySet:h[0],culture:b.culture,wrapX:void 0===b.wrapX||b.wrapX};b.maxZoom&&(p.maxZoom=b.maxZoom),g=new a.source.BingMaps(p);break;case"MapQuest":if(!b.layer||-1===j.indexOf(b.layer))return void c.error("[AngularJS - Openlayers] - MapQuest layers needs a valid 'layer' property.");g=new a.source.MapQuest({attributions:t(b),layer:b.layer,wrapX:void 0===b.wrapX||b.wrapX});break;case"EsriBaseMaps":if(!b.layer||-1===k.indexOf(b.layer))return void c.error("[AngularJS - Openlayers] - ESRI layers needs a valid 'layer' property.");var r="https://services.arcgisonline.com/ArcGIS/rest/services/",u=r+b.layer+"/MapServer/tile/{z}/{y}/{x}";g=new a.source.XYZ({attributions:t(b),tileLoadFunction:b.tileLoadFunction,url:u,wrapX:void 0===b.wrapX||b.wrapX});break;case"TileArcGISRest":b.url||c.error("[AngularJS - Openlayers] - TileArcGISRest Layer needs valid url"),g=new a.source.TileArcGISRest({attributions:t(b),tileLoadFunction:b.tileLoadFunction,url:b.url,wrapX:void 0===b.wrapX||b.wrapX});break;case"GeoJSON":if(!b.geojson&&!b.url)return void c.error("[AngularJS - Openlayers] - You need a geojson property to add a GeoJSON layer.");if(e(b.url))g=new a.source.Vector({format:new a.format.GeoJSON,url:b.url});else{g=new a.source.Vector;var v,w=f;v=e(b.geojson.projection)?new a.proj.get(b.geojson.projection):f;var x=m.readFeatures(b.geojson.object,{featureProjection:w.getCode(),dataProjection:v.getCode()});g.addFeatures(x)}break;case"WKT":if(!b.wkt&&!b.wkt.data)return void c.error("[AngularJS - Openlayers] - You need a WKT property to add a WKT format vector layer.");g=new a.source.Vector;var y,z=new a.format.WKT;y=e(b.wkt.projection)?new a.proj.get(b.wkt.projection):f;var A=z.readFeatures(b.wkt.data,{featureProjection:f.getCode(),dataProjection:y.getCode()});g.addFeatures(A);break;case"JSONP":if(!b.url)return void c.error("[AngularJS - Openlayers] - You need an url properly configured to add a JSONP layer.");e(b.url)&&(g=new a.source.ServerVector({format:m,loader:function(){var a=b.url+"&outputFormat=text/javascript&format_options=callback:JSON_CALLBACK";d.jsonp(a,{cache:b.cache}).success(function(a){g.addFeatures(m.readFeatures(a))}).error(function(a){c(a)})},projection:f}));break;case"TopoJSON":if(!b.topojson&&!b.url)return void c.error("[AngularJS - Openlayers] - You need a topojson property to add a TopoJSON layer.");g=b.url?new a.source.Vector({format:new a.format.TopoJSON,url:b.url}):new a.source.Vector(angular.extend(b.topojson,{format:new a.format.TopoJSON}));break;case"TileJSON":g=new a.source.TileJSON({url:b.url,attributions:t(b),tileLoadFunction:b.tileLoadFunction,crossOrigin:"anonymous",wrapX:void 0===b.wrapX||b.wrapX});break;case"TileVector":b.url&&b.format||c.error("[AngularJS - Openlayers] - TileVector Layer needs valid url and format properties"),g=new a.source.VectorTile({url:b.url,projection:f,attributions:t(b),tileLoadFunction:b.tileLoadFunction,format:b.format,tileGrid:new a.tilegrid.createXYZ({maxZoom:b.maxZoom||19}),wrapX:void 0===b.wrapX||b.wrapX});break;case"TileTMS":b.url&&b.tileGrid||c.error("[AngularJS - Openlayers] - TileTMS Layer needs valid url and tileGrid properties"),g=new a.source.TileImage({url:b.url,maxExtent:b.maxExtent,attributions:t(b),tileLoadFunction:b.tileLoadFunction,tileGrid:new a.tilegrid.TileGrid({origin:b.tileGrid.origin,resolutions:b.tileGrid.resolutions}),tileUrlFunction:function(a){var c=a[0],d=a[1],e=a[2];return d<0||e<0?"":b.url+c+"/"+d+"/"+e+".png"},wrapX:void 0===b.wrapX||b.wrapX});break;case"TileImage":g=new a.source.TileImage({url:b.url,attributions:t(b),tileLoadFunction:b.tileLoadFunction,tileGrid:new a.tilegrid.TileGrid({origin:b.tileGrid.origin,resolutions:b.tileGrid.resolutions}),tileUrlFunction:function(a){var c=a[0],d=a[1],e=-a[2]-1;return b.url.replace("{z}",c.toString()).replace("{x}",d.toString()).replace("{y}",e.toString())},wrapX:void 0===b.wrapX||b.wrapX});break;case"KML":var B=b.extractStyles||!1;g=new a.source.Vector({url:b.url,format:new a.format.KML,radius:b.radius,extractStyles:B});break;case"Stamen":if(!b.layer||!q(b.layer))return void c.error("[AngularJS - Openlayers] - You need a valid Stamen layer.");g=new a.source.Stamen({tileLoadFunction:b.tileLoadFunction,layer:b.layer,wrapX:void 0===b.wrapX||b.wrapX});break;case"ImageStatic":if(!b.url||!angular.isArray(b.imageSize)||2!==b.imageSize.length)return void c.error("[AngularJS - Openlayers] - You need a image URL to create a ImageStatic layer.");g=new a.source.ImageStatic({url:b.url,attributions:t(b),imageSize:b.imageSize,projection:f,imageExtent:b.imageExtent?b.imageExtent:f.getExtent(),imageLoadFunction:b.imageLoadFunction});break;case"XYZ":b.url||b.urls||b.tileUrlFunction||c.error("[AngularJS - Openlayers] - XYZ Layer needs valid url(s) or tileUrlFunction properties"),g=new a.source.XYZ({url:b.url,urls:b.urls,attributions:t(b),minZoom:b.minZoom,maxZoom:b.maxZoom,projection:b.projection,tileUrlFunction:b.tileUrlFunction,tileLoadFunction:b.tileLoadFunction,wrapX:void 0===b.wrapX||b.wrapX});break;case"Zoomify":b.url&&angular.isArray(b.imageSize)&&2===b.imageSize.length||c.error("[AngularJS - Openlayers] - Zoomify Layer needs valid url and imageSize properties"),g=new a.source.Zoomify({url:b.url,size:b.imageSize,wrapX:void 0===b.wrapX||b.wrapX})}return g||c.warn('[AngularJS - Openlayers] - No source could be found for type "'+b.type+'"'),g},s=function(a){var b=a;if(a&&"object"==typeof a){b="[object Array]"===Object.prototype.toString.call(a)?[]:{};for(var c in a)b[c]=s(a[c])}return b},t=function(b){var c=[];if(e(b.attribution))!1!==b.attribution&&c.unshift(new a.Attribution({html:b.attribution}));else{var d=u(b);d&&c.unshift(d)}return c},u=function(b){if(b&&b.type){var c=a.source[b.type];if(c)for(var d in c)if(c.hasOwnProperty(d)&&d.toLowerCase().indexOf("attribution")>-1)return a.source[b.type][d]}return null},v=function(b){var c=new a.layer.Group;return c.set("name",b),c},w=function(b,c){var d;return angular.forEach(b,function(b){if(b instanceof a.layer.Group&&b.get("name")===c)return void(d=b)}),d},x=function(a,b){for(var c,d=0;d',controller:["$scope",function(a){var b=c.defer();a.getMap=function(){return b.promise},a.setMap=function(a){b.resolve(a)},this.getOpenlayersScope=function(){return a}}],link:function(b,c,d){var h=e.isDefined,i=e.createLayer,j=e.setMapEvents,k=e.setViewEvents,l=e.createView,m=f.setDefaults(b);h(d.width)&&(isNaN(d.width)?c.css("width",d.width):c.css("width",d.width+"px")),h(d.height)&&(isNaN(d.height)?c.css("height",d.height):c.css("height",d.height+"px")),h(d.lat)&&(m.center.lat=parseFloat(d.lat)),h(d.lon)&&(m.center.lon=parseFloat(d.lon)),h(d.zoom)&&(m.center.zoom=parseFloat(d.zoom));var n=a.control.defaults(m.controls),o=a.interaction.defaults(m.interactions),p=l(m.view),q=new a.Map({target:c[0],controls:n,interactions:o,renderer:m.renderer,view:p,loadTilesWhileAnimating:m.loadTilesWhileAnimating,loadTilesWhileInteracting:m.loadTilesWhileInteracting});if(b.$on("$destroy",function(){g.resetMap(d.id),q.setTarget(null),q=null}),!d.customLayers){var r={type:"Tile",source:{type:"OSM"}},s=i(r,p.getProjection(),"default");q.addLayer(s),q.set("default",!0)}if(!h(d.olCenter)){var t=a.proj.transform([m.center.lon,m.center.lat],m.center.projection,p.getProjection());p.setCenter(t),p.setZoom(m.center.zoom)}j(m.events,q,b),k(m.events,q,b),b.setMap(q),g.setMap(q,d.id)}}}]),angular.module("openlayers-directive").directive("olCenter",["$log","$location","olMapDefaults","olHelpers",function(b,c,d,e){return{restrict:"A",scope:!1,replace:!1,require:"openlayers",link:function(f,g,h,i){var j=e.safeApply,k=e.isValidCenter,l=e.isDefined,m=e.isArray,n=e.isNumber,o=e.isSameCenterOnMap,p=e.setCenter,q=e.setZoom,r=i.getOpenlayersScope();r.getMap().then(function(f){var g=d.getDefaults(r),i=f.getView(),s=r.center;if(-1!==h.olCenter.search("-"))return b.error('[AngularJS - Openlayers] The "center" variable can\'t use a "-" on his key name: "'+h.center+'".'),void p(i,g.view.projection,g.center,f);l(s)||(s={}),k(s)||(b.warn("[AngularJS - Openlayers] invalid 'center'"),s.lat=g.center.lat,s.lon=g.center.lon,s.zoom=g.center.zoom,s.projection=g.center.projection),s.projection||("pixel"!==g.view.projection?s.projection=g.center.projection:s.projection="pixel"),n(s.zoom)||(s.zoom=1),p(i,g.view.projection,s,f),i.setZoom(s.zoom);if(!0===s.centerUrlHash){var t=function(){var a,b=c.search();if(l(b.c)){var d=b.c.split(":");3===d.length&&(a={lat:parseFloat(d[0]),lon:parseFloat(d[1]),zoom:parseInt(d[2],10)})}return a};t(),r.$on("$locationChangeSuccess",function(){var a=t();a&&!o(a,f)&&j(r,function(b){b.center.lat=a.lat,b.center.lon=a.lon,b.center.zoom=a.zoom})})}var u;r.$watchCollection("center",function(c){if(c){if(c.projection||(c.projection=g.center.projection),c.autodiscover)return u||(u=new a.Geolocation({projection:a.proj.get(c.projection)}),u.on("change",function(){if(c.autodiscover){var a=u.getPosition();j(r,function(b){b.center.lat=a[1],b.center.lon=a[0],b.center.zoom=12,b.center.autodiscover=!1,u.setTracking(!1)})}})),void u.setTracking(!0);k(c)||(b.warn("[AngularJS - Openlayers] invalid 'center'"),c=g.center);var d=i.getCenter();if(d)if("pixel"===g.view.projection||"pixel"===c.projection)i.setCenter(c.coord);else{var e=a.proj.transform(d,g.view.projection,c.projection);e[1]===c.lat&&e[0]===c.lon||p(i,g.view.projection,c,f)}i.getZoom()!==c.zoom&&q(i,c.zoom,f)}});var v=f.on("moveend",function(){j(r,function(b){if(l(b.center)){var d=f.getView().getCenter();if(b.center.zoom=i.getZoom(),"pixel"===g.view.projection||"pixel"===b.center.projection)return void(b.center.coord=d);if(b.center){var h=a.proj.transform(d,g.view.projection,b.center.projection);if(b.center.lat=h[1],b.center.lon=h[0],e.notifyCenterUrlHashChanged(r,b.center,c.search()),m(b.center.bounds)){var j=i.calculateExtent(f.getSize()),k=b.center.projection,n=g.view.projection;b.center.bounds=a.proj.transformExtent(j,n,k)}}}})});r.$on("$destroy",function(){a.Observable.unByKey(v)})})}}}]),angular.module("openlayers-directive").directive("olLayer",["$log","$q","olMapDefaults","olHelpers",function(a,b,c,d){return{restrict:"E",scope:{properties:"=olLayerProperties",onLayerCreated:"&"},replace:!1,require:"^openlayers",link:function(a,b,e,f){var g=d.isDefined,h=d.equals,i=f.getOpenlayersScope(),j=d.createLayer,k=d.setVectorLayerEvents,l=d.detectLayerType,m=d.createStyle,n=d.isBoolean,o=d.addLayerBeforeMarkers,p=d.isNumber,q=d.insertLayer,r=d.removeLayer,s=d.addLayerToGroup,t=d.removeLayerFromGroup,u=d.getGroup;i.getMap().then(function(b){var d,f=b.getView().getProjection(),v=c.setDefaults(i),w=b.getLayers();if(a.$on("$destroy",function(){a.properties.group?t(w,d,a.properties.group):r(w,d.index),b.removeLayer(d)}),g(a.properties))a.$watch("properties",function(c,e){if(g(c.source)&&g(c.source.type)){if(!g(c.visible))return void(c.visible=!0);if(!g(c.opacity))return void(c.opacity=1);var i,x,y;if(g(d)){var z=function(a){return function(b){return b!==a}}(d);if(g(e)&&!h(c.source,e.source)){var A=d.index;y=w,x=d.get("group"),x&&(y=u(w,x).getLayers()),y.removeAt(A),d=j(c,f,a.onLayerCreated),d.set("group",x),g(d)&&(q(y,A,d),"Vector"===l(c)&&k(v.events,b,a,c.name))}(g(e)&&c.opacity!==e.opacity||z(d))&&(p(c.opacity)||p(parseFloat(c.opacity)))&&d.setOpacity(c.opacity),g(c.index)&&c.index!==d.index&&(y=w,x=d.get("group"),x&&(y=u(w,x).getLayers()),r(y,d.index),q(y,c.index,d)),g(c.group)&&c.group!==e.group&&(t(w,d,e.group),s(w,d,c.group)),g(e)&&n(c.visible)&&(c.visible!==e.visible||z(d)||d.getVisible()!==c.visible)&&d.setVisible(c.visible),(g(c.style)&&!h(c.style,e.style)||z(d))&&(i=angular.isFunction(c.style)?c.style:m(c.style),d.setStyle&&angular.isFunction(d.setStyle)&&d.setStyle(i)),h(c.minResolution,e.minResolution)&&!z(d)||g(c.minResolution)&&d.setMinResolution(c.minResolution),h(c.maxResolution,e.maxResolution)&&!z(d)||g(c.maxResolution)&&d.setMaxResolution(c.maxResolution)}else d=j(c,f,a.onLayerCreated),g(c.group)?s(w,d,c.group):g(c.index)?q(w,c.index,d):o(w,d),"Vector"===l(c)&&k(v.events,b,a,c.name),n(c.visible)&&d.setVisible(c.visible),c.opacity&&d.setOpacity(c.opacity),angular.isArray(c.extent)&&d.setExtent(c.extent),c.style&&(i=angular.isFunction(c.style)?c.style:m(c.style),d.setStyle&&angular.isFunction(d.setStyle)&&d.setStyle(i)),c.minResolution&&d.setMinResolution(c.minResolution),c.maxResolution&&d.setMaxResolution(c.maxResolution)}},!0);else if(g(e.sourceType)&&g(e.sourceUrl)){var x={source:{url:e.sourceUrl,type:e.sourceType}};d=j(x,f,e.layerName,a.onLayerCreated),"Vector"===l(x)&&k(v.events,b,a,e.name),o(w,d)}})}}}]),angular.module("openlayers-directive").directive("olPath",["$log","$q","olMapDefaults","olHelpers",function(a,b,c,d){return{restrict:"E",scope:{properties:"=olGeomProperties",style:"=olStyle"},require:"^openlayers",replace:!0,template:'',link:function(a,b,e,f){var g=d.isDefined,h=d.createFeature,i=d.createOverlay,j=d.createVectorLayer,k=d.insertLayer,l=d.removeLayer,m=f.getOpenlayersScope();m.getMap().then(function(d){var f=c.getDefaults(m),n=f.view.projection,o=j(),p=d.getLayers();if(k(p,p.getLength(),o),a.$on("$destroy",function(){l(p,o.index)}),g(e.coords)){var q=e.proj||"EPSG:4326",r=JSON.parse(e.coords),s={type:"Polygon",coords:r,projection:q,style:a.style?a.style:f.styles.path},t=h(s,n);if(o.getSource().addFeature(t),e.message){a.message=e.message;var u=t.getGeometry().getExtent(),v=i(b,u);d.addOverlay(v)}}else;})}}}]),angular.module("openlayers-directive").directive("olView",["$log","$q","olData","olMapDefaults","olHelpers",function(b,c,d,e,f){return{restrict:"A",scope:!1,replace:!1,require:"openlayers",link:function(b,c,d,g){var h=g.getOpenlayersScope(),i=f.isNumber,j=f.safeApply,k=f.createView;h.getMap().then(function(b){var c=e.getDefaults(h),d=h.view;d.projection||(d.projection=c.view.projection),d.maxZoom||(d.maxZoom=c.view.maxZoom),d.minZoom||(d.minZoom=c.view.minZoom),d.rotation||(d.rotation=c.view.rotation);var f=k(d);b.setView(f),h.$watchCollection("view",function(a){i(a.rotation)&&f.setRotation(a.rotation)});var g=f.on("change:rotation",function(){j(h,function(a){a.view.rotation=b.getView().getRotation()})});h.$on("$destroy",function(){a.Observable.unByKey(g)})})}}}]),angular.module("openlayers-directive").directive("olControl",["$log","$q","olData","olMapDefaults","olHelpers",function(a,b,c,d,e){return{restrict:"E",scope:{properties:"=olControlProperties"},replace:!1,require:"^openlayers",link:function(a,b,c,d){var f,g,h=e.isDefined,i=d.getOpenlayersScope(),j=e.getControlClasses,k=j();i.getMap().then(function(b){function d(a){a&&a.control?(f&&b.removeControl(f),f=a.control,b.addControl(f)):c.name&&(h(a)&&(g=a),f&&b.removeControl(f),f=new k[c.name](g),b.addControl(f))}a.$on("$destroy",function(){b.removeControl(f)}),a.$watch("properties",function(a){h(a)&&d(a)}),d(a.properties)})}}}]),angular.module("openlayers-directive").directive("olMarker",["$log","$q","olMapDefaults","olHelpers",function(b,c,d,e){var f=function(){return{projection:"EPSG:4326",lat:0,lon:0,coord:[],show:!0,showOnMouseOver:!1,showOnMouseClick:!1,keepOneOverlayVisible:!1}},g=function(){function a(a){return b.map(function(a){return a.map}).indexOf(a)}var b=[];return{getInst:function(c,d){var f=a(d);if(-1===f){var g=e.createVectorLayer();g.set("markers",!0),d.addLayer(g),b.push({map:d,markerLayer:g,instScopes:[]}),f=b.length-1}return b[f].instScopes.push(c),b[f].markerLayer},deregisterScope:function(c,d){var e=a(d);if(-1===e)throw Error("This map has no markers");var f=b[e].instScopes,g=f.indexOf(c);if(-1===g)throw Error("Scope wan't registered");f.splice(g,1),f.length||(d.removeLayer(b[e].markerLayer),delete b[e].markerLayer,delete b[e])}}}();return{restrict:"E",scope:{lat:"=lat",lon:"=lon",label:"=label",properties:"=olMarkerProperties",style:"=olStyle"},transclude:!0,require:"^openlayers",replace:!0,template:'',link:function(c,h,i,j){var k=e.isDefined,l=j.getOpenlayersScope(),m=e.createFeature,n=e.createOverlay,o=h.find("ng-transclude").children().length>0;l.getMap().then(function(e){function j(){c.properties&&(e.getViewport().removeEventListener("mousemove",c.properties.handleInteraction),e.getViewport().removeEventListener("click",c.properties.handleTapInteraction),e.getViewport().querySelector("canvas.ol-unselectable").removeEventListener("touchend",c.properties.handleTapInteraction),e.getViewport().removeEventListener("mousemove",c.properties.showAtLeastOneOverlay),e.getViewport().removeEventListener("click",c.properties.removeAllOverlays),e.getViewport().querySelector("canvas.ol-unselectable").removeEventListener("touchmove",c.properties.activateCooldown))}var p,q,r,s=g.getInst(c,e),t=f(),u=d.getDefaults(l),v=u.view.projection,w=null,x=null;if(c.handleDrag=function(b){var d=b.coordinate,f=e.getView().getProjection().getCode();if(d="pixel"===f?d.map(function(a){return parseInt(a,10)}):a.proj.transform(d,f,"EPSG:4326"),"pointerdown"===b.type){var g=e.forEachFeatureAtPixel(b.pixel,function(a){return a});if(!(x=g?g.get("marker"):null)||!x.draggable)return void(x=null);e.getTarget().style.cursor="pointer",w="pixel"===f?[d[0]-x.coord[0],d[1]-x.coord[1]]:[d[0]-x.lon,d[1]-x.lat],b.preventDefault()}else w&&x&&("pointerup"===b.type?(e.getTarget().style.cursor="",w=null,x=null,b.preventDefault()):"pointerdrag"===b.type&&(b.preventDefault(),c.$apply(function(){"pixel"===f?(x.coord[0]=d[0]-w[0],x.coord[1]=d[1]-w[1]):(x.lon=d[0]-w[0],x.lat=d[1]-w[1])})))},e.on("pointerdown",c.handleDrag),e.on("pointerup",c.handleDrag),e.on("pointerdrag",c.handleDrag),c.$on("$destroy",function(){s.getSource().removeFeature(r),k(p)&&e.removeOverlay(p),g.deregisterScope(c,e),e.un("pointerdown",c.handleDrag),e.un("pointerup",c.handleDrag),e.un("pointerdrag",c.handleDrag),j()}),!k(c.properties))return t.lat=c.lat?c.lat:t.lat,t.lon=c.lon?c.lon:t.lon,t.message=i.message,t.style=c.style?c.style:u.styles.marker,r=m(t,v),k(r)||b.error("[AngularJS - Openlayers] Received invalid data on the marker."),r.set("marker",c),s.getSource().addFeature(r),void((t.message||o)&&(c.message=i.message,q=a.proj.transform([t.lon,t.lat],t.projection,v),p=n(h,q),e.addOverlay(p)));c.$watch("properties",function(d){if(j(),d.handleInteraction=function(b){var c=!1;if(i.hasOwnProperty("ngClick")&&(c=!0),!d.label.show||c){var f=!1,g=e.getEventPixel(b),j=e.forEachFeatureAtPixel(g,function(a){return a}),l=!1;if(j===r){if(l=!0,f=!0,c&&("click"===b.type||"touchend"===b.type))return h.triggerHandler("click"),b.preventDefault(),void b.stopPropagation();k(p)||(q="pixel"===t.projection?d.coord:a.proj.transform([d.lon,d.lat],t.projection,v),p=n(h,q),e.addOverlay(p)),e.getTarget().style.cursor="pointer"}!f&&p&&(l=!0,e.removeOverlay(p),p=void 0,e.getTarget().style.cursor=""),l&&b.preventDefault()}},d.handleTapInteraction=function(){var a,b=!1;return d.activateCooldown=function(){b=!0,a&&clearTimeout(a),a=setTimeout(function(){b=!1,a=null},500)},d.activateCooldown&&e.getViewport().querySelector("canvas.ol-unselectable").removeEventListener("touchmove",d.activateCooldown),e.getViewport().querySelector("canvas.ol-unselectable").addEventListener("touchmove",d.activateCooldown),function(){b||(d.handleInteraction.apply(null,arguments),d.activateCooldown())}}(),d.showAtLeastOneOverlay=function(b){if(!d.label.show){var c=!1,f=e.getEventPixel(b),g=e.forEachFeatureAtPixel(f,function(a){return a}),i=!1;g===r&&(i=!0,c=!0,k(p)||(q="pixel"===t.projection?t.coord:a.proj.transform([t.lon,t.lat],t.projection,v),p=n(h,q),angular.forEach(e.getOverlays(),function(a){e.removeOverlay(a)}),e.addOverlay(p)),e.getTarget().style.cursor="pointer"),!c&&p&&(i=!0,p=void 0,e.getTarget().style.cursor=""),i&&b.preventDefault()}},d.removeAllOverlays=function(a){angular.forEach(e.getOverlays(),function(a){e.removeOverlay(a)}),a.preventDefault()},k(r)){var f;if(f="pixel"===d.projection?d.coord:a.proj.transform([d.lon,d.lat],t.projection,e.getView().getProjection()),!angular.equals(r.getGeometry().getCoordinates(),f)){var g=new a.geom.Point(f);r.setGeometry(g)}}else t.projection=d.projection?d.projection:t.projection,t.coord=d.coord?d.coord:t.coord,t.lat=d.lat?d.lat:t.lat,t.lon=d.lon?d.lon:t.lon,k(d.style)?t.style=d.style:t.style=u.styles.marker,r=m(t,v),k(r)||b.error("[AngularJS - Openlayers] Received invalid data on the marker."),r.set("marker",d),s.getSource().addFeature(r);k(p)&&e.removeOverlay(p),k(d.label)&&(c.message=d.label.message,(o||k(c.message)&&0!==c.message.length)&&(d.label&&!0===d.label.show&&(q="pixel"===t.projection?t.coord:a.proj.transform([d.lon,d.lat],t.projection,v),p=n(h,q),e.addOverlay(p)),p&&d.label&&!1===d.label.show&&(e.removeOverlay(p),p=void 0),d.label&&!1===d.label.show&&d.label.showOnMouseOver&&e.getViewport().addEventListener("mousemove",d.handleInteraction),(d.label&&!1===d.label.show&&d.label.showOnMouseClick||i.hasOwnProperty("ngClick"))&&(e.getViewport().addEventListener("click",d.handleTapInteraction),e.getViewport().querySelector("canvas.ol-unselectable").addEventListener("touchend",d.handleTapInteraction)),d.label&&!1===d.label.show&&d.label.keepOneOverlayVisible&&(e.getViewport().addEventListener("mousemove",d.showAtLeastOneOverlay),e.getViewport().addEventListener("click",d.removeAllOverlays))))},!0)})}}}]),angular.module("openlayers-directive").service("olData",["$log","$q",function(a,b){function c(b,c){var d,e;if(angular.isDefined(c))d=c;else if(1===Object.keys(b).length)for(e in b)b.hasOwnProperty(e)&&(d=e);else 0===Object.keys(b).length?d="main":a.error("[AngularJS - Openlayers] - You have more than 1 map on the DOM, you must provide the map ID to the olData.getXXX call");return d}var d={},e=function(a,b){a[c(a,b)].resolvedDefer=!0},f=function(a,d){var e,f=c(a,d);return angular.isDefined(a[f])&&!0!==a[f].resolvedDefer?e=a[f].defer:(e=b.defer(),a[f]={defer:e,resolvedDefer:!1}),e},g=function(a,b){var d=c(a,b);return angular.isDefined(a[d])&&!1!==a[d].resolvedDefer?a[d].defer:f(a,b)};this.setMap=function(a,b){f(d,b).resolve(a),e(d,b)},this.getMap=function(a){return g(d,a).promise},this.resetMap=function(a){angular.isDefined(d[a])&&delete d[a]}}]),angular.module("openlayers-directive").factory("olHelpers",["$q","$log","$http",function(b,c,d){var e=function(a){return angular.isDefined(a)},f=function(a){return angular.isDefined(a)&&null!==a},g=function(a,b,c){a.on(b,function(d){var e=d.coordinate,f=a.getView().getProjection().getCode();"pixel"===f&&(e=e.map(function(a){return parseInt(a,10)})),c.$emit("openlayers.map."+b,{coord:e,projection:f,event:d})})},h=["Road","Aerial","AerialWithLabels","collinsBart","ordnanceSurvey"],i=function(){return{attribution:a.control.Attribution,fullscreen:a.control.FullScreen,mouseposition:a.control.MousePosition,overviewmap:a.control.OverviewMap,rotate:a.control.Rotate,scaleline:a.control.ScaleLine,zoom:a.control.Zoom,zoomslider:a.control.ZoomSlider,zoomtoextent:a.control.ZoomToExtent}},j=["osm","sat","hyb"],k=["World_Imagery","World_Street_Map","World_Topo_Map","World_Physical_Map","World_Terrain_Base","Ocean_Basemap","NatGeo_World_Map"],l={style:a.style.Style,fill:a.style.Fill,stroke:a.style.Stroke,circle:a.style.Circle,icon:a.style.Icon,image:a.style.Image,regularshape:a.style.RegularShape,text:a.style.Text},m=function(a,b){return b&&a instanceof b?a:b?new b(a):a},n=function a(b,c){var d;if(c?d=b[c]:(c="style",d=b),"style"===c&&b instanceof Function)return b;if(!(d instanceof Object))return d;var e;if("[object Object]"===Object.prototype.toString.call(d)){e={};var f=l[c];if(f&&d instanceof f)return d;Object.getOwnPropertyNames(d).forEach(function(b,g,h){var i=l[b];if(f&&i&&i.prototype instanceof l[c])return console.assert(1===h.length,"Extra parameters for "+c),e=a(d,b),m(e,i);e[b]=a(d,b),"text"!==b&&"string"!=typeof e[b]&&(e[b]=m(e[b],l[b]))})}else e=d;return m(e,l[c])},o=function(a){if(a.type)return a.type;switch(a.source.type){case"ImageWMS":case"ImageStatic":return"Image";case"GeoJSON":case"JSONP":case"TopoJSON":case"KML":case"WKT":return"Vector";case"TileVector":case"MVT":return"TileVector";default:return"Tile"}},p=function(b){var d;switch(b.projection){case"pixel":if(!e(b.extent))return void c.error("[AngularJS - Openlayers] - You must provide the extent of the image if using pixel projection");d=new a.proj.Projection({code:"pixel",units:"pixels",extent:b.extent});break;default:d=new a.proj.get(b.projection)}return d},q=function(a){return-1!==["watercolor","terrain","toner"].indexOf(a)},r=function(b,f){var g,i,l,m=new a.format.GeoJSON;switch(b.type){case"MapBox":if(!b.mapId||!b.accessToken)return void c.error("[AngularJS - Openlayers] - MapBox layer requires the map id and the access token");l="https://api.tiles.mapbox.com/v4/"+b.mapId+"/{z}/{x}/{y}.png?access_token="+b.accessToken,i=window.devicePixelRatio,i>1&&(l=l.replace(".png","@2x.png")),g=new a.source.XYZ({url:l,tileLoadFunction:b.tileLoadFunction,attributions:t(b),tilePixelRatio:i>1?2:1,wrapX:void 0===b.wrapX||b.wrapX});break;case"MapBoxStudio":if(!b.mapId||!b.accessToken||!b.userId)return void c.error("[AngularJS - Openlayers] - MapBox Studio layer requires the map id, user id and the access token");l="https://api.mapbox.com/styles/v1/"+b.userId+"/"+b.mapId+"/tiles/{z}/{x}/{y}?access_token="+b.accessToken,i=window.devicePixelRatio,i>1&&(l=l.replace("{y}?access_token","{y}@2x?access_token")),g=new a.source.XYZ({url:l,tileLoadFunction:b.tileLoadFunction,attributions:t(b),tilePixelRatio:i>1?2:1,tileSize:b.tileSize||[512,512],wrapX:void 0===b.wrapX||b.wrapX});break;case"MVT":if(!b.url)return void c.error("[AngularJS - Openlayers] - MVT layer requires the source url");g=new a.source.VectorTile({attributions:b.attributions||"",format:new a.format.MVT,tileGrid:a.tilegrid.createXYZ({maxZoom:b.maxZoom||22}),tilePixelRatio:b.tilePixelRatio||16,url:b.url});break;case"ImageWMS":b.url&&b.params||c.error("[AngularJS - Openlayers] - ImageWMS Layer needs valid server url and params properties"),g=new a.source.ImageWMS({url:b.url,imageLoadFunction:b.imageLoadFunction,attributions:t(b),crossOrigin:void 0===b.crossOrigin?"anonymous":b.crossOrigin,params:s(b.params),ratio:b.ratio});break;case"TileWMS":(b.url||b.urls)&&b.params||c.error("[AngularJS - Openlayers] - TileWMS Layer needs valid url (or urls) and params properties");var n={tileLoadFunction:b.tileLoadFunction,crossOrigin:void 0===b.crossOrigin?"anonymous":b.crossOrigin,params:s(b.params),attributions:t(b),wrapX:void 0===b.wrapX||b.wrapX};b.serverType&&(n.serverType=b.serverType),b.url&&(n.url=b.url),b.urls&&(n.urls=b.urls),g=new a.source.TileWMS(n);break;case"WMTS":(b.url||b.urls)&&b.tileGrid||c.error("[AngularJS - Openlayers] - WMTS Layer needs valid url (or urls) and tileGrid properties");var o={tileLoadFunction:b.tileLoadFunction,projection:f,layer:b.layer,attributions:t(b),matrixSet:"undefined"===b.matrixSet?f:b.matrixSet,format:"undefined"===b.format?"image/jpeg":b.format,requestEncoding:"undefined"===b.requestEncoding?"KVP":b.requestEncoding,tileGrid:new a.tilegrid.WMTS({origin:b.tileGrid.origin,resolutions:b.tileGrid.resolutions,matrixIds:b.tileGrid.matrixIds}),style:"undefined"===b.style?"normal":b.style,wrapX:void 0===b.wrapX||b.wrapX};e(b.url)&&(o.url=b.url),e(b.urls)&&(o.urls=b.urls),g=new a.source.WMTS(o);break;case"OSM":g=new a.source.OSM({tileLoadFunction:b.tileLoadFunction,attributions:t(b),wrapX:void 0===b.wrapX||b.wrapX}),b.url&&g.setUrl(b.url);break;case"BingMaps":if(!b.key)return void c.error("[AngularJS - Openlayers] - You need an API key to show the Bing Maps.");var p={key:b.key,tileLoadFunction:b.tileLoadFunction,attributions:t(b),imagerySet:b.imagerySet?b.imagerySet:h[0],culture:b.culture,wrapX:void 0===b.wrapX||b.wrapX};b.maxZoom&&(p.maxZoom=b.maxZoom),g=new a.source.BingMaps(p);break;case"MapQuest":if(!b.layer||-1===j.indexOf(b.layer))return void c.error("[AngularJS - Openlayers] - MapQuest layers needs a valid 'layer' property.");g=new a.source.MapQuest({attributions:t(b),layer:b.layer,wrapX:void 0===b.wrapX||b.wrapX});break;case"EsriBaseMaps":if(!b.layer||-1===k.indexOf(b.layer))return void c.error("[AngularJS - Openlayers] - ESRI layers needs a valid 'layer' property.");var r="https://services.arcgisonline.com/ArcGIS/rest/services/",u=r+b.layer+"/MapServer/tile/{z}/{y}/{x}";g=new a.source.XYZ({attributions:t(b),tileLoadFunction:b.tileLoadFunction,url:u,wrapX:void 0===b.wrapX||b.wrapX});break;case"TileArcGISRest":b.url||c.error("[AngularJS - Openlayers] - TileArcGISRest Layer needs valid url"),g=new a.source.TileArcGISRest({attributions:t(b),tileLoadFunction:b.tileLoadFunction,url:b.url,wrapX:void 0===b.wrapX||b.wrapX});break;case"GeoJSON":if(!b.geojson&&!b.url)return void c.error("[AngularJS - Openlayers] - You need a geojson property to add a GeoJSON layer.");if(e(b.url))g=new a.source.Vector({format:new a.format.GeoJSON,url:b.url});else{g=new a.source.Vector;var v,w=f;v=e(b.geojson.projection)?new a.proj.get(b.geojson.projection):f;var x=m.readFeatures(b.geojson.object,{featureProjection:w.getCode(),dataProjection:v.getCode()});g.addFeatures(x)}break;case"WKT":if(!b.wkt&&!b.wkt.data)return void c.error("[AngularJS - Openlayers] - You need a WKT property to add a WKT format vector layer.");g=new a.source.Vector;var y,z=new a.format.WKT;y=e(b.wkt.projection)?new a.proj.get(b.wkt.projection):f;var A=z.readFeatures(b.wkt.data,{featureProjection:f.getCode(),dataProjection:y.getCode()});g.addFeatures(A);break;case"JSONP":if(!b.url)return void c.error("[AngularJS - Openlayers] - You need an url properly configured to add a JSONP layer.");e(b.url)&&(g=new a.source.ServerVector({format:m,loader:function(){var a=b.url+"&outputFormat=text/javascript&format_options=callback:JSON_CALLBACK";d.jsonp(a,{cache:b.cache}).success(function(a){g.addFeatures(m.readFeatures(a))}).error(function(a){c(a)})},projection:f}));break;case"TopoJSON":if(!b.topojson&&!b.url)return void c.error("[AngularJS - Openlayers] - You need a topojson property to add a TopoJSON layer.");g=b.url?new a.source.Vector({format:new a.format.TopoJSON,url:b.url}):new a.source.Vector(angular.extend(b.topojson,{format:new a.format.TopoJSON}));break;case"TileJSON":g=new a.source.TileJSON({url:b.url,attributions:t(b),tileLoadFunction:b.tileLoadFunction,crossOrigin:"anonymous",wrapX:void 0===b.wrapX||b.wrapX});break;case"TileVector":b.url&&b.format||c.error("[AngularJS - Openlayers] - TileVector Layer needs valid url and format properties"),g=new a.source.VectorTile({url:b.url,projection:f,attributions:t(b),tileLoadFunction:b.tileLoadFunction,format:b.format,tileGrid:new a.tilegrid.createXYZ({maxZoom:b.maxZoom||19}),wrapX:void 0===b.wrapX||b.wrapX});break;case"TileTMS":b.url&&b.tileGrid||c.error("[AngularJS - Openlayers] - TileTMS Layer needs valid url and tileGrid properties"),g=new a.source.TileImage({url:b.url,maxExtent:b.maxExtent,attributions:t(b),tileLoadFunction:b.tileLoadFunction,tileGrid:new a.tilegrid.TileGrid({origin:b.tileGrid.origin,resolutions:b.tileGrid.resolutions}),tileUrlFunction:function(a){var c=a[0],d=a[1],e=a[2];return d<0||e<0?"":b.url+c+"/"+d+"/"+e+".png"},wrapX:void 0===b.wrapX||b.wrapX});break;case"TileImage":g=new a.source.TileImage({url:b.url,attributions:t(b),tileLoadFunction:b.tileLoadFunction,tileGrid:new a.tilegrid.TileGrid({origin:b.tileGrid.origin,resolutions:b.tileGrid.resolutions}),tileUrlFunction:function(a){var c=a[0],d=a[1],e=-a[2]-1;return b.url.replace("{z}",c.toString()).replace("{x}",d.toString()).replace("{y}",e.toString())},wrapX:void 0===b.wrapX||b.wrapX});break;case"KML":var B=b.extractStyles||!1;g=new a.source.Vector({url:b.url,format:new a.format.KML,radius:b.radius,extractStyles:B});break;case"Stamen":if(!b.layer||!q(b.layer))return void c.error("[AngularJS - Openlayers] - You need a valid Stamen layer.");g=new a.source.Stamen({tileLoadFunction:b.tileLoadFunction,layer:b.layer,wrapX:void 0===b.wrapX||b.wrapX});break;case"ImageStatic":if(!b.url||!angular.isArray(b.imageSize)||2!==b.imageSize.length)return void c.error("[AngularJS - Openlayers] - You need a image URL to create a ImageStatic layer.");g=new a.source.ImageStatic({url:b.url,attributions:t(b),imageSize:b.imageSize,projection:f,imageExtent:b.imageExtent?b.imageExtent:f.getExtent(),imageLoadFunction:b.imageLoadFunction});break;case"XYZ":b.url||b.urls||b.tileUrlFunction||c.error("[AngularJS - Openlayers] - XYZ Layer needs valid url(s) or tileUrlFunction properties"),g=new a.source.XYZ({url:b.url,urls:b.urls,attributions:t(b),minZoom:b.minZoom,maxZoom:b.maxZoom,projection:b.projection,tileUrlFunction:b.tileUrlFunction,tileLoadFunction:b.tileLoadFunction,wrapX:void 0===b.wrapX||b.wrapX});break;case"Zoomify":b.url&&angular.isArray(b.imageSize)&&2===b.imageSize.length||c.error("[AngularJS - Openlayers] - Zoomify Layer needs valid url and imageSize properties"),g=new a.source.Zoomify({url:b.url,size:b.imageSize,wrapX:void 0===b.wrapX||b.wrapX})}return g||c.warn('[AngularJS - Openlayers] - No source could be found for type "'+b.type+'"'),g},s=function(a){var b=a;if(a&&"object"==typeof a){b="[object Array]"===Object.prototype.toString.call(a)?[]:{};for(var c in a)b[c]=s(a[c])}return b},t=function(b){var c=[];if(e(b.attribution))!1!==b.attribution&&c.unshift(new a.Attribution({html:b.attribution}));else{var d=u(b);d&&c.unshift(d)}return c},u=function(b){if(b&&b.type){var c=a.source[b.type];if(c)for(var d in c)if(c.hasOwnProperty(d)&&d.toLowerCase().indexOf("attribution")>-1)return a.source[b.type][d]}return null},v=function(b){var c=new a.layer.Group;return c.set("name",b),c},w=function(b,c){var d;return angular.forEach(b,function(b){if(b instanceof a.layer.Group&&b.get("name")===c)return void(d=b)}),d},x=function(a,b){for(var c,d=0;d',
+ controller: function($scope) {
+ var _map = $q.defer();
+ $scope.getMap = function() {
+ return _map.promise;
+ };
+
+ $scope.setMap = function(map) {
+ _map.resolve(map);
+ };
+
+ this.getOpenlayersScope = function() {
+ return $scope;
+ };
+ },
+ link: function(scope, element, attrs) {
+ var isDefined = olHelpers.isDefined;
+ var createLayer = olHelpers.createLayer;
+ var setMapEvents = olHelpers.setMapEvents;
+ var setViewEvents = olHelpers.setViewEvents;
+ var createView = olHelpers.createView;
+ var defaults = olMapDefaults.setDefaults(scope);
+
+ // Set width and height if they are defined
+ if (isDefined(attrs.width)) {
+ if (isNaN(attrs.width)) {
+ element.css('width', attrs.width);
+ } else {
+ element.css('width', attrs.width + 'px');
+ }
+ }
+
+ if (isDefined(attrs.height)) {
+ if (isNaN(attrs.height)) {
+ element.css('height', attrs.height);
+ } else {
+ element.css('height', attrs.height + 'px');
+ }
+ }
+
+ if (isDefined(attrs.lat)) {
+ defaults.center.lat = parseFloat(attrs.lat);
+ }
+
+ if (isDefined(attrs.lon)) {
+ defaults.center.lon = parseFloat(attrs.lon);
+ }
+
+ if (isDefined(attrs.zoom)) {
+ defaults.center.zoom = parseFloat(attrs.zoom);
+ }
+
+ var controls = ol.control.defaults(defaults.controls);
+ var interactions = ol.interaction.defaults(defaults.interactions);
+ var view = createView(defaults.view);
+
+ // Create the Openlayers Map Object with the options
+ var map = new ol.Map({
+ target: element[0],
+ controls: controls,
+ interactions: interactions,
+ renderer: defaults.renderer,
+ view: view,
+ loadTilesWhileAnimating: defaults.loadTilesWhileAnimating,
+ loadTilesWhileInteracting: defaults.loadTilesWhileInteracting
+ });
+
+ scope.$on('$destroy', function() {
+ olData.resetMap(attrs.id);
+ map.setTarget(null);
+ map = null;
+ });
+
+ // If no layer is defined, set the default tileLayer
+ if (!attrs.customLayers) {
+ var l = {
+ type: 'Tile',
+ source: {
+ type: 'OSM'
+ }
+ };
+ var layer = createLayer(l, view.getProjection(), 'default');
+ map.addLayer(layer);
+ map.set('default', true);
+ }
+
+ if (!isDefined(attrs.olCenter)) {
+ var c = ol.proj.transform([defaults.center.lon,
+ defaults.center.lat
+ ],
+ defaults.center.projection, view.getProjection()
+ );
+ view.setCenter(c);
+ view.setZoom(defaults.center.zoom);
+ }
+
+ // Set the Default events for the map
+ setMapEvents(defaults.events, map, scope);
+
+ //Set the Default events for the map view
+ setViewEvents(defaults.events, map, scope);
+
+ // Resolve the map object to the promises
+ scope.setMap(map);
+ olData.setMap(map, attrs.id);
+
+ }
+ };
+ });
+
+angular.module('openlayers-directive').directive('olCenter', function($log, $location, olMapDefaults, olHelpers) {
+
+ return {
+ restrict: 'A',
+ scope: false,
+ replace: false,
+ require: 'openlayers',
+
+ link: function(scope, element, attrs, controller) {
+ var safeApply = olHelpers.safeApply;
+ var isValidCenter = olHelpers.isValidCenter;
+ var isDefined = olHelpers.isDefined;
+ var isArray = olHelpers.isArray;
+ var isNumber = olHelpers.isNumber;
+ var isSameCenterOnMap = olHelpers.isSameCenterOnMap;
+ var setCenter = olHelpers.setCenter;
+ var setZoom = olHelpers.setZoom;
+ var olScope = controller.getOpenlayersScope();
+
+ olScope.getMap().then(function(map) {
+ var defaults = olMapDefaults.getDefaults(olScope);
+ var view = map.getView();
+ var center = olScope.center;
+
+ if (attrs.olCenter.search('-') !== -1) {
+ $log.error('[AngularJS - Openlayers] The "center" variable can\'t use ' +
+ 'a "-" on his key name: "' + attrs.center + '".');
+ setCenter(view, defaults.view.projection, defaults.center, map);
+ return;
+ }
+
+ if (!isDefined(center)) {
+ center = {};
+ }
+
+ if (!isValidCenter(center)) {
+ $log.warn('[AngularJS - Openlayers] invalid \'center\'');
+ center.lat = defaults.center.lat;
+ center.lon = defaults.center.lon;
+ center.zoom = defaults.center.zoom;
+ center.projection = defaults.center.projection;
+ }
+
+ if (!center.projection) {
+ if (defaults.view.projection !== 'pixel') {
+ center.projection = defaults.center.projection;
+ } else {
+ center.projection = 'pixel';
+ }
+ }
+
+ if (!isNumber(center.zoom)) {
+ center.zoom = 1;
+ }
+
+ setCenter(view, defaults.view.projection, center, map);
+ view.setZoom(center.zoom);
+
+ var centerUrlHash;
+ if (center.centerUrlHash === true) {
+ var extractCenterFromUrl = function() {
+ var search = $location.search();
+ var centerParam;
+ if (isDefined(search.c)) {
+ var cParam = search.c.split(':');
+ if (cParam.length === 3) {
+ centerParam = {
+ lat: parseFloat(cParam[0]),
+ lon: parseFloat(cParam[1]),
+ zoom: parseInt(cParam[2], 10)
+ };
+ }
+ }
+ return centerParam;
+ };
+ centerUrlHash = extractCenterFromUrl();
+
+ olScope.$on('$locationChangeSuccess', function() {
+ var urlCenter = extractCenterFromUrl();
+ if (urlCenter && !isSameCenterOnMap(urlCenter, map)) {
+ safeApply(olScope, function(scope) {
+ scope.center.lat = urlCenter.lat;
+ scope.center.lon = urlCenter.lon;
+ scope.center.zoom = urlCenter.zoom;
+ });
+ }
+ });
+ }
+
+ var geolocation;
+ olScope.$watchCollection('center', function(center) {
+
+ if (!center) {
+ return;
+ }
+
+ if (!center.projection) {
+ center.projection = defaults.center.projection;
+ }
+
+ if (center.autodiscover) {
+ if (!geolocation) {
+ geolocation = new ol.Geolocation({
+ projection: ol.proj.get(center.projection)
+ });
+
+ geolocation.on('change', function() {
+ if (center.autodiscover) {
+ var location = geolocation.getPosition();
+ safeApply(olScope, function(scope) {
+ scope.center.lat = location[1];
+ scope.center.lon = location[0];
+ scope.center.zoom = 12;
+ scope.center.autodiscover = false;
+ geolocation.setTracking(false);
+ });
+ }
+ });
+ }
+ geolocation.setTracking(true);
+ return;
+ }
+
+ if (!isValidCenter(center)) {
+ $log.warn('[AngularJS - Openlayers] invalid \'center\'');
+ center = defaults.center;
+ }
+
+ var viewCenter = view.getCenter();
+ if (viewCenter) {
+ if (defaults.view.projection === 'pixel' || center.projection === 'pixel') {
+ view.setCenter(center.coord);
+ } else {
+ var actualCenter =
+ ol.proj.transform(viewCenter, defaults.view.projection, center.projection);
+ if (!(actualCenter[1] === center.lat && actualCenter[0] === center.lon)) {
+ setCenter(view, defaults.view.projection, center, map);
+ }
+ }
+ }
+
+ if (view.getZoom() !== center.zoom) {
+ setZoom(view, center.zoom, map);
+ }
+ });
+
+ var moveEndEventKey = map.on('moveend', function() {
+ safeApply(olScope, function(scope) {
+
+ if (!isDefined(scope.center)) {
+ return;
+ }
+
+ var center = map.getView().getCenter();
+ scope.center.zoom = view.getZoom();
+
+ if (defaults.view.projection === 'pixel' || scope.center.projection === 'pixel') {
+ scope.center.coord = center;
+ return;
+ }
+
+ if (scope.center) {
+ var proj = ol.proj.transform(center, defaults.view.projection, scope.center.projection);
+ scope.center.lat = proj[1];
+ scope.center.lon = proj[0];
+
+ // Notify the controller about a change in the center position
+ olHelpers.notifyCenterUrlHashChanged(olScope, scope.center, $location.search());
+
+ // Calculate the bounds if needed
+ if (isArray(scope.center.bounds)) {
+ var extent = view.calculateExtent(map.getSize());
+ var centerProjection = scope.center.projection;
+ var viewProjection = defaults.view.projection;
+ scope.center.bounds = ol.proj.transformExtent(extent, viewProjection, centerProjection);
+ }
+ }
+ });
+ });
+
+ olScope.$on('$destroy', function() {
+ ol.Observable.unByKey(moveEndEventKey);
+ });
+ });
+ }
+ };
+});
+
+angular.module('openlayers-directive').directive('olLayer', function($log, $q, olMapDefaults, olHelpers) {
+
+ return {
+ restrict: 'E',
+ scope: {
+ properties: '=olLayerProperties',
+ onLayerCreated: '&'
+ },
+ replace: false,
+ require: '^openlayers',
+ link: function(scope, element, attrs, controller) {
+ var isDefined = olHelpers.isDefined;
+ var equals = olHelpers.equals;
+ var olScope = controller.getOpenlayersScope();
+ var createLayer = olHelpers.createLayer;
+ var setVectorLayerEvents = olHelpers.setVectorLayerEvents;
+ var detectLayerType = olHelpers.detectLayerType;
+ var createStyle = olHelpers.createStyle;
+ var isBoolean = olHelpers.isBoolean;
+ var addLayerBeforeMarkers = olHelpers.addLayerBeforeMarkers;
+ var isNumber = olHelpers.isNumber;
+ var insertLayer = olHelpers.insertLayer;
+ var removeLayer = olHelpers.removeLayer;
+ var addLayerToGroup = olHelpers.addLayerToGroup;
+ var removeLayerFromGroup = olHelpers.removeLayerFromGroup;
+ var getGroup = olHelpers.getGroup;
+
+ olScope.getMap().then(function(map) {
+ var projection = map.getView().getProjection();
+ var defaults = olMapDefaults.setDefaults(olScope);
+ var layerCollection = map.getLayers();
+ var olLayer;
+
+ scope.$on('$destroy', function() {
+ if (scope.properties.group) {
+ removeLayerFromGroup(layerCollection, olLayer, scope.properties.group);
+ } else {
+ removeLayer(layerCollection, olLayer.index);
+ }
+
+ map.removeLayer(olLayer);
+ });
+
+ if (!isDefined(scope.properties)) {
+ if (isDefined(attrs.sourceType) && isDefined(attrs.sourceUrl)) {
+ var l = {
+ source: {
+ url: attrs.sourceUrl,
+ type: attrs.sourceType
+ }
+ };
+
+ olLayer = createLayer(l, projection, attrs.layerName, scope.onLayerCreated);
+ if (detectLayerType(l) === 'Vector') {
+ setVectorLayerEvents(defaults.events, map, scope, attrs.name);
+ }
+ addLayerBeforeMarkers(layerCollection, olLayer);
+ }
+ return;
+ }
+
+ scope.$watch('properties', function(properties, oldProperties) {
+ if (!isDefined(properties.source) || !isDefined(properties.source.type)) {
+ return;
+ }
+
+ if (!isDefined(properties.visible)) {
+ properties.visible = true;
+ return;
+ }
+
+ if (!isDefined(properties.opacity)) {
+ properties.opacity = 1;
+ return;
+ }
+
+ var style;
+ var group;
+ var collection;
+ if (!isDefined(olLayer)) {
+ olLayer = createLayer(properties, projection, scope.onLayerCreated);
+ if (isDefined(properties.group)) {
+ addLayerToGroup(layerCollection, olLayer, properties.group);
+ } else if (isDefined(properties.index)) {
+ insertLayer(layerCollection, properties.index, olLayer);
+ } else {
+ addLayerBeforeMarkers(layerCollection, olLayer);
+ }
+
+ if (detectLayerType(properties) === 'Vector') {
+ setVectorLayerEvents(defaults.events, map, scope, properties.name);
+ }
+
+ if (isBoolean(properties.visible)) {
+ olLayer.setVisible(properties.visible);
+ }
+
+ if (properties.opacity) {
+ olLayer.setOpacity(properties.opacity);
+ }
+
+ if (angular.isArray(properties.extent)) {
+ olLayer.setExtent(properties.extent);
+ }
+
+ if (properties.style) {
+ if (!angular.isFunction(properties.style)) {
+ style = createStyle(properties.style);
+ } else {
+ style = properties.style;
+ }
+ // not every layer has a setStyle method
+ if (olLayer.setStyle && angular.isFunction(olLayer.setStyle)) {
+ olLayer.setStyle(style);
+ }
+ }
+
+ if (properties.minResolution) {
+ olLayer.setMinResolution(properties.minResolution);
+ }
+
+ if (properties.maxResolution) {
+ olLayer.setMaxResolution(properties.maxResolution);
+ }
+
+ } else {
+ var isNewLayer = (function(olLayer) {
+ // this function can be used to verify whether a new layer instance has
+ // been created. This is needed in order to re-assign styles, opacity
+ // etc...
+ return function(layer) {
+ return layer !== olLayer;
+ };
+ })(olLayer);
+
+ // set source properties
+ if (isDefined(oldProperties) && !equals(properties.source, oldProperties.source)) {
+ var idx = olLayer.index;
+ collection = layerCollection;
+ group = olLayer.get('group');
+
+ if (group) {
+ collection = getGroup(layerCollection, group).getLayers();
+ }
+
+ collection.removeAt(idx);
+
+ olLayer = createLayer(properties, projection, scope.onLayerCreated);
+ olLayer.set('group', group);
+
+ if (isDefined(olLayer)) {
+ insertLayer(collection, idx, olLayer);
+
+ if (detectLayerType(properties) === 'Vector') {
+ setVectorLayerEvents(defaults.events, map, scope, properties.name);
+ }
+ }
+ }
+
+ // set opacity
+ if (isDefined(oldProperties) &&
+ properties.opacity !== oldProperties.opacity || isNewLayer(olLayer)) {
+ if (isNumber(properties.opacity) || isNumber(parseFloat(properties.opacity))) {
+ olLayer.setOpacity(properties.opacity);
+ }
+ }
+
+ // set index
+ if (isDefined(properties.index) && properties.index !== olLayer.index) {
+ collection = layerCollection;
+ group = olLayer.get('group');
+
+ if (group) {
+ collection = getGroup(layerCollection, group).getLayers();
+ }
+
+ removeLayer(collection, olLayer.index);
+ insertLayer(collection, properties.index, olLayer);
+ }
+
+ // set group
+ if (isDefined(properties.group) && properties.group !== oldProperties.group) {
+ removeLayerFromGroup(layerCollection, olLayer, oldProperties.group);
+ addLayerToGroup(layerCollection, olLayer, properties.group);
+ }
+
+ // set visibility
+ if (isDefined(oldProperties) &&
+ isBoolean(properties.visible) &&
+ (
+ properties.visible !== oldProperties.visible ||
+ isNewLayer(olLayer) ||
+ // to make sure the underlying ol3 object is always synched
+ olLayer.getVisible() !== properties.visible
+ )
+ ) {
+ olLayer.setVisible(properties.visible);
+ }
+
+ // set style
+ if (isDefined(properties.style) &&
+ !equals(properties.style, oldProperties.style) || isNewLayer(olLayer)) {
+ if (!angular.isFunction(properties.style)) {
+ style = createStyle(properties.style);
+ } else {
+ style = properties.style;
+ }
+ // not every layer has a setStyle method
+ if (olLayer.setStyle && angular.isFunction(olLayer.setStyle)) {
+ olLayer.setStyle(style);
+ }
+ }
+
+ //set min resolution
+ if (!equals(properties.minResolution, oldProperties.minResolution) || isNewLayer(olLayer)) {
+ if (isDefined(properties.minResolution)) {
+ olLayer.setMinResolution(properties.minResolution);
+ }
+ }
+
+ //set max resolution
+ if (!equals(properties.maxResolution, oldProperties.maxResolution) || isNewLayer(olLayer)) {
+ if (isDefined(properties.maxResolution)) {
+ olLayer.setMaxResolution(properties.maxResolution);
+ }
+ }
+ }
+ }, true);
+ });
+ }
+ };
+});
+
+angular.module('openlayers-directive').directive('olPath', function($log, $q, olMapDefaults, olHelpers) {
+
+ return {
+ restrict: 'E',
+ scope: {
+ properties: '=olGeomProperties',
+ style: '=olStyle'
+ },
+ require: '^openlayers',
+ replace: true,
+ template: '',
+
+ link: function(scope, element, attrs, controller) {
+ var isDefined = olHelpers.isDefined;
+ var createFeature = olHelpers.createFeature;
+ var createOverlay = olHelpers.createOverlay;
+ var createVectorLayer = olHelpers.createVectorLayer;
+ var insertLayer = olHelpers.insertLayer;
+ var removeLayer = olHelpers.removeLayer;
+ var olScope = controller.getOpenlayersScope();
+
+ olScope.getMap().then(function(map) {
+ var mapDefaults = olMapDefaults.getDefaults(olScope);
+ var viewProjection = mapDefaults.view.projection;
+
+ var layer = createVectorLayer();
+ var layerCollection = map.getLayers();
+
+ insertLayer(layerCollection, layerCollection.getLength(), layer);
+
+ scope.$on('$destroy', function() {
+ removeLayer(layerCollection, layer.index);
+ });
+
+ if (isDefined(attrs.coords)) {
+ var proj = attrs.proj || 'EPSG:4326';
+ var coords = JSON.parse(attrs.coords);
+ var data = {
+ type: 'Polygon',
+ coords: coords,
+ projection: proj,
+ style: scope.style ? scope.style : mapDefaults.styles.path
+ };
+ var feature = createFeature(data, viewProjection);
+ layer.getSource().addFeature(feature);
+
+ if (attrs.message) {
+ scope.message = attrs.message;
+ var extent = feature.getGeometry().getExtent();
+ var label = createOverlay(element, extent);
+ map.addOverlay(label);
+ }
+ return;
+ }
+ });
+ }
+ };
+});
+
+angular.module('openlayers-directive').directive('olView', function($log, $q, olData, olMapDefaults, olHelpers) {
+ return {
+ restrict: 'A',
+ scope: false,
+ replace: false,
+ require: 'openlayers',
+ link: function(scope, element, attrs, controller) {
+ var olScope = controller.getOpenlayersScope();
+ var isNumber = olHelpers.isNumber;
+ var safeApply = olHelpers.safeApply;
+ var createView = olHelpers.createView;
+
+ olScope.getMap().then(function(map) {
+ var defaults = olMapDefaults.getDefaults(olScope);
+ var view = olScope.view;
+
+ if (!view.projection) {
+ view.projection = defaults.view.projection;
+ }
+
+ if (!view.maxZoom) {
+ view.maxZoom = defaults.view.maxZoom;
+ }
+
+ if (!view.minZoom) {
+ view.minZoom = defaults.view.minZoom;
+ }
+
+ if (!view.rotation) {
+ view.rotation = defaults.view.rotation;
+ }
+
+ var mapView = createView(view);
+ map.setView(mapView);
+
+ olScope.$watchCollection('view', function(view) {
+ if (isNumber(view.rotation)) {
+ mapView.setRotation(view.rotation);
+ }
+ });
+
+ var rotationEventKey = mapView.on('change:rotation', function() {
+ safeApply(olScope, function(scope) {
+ scope.view.rotation = map.getView().getRotation();
+ });
+ });
+
+ olScope.$on('$destroy', function() {
+ ol.Observable.unByKey(rotationEventKey);
+ });
+
+ });
+ }
+ };
+});
+
+angular.module('openlayers-directive')
+.directive('olControl', function($log, $q, olData, olMapDefaults, olHelpers) {
+ return {
+ restrict: 'E',
+ scope: {
+ properties: '=olControlProperties'
+ },
+ replace: false,
+ require: '^openlayers',
+ link: function(scope, element, attrs, controller) {
+ var isDefined = olHelpers.isDefined;
+ var olScope = controller.getOpenlayersScope();
+ var olControl;
+ var olControlOps;
+ var getControlClasses = olHelpers.getControlClasses;
+ var controlClasses = getControlClasses();
+
+ olScope.getMap().then(function(map) {
+
+ scope.$on('$destroy', function() {
+ map.removeControl(olControl);
+ });
+
+ scope.$watch('properties', function(properties) {
+ if (!isDefined(properties)) {
+ return;
+ }
+
+ initCtrls(properties);
+ });
+
+ function initCtrls(properties) {
+ if (properties && properties.control) {
+ // the control instance is already defined,
+ // so simply use it and go ahead
+
+ // is there already a control, so destroy and recreate it?
+ if (olControl) {
+ map.removeControl(olControl);
+ }
+
+ olControl = properties.control;
+ map.addControl(olControl);
+ } else {
+
+ // the name is the key to instantiate an ol3 control
+ if (attrs.name) {
+ if (isDefined(properties)) {
+ olControlOps = properties;
+ }
+
+ // is there already a control, so destroy and recreate it?
+ if (olControl) {
+ map.removeControl(olControl);
+ }
+
+ olControl = new controlClasses[attrs.name](olControlOps);
+ map.addControl(olControl);
+ }
+ }
+ }
+
+ initCtrls(scope.properties);
+
+ });
+
+ }
+ };
+});
+
+angular.module('openlayers-directive').directive('olMarker', function($log, $q, olMapDefaults, olHelpers) {
+
+ var getMarkerDefaults = function() {
+ return {
+ projection: 'EPSG:4326',
+ lat: 0,
+ lon: 0,
+ coord: [],
+ show: true,
+ showOnMouseOver: false,
+ showOnMouseClick: false,
+ keepOneOverlayVisible: false
+ };
+ };
+
+ var markerLayerManager = (function() {
+ var mapDict = [];
+
+ function getMapIndex(map) {
+ return mapDict.map(function(record) {
+ return record.map;
+ }).indexOf(map);
+ }
+
+ return {
+ getInst: function getMarkerLayerInst(scope, map) {
+ var mapIndex = getMapIndex(map);
+
+ if (mapIndex === -1) {
+ var markerLayer = olHelpers.createVectorLayer();
+ markerLayer.set('markers', true);
+ map.addLayer(markerLayer);
+ mapDict.push({
+ map: map,
+ markerLayer: markerLayer,
+ instScopes: []
+ });
+ mapIndex = mapDict.length - 1;
+ }
+
+ mapDict[mapIndex].instScopes.push(scope);
+
+ return mapDict[mapIndex].markerLayer;
+ },
+ deregisterScope: function deregisterScope(scope, map) {
+ var mapIndex = getMapIndex(map);
+ if (mapIndex === -1) {
+ throw Error('This map has no markers');
+ }
+
+ var scopes = mapDict[mapIndex].instScopes;
+ var scopeIndex = scopes.indexOf(scope);
+ if (scopeIndex === -1) {
+ throw Error('Scope wan\'t registered');
+ }
+
+ scopes.splice(scopeIndex, 1);
+
+ if (!scopes.length) {
+ map.removeLayer(mapDict[mapIndex].markerLayer);
+ delete mapDict[mapIndex].markerLayer;
+ delete mapDict[mapIndex];
+ }
+ }
+ };
+ })();
+ return {
+ restrict: 'E',
+ scope: {
+ lat: '=lat',
+ lon: '=lon',
+ label: '=label',
+ properties: '=olMarkerProperties',
+ style: '=olStyle'
+ },
+ transclude: true,
+ require: '^openlayers',
+ replace: true,
+ template:
+ '',
+
+ link: function(scope, element, attrs, controller) {
+ var isDefined = olHelpers.isDefined;
+ var olScope = controller.getOpenlayersScope();
+ var createFeature = olHelpers.createFeature;
+ var createOverlay = olHelpers.createOverlay;
+
+ var hasTranscluded = element.find('ng-transclude').children().length > 0;
+
+ olScope.getMap().then(function(map) {
+ var markerLayer = markerLayerManager.getInst(scope, map);
+ var data = getMarkerDefaults();
+
+ var mapDefaults = olMapDefaults.getDefaults(olScope);
+ var viewProjection = mapDefaults.view.projection;
+ var label;
+ var pos;
+ var marker;
+
+ // This function handles dragging a marker
+ var pickOffset = null;
+ var pickProperties = null;
+ scope.handleDrag = function(evt) {
+ var coord = evt.coordinate;
+ var proj = map.getView().getProjection().getCode();
+ if (proj === 'pixel') {
+ coord = coord.map(function(v) {
+ return parseInt(v, 10);
+ });
+ } else {
+ coord = ol.proj.transform(coord, proj, 'EPSG:4326');
+ }
+
+ if (evt.type === 'pointerdown') {
+ // Get feature under mouse if any
+ var feature = map.forEachFeatureAtPixel(evt.pixel, function(feature) {
+ return feature;
+ });
+ // Get associated marker properties
+ pickProperties = (feature ? feature.get('marker') : null);
+ if (!pickProperties || !pickProperties.draggable) {
+ pickProperties = null;
+ return;
+ }
+ map.getTarget().style.cursor = 'pointer';
+ if (proj === 'pixel') {
+ pickOffset = [coord[0] - pickProperties.coord[0], coord[1] - pickProperties.coord[1]];
+ } else {
+ pickOffset = [coord[0] - pickProperties.lon, coord[1] - pickProperties.lat];
+ }
+ evt.preventDefault();
+ } else if (pickOffset && pickProperties) {
+ if (evt.type === 'pointerup') {
+ map.getTarget().style.cursor = '';
+ pickOffset = null;
+ pickProperties = null;
+ evt.preventDefault();
+ } else if (evt.type === 'pointerdrag') {
+ evt.preventDefault();
+ scope.$apply(function() {
+ // Add current delta to marker initial position
+ if (proj === 'pixel') {
+ pickProperties.coord[0] = coord[0] - pickOffset[0];
+ pickProperties.coord[1] = coord[1] - pickOffset[1];
+ } else {
+ pickProperties.lon = coord[0] - pickOffset[0];
+ pickProperties.lat = coord[1] - pickOffset[1];
+ }
+ });
+ }
+ }
+ };
+
+ function unregisterHandlers() {
+ if (!scope.properties) { return ; }
+ // Remove previous listeners if any
+ map.getViewport().removeEventListener('mousemove', scope.properties.handleInteraction);
+ map.getViewport().removeEventListener('click', scope.properties.handleTapInteraction);
+ map.getViewport().querySelector('canvas.ol-unselectable').removeEventListener(
+ 'touchend', scope.properties.handleTapInteraction);
+ map.getViewport().removeEventListener('mousemove', scope.properties.showAtLeastOneOverlay);
+ map.getViewport().removeEventListener('click', scope.properties.removeAllOverlays);
+ map.getViewport().querySelector('canvas.ol-unselectable').removeEventListener(
+ 'touchmove', scope.properties.activateCooldown);
+ }
+
+ // Setup generic handlers for marker drag
+ map.on('pointerdown', scope.handleDrag);
+ map.on('pointerup', scope.handleDrag);
+ map.on('pointerdrag', scope.handleDrag);
+
+ scope.$on('$destroy', function() {
+ markerLayer.getSource().removeFeature(marker);
+ if (isDefined(label)) {
+ map.removeOverlay(label);
+ }
+ markerLayerManager.deregisterScope(scope, map);
+ map.un('pointerdown', scope.handleDrag);
+ map.un('pointerup', scope.handleDrag);
+ map.un('pointerdrag', scope.handleDrag);
+ unregisterHandlers();
+ });
+
+ if (!isDefined(scope.properties)) {
+ data.lat = scope.lat ? scope.lat : data.lat;
+ data.lon = scope.lon ? scope.lon : data.lon;
+ data.message = attrs.message;
+ data.style = scope.style ? scope.style : mapDefaults.styles.marker;
+
+ marker = createFeature(data, viewProjection);
+ if (!isDefined(marker)) {
+ $log.error('[AngularJS - Openlayers] Received invalid data on ' +
+ 'the marker.');
+ }
+ // Add a link between the feature and the marker properties
+ marker.set('marker', scope);
+ markerLayer.getSource().addFeature(marker);
+
+ if (data.message || hasTranscluded) {
+ scope.message = attrs.message;
+ pos = ol.proj.transform([data.lon, data.lat], data.projection,
+ viewProjection);
+ label = createOverlay(element, pos);
+ map.addOverlay(label);
+ }
+ return;
+ }
+
+ scope.$watch('properties', function(properties) {
+
+ unregisterHandlers();
+
+ // This function handles popup on mouse over/click
+ properties.handleInteraction = function(evt) {
+ var ngClick = false;
+ if (attrs.hasOwnProperty('ngClick')) {
+ ngClick = true;
+ }
+
+ if (properties.label.show && !ngClick) {
+ return;
+ }
+ var found = false;
+ var pixel = map.getEventPixel(evt);
+ var feature = map.forEachFeatureAtPixel(pixel, function(feature) {
+ return feature;
+ });
+
+ var actionTaken = false;
+ if (feature === marker) {
+ actionTaken = true;
+ found = true;
+ if (ngClick && (evt.type === 'click' || evt.type === 'touchend')) {
+ element.triggerHandler('click');
+ evt.preventDefault();
+ evt.stopPropagation();
+ return;
+ }
+ if (!isDefined(label)) {
+ if (data.projection === 'pixel') {
+ pos = properties.coord;
+ } else {
+ pos = ol.proj.transform([properties.lon, properties.lat],
+ data.projection, viewProjection);
+ }
+ label = createOverlay(element, pos);
+ map.addOverlay(label);
+ }
+ map.getTarget().style.cursor = 'pointer';
+ }
+
+ if (!found && label) {
+ actionTaken = true;
+ map.removeOverlay(label);
+ label = undefined;
+ map.getTarget().style.cursor = '';
+ }
+
+ if (actionTaken) {
+ evt.preventDefault();
+ }
+ };
+
+ // Made to filter out click/tap events if both are being triggered on this platform
+ properties.handleTapInteraction = (function() {
+ var cooldownActive = false;
+ var prevTimeout;
+
+ // Sets the cooldown flag to filter out any subsequent events within 500 ms
+ properties.activateCooldown = function() {
+ cooldownActive = true;
+ if (prevTimeout) {
+ clearTimeout(prevTimeout);
+ }
+ prevTimeout = setTimeout(function() {
+ cooldownActive = false;
+ prevTimeout = null;
+ }, 500);
+ };
+
+ // Preventing from 'touchend' to be considered a tap, if fired immediately after 'touchmove'
+ if (properties.activateCooldown) {
+ map.getViewport().querySelector('canvas.ol-unselectable').removeEventListener(
+ 'touchmove', properties.activateCooldown);
+ }
+ map.getViewport().querySelector('canvas.ol-unselectable').addEventListener(
+ 'touchmove', properties.activateCooldown);
+
+ return function() {
+ if (!cooldownActive) {
+ properties.handleInteraction.apply(null, arguments);
+ properties.activateCooldown();
+ }
+ };
+ })();
+
+ properties.showAtLeastOneOverlay = function(evt) {
+ if (properties.label.show) {
+ return;
+ }
+ var found = false;
+ var pixel = map.getEventPixel(evt);
+ var feature = map.forEachFeatureAtPixel(pixel, function(feature) {
+ return feature;
+ });
+
+ var actionTaken = false;
+ if (feature === marker) {
+ actionTaken = true;
+ found = true;
+ if (!isDefined(label)) {
+ if (data.projection === 'pixel') {
+ pos = data.coord;
+ } else {
+ pos = ol.proj.transform([data.lon, data.lat],
+ data.projection, viewProjection);
+ }
+ label = createOverlay(element, pos);
+ angular.forEach(map.getOverlays(), function(value) {
+ map.removeOverlay(value);
+ });
+ map.addOverlay(label);
+ }
+ map.getTarget().style.cursor = 'pointer';
+ }
+
+ if (!found && label) {
+ actionTaken = true;
+ label = undefined;
+ map.getTarget().style.cursor = '';
+ }
+
+ if (actionTaken) {
+ evt.preventDefault();
+ }
+ };
+
+ properties.removeAllOverlays = function(evt) {
+ angular.forEach(map.getOverlays(), function(value) {
+ map.removeOverlay(value);
+ });
+ evt.preventDefault();
+ };
+
+ if (!isDefined(marker)) {
+ data.projection = properties.projection ? properties.projection :
+ data.projection;
+ data.coord = properties.coord ? properties.coord : data.coord;
+ data.lat = properties.lat ? properties.lat : data.lat;
+ data.lon = properties.lon ? properties.lon : data.lon;
+
+ if (isDefined(properties.style)) {
+ data.style = properties.style;
+ } else {
+ data.style = mapDefaults.styles.marker;
+ }
+
+ marker = createFeature(data, viewProjection);
+ if (!isDefined(marker)) {
+ $log.error('[AngularJS - Openlayers] Received invalid data on ' +
+ 'the marker.');
+ }
+ // Add a link between the feature and the marker properties
+ marker.set('marker', properties);
+ markerLayer.getSource().addFeature(marker);
+ } else {
+ var requestedPosition;
+ if (properties.projection === 'pixel') {
+ requestedPosition = properties.coord;
+ } else {
+ requestedPosition = ol.proj.transform([properties.lon, properties.lat], data.projection,
+ map.getView().getProjection());
+ }
+
+ if (!angular.equals(marker.getGeometry().getCoordinates(), requestedPosition)) {
+ var geometry = new ol.geom.Point(requestedPosition);
+ marker.setGeometry(geometry);
+ }
+ }
+
+ if (isDefined(label)) {
+ map.removeOverlay(label);
+ }
+
+ if (!isDefined(properties.label)) {
+ return;
+ }
+
+ scope.message = properties.label.message;
+ if (!hasTranscluded && (!isDefined(scope.message) || scope.message.length === 0)) {
+ return;
+ }
+
+ if (properties.label && properties.label.show === true) {
+ if (data.projection === 'pixel') {
+ pos = data.coord;
+ } else {
+ pos = ol.proj.transform([properties.lon, properties.lat], data.projection,
+ viewProjection);
+ }
+ label = createOverlay(element, pos);
+ map.addOverlay(label);
+ }
+
+ if (label && properties.label && properties.label.show === false) {
+ map.removeOverlay(label);
+ label = undefined;
+ }
+
+ // Then setup new ones according to properties
+ if (properties.label && properties.label.show === false &&
+ properties.label.showOnMouseOver) {
+ map.getViewport().addEventListener('mousemove', properties.handleInteraction);
+ }
+
+ if ((properties.label && properties.label.show === false &&
+ properties.label.showOnMouseClick) ||
+ attrs.hasOwnProperty('ngClick')) {
+ map.getViewport().addEventListener('click', properties.handleTapInteraction);
+ map.getViewport().querySelector('canvas.ol-unselectable').addEventListener(
+ 'touchend', properties.handleTapInteraction);
+ }
+
+ if ((properties.label && properties.label.show === false &&
+ properties.label.keepOneOverlayVisible)) {
+ map.getViewport().addEventListener('mousemove', properties.showAtLeastOneOverlay);
+ map.getViewport().addEventListener('click', properties.removeAllOverlays);
+ }
+ }, true);
+ });
+ }
+ };
+});
+
+angular.module('openlayers-directive').service('olData', function($log, $q) {
+
+ var maps = {};
+
+ var setResolvedDefer = function(d, mapId) {
+ var id = obtainEffectiveMapId(d, mapId);
+ d[id].resolvedDefer = true;
+ };
+
+ var getUnresolvedDefer = function(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 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;
+ };
+
+ this.setMap = function(olMap, scopeId) {
+ var defer = getUnresolvedDefer(maps, scopeId);
+ defer.resolve(olMap);
+ setResolvedDefer(maps, scopeId);
+ };
+
+ this.getMap = function(scopeId) {
+ var defer = getDefer(maps, scopeId);
+ return defer.promise;
+ };
+
+ function obtainEffectiveMapId(d, mapId) {
+ var id;
+ var i;
+ if (!angular.isDefined(mapId)) {
+ if (Object.keys(d).length === 1) {
+ for (i in d) {
+ if (d.hasOwnProperty(i)) {
+ id = i;
+ }
+ }
+ } else if (Object.keys(d).length === 0) {
+ id = 'main';
+ } else {
+ $log.error('[AngularJS - Openlayers] - You have more than 1 map on the DOM, ' +
+ 'you must provide the map ID to the olData.getXXX call');
+ }
+ } else {
+ id = mapId;
+ }
+ return id;
+ }
+
+ this.resetMap = function(scopeId) {
+ if (angular.isDefined(maps[scopeId])) {
+ delete maps[scopeId];
+ }
+ };
+
+});
+
+angular.module('openlayers-directive').factory('olHelpers', function($q, $log, $http) {
+
+ var isDefined = function(value) {
+ return angular.isDefined(value);
+ };
+
+ var isDefinedAndNotNull = function(value) {
+ return angular.isDefined(value) && value !== null;
+ };
+
+ var setEvent = function(map, eventType, scope) {
+ map.on(eventType, function(event) {
+ var coord = event.coordinate;
+ var proj = map.getView().getProjection().getCode();
+ if (proj === 'pixel') {
+ coord = coord.map(function(v) {
+ return parseInt(v, 10);
+ });
+ }
+ scope.$emit('openlayers.map.' + eventType, {
+ 'coord': coord,
+ 'projection': proj,
+ 'event': event
+ });
+ });
+ };
+
+ var bingImagerySets = [
+ 'Road',
+ 'Aerial',
+ 'AerialWithLabels',
+ 'collinsBart',
+ 'ordnanceSurvey'
+ ];
+
+ var getControlClasses = function() {
+ return {
+ attribution: ol.control.Attribution,
+ fullscreen: ol.control.FullScreen,
+ mouseposition: ol.control.MousePosition,
+ overviewmap: ol.control.OverviewMap,
+ rotate: ol.control.Rotate,
+ scaleline: ol.control.ScaleLine,
+ zoom: ol.control.Zoom,
+ zoomslider: ol.control.ZoomSlider,
+ zoomtoextent: ol.control.ZoomToExtent
+ };
+ };
+
+ var mapQuestLayers = ['osm', 'sat', 'hyb'];
+
+ var esriBaseLayers = ['World_Imagery', 'World_Street_Map', 'World_Topo_Map',
+ 'World_Physical_Map', 'World_Terrain_Base',
+ 'Ocean_Basemap', 'NatGeo_World_Map'];
+
+ var styleMap = {
+ 'style': ol.style.Style,
+ 'fill': ol.style.Fill,
+ 'stroke': ol.style.Stroke,
+ 'circle': ol.style.Circle,
+ 'icon': ol.style.Icon,
+ 'image': ol.style.Image,
+ 'regularshape': ol.style.RegularShape,
+ 'text': ol.style.Text
+ };
+
+ var optionalFactory = function(style, Constructor) {
+ if (Constructor && style instanceof Constructor) {
+ return style;
+ } else if (Constructor) {
+ return new Constructor(style);
+ } else {
+ return style;
+ }
+ };
+
+ //Parse the style tree calling the appropriate constructors.
+ //The keys in styleMap can be used and the OpenLayers constructors can be
+ //used directly.
+ var createStyle = function recursiveStyle(data, styleName) {
+ var style;
+ if (!styleName) {
+ styleName = 'style';
+ style = data;
+ } else {
+ style = data[styleName];
+ }
+ //Instead of defining one style for the layer, we've been given a style function
+ //to apply to each feature.
+ if (styleName === 'style' && data instanceof Function) {
+ return data;
+ }
+
+ if (!(style instanceof Object)) {
+ return style;
+ }
+
+ var styleObject;
+ if (Object.prototype.toString.call(style) === '[object Object]') {
+ styleObject = {};
+ var styleConstructor = styleMap[styleName];
+ if (styleConstructor && style instanceof styleConstructor) {
+ return style;
+ }
+ Object.getOwnPropertyNames(style).forEach(function(val, idx, array) {
+ //Consider the case
+ //image: {
+ // circle: {
+ // fill: {
+ // color: 'red'
+ // }
+ // }
+ //
+ //An ol.style.Circle is an instance of ol.style.Image, so we do not want to construct
+ //an Image and then construct a Circle. We assume that if we have an instanceof
+ //relationship, that the JSON parent has exactly one child.
+ //We check to see if an inheritance relationship exists.
+ //If it does, then for the parent we create an instance of the child.
+ var valConstructor = styleMap[val];
+ if (styleConstructor && valConstructor &&
+ valConstructor.prototype instanceof styleMap[styleName]) {
+ console.assert(array.length === 1, 'Extra parameters for ' + styleName);
+ styleObject = recursiveStyle(style, val);
+ return optionalFactory(styleObject, valConstructor);
+ } else {
+ styleObject[val] = recursiveStyle(style, val);
+
+ // if the value is 'text' and it contains a String, then it should be interpreted
+ // as such, 'cause the text style might effectively contain a text to display
+ if (val !== 'text' && typeof styleObject[val] !== 'string') {
+ styleObject[val] = optionalFactory(styleObject[val], styleMap[val]);
+ }
+ }
+ });
+ } else {
+ styleObject = style;
+ }
+ return optionalFactory(styleObject, styleMap[styleName]);
+ };
+
+ var detectLayerType = function(layer) {
+ if (layer.type) {
+ return layer.type;
+ } else {
+ switch (layer.source.type) {
+ case 'ImageWMS':
+ return 'Image';
+ case 'ImageStatic':
+ return 'Image';
+ case 'GeoJSON':
+ case 'JSONP':
+ case 'TopoJSON':
+ case 'KML':
+ case 'WKT':
+ return 'Vector';
+ case 'TileVector':
+ case 'MVT':
+ return 'TileVector';
+ default:
+ return 'Tile';
+ }
+ }
+ };
+
+ var createProjection = function(view) {
+ var oProjection;
+
+ switch (view.projection) {
+ case 'pixel':
+ if (!isDefined(view.extent)) {
+ $log.error('[AngularJS - Openlayers] - You must provide the extent of the image ' +
+ 'if using pixel projection');
+ return;
+ }
+ oProjection = new ol.proj.Projection({
+ code: 'pixel',
+ units: 'pixels',
+ extent: view.extent
+ });
+ break;
+ default:
+ oProjection = new ol.proj.get(view.projection);
+ break;
+ }
+
+ return oProjection;
+ };
+
+ var isValidStamenLayer = function(layer) {
+ return ['watercolor', 'terrain', 'toner'].indexOf(layer) !== -1;
+ };
+
+ var createSource = function(source, projection) {
+ var oSource;
+ var pixelRatio;
+ var url;
+ var geojsonFormat = new ol.format.GeoJSON(); // used in various switch stmnts below
+
+ switch (source.type) {
+ case 'MapBox':
+ if (!source.mapId || !source.accessToken) {
+ $log.error('[AngularJS - Openlayers] - MapBox layer requires the map id and the access token');
+ return;
+ }
+ url = 'https://api.tiles.mapbox.com/v4/' + source.mapId + '/{z}/{x}/{y}.png?access_token=' +
+ source.accessToken;
+
+ pixelRatio = window.devicePixelRatio;
+
+ if (pixelRatio > 1) {
+ url = url.replace('.png', '@2x.png');
+ }
+
+ oSource = new ol.source.XYZ({
+ url: url,
+ tileLoadFunction: source.tileLoadFunction,
+ attributions: createAttribution(source),
+ tilePixelRatio: pixelRatio > 1 ? 2 : 1,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+ case 'MapBoxStudio':
+ if (!source.mapId || !source.accessToken || !source.userId) {
+ $log.error('[AngularJS - Openlayers] - MapBox Studio layer requires the map id' +
+ ', user id and the access token');
+ return;
+ }
+ url = 'https://api.mapbox.com/styles/v1/' + source.userId +
+ '/' + source.mapId + '/tiles/{z}/{x}/{y}?access_token=' +
+ source.accessToken;
+
+ pixelRatio = window.devicePixelRatio;
+
+ if (pixelRatio > 1) {
+ url = url.replace('{y}?access_token', '{y}@2x?access_token');
+ }
+
+ oSource = new ol.source.XYZ({
+ url: url,
+ tileLoadFunction: source.tileLoadFunction,
+ attributions: createAttribution(source),
+ tilePixelRatio: pixelRatio > 1 ? 2 : 1,
+ tileSize: source.tileSize || [512, 512],
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+ case 'MVT':
+ if (!source.url) {
+ $log.error('[AngularJS - Openlayers] - MVT layer requires the source url');
+ return;
+ }
+ oSource = new ol.source.VectorTile({
+ attributions: source.attributions || '',
+ format: new ol.format.MVT(),
+ tileGrid: ol.tilegrid.createXYZ({maxZoom: source.maxZoom || 22}),
+ tilePixelRatio: source.tilePixelRatio || 16,
+ url: source.url
+ });
+ break;
+ case 'ImageWMS':
+ if (!source.url || !source.params) {
+ $log.error('[AngularJS - Openlayers] - ImageWMS Layer needs ' +
+ 'valid server url and params properties');
+ }
+ oSource = new ol.source.ImageWMS({
+ url: source.url,
+ imageLoadFunction: source.imageLoadFunction,
+ attributions: createAttribution(source),
+ crossOrigin: (typeof source.crossOrigin === 'undefined') ? 'anonymous' : source.crossOrigin,
+ params: deepCopy(source.params),
+ ratio: source.ratio
+ });
+ break;
+
+ case 'TileWMS':
+ if ((!source.url && !source.urls) || !source.params) {
+ $log.error('[AngularJS - Openlayers] - TileWMS Layer needs ' +
+ 'valid url (or urls) and params properties');
+ }
+
+ var wmsConfiguration = {
+ tileLoadFunction: source.tileLoadFunction,
+ crossOrigin: (typeof source.crossOrigin === 'undefined') ? 'anonymous' : source.crossOrigin,
+ params: deepCopy(source.params),
+ attributions: createAttribution(source),
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ };
+
+ if (source.serverType) {
+ wmsConfiguration.serverType = source.serverType;
+ }
+
+ if (source.url) {
+ wmsConfiguration.url = source.url;
+ }
+
+ if (source.urls) {
+ wmsConfiguration.urls = source.urls;
+ }
+
+ oSource = new ol.source.TileWMS(wmsConfiguration);
+ break;
+
+ case 'WMTS':
+ if ((!source.url && !source.urls) || !source.tileGrid) {
+ $log.error('[AngularJS - Openlayers] - WMTS Layer needs valid url ' +
+ '(or urls) and tileGrid properties');
+ }
+
+ var wmtsConfiguration = {
+ tileLoadFunction: source.tileLoadFunction,
+ projection: projection,
+ layer: source.layer,
+ attributions: createAttribution(source),
+ matrixSet: (source.matrixSet === 'undefined') ? projection : source.matrixSet,
+ format: (source.format === 'undefined') ? 'image/jpeg' : source.format,
+ requestEncoding: (source.requestEncoding === 'undefined') ?
+ 'KVP' : source.requestEncoding,
+ tileGrid: new ol.tilegrid.WMTS({
+ origin: source.tileGrid.origin,
+ resolutions: source.tileGrid.resolutions,
+ matrixIds: source.tileGrid.matrixIds
+ }),
+ style: (source.style === 'undefined') ? 'normal' : source.style,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ };
+
+ if (isDefined(source.url)) {
+ wmtsConfiguration.url = source.url;
+ }
+
+ if (isDefined(source.urls)) {
+ wmtsConfiguration.urls = source.urls;
+ }
+
+ oSource = new ol.source.WMTS(wmtsConfiguration);
+ break;
+
+ case 'OSM':
+ oSource = new ol.source.OSM({
+ tileLoadFunction: source.tileLoadFunction,
+ attributions: createAttribution(source),
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+
+ if (source.url) {
+ oSource.setUrl(source.url);
+ }
+
+ break;
+ case 'BingMaps':
+ if (!source.key) {
+ $log.error('[AngularJS - Openlayers] - You need an API key to show the Bing Maps.');
+ return;
+ }
+
+ var bingConfiguration = {
+ key: source.key,
+ tileLoadFunction: source.tileLoadFunction,
+ attributions: createAttribution(source),
+ imagerySet: source.imagerySet ? source.imagerySet : bingImagerySets[0],
+ culture: source.culture,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ };
+
+ if (source.maxZoom) {
+ bingConfiguration.maxZoom = source.maxZoom;
+ }
+
+ oSource = new ol.source.BingMaps(bingConfiguration);
+ break;
+
+ case 'MapQuest':
+ if (!source.layer || mapQuestLayers.indexOf(source.layer) === -1) {
+ $log.error('[AngularJS - Openlayers] - MapQuest layers needs a valid \'layer\' property.');
+ return;
+ }
+
+ oSource = new ol.source.MapQuest({
+ attributions: createAttribution(source),
+ layer: source.layer,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+
+ break;
+
+ case 'EsriBaseMaps':
+ if (!source.layer || esriBaseLayers.indexOf(source.layer) === -1) {
+ $log.error('[AngularJS - Openlayers] - ESRI layers needs a valid \'layer\' property.');
+ return;
+ }
+
+ var _urlBase = 'https://services.arcgisonline.com/ArcGIS/rest/services/';
+ var _url = _urlBase + source.layer + '/MapServer/tile/{z}/{y}/{x}';
+
+ oSource = new ol.source.XYZ({
+ attributions: createAttribution(source),
+ tileLoadFunction: source.tileLoadFunction,
+ url: _url,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+
+ break;
+
+ case 'TileArcGISRest':
+ if (!source.url) {
+ $log.error('[AngularJS - Openlayers] - TileArcGISRest Layer needs valid url');
+ }
+
+ oSource = new ol.source.TileArcGISRest({
+ attributions: createAttribution(source),
+ tileLoadFunction: source.tileLoadFunction,
+ url: source.url,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+
+ break;
+
+ case 'GeoJSON':
+ if (!(source.geojson || source.url)) {
+ $log.error('[AngularJS - Openlayers] - You need a geojson ' +
+ 'property to add a GeoJSON layer.');
+ return;
+ }
+
+ if (isDefined(source.url)) {
+ oSource = new ol.source.Vector({
+ format: new ol.format.GeoJSON(),
+ url: source.url
+ });
+ } else {
+ oSource = new ol.source.Vector();
+
+ var projectionToUse = projection;
+ var dataProjection; // Projection of geojson data
+ if (isDefined(source.geojson.projection)) {
+ dataProjection = new ol.proj.get(source.geojson.projection);
+ } else {
+ dataProjection = projection; // If not defined, features will not be reprojected.
+ }
+
+ var features = geojsonFormat.readFeatures(
+ source.geojson.object, {
+ featureProjection: projectionToUse.getCode(),
+ dataProjection: dataProjection.getCode()
+ });
+
+ oSource.addFeatures(features);
+ }
+
+ break;
+
+ case 'WKT':
+ if (!(source.wkt) && !(source.wkt.data)) {
+ $log.error('[AngularJS - Openlayers] - You need a WKT ' +
+ 'property to add a WKT format vector layer.');
+ return;
+ }
+
+ oSource = new ol.source.Vector();
+ var wktFormatter = new ol.format.WKT();
+ var wktProjection; // Projection of wkt data
+ if (isDefined(source.wkt.projection)) {
+ wktProjection = new ol.proj.get(source.wkt.projection);
+ } else {
+ wktProjection = projection; // If not defined, features will not be reprojected.
+ }
+
+ var wktFeatures = wktFormatter.readFeatures(
+ source.wkt.data, {
+ featureProjection: projection.getCode(),
+ dataProjection: wktProjection.getCode()
+ });
+
+ oSource.addFeatures(wktFeatures);
+ break;
+
+ case 'JSONP':
+ if (!(source.url)) {
+ $log.error('[AngularJS - Openlayers] - You need an url properly configured to add a JSONP layer.');
+ return;
+ }
+
+ if (isDefined(source.url)) {
+ oSource = new ol.source.ServerVector({
+ format: geojsonFormat,
+ loader: function(/*extent, resolution, projection*/) {
+ var url = source.url +
+ '&outputFormat=text/javascript&format_options=callback:JSON_CALLBACK';
+ $http.jsonp(url, { cache: source.cache})
+ .success(function(response) {
+ oSource.addFeatures(geojsonFormat.readFeatures(response));
+ })
+ .error(function(response) {
+ $log(response);
+ });
+ },
+ projection: projection
+ });
+ }
+ break;
+ case 'TopoJSON':
+ if (!(source.topojson || source.url)) {
+ $log.error('[AngularJS - Openlayers] - You need a topojson ' +
+ 'property to add a TopoJSON layer.');
+ return;
+ }
+
+ if (source.url) {
+ oSource = new ol.source.Vector({
+ format: new ol.format.TopoJSON(),
+ url: source.url
+ });
+ } else {
+ oSource = new ol.source.Vector(angular.extend(source.topojson, {
+ format: new ol.format.TopoJSON()
+ }));
+ }
+ break;
+ case 'TileJSON':
+ oSource = new ol.source.TileJSON({
+ url: source.url,
+ attributions: createAttribution(source),
+ tileLoadFunction: source.tileLoadFunction,
+ crossOrigin: 'anonymous',
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+
+ case 'TileVector':
+ if (!source.url || !source.format) {
+ $log.error('[AngularJS - Openlayers] - TileVector Layer needs valid url and format properties');
+ }
+ oSource = new ol.source.VectorTile({
+ url: source.url,
+ projection: projection,
+ attributions: createAttribution(source),
+ tileLoadFunction: source.tileLoadFunction,
+ format: source.format,
+ tileGrid: new ol.tilegrid.createXYZ({
+ maxZoom: source.maxZoom || 19
+ }),
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+
+ case 'TileTMS':
+ if (!source.url || !source.tileGrid) {
+ $log.error('[AngularJS - Openlayers] - TileTMS Layer needs valid url and tileGrid properties');
+ }
+ oSource = new ol.source.TileImage({
+ url: source.url,
+ maxExtent: source.maxExtent,
+ attributions: createAttribution(source),
+ tileLoadFunction: source.tileLoadFunction,
+ tileGrid: new ol.tilegrid.TileGrid({
+ origin: source.tileGrid.origin,
+ resolutions: source.tileGrid.resolutions
+ }),
+ tileUrlFunction: function(tileCoord) {
+
+ var z = tileCoord[0];
+ var x = tileCoord[1];
+ var y = tileCoord[2]; //(1 << z) - tileCoord[2] - 1;
+
+ if (x < 0 || y < 0) {
+ return '';
+ }
+
+ var url = source.url + z + '/' + x + '/' + y + '.png';
+
+ return url;
+ },
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+ case 'TileImage':
+ oSource = new ol.source.TileImage({
+ url: source.url,
+ attributions: createAttribution(source),
+ tileLoadFunction: source.tileLoadFunction,
+ tileGrid: new ol.tilegrid.TileGrid({
+ origin: source.tileGrid.origin, // top left corner of the pixel projection's extent
+ resolutions: source.tileGrid.resolutions
+ }),
+ tileUrlFunction: function(tileCoord/*, pixelRatio, projection*/) {
+ var z = tileCoord[0];
+ var x = tileCoord[1];
+ var y = -tileCoord[2] - 1;
+ var url = source.url
+ .replace('{z}', z.toString())
+ .replace('{x}', x.toString())
+ .replace('{y}', y.toString());
+ return url;
+ },
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+ case 'KML':
+ var extractStyles = source.extractStyles || false;
+ oSource = new ol.source.Vector({
+ url: source.url,
+ format: new ol.format.KML(),
+ radius: source.radius,
+ extractStyles: extractStyles
+ });
+ break;
+ case 'Stamen':
+ if (!source.layer || !isValidStamenLayer(source.layer)) {
+ $log.error('[AngularJS - Openlayers] - You need a valid Stamen layer.');
+ return;
+ }
+ oSource = new ol.source.Stamen({
+ tileLoadFunction: source.tileLoadFunction,
+ layer: source.layer,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+ case 'ImageStatic':
+ if (!source.url || !angular.isArray(source.imageSize) || source.imageSize.length !== 2) {
+ $log.error('[AngularJS - Openlayers] - You need a image URL to create a ImageStatic layer.');
+ return;
+ }
+
+ oSource = new ol.source.ImageStatic({
+ url: source.url,
+ attributions: createAttribution(source),
+ imageSize: source.imageSize,
+ projection: projection,
+ imageExtent: source.imageExtent ? source.imageExtent : projection.getExtent(),
+ imageLoadFunction: source.imageLoadFunction
+ });
+ break;
+ case 'XYZ':
+ if (!source.url && !source.urls && !source.tileUrlFunction) {
+ $log.error('[AngularJS - Openlayers] - XYZ Layer needs valid url(s) or tileUrlFunction properties');
+ }
+ oSource = new ol.source.XYZ({
+ url: source.url,
+ urls: source.urls,
+ attributions: createAttribution(source),
+ minZoom: source.minZoom,
+ maxZoom: source.maxZoom,
+ projection: source.projection,
+ tileUrlFunction: source.tileUrlFunction,
+ tileLoadFunction: source.tileLoadFunction,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+ case 'Zoomify':
+ if (!source.url || !angular.isArray(source.imageSize) || source.imageSize.length !== 2) {
+ $log.error('[AngularJS - Openlayers] - Zoomify Layer needs valid url and imageSize properties');
+ }
+ oSource = new ol.source.Zoomify({
+ url: source.url,
+ size: source.imageSize,
+ wrapX: source.wrapX !== undefined ? source.wrapX : true
+ });
+ break;
+ }
+
+ // log a warning when no source could be created for the given type
+ if (!oSource) {
+ $log.warn('[AngularJS - Openlayers] - No source could be found for type "' + source.type + '"');
+ }
+
+ return oSource;
+ };
+
+ var deepCopy = function(oldObj) {
+ var newObj = oldObj;
+ if (oldObj && typeof oldObj === 'object') {
+ newObj = Object.prototype.toString.call(oldObj) === '[object Array]' ? [] : {};
+ for (var i in oldObj) {
+ newObj[i] = deepCopy(oldObj[i]);
+ }
+ }
+ return newObj;
+ };
+
+ var createAttribution = function(source) {
+ var attributions = [];
+ if (isDefined(source.attribution)) {
+ // opt-out -> default tries to show an attribution
+ if (!(source.attribution === false)) { // jshint ignore:line
+ // we got some HTML so display that as the attribution
+ attributions.unshift(new ol.Attribution({html: source.attribution}));
+ }
+ } else {
+ // try to infer automatically
+ var attrib = extractAttributionFromSource(source);
+ if (attrib) {
+ attributions.unshift(attrib);
+ }
+ }
+
+ return attributions;
+ };
+
+ var extractAttributionFromSource = function(source) {
+ if (source && source.type) {
+ var ol3SourceInstance = ol.source[source.type];
+ if (ol3SourceInstance) {
+ // iterate over the object's props and try
+ // to find the attribution one as it differs
+ for (var prop in ol3SourceInstance) {
+ if (ol3SourceInstance.hasOwnProperty(prop)) {
+ if (prop.toLowerCase().indexOf('attribution') > -1) {
+ return ol.source[source.type][prop];
+ }
+ }
+ }
+ }
+ }
+
+ return null;
+ };
+
+ var createGroup = function(name) {
+ var olGroup = new ol.layer.Group();
+ olGroup.set('name', name);
+
+ return olGroup;
+ };
+
+ var getGroup = function(layers, name) {
+ var layer;
+
+ angular.forEach(layers, function(l) {
+ if (l instanceof ol.layer.Group && l.get('name') === name) {
+ layer = l;
+ return;
+ }
+ });
+
+ return layer;
+ };
+
+ var addLayerBeforeMarkers = function(layers, layer) {
+ var markersIndex;
+ for (var i = 0; i < layers.getLength(); i++) {
+ var l = layers.item(i);
+
+ if (l.get('markers')) {
+ markersIndex = i;
+ break;
+ }
+ }
+
+ if (isDefined(markersIndex)) {
+ var markers = layers.item(markersIndex);
+ layer.index = markersIndex;
+ layers.setAt(markersIndex, layer);
+ markers.index = layers.getLength();
+ layers.push(markers);
+ } else {
+ layer.index = layers.getLength();
+ layers.push(layer);
+ }
+
+ };
+
+ var removeLayer = function(layers, index) {
+ layers.removeAt(index);
+ for (var i = index; i < layers.getLength(); i++) {
+ var l = layers.item(i);
+ if (l === null) {
+ layers.insertAt(i, null);
+ break;
+ } else {
+ l.index = i;
+ }
+ }
+ };
+
+ return {
+ // Determine if a reference is defined
+ isDefined: isDefined,
+
+ // Determine if a reference is a number
+ isNumber: function(value) {
+ return angular.isNumber(value);
+ },
+
+ createView: function(view) {
+ var projection = createProjection(view);
+
+ var viewConfig = {
+ projection: projection,
+ maxZoom: view.maxZoom,
+ minZoom: view.minZoom
+ };
+
+ if (view.center) {
+ viewConfig.center = view.center;
+ }
+ if (view.extent) {
+ viewConfig.extent = view.extent;
+ }
+ if (view.zoom) {
+ viewConfig.zoom = view.zoom;
+ }
+ if (view.resolutions) {
+ viewConfig.resolutions = view.resolutions;
+ }
+
+ return new ol.View(viewConfig);
+ },
+
+ // Determine if a reference is defined and not null
+ isDefinedAndNotNull: isDefinedAndNotNull,
+
+ // Determine if a reference is a string
+ isString: function(value) {
+ return angular.isString(value);
+ },
+
+ // Determine if a reference is an array
+ isArray: function(value) {
+ return angular.isArray(value);
+ },
+
+ // Determine if a reference is an object
+ isObject: function(value) {
+ return angular.isObject(value);
+ },
+
+ // Determine if two objects have the same properties
+ equals: function(o1, o2) {
+ return angular.equals(o1, o2);
+ },
+
+ isValidCenter: function(center) {
+ return angular.isDefined(center) &&
+ (typeof center.autodiscover === 'boolean' ||
+ angular.isNumber(center.lat) && angular.isNumber(center.lon) ||
+ (angular.isArray(center.coord) && center.coord.length === 2 &&
+ angular.isNumber(center.coord[0]) && angular.isNumber(center.coord[1])) ||
+ (angular.isArray(center.bounds) && center.bounds.length === 4 &&
+ angular.isNumber(center.bounds[0]) && angular.isNumber(center.bounds[1]) &&
+ angular.isNumber(center.bounds[1]) && angular.isNumber(center.bounds[2])));
+ },
+
+ safeApply: function($scope, fn) {
+ var phase = $scope.$root.$$phase;
+ if (phase === '$apply' || phase === '$digest') {
+ $scope.$eval(fn);
+ } else {
+ $scope.$apply(fn);
+ }
+ },
+
+ isSameCenterOnMap: function(center, map) {
+ var urlProj = center.projection || 'EPSG:4326';
+ var urlCenter = [center.lon, center.lat];
+ var mapProj = map.getView().getProjection();
+ var mapCenter = ol.proj.transform(map.getView().getCenter(), mapProj, urlProj);
+ var zoom = map.getView().getZoom();
+ if (mapCenter[1].toFixed(4) === urlCenter[1].toFixed(4) &&
+ mapCenter[0].toFixed(4) === urlCenter[0].toFixed(4) &&
+ zoom === center.zoom) {
+ return true;
+ }
+ return false;
+ },
+
+ setCenter: function(view, projection, newCenter, map) {
+
+ if (map && view.getCenter()) {
+ view.animate({
+ duration: 150,
+ center: view.getCenter()
+ });
+ }
+
+ if (newCenter.projection === projection) {
+ view.setCenter([newCenter.lon, newCenter.lat]);
+ } else {
+ var coord = [newCenter.lon, newCenter.lat];
+ view.setCenter(ol.proj.transform(coord, newCenter.projection, projection));
+ }
+ },
+
+ setZoom: function(view, zoom, map) {
+ view.animate({
+ duration: 150,
+ resolution: map.getView().getResolution(),
+ zoom: zoom
+ });
+ view.setZoom(zoom);
+ },
+
+ isBoolean: function(value) {
+ return typeof value === 'boolean';
+ },
+
+ createStyle: createStyle,
+
+ setMapEvents: function(events, map, scope) {
+ if (isDefined(events) && angular.isArray(events.map)) {
+ for (var i in events.map) {
+ var event = events.map[i];
+ setEvent(map, event, scope);
+ }
+ }
+ },
+
+ setVectorLayerEvents: function(events, map, scope, layerName) {
+ if (isDefined(events) && angular.isArray(events.layers)) {
+ angular.forEach(events.layers, function(eventType) {
+ angular.element(map.getViewport()).on(eventType, function(evt) {
+ var pixel = map.getEventPixel(evt);
+ var feature = map.forEachFeatureAtPixel(pixel, function(feature, olLayer) {
+ // only return the feature if it is in this layer (based on the name)
+ return (isDefinedAndNotNull(olLayer) && olLayer.get('name') === layerName) ? feature : null;
+ });
+ if (isDefinedAndNotNull(feature)) {
+ scope.$emit('openlayers.layers.' + layerName + '.' + eventType, feature, evt);
+ }
+ });
+ });
+ }
+ },
+
+ setViewEvents: function(events, map, scope) {
+ if (isDefined(events) && angular.isArray(events.view)) {
+ var view = map.getView();
+ angular.forEach(events.view, function(eventType) {
+ view.on(eventType, function(event) {
+ scope.$emit('openlayers.view.' + eventType, view, event);
+ });
+ });
+ }
+ },
+
+ detectLayerType: detectLayerType,
+
+ createLayer: function(layer, projection, name, onLayerCreatedFn) {
+ var oLayer;
+ var type = detectLayerType(layer);
+ var oSource = createSource(layer.source, projection);
+ if (!oSource) {
+ return;
+ }
+
+ // handle function overloading. 'name' argument may be
+ // our onLayerCreateFn since name is optional
+ if (typeof(name) === 'function' && !onLayerCreatedFn) {
+ onLayerCreatedFn = name;
+ name = undefined; // reset, otherwise it'll be used later on
+ }
+
+ // Manage clustering
+ if ((type === 'Vector') && layer.clustering) {
+ oSource = new ol.source.Cluster({
+ source: oSource,
+ distance: layer.clusteringDistance
+ });
+ }
+
+ var layerConfig = {};
+
+ // copy over eventual properties set on the passed layerconfig which
+ // can later be retrieved via layer.get('propName');
+ for (var property in layer) {
+ if (layer.hasOwnProperty(property) &&
+ // ignore props like source or those angular might add (starting with $)
+ // don't use startsWith as it is not supported in IE
+ property.indexOf('$', 0) !== 0 &&
+ property.indexOf('source', 0) !== 0 &&
+ property.indexOf('style', 0) !== 0
+ ) {
+ layerConfig[property] = layer[property];
+ }
+ }
+
+ layerConfig.source = oSource;
+
+ // ol.layer.Layer configuration options
+ if (isDefinedAndNotNull(layer.opacity)) {
+ layerConfig.opacity = layer.opacity;
+ }
+ if (isDefinedAndNotNull(layer.visible)) {
+ layerConfig.visible = layer.visible;
+ }
+ if (isDefinedAndNotNull(layer.extent)) {
+ layerConfig.extent = layer.extent;
+ }
+ if (isDefinedAndNotNull(layer.zIndex)) {
+ layerConfig.zIndex = layer.zIndex;
+ }
+ if (isDefinedAndNotNull(layer.minResolution)) {
+ layerConfig.minResolution = layer.minResolution;
+ }
+ if (isDefinedAndNotNull(layer.maxResolution)) {
+ layerConfig.maxResolution = layer.maxResolution;
+ }
+ if (isDefinedAndNotNull(layer.style) && type === 'TileVector') {
+ layerConfig.style = layer.style;
+ }
+
+ switch (type) {
+ case 'Image':
+ oLayer = new ol.layer.Image(layerConfig);
+ break;
+ case 'Tile':
+ oLayer = new ol.layer.Tile(layerConfig);
+ break;
+ case 'Heatmap':
+ oLayer = new ol.layer.Heatmap(layerConfig);
+ break;
+ case 'Vector':
+ oLayer = new ol.layer.Vector(layerConfig);
+ break;
+ case 'TileVector':
+ oLayer = new ol.layer.VectorTile(layerConfig);
+ break;
+ }
+
+ // set a layer name if given
+ if (isDefined(name)) {
+ oLayer.set('name', name);
+ } else if (isDefined(layer.name)) {
+ oLayer.set('name', layer.name);
+ }
+
+ // set custom layer properties if given
+ if (isDefined(layer.customAttributes)) {
+ for (var key in layer.customAttributes) {
+ oLayer.set(key, layer.customAttributes[key]);
+ }
+ }
+
+ // invoke the onSourceCreated callback
+ if (onLayerCreatedFn) {
+ onLayerCreatedFn({
+ oLayer: oLayer
+ });
+ }
+
+ return oLayer;
+ },
+
+ createVectorLayer: function() {
+ return new ol.layer.Vector({
+ source: new ol.source.Vector()
+ });
+ },
+
+ notifyCenterUrlHashChanged: function(scope, center, search) {
+ if (center.centerUrlHash) {
+ var centerUrlHash = center.lat.toFixed(4) + ':' + center.lon.toFixed(4) + ':' + center.zoom;
+ if (!isDefined(search.c) || search.c !== centerUrlHash) {
+ scope.$emit('centerUrlHash', centerUrlHash);
+ }
+ }
+ },
+
+ getControlClasses: getControlClasses,
+
+ detectControls: function(controls) {
+ var actualControls = {};
+ var controlClasses = getControlClasses();
+
+ controls.forEach(function(control) {
+ for (var i in controlClasses) {
+ if (control instanceof controlClasses[i]) {
+ actualControls[i] = control;
+ }
+ }
+ });
+
+ return actualControls;
+ },
+
+ createFeature: function(data, viewProjection) {
+ var geometry;
+
+ switch (data.type) {
+ case 'Polygon':
+ geometry = new ol.geom.Polygon(data.coords);
+ break;
+ default:
+ if (isDefined(data.coord) && data.projection === 'pixel') {
+ geometry = new ol.geom.Point(data.coord);
+ } else {
+ geometry = new ol.geom.Point([data.lon, data.lat]);
+ }
+ break;
+ }
+
+ if (isDefined(data.projection) && data.projection !== 'pixel') {
+ geometry = geometry.transform(data.projection, viewProjection);
+ }
+
+ var feature = new ol.Feature({
+ geometry: geometry
+ });
+
+ if (isDefined(data.style)) {
+ var style = createStyle(data.style);
+ feature.setStyle(style);
+ }
+ return feature;
+ },
+
+ addLayerBeforeMarkers: addLayerBeforeMarkers,
+
+ getGroup: getGroup,
+
+ addLayerToGroup: function(layers, layer, name) {
+ var groupLayer = getGroup(layers, name);
+
+ if (!isDefined(groupLayer)) {
+ groupLayer = createGroup(name);
+ addLayerBeforeMarkers(layers, groupLayer);
+ }
+
+ layer.set('group', name);
+ addLayerBeforeMarkers(groupLayer.getLayers(), layer);
+ },
+
+ removeLayerFromGroup: function(layers, layer, name) {
+ var groupLayer = getGroup(layers, name);
+ layer.set('group');
+ removeLayer(groupLayer.getLayers(), layer.index);
+ },
+
+ removeLayer: removeLayer,
+
+ insertLayer: function(layers, index, layer) {
+ if (layers.getLength() < index) {
+ // fill up with "null layers" till we get to the desired index
+ while (layers.getLength() < index) {
+ var nullLayer = new ol.layer.Image();
+ nullLayer.index = layers.getLength(); // add index which will be equal to the length in this case
+ nullLayer.name = '(null-layer)'; // we need a marker somehow
+ layers.push(nullLayer);
+ }
+ layer.index = index;
+ layers.push(layer);
+ } else {
+ layer.index = index;
+ layers.insertAt(layer.index, layer);
+
+ // remove eventual null layers
+ for (var i = index + 1; i < layers.getLength(); i++) {
+ var l = layers.item(i);
+ if (l.name === '(null-layer)') {
+ layers.removeAt(i);
+ break;
+ } else {
+ l.index = i;
+ }
+ }
+ }
+ },
+
+ createOverlay: function(element, pos) {
+ element.css('display', 'block');
+ var ov = new ol.Overlay({
+ position: pos,
+ element: element[0],
+ positioning: 'center-left'
+ });
+
+ return ov;
+ }
+ };
+});
+
+angular.module('openlayers-directive').factory('olMapDefaults', function($q, olHelpers) {
+
+ var base64icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAGmklEQVRYw' +
+ '7VXeUyTZxjvNnfELFuyIzOabermMZEeQC/OclkO49CpOHXOLJl/CAURuYbQi3KLgEhbrhZ1aDwmaoGq' +
+ 'KII6odATmH/scDFbdC7LvFqOCc+e95s2VG50X/LLm/f4/Z7neY/ne18aANCmAr5E/xZf1uDOkTcGcWR' +
+ '6hl9247tT5U7Y6SNvWsKT63P58qbfeLJG8M5qcgTknrvvrdDbsT7Ml+tv82X6vVxJE33aRmgSyYtcWV' +
+ 'MqX97Yv2JvW39UhRE2HuyBL+t+gK1116ly06EeWFNlAmHxlQE0OMiV6mQCScusKRlhS3QLeVJdl1+23' +
+ 'h5dY4FNB3thrbYboqptEFlphTC1hSpJnbRvxP4NWgsE5Jyz86QNNi/5qSUTGuFk1gu54tN9wuK2wc3o' +
+ '+Wc13RCmsoBwEqzGcZsxsvCSy/9wJKf7UWf1mEY8JWfewc67UUoDbDjQC+FqK4QqLVMGGR9d2wurKzq' +
+ 'Bk3nqIT/9zLxRRjgZ9bqQgub+DdoeCC03Q8j+0QhFhBHR/eP3U/zCln7Uu+hihJ1+bBNffLIvmkyP0g' +
+ 'pBZWYXhKussK6mBz5HT6M1Nqpcp+mBCPXosYQfrekGvrjewd59/GvKCE7TbK/04/ZV5QZYVWmDwH1mF' +
+ '3xa2Q3ra3DBC5vBT1oP7PTj4C0+CcL8c7C2CtejqhuCnuIQHaKHzvcRfZpnylFfXsYJx3pNLwhKzRAw' +
+ 'AhEqG0SpusBHfAKkxw3w4627MPhoCH798z7s0ZnBJ/MEJbZSbXPhER2ih7p2ok/zSj2cEJDd4CAe+5W' +
+ 'YnBCgR2uruyEw6zRoW6/DWJ/OeAP8pd/BGtzOZKpG8oke0SX6GMmRk6GFlyAc59K32OTEinILRJRcha' +
+ 'h8HQwND8N435Z9Z0FY1EqtxUg+0SO6RJ/mmXz4VuS+DpxXC3gXmZwIL7dBSH4zKE50wESf8qwVgrP1E' +
+ 'IlTO5JP9Igu0aexdh28F1lmAEGJGfh7jE6ElyM5Rw/FDcYJjWhbeiBYoYNIpc2FT/SILivp0F1ipDWk' +
+ '4BIEo2VuodEJUifhbiltnNBIXPUFCMpthtAyqws/BPlEF/VbaIxErdxPphsU7rcCp8DohC+GvBIPJS/' +
+ 'tW2jtvTmmAeuNO8BNOYQeG8G/2OzCJ3q+soYB5i6NhMaKr17FSal7GIHheuV3uSCY8qYVuEm1cOzqdW' +
+ 'r7ku/R0BDoTT+DT+ohCM6/CCvKLKO4RI+dXPeAuaMqksaKrZ7L3FE5FIFbkIceeOZ2OcHO6wIhTkNo0' +
+ 'ffgjRGxEqogXHYUPHfWAC/lADpwGcLRY3aeK4/oRGCKYcZXPVoeX/kelVYY8dUGf8V5EBRbgJXT5QIP' +
+ 'hP9ePJi428JKOiEYhYXFBqou2Guh+p/mEB1/RfMw6rY7cxcjTrneI1FrDyuzUSRm9miwEJx8E/gUmql' +
+ 'yvHGkneiwErR21F3tNOK5Tf0yXaT+O7DgCvALTUBXdM4YhC/IawPU+2PduqMvuaR6eoxSwUk75ggqsY' +
+ 'J7VicsnwGIkZBSXKOUww73WGXyqP+J2/b9c+gi1YAg/xpwck3gJuucNrh5JvDPvQr0WFXf0piyt8f8/' +
+ 'WI0hV4pRxxkQZdJDfDJNOAmM0Ag8jyT6hz0WGXWuP94Yh2jcfjmXAGvHCMslRimDHYuHuDsy2QtHuIa' +
+ 'vznhbYURq5R57KpzBBRZKPJi8eQg48h4j8SDdowifdIrEVdU+gbO6QNvRRt4ZBthUaZhUnjlYObNagV' +
+ '3keoeru3rU7rcuceqU1mJBxy+BWZYlNEBH+0eH4vRiB+OYybU2hnblYlTvkHinM4m54YnxSyaZYSF6R' +
+ '3jwgP7udKLGIX6r/lbNa9N6y5MFynjWDtrHd75ZvTYAPO/6RgF0k76mQla3FGq7dO+cH8sKn0Vo7nDl' +
+ 'lwAhqwLPkxrHwWmHJOo+AKJ4rab5OgrM7rVu8eWb2Pu0Dh4eDgXoOfvp7Y7QeqknRmvcTBEyq9m/HQQ' +
+ 'SCSz6LHq3z0yzsNySRfMS253wl2KyRDbcZPcfJKjZmSEOjcxyi+Y8dUOtsIEH6R2wNykdqrkYJ0RV92' +
+ 'H0W58pkfQk7cKevsLK10Py8SdMGfXNXATY+pPbyJR/ET6n9nIfztNtZYRV9XniQu9IA2vOVgy4ir7GC' +
+ 'LVmmd+zjkH0eAF9Po6K61pmCXHxU5rHMYd1ftc3owjwRSVRzLjKvqZEty6cRUD7jGqiOdu5HG6MdHjN' +
+ 'cNYGqfDm5YRzLBBCCDl/2bk8a8gdbqcfwECu62Fg/HrggAAAABJRU5ErkJggg==';
+
+ var _getDefaults = function() {
+ return {
+ view: {
+ projection: 'EPSG:3857',
+ minZoom: undefined,
+ maxZoom: undefined,
+ rotation: 0,
+ extent: undefined
+ },
+ center: {
+ lat: 0,
+ lon: 0,
+ zoom: 1,
+ autodiscover: false,
+ bounds: [],
+ centerUrlHash: false,
+ projection: 'EPSG:4326'
+ },
+ styles: {
+ path: {
+ stroke: {
+ color: 'blue',
+ width: 8
+ }
+ },
+ marker: {
+ image: new ol.style.Icon({
+ anchor: [0.5, 1],
+ anchorXUnits: 'fraction',
+ anchorYUnits: 'fraction',
+ opacity: 0.90,
+ src: base64icon
+ })
+ }
+ },
+ events: {
+ map: [],
+ markers: [],
+ layers: []
+ },
+ controls: {
+ attribution: true,
+ rotate: false,
+ zoom: true
+ },
+ interactions: {
+ mouseWheelZoom: false
+ },
+ renderer: 'canvas'
+ };
+ };
+
+ var isDefined = olHelpers.isDefined;
+ var defaults = {};
+
+ // Get the _defaults dictionary, and override the properties defined by the user
+ return {
+ getDefaults: function(scope) {
+ if (!isDefined(scope)) {
+ for (var i in defaults) {
+ return defaults[i];
+ }
+ }
+ return defaults[scope.$id];
+ },
+
+ setDefaults: function(scope) {
+ var userDefaults = scope.defaults;
+ var scopeId = scope.$id;
+ var newDefaults = _getDefaults();
+
+ if (isDefined(userDefaults)) {
+
+ if (isDefined(userDefaults.layers)) {
+ newDefaults.layers = angular.copy(userDefaults.layers);
+ }
+
+ if (isDefined(userDefaults.controls)) {
+ newDefaults.controls = angular.copy(userDefaults.controls);
+ }
+
+ if (isDefined(userDefaults.events)) {
+ newDefaults.events = angular.copy(userDefaults.events);
+ }
+
+ if (isDefined(userDefaults.interactions)) {
+ newDefaults.interactions = angular.copy(userDefaults.interactions);
+ }
+
+ if (isDefined(userDefaults.renderer)) {
+ newDefaults.renderer = userDefaults.renderer;
+ }
+
+ if (isDefined(userDefaults.view)) {
+ newDefaults.view.maxZoom = userDefaults.view.maxZoom || newDefaults.view.maxZoom;
+ newDefaults.view.minZoom = userDefaults.view.minZoom || newDefaults.view.minZoom;
+ newDefaults.view.projection = userDefaults.view.projection || newDefaults.view.projection;
+ newDefaults.view.extent = userDefaults.view.extent || newDefaults.view.extent;
+ newDefaults.view.resolutions = userDefaults.view.resolutions || newDefaults.view.resolutions;
+ }
+
+ if (isDefined(userDefaults.styles)) {
+ newDefaults.styles = angular.extend(newDefaults.styles, userDefaults.styles);
+ }
+
+ if (isDefined(userDefaults.loadTilesWhileAnimating)) {
+ newDefaults.loadTilesWhileAnimating = userDefaults.loadTilesWhileAnimating;
+ }
+
+ if (isDefined(userDefaults.loadTilesWhileInteracting)) {
+ newDefaults.loadTilesWhileInteracting = userDefaults.loadTilesWhileInteracting;
+ }
+ }
+
+ defaults[scopeId] = newDefaults;
+ return newDefaults;
+ }
+ };
+});
+
+}));
\ No newline at end of file
diff --git a/package.json b/package.json
index ba132c9c..1053ed32 100644
--- a/package.json
+++ b/package.json
@@ -1,65 +1,70 @@
{
- "name": "angular-openlayers-directive",
- "author": "David Rubert ",
- "contributors": ["Juri Strumpflohner "],
- "description":
- "angular-openlayers-directive - An AngularJS directive to easily interact with Openlayers maps",
- "homepage": "http://tombatossals.github.io/angular-openlayers-directive/",
- "repository": {
- "type": "git",
- "url": "https://github.com/tombatossals/angular-openlayers-directive"
- },
- "keywords": ["angularjs", "openlayers", "cli"],
- "license": "MIT",
- "dependencies": {
- "angular": "~1.4.8",
- "angular-sanitize": "~1.4.8",
- "openlayers": "4.3.4"
- },
- "devDependencies": {
- "grunt": "~0.4.5",
- "grunt-bower-task": "~0.4.0",
- "grunt-bump": "^0.3.0",
- "grunt-contrib-concat": "~0.5.0",
- "grunt-contrib-connect": "^0.11.2",
- "grunt-contrib-jshint": "^0.11.0",
- "grunt-contrib-uglify": "^0.9.1",
- "grunt-contrib-watch": "~0.6.1",
- "grunt-conventional-changelog": "^4.1.0",
- "grunt-jscs": "^2.1.0",
- "grunt-karma": "^0.12.0",
- "grunt-karma-coveralls": "~2.5.3",
- "grunt-ng-annotate": "^1.0.1",
- "grunt-open": "~0.2.3",
- "grunt-protractor-runner": "^2.0.0",
- "grunt-shell": "~1.1.1",
- "grunt-shell-spawn": "~0.3.0",
- "jasmine-core": "^2.2.0",
- "karma": "^0.13.9",
- "karma-chrome-launcher": "^0.2.0",
- "karma-coffee-preprocessor": "^0.3.0",
- "karma-coverage": "^0.5.0",
- "karma-firefox-launcher": "~0.1.4",
- "karma-html2js-preprocessor": "~0.1.0",
- "karma-jasmine": "~0.3.3",
- "karma-phantomjs-launcher": "^0.2.1",
- "karma-requirejs": "~0.2.2",
- "karma-script-launcher": "~0.1.0",
- "load-grunt-config": "^0.17.1",
- "matchdep": "~0.3.0",
- "phantomjs": "^2.1.0",
- "protractor": "~2.5.0",
- "publish-latest": "^1.1.2",
- "semantic-release": "^4.3.5"
- },
- "scripts": {
- "test": "grunt build && grunt karma:unit",
- "test.watch": "grunt build && grunt karma:dev",
- "build": "grunt build",
- "prepublish": "npm run build",
- "postpublish": "publish-latest",
- "semantic-release":
- "semantic-release pre && npm publish && semantic-release post"
- },
- "main": "dist/angular-openlayers-directive"
-}
+ "name": "angular-openlayers-directive",
+ "author": "David Rubert ",
+ "contributors": [
+ "Juri Strumpflohner "
+ ],
+ "description": "angular-openlayers-directive - An AngularJS directive to easily interact with Openlayers maps",
+ "homepage": "http://tombatossals.github.io/angular-openlayers-directive/",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/tombatossals/angular-openlayers-directive"
+ },
+ "keywords": [
+ "angularjs",
+ "openlayers",
+ "cli"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "angular": "~1.4.8",
+ "angular-sanitize": "~1.4.8",
+ "openlayers": "4.3.4"
+ },
+ "devDependencies": {
+ "grunt": "~0.4.5",
+ "grunt-bower-task": "~0.4.0",
+ "grunt-bump": "^0.3.0",
+ "grunt-contrib-concat": "~0.5.0",
+ "grunt-contrib-connect": "^0.11.2",
+ "grunt-contrib-jshint": "^0.11.0",
+ "grunt-contrib-uglify": "^0.9.1",
+ "grunt-contrib-watch": "~0.6.1",
+ "grunt-conventional-changelog": "^4.1.0",
+ "grunt-jscs": "^2.1.0",
+ "grunt-karma": "^0.12.0",
+ "grunt-karma-coveralls": "~2.5.3",
+ "grunt-ng-annotate": "^1.0.1",
+ "grunt-open": "~0.2.3",
+ "grunt-protractor-runner": "^2.0.0",
+ "grunt-shell": "~1.1.1",
+ "grunt-shell-spawn": "~0.3.0",
+ "jasmine-core": "^2.2.0",
+ "karma": "^0.13.9",
+ "karma-chrome-launcher": "^0.2.0",
+ "karma-coffee-preprocessor": "^0.3.0",
+ "karma-coverage": "^0.5.0",
+ "karma-firefox-launcher": "~0.1.4",
+ "karma-html2js-preprocessor": "~0.1.0",
+ "karma-jasmine": "~0.3.3",
+ "karma-phantomjs-launcher": "^0.2.1",
+ "karma-requirejs": "~0.2.2",
+ "karma-script-launcher": "~0.1.0",
+ "load-grunt-config": "^0.17.1",
+ "matchdep": "~0.3.0",
+ "phantomjs": "^2.1.0",
+ "protractor": "~2.5.0",
+ "publish-latest": "^1.1.2",
+ "semantic-release": "^4.3.5"
+ },
+ "scripts": {
+ "test": "grunt build && grunt karma:unit",
+ "test.watch": "grunt build && grunt karma:dev",
+ "build": "grunt build",
+ "prepublish": "npm run build",
+ "postpublish": "publish-latest",
+ "semantic-release": "semantic-release pre && npm publish && semantic-release post"
+ },
+ "main": "dist/angular-openlayers-directive",
+ "version": "2.1.0"
+}
\ No newline at end of file