From 6c62f050d8ce9c45883f55c1ac176f2ea544971b Mon Sep 17 00:00:00 2001 From: Jefferson Neves Date: Thu, 10 Aug 2017 04:01:39 -0300 Subject: [PATCH 1/3] docs(Contributing): Add a mention to bower install (#380) --- CONTRIBUTING.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3fd1ee5a..6524ac4e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -89,7 +89,7 @@ Software development life-cycle ------------------------------- There are a some Grunt tasks defined to ease the development cycle. Let's see how to use them: -First, make sure you have npm and grunt-cli installed globally. Let's install the dependencies. +First, make sure you have npm, bower and grunt-cli installed globally. Let's install the dependencies. ``` # Inside the project dir, install the dependencies @@ -110,6 +110,17 @@ npm http GET https://registry.npmjs.org/grunt-ngmin ├── useragent@2.0.7 (lru-cache@2.2.4) ├── connect@2.8.8 (methods@0.0.1, uid2@0.0.2, fresh@0.2.0, cookie@0.1.0, ..., send@0.1.4) └── socket.io@0.9.16 (base64id@0.1.0, policyfile@0.0.4, redis@0.7.3, socket.io-client@0.9.16) +... +$ bower install +... +bower jquery#* install jquery#3.2.1 +bower angular#1.4.9 install angular#1.4.9 +bower angular-sanitize#1.4.9 install angular-sanitize#1.4.9 +bower js-polyfills#^0.1.20 install js-polyfills#0.1.34 +bower angular-mocks#1.4.9 install angular-mocks#1.4.9 +bower angular-route#1.4.9 install angular-route#1.4.9 +bower openlayers#* install openlayers#e-tag:a8ff409d6 +... $ ``` From 33008fe81518564326b461d1c621104c4b27e43b Mon Sep 17 00:00:00 2001 From: Juri Strumpflohner Date: Thu, 24 Aug 2017 09:06:51 +0200 Subject: [PATCH 2/3] feat(olHelper): auto-detection of attribution (#382) This change tries to infer the attribution given the source configuration of the ol3 object. Apparently each OL3 source type might have different properties for storing the attribution. Thus the code needs to iterate over them and "search" for the presence of it. WARNING: This also slightly changes the default behavior, meaning while currently the attribution had to be enabled explicitly you now have to opt-out by setting the `source.attribution = false`. --- src/services/olHelpers.js | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/services/olHelpers.js b/src/services/olHelpers.js index 45bdd962..00020d29 100644 --- a/src/services/olHelpers.js +++ b/src/services/olHelpers.js @@ -680,11 +680,41 @@ angular.module('openlayers-directive').factory('olHelpers', function($q, $log, $ var createAttribution = function(source) { var attributions = []; if (isDefined(source.attribution)) { - attributions.unshift(new ol.Attribution({html: 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); From 7eeba47af45ae5e5beb3aee77ef1878161613c51 Mon Sep 17 00:00:00 2001 From: David Rubert Date: Thu, 24 Aug 2017 07:10:52 +0000 Subject: [PATCH 3/3] v1.20.0 --- dist/angular-openlayers-directive.css | 58 + dist/angular-openlayers-directive.js | 2581 +++++++++++++++++ dist/angular-openlayers-directive.min.js | 32 + ...ular-openlayers-directive.min.no-header.js | 3 + dist/angular-openlayers-directive.pre.js | 2581 +++++++++++++++++ package.json | 5 +- 6 files changed, 5258 insertions(+), 2 deletions(-) create mode 100644 dist/angular-openlayers-directive.css create mode 100644 dist/angular-openlayers-directive.js create mode 100644 dist/angular-openlayers-directive.min.js create mode 100644 dist/angular-openlayers-directive.min.no-header.js create mode 100644 dist/angular-openlayers-directive.pre.js 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..9f401332 --- /dev/null +++ b/dist/angular-openlayers-directive.js @@ -0,0 +1,2581 @@ +(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); + }); + + // 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() { + map.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() { + map.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 = 'http://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.tileUrlFunction) { + $log.error('[AngularJS - Openlayers] - XYZ Layer needs valid url or tileUrlFunction properties'); + } + oSource = new ol.source.XYZ({ + url: source.url, + 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()) { + var pan = ol.animation.pan({ + duration: 150, + source: (view.getCenter()) + }); + map.beforeRender(pan); + } + + 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) { + var z = ol.animation.zoom({ + duration: 150, + resolution: map.getView().getResolution() + }); + map.beforeRender(z); + 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..ba20c654 --- /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 24-08-2017 */ +!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)}),!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(){f.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(a,b,c,d,e){return{restrict:"A",scope:!1,replace:!1,require:"openlayers",link:function(a,b,c,f){var g=f.getOpenlayersScope(),h=e.isNumber,i=e.safeApply,j=e.createView;g.getMap().then(function(a){var b=d.getDefaults(g),c=g.view;c.projection||(c.projection=b.view.projection),c.maxZoom||(c.maxZoom=b.view.maxZoom),c.minZoom||(c.minZoom=b.view.minZoom),c.rotation||(c.rotation=b.view.rotation);var e=j(c);a.setView(e),g.$watchCollection("view",function(a){h(a.rotation)&&e.setRotation(a.rotation)});var f=e.on("change:rotation",function(){i(g,function(b){b.view.rotation=a.getView().getRotation()})});g.$on("$destroy",function(){a.unByKey(f)})})}}}]),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="http://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.tileUrlFunction||c.error("[AngularJS - Openlayers] - XYZ Layer needs valid url or tileUrlFunction properties"),g=new a.source.XYZ({url:b.url,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)}),!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(){f.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(a,b,c,d,e){return{restrict:"A",scope:!1,replace:!1,require:"openlayers",link:function(a,b,c,f){var g=f.getOpenlayersScope(),h=e.isNumber,i=e.safeApply,j=e.createView;g.getMap().then(function(a){var b=d.getDefaults(g),c=g.view;c.projection||(c.projection=b.view.projection),c.maxZoom||(c.maxZoom=b.view.maxZoom),c.minZoom||(c.minZoom=b.view.minZoom),c.rotation||(c.rotation=b.view.rotation);var e=j(c);a.setView(e),g.$watchCollection("view",function(a){h(a.rotation)&&e.setRotation(a.rotation)});var f=e.on("change:rotation",function(){i(g,function(b){b.view.rotation=a.getView().getRotation()})});g.$on("$destroy",function(){a.unByKey(f)})})}}}]),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="http://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.tileUrlFunction||c.error("[AngularJS - Openlayers] - XYZ Layer needs valid url or tileUrlFunction properties"),g=new a.source.XYZ({url:b.url,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); + }); + + // 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() { + map.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() { + map.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 = 'http://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.tileUrlFunction) { + $log.error('[AngularJS - Openlayers] - XYZ Layer needs valid url or tileUrlFunction properties'); + } + oSource = new ol.source.XYZ({ + url: source.url, + 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()) { + var pan = ol.animation.pan({ + duration: 150, + source: (view.getCenter()) + }); + map.beforeRender(pan); + } + + 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) { + var z = ol.animation.zoom({ + duration: 150, + resolution: map.getView().getResolution() + }); + map.beforeRender(z); + 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 0f7aa16f..68e55764 100644 --- a/package.json +++ b/package.json @@ -65,5 +65,6 @@ "postpublish": "publish-latest", "semantic-release": "semantic-release pre && npm publish && semantic-release post" }, - "main": "dist/angular-openlayers-directive" -} + "main": "dist/angular-openlayers-directive", + "version": "1.20.0" +} \ No newline at end of file