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..c5ed0972 --- /dev/null +++ b/dist/angular-openlayers-directive.js @@ -0,0 +1,2544 @@ +(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)) { + 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)) { + attributions.unshift(new ol.Attribution({html: source.attribution})); + } + return attributions; + }; + + 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 $) + !property.startsWith('$') && + !property.startsWith('source') && + !property.startsWith('style') + ) { + 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..2be34182 --- /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 15-02-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(h.olCenter.search("-")!==-1)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);var t;if(s.centerUrlHash===!0){var u=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=u(),r.$on("$locationChangeSuccess",function(){var a=u();a&&!o(a,f)&&j(r,function(b){b.center.lat=a.lat,b.center.lon=a.lon,b.center.zoom=a.zoom})})}var v;r.$watchCollection("center",function(c){if(c){if(c.projection||(c.projection=g.center.projection),c.autodiscover)return v||(v=new a.Geolocation({projection:a.proj.get(c.projection)}),v.on("change",function(){if(c.autodiscover){var a=v.getPosition();j(r,function(b){b.center.lat=a[1],b.center.lon=a[0],b.center.zoom=12,b.center.autodiscover=!1,v.setTracking(!1)})}})),void v.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 w=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(w)})})}}}]),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.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(f===-1){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(e===-1)throw Error("This map has no markers");var f=b[e].instScopes,g=f.indexOf(c);if(g===-1)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;return 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||!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)?void 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&&d.label.show===!0&&(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&&d.label.show===!1&&(e.removeOverlay(p),p=void 0),d.label&&d.label.show===!1&&d.label.showOnMouseOver&&e.getViewport().addEventListener("mousemove",d.handleInteraction),(d.label&&d.label.show===!1&&d.label.showOnMouseClick||i.hasOwnProperty("ngClick"))&&(e.getViewport().addEventListener("click",d.handleTapInteraction),e.getViewport().querySelector("canvas.ol-unselectable").addEventListener("touchend",d.handleTapInteraction)),d.label&&d.label.show===!1&&d.label.keepOneOverlayVisible&&(e.getViewport().addEventListener("mousemove",d.showAtLeastOneOverlay),e.getViewport().addEventListener("click",d.removeAllOverlays))))},!0):(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))))})}}}]),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){var d=c(a,b);a[d].resolvedDefer=!0},f=function(a,d){var e,f=c(a,d);return angular.isDefined(a[f])&&a[f].resolvedDefer!==!0?e=a[f].defer:(e=b.defer(),a[f]={defer:e,resolvedDefer:!1}),e},g=function(a,b){var d,e=c(a,b);return d=angular.isDefined(a[e])&&a[e].resolvedDefer!==!1?a[e].defer:f(a,b)};this.setMap=function(a,b){var c=f(d,b);c.resolve(a),e(d,b)},this.getMap=function(a){var b=g(d,a);return b.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];return f&&i&&i.prototype instanceof l[c]?(console.assert(1===h.length,"Extra parameters for "+c),e=a(d,b),m(e,i)):(e[b]=a(d,b),void("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":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"}},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["watercolor","terrain","toner"].indexOf(a)!==-1},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:"undefined"==typeof 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:"undefined"==typeof 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||j.indexOf(b.layer)===-1)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||k.indexOf(b.layer)===-1)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];if(d<0||e<0)return"";var f=b.url+c+"/"+d+"/"+e+".png";return f},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,f=b.url.replace("{z}",c.toString()).replace("{x}",d.toString()).replace("{y}",e.toString());return f},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=[];return e(b.attribution)&&c.unshift(new a.Attribution({html:b.attribution})),c},u=function(b){var c=new a.layer.Group;return c.set("name",b),c},v=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},w=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(h.olCenter.search("-")!==-1)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);var t;if(s.centerUrlHash===!0){var u=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=u(),r.$on("$locationChangeSuccess",function(){var a=u();a&&!o(a,f)&&j(r,function(b){b.center.lat=a.lat,b.center.lon=a.lon,b.center.zoom=a.zoom})})}var v;r.$watchCollection("center",function(c){if(c){if(c.projection||(c.projection=g.center.projection),c.autodiscover)return v||(v=new a.Geolocation({projection:a.proj.get(c.projection)}),v.on("change",function(){if(c.autodiscover){var a=v.getPosition();j(r,function(b){b.center.lat=a[1],b.center.lon=a[0],b.center.zoom=12,b.center.autodiscover=!1,v.setTracking(!1)})}})),void v.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 w=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(w)})})}}}]),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.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(f===-1){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(e===-1)throw Error("This map has no markers");var f=b[e].instScopes,g=f.indexOf(c);if(g===-1)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;return 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||!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)?void 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&&d.label.show===!0&&(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&&d.label.show===!1&&(e.removeOverlay(p),p=void 0),d.label&&d.label.show===!1&&d.label.showOnMouseOver&&e.getViewport().addEventListener("mousemove",d.handleInteraction),(d.label&&d.label.show===!1&&d.label.showOnMouseClick||i.hasOwnProperty("ngClick"))&&(e.getViewport().addEventListener("click",d.handleTapInteraction),e.getViewport().querySelector("canvas.ol-unselectable").addEventListener("touchend",d.handleTapInteraction)),d.label&&d.label.show===!1&&d.label.keepOneOverlayVisible&&(e.getViewport().addEventListener("mousemove",d.showAtLeastOneOverlay),e.getViewport().addEventListener("click",d.removeAllOverlays))))},!0):(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))))})}}}]),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){var d=c(a,b);a[d].resolvedDefer=!0},f=function(a,d){var e,f=c(a,d);return angular.isDefined(a[f])&&a[f].resolvedDefer!==!0?e=a[f].defer:(e=b.defer(),a[f]={defer:e,resolvedDefer:!1}),e},g=function(a,b){var d,e=c(a,b);return d=angular.isDefined(a[e])&&a[e].resolvedDefer!==!1?a[e].defer:f(a,b)};this.setMap=function(a,b){var c=f(d,b);c.resolve(a),e(d,b)},this.getMap=function(a){var b=g(d,a);return b.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];return f&&i&&i.prototype instanceof l[c]?(console.assert(1===h.length,"Extra parameters for "+c),e=a(d,b),m(e,i)):(e[b]=a(d,b),void("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":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"}},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["watercolor","terrain","toner"].indexOf(a)!==-1},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:"undefined"==typeof 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:"undefined"==typeof 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||j.indexOf(b.layer)===-1)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||k.indexOf(b.layer)===-1)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];if(d<0||e<0)return"";var f=b.url+c+"/"+d+"/"+e+".png";return f},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,f=b.url.replace("{z}",c.toString()).replace("{x}",d.toString()).replace("{y}",e.toString());return f},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=[];return e(b.attribution)&&c.unshift(new a.Attribution({html:b.attribution})),c},u=function(b){var c=new a.layer.Group;return c.set("name",b),c},v=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},w=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)) { + 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)) { + attributions.unshift(new ol.Attribution({html: source.attribution})); + } + return attributions; + }; + + 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 $) + !property.startsWith('$') && + !property.startsWith('source') && + !property.startsWith('style') + ) { + 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 992e6382..ef74d8ad 100644 --- a/package.json +++ b/package.json @@ -64,5 +64,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.19.1" +} \ No newline at end of file