UNPKG

ngmap

Version:
1,517 lines (1,342 loc) 111 kB
(function(root, factory) { if (typeof exports === "object") { module.exports = factory(require('angular')); } else if (typeof define === "function" && define.amd) { define(['angular'], factory); } else{ factory(root.angular); } }(this, function(angular) { /** * AngularJS Google Maps Ver. 1.18.4 * * The MIT License (MIT) * * Copyright (c) 2014, 2015, 1016 Allen Kim * * 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.module('ngMap', []); /** * @ngdoc controller * @name MapController */ (function() { 'use strict'; var Attr2MapOptions; var __MapController = function( $scope, $element, $attrs, $parse, $interpolate, _Attr2MapOptions_, NgMap, NgMapPool, escapeRegExp ) { Attr2MapOptions = _Attr2MapOptions_; var vm = this; var exprStartSymbol = $interpolate.startSymbol(); var exprEndSymbol = $interpolate.endSymbol(); vm.mapOptions; /** @memberof __MapController */ vm.mapEvents; /** @memberof __MapController */ vm.eventListeners; /** @memberof __MapController */ /** * Add an object to the collection of group * @memberof __MapController * @function addObject * @param groupName the name of collection that object belongs to * @param obj an object to add into a collection, i.e. marker, shape */ vm.addObject = function(groupName, obj) { if (vm.map) { vm.map[groupName] = vm.map[groupName] || {}; var len = Object.keys(vm.map[groupName]).length; vm.map[groupName][obj.id || len] = obj; if (vm.map instanceof google.maps.Map) { //infoWindow.setMap works like infoWindow.open if (groupName != "infoWindows" && obj.setMap) { obj.setMap && obj.setMap(vm.map); } if (obj.centered && obj.position) { vm.map.setCenter(obj.position); } (groupName == 'markers') && vm.objectChanged('markers'); (groupName == 'customMarkers') && vm.objectChanged('customMarkers'); } } }; /** * Delete an object from the collection and remove from map * @memberof __MapController * @function deleteObject * @param {Array} objs the collection of objects. i.e., map.markers * @param {Object} obj the object to be removed. i.e., marker */ vm.deleteObject = function(groupName, obj) { /* delete from group */ if (obj.map) { var objs = obj.map[groupName]; for (var name in objs) { if (objs[name] === obj) { void 0; google.maps.event.clearInstanceListeners(obj); delete objs[name]; } } /* delete from map */ obj.map && obj.setMap && obj.setMap(null); (groupName == 'markers') && vm.objectChanged('markers'); (groupName == 'customMarkers') && vm.objectChanged('customMarkers'); } }; /** * @memberof __MapController * @function observeAttrSetObj * @param {Hash} orgAttrs attributes before its initialization * @param {Hash} attrs attributes after its initialization * @param {Object} obj map object that an action is to be done * @description watch changes of attribute values and * do appropriate action based on attribute name */ vm.observeAttrSetObj = function(orgAttrs, attrs, obj) { if (attrs.noWatcher) { return false; } var attrsToObserve = Attr2MapOptions.getAttrsToObserve(orgAttrs); for (var i=0; i<attrsToObserve.length; i++) { var attrName = attrsToObserve[i]; attrs.$observe(attrName, NgMap.observeAndSet(attrName, obj)); } }; /** * @memberof __MapController * @function zoomToIncludeMarkers */ vm.zoomToIncludeMarkers = function() { // Only fit to bounds if we have any markers // object.keys is supported in all major browsers (IE9+) if ((vm.map.markers != null && Object.keys(vm.map.markers).length > 0) || (vm.map.customMarkers != null && Object.keys(vm.map.customMarkers).length > 0)) { var bounds = new google.maps.LatLngBounds(); for (var k1 in vm.map.markers) { bounds.extend(vm.map.markers[k1].getPosition()); } for (var k2 in vm.map.customMarkers) { bounds.extend(vm.map.customMarkers[k2].getPosition()); } if (vm.mapOptions.maximumZoom) { vm.enableMaximumZoomCheck = true; //enable zoom check after resizing for markers } vm.map.fitBounds(bounds); } }; /** * @memberof __MapController * @function objectChanged * @param {String} group name of group e.g., markers */ vm.objectChanged = function(group) { if ( vm.map && (group == 'markers' || group == 'customMarkers') && vm.map.zoomToIncludeMarkers == 'auto' ) { vm.zoomToIncludeMarkers(); } }; /** * @memberof __MapController * @function initializeMap * @description * . initialize Google map on <div> tag * . set map options, events, and observers * . reset zoom to include all (custom)markers */ vm.initializeMap = function() { var mapOptions = vm.mapOptions, mapEvents = vm.mapEvents; var lazyInitMap = vm.map; //prepared for lazy init vm.map = NgMapPool.getMapInstance($element[0]); NgMap.setStyle($element[0]); // set objects for lazyInit if (lazyInitMap) { /** * rebuild mapOptions for lazyInit * because attributes values might have been changed */ var filtered = Attr2MapOptions.filter($attrs); var options = Attr2MapOptions.getOptions(filtered); var controlOptions = Attr2MapOptions.getControlOptions(filtered); mapOptions = angular.extend(options, controlOptions); void 0; for (var group in lazyInitMap) { var groupMembers = lazyInitMap[group]; //e.g. markers if (typeof groupMembers == 'object') { for (var id in groupMembers) { vm.addObject(group, groupMembers[id]); } } } vm.map.showInfoWindow = vm.showInfoWindow; vm.map.hideInfoWindow = vm.hideInfoWindow; } // set options mapOptions.zoom = (mapOptions.zoom && !isNaN(mapOptions.zoom)) ? +mapOptions.zoom : 15; var center = mapOptions.center; var exprRegExp = new RegExp(escapeRegExp(exprStartSymbol) + '.*' + escapeRegExp(exprEndSymbol)); if (!mapOptions.center || ((typeof center === 'string') && center.match(exprRegExp)) ) { mapOptions.center = new google.maps.LatLng(0, 0); } else if( (typeof center === 'string') && center.match(/^[0-9.-]*,[0-9.-]*$/) ){ var lat = parseFloat(center.split(',')[0]); var lng = parseFloat(center.split(',')[1]); mapOptions.center = new google.maps.LatLng(lat, lng); } else if (!(center instanceof google.maps.LatLng)) { var geoCenter = mapOptions.center; delete mapOptions.center; NgMap.getGeoLocation(geoCenter, mapOptions.geoLocationOptions). then(function (latlng) { vm.map.setCenter(latlng); var geoCallback = mapOptions.geoCallback; geoCallback && $parse(geoCallback)($scope); }, function () { if (mapOptions.geoFallbackCenter) { vm.map.setCenter(mapOptions.geoFallbackCenter); } }); } vm.map.setOptions(mapOptions); // set events for (var eventName in mapEvents) { var event = mapEvents[eventName]; var listener = google.maps.event.addListener(vm.map, eventName, event); vm.eventListeners[eventName] = listener; } // set observers vm.observeAttrSetObj(orgAttrs, $attrs, vm.map); vm.singleInfoWindow = mapOptions.singleInfoWindow; google.maps.event.trigger(vm.map, 'resize'); google.maps.event.addListenerOnce(vm.map, "idle", function () { NgMap.addMap(vm); if (mapOptions.zoomToIncludeMarkers) { vm.zoomToIncludeMarkers(); } //TODO: it's for backward compatibiliy. will be removed $scope.map = vm.map; $scope.$emit('mapInitialized', vm.map); //callback if ($attrs.mapInitialized) { $parse($attrs.mapInitialized)($scope, {map: vm.map}); } }); //add maximum zoom listeners if zoom-to-include-markers and and maximum-zoom are valid attributes if (mapOptions.zoomToIncludeMarkers && mapOptions.maximumZoom) { google.maps.event.addListener(vm.map, 'zoom_changed', function() { if (vm.enableMaximumZoomCheck == true) { vm.enableMaximumZoomCheck = false; google.maps.event.addListenerOnce(vm.map, 'bounds_changed', function() { vm.map.setZoom(Math.min(mapOptions.maximumZoom, vm.map.getZoom())); }); } }); } }; $scope.google = google; //used by $scope.eval to avoid eval() /** * get map options and events */ var orgAttrs = Attr2MapOptions.orgAttributes($element); var filtered = Attr2MapOptions.filter($attrs); var options = Attr2MapOptions.getOptions(filtered, {scope: $scope}); var controlOptions = Attr2MapOptions.getControlOptions(filtered); var mapOptions = angular.extend(options, controlOptions); var mapEvents = Attr2MapOptions.getEvents($scope, filtered); void 0; Object.keys(mapEvents).length && void 0; vm.mapOptions = mapOptions; vm.mapEvents = mapEvents; vm.eventListeners = {}; if (options.lazyInit) { // allows controlled initialization // parse angular expression for dynamic ids if (!!$attrs.id && // starts with, at position 0 $attrs.id.indexOf(exprStartSymbol, 0) === 0 && // ends with $attrs.id.indexOf(exprEndSymbol, $attrs.id.length - exprEndSymbol.length) !== -1) { var idExpression = $attrs.id.slice(2,-2); var mapId = $parse(idExpression)($scope); } else { var mapId = $attrs.id; } vm.map = {id: mapId}; //set empty, not real, map NgMap.addMap(vm); } else { vm.initializeMap(); } //Trigger Resize if(options.triggerResize) { google.maps.event.trigger(vm.map, 'resize'); } $element.bind('$destroy', function() { NgMapPool.returnMapInstance(vm.map); NgMap.deleteMap(vm); }); }; // __MapController __MapController.$inject = [ '$scope', '$element', '$attrs', '$parse', '$interpolate', 'Attr2MapOptions', 'NgMap', 'NgMapPool', 'escapeRegexpFilter' ]; angular.module('ngMap').controller('__MapController', __MapController); })(); /** * @ngdoc directive * @name bicycling-layer * @param Attr2Options {service} * convert html attribute to Google map api options * @description * Requires: map directive * Restrict To: Element * * @example * * <map zoom="13" center="34.04924594193164, -118.24104309082031"> * <bicycling-layer></bicycling-layer> * </map> */ (function() { 'use strict'; var parser; var linkFunc = function(scope, element, attrs, mapController) { mapController = mapController[0]||mapController[1]; var orgAttrs = parser.orgAttributes(element); var filtered = parser.filter(attrs); var options = parser.getOptions(filtered, {scope: scope}); var events = parser.getEvents(scope, filtered); void 0; var layer = getLayer(options, events); mapController.addObject('bicyclingLayers', layer); mapController.observeAttrSetObj(orgAttrs, attrs, layer); //observers element.bind('$destroy', function() { mapController.deleteObject('bicyclingLayers', layer); }); }; var getLayer = function(options, events) { var layer = new google.maps.BicyclingLayer(options); for (var eventName in events) { google.maps.event.addListener(layer, eventName, events[eventName]); } return layer; }; var bicyclingLayer= function(Attr2MapOptions) { parser = Attr2MapOptions; return { restrict: 'E', require: ['?^map','?^ngMap'], link: linkFunc }; }; bicyclingLayer.$inject = ['Attr2MapOptions']; angular.module('ngMap').directive('bicyclingLayer', bicyclingLayer); })(); /** * @ngdoc directive * @name custom-control * @param Attr2Options {service} convert html attribute to Google map api options * @param $compile {service} AngularJS $compile service * @description * Build custom control and set to the map with position * * Requires: map directive * * Restrict To: Element * * @attr {String} position position of this control * i.e. TOP_RIGHT * @attr {Number} index index of the control * @example * * Example: * <map center="41.850033,-87.6500523" zoom="3"> * <custom-control id="home" position="TOP_LEFT" index="1"> * <div style="background-color: white;"> * <b>Home</b> * </div> * </custom-control> * </map> * */ (function() { 'use strict'; var parser, NgMap; var linkFunc = function(scope, element, attrs, mapController, $transclude) { mapController = mapController[0]||mapController[1]; var filtered = parser.filter(attrs); var options = parser.getOptions(filtered, {scope: scope}); var events = parser.getEvents(scope, filtered); var innerScope = scope.$new(); /** * build a custom control element */ var customControlEl = element[0].parentElement.removeChild(element[0]); var content = $transclude( innerScope, function( clone ) { element.empty(); element.append( clone ); element.on( '$destroy', function() { innerScope.$destroy(); }); }); /** * set events */ for (var eventName in events) { google.maps.event.addDomListener(customControlEl, eventName, events[eventName]); } mapController.addObject('customControls', customControlEl); var position = options.position; mapController.map.controls[google.maps.ControlPosition[position]].push(customControlEl); element.bind('$destroy', function() { mapController.deleteObject('customControls', customControlEl); }); }; var customControl = function(Attr2MapOptions, _NgMap_) { parser = Attr2MapOptions, NgMap = _NgMap_; return { restrict: 'E', require: ['?^map','?^ngMap'], link: linkFunc, transclude: true }; // return }; customControl.$inject = ['Attr2MapOptions', 'NgMap']; angular.module('ngMap').directive('customControl', customControl); })(); /** * @ngdoc directive * @memberof ngmap * @name custom-marker * @param Attr2Options {service} convert html attribute to Google map api options * @param $timeout {service} AngularJS $timeout * @description * Marker with html * Requires: map directive * Restrict To: Element * * @attr {String} position required, position on map * @attr {Number} z-index optional * @attr {Boolean} visible optional * @example * * Example: * <map center="41.850033,-87.6500523" zoom="3"> * <custom-marker position="41.850033,-87.6500523"> * <div> * <b>Home</b> * </div> * </custom-marker> * </map> * */ /* global document */ (function() { 'use strict'; var parser, $timeout, $compile, NgMap; var supportedTransform = (function getSupportedTransform() { var prefixes = 'transform WebkitTransform MozTransform OTransform msTransform'.split(' '); var div = document.createElement('div'); for(var i = 0; i < prefixes.length; i++) { if(div && div.style[prefixes[i]] !== undefined) { return prefixes[i]; } } return false; })(); var CustomMarker = function(options) { options = options || {}; this.el = document.createElement('div'); this.el.style.display = 'block'; this.el.style.visibility = "hidden"; this.visible = true; for (var key in options) { /* jshint ignore:line */ this[key] = options[key]; } }; var setCustomMarker = function() { CustomMarker.prototype = new google.maps.OverlayView(); CustomMarker.prototype.setContent = function(html, scope) { this.el.innerHTML = html; this.el.style.position = 'absolute'; this.el.style.top = 0; this.el.style.left = 0; if (scope) { $compile(angular.element(this.el).contents())(scope); } }; CustomMarker.prototype.getDraggable = function() { return this.draggable; }; CustomMarker.prototype.setDraggable = function(draggable) { this.draggable = draggable; }; CustomMarker.prototype.getPosition = function() { return this.position; }; CustomMarker.prototype.setPosition = function(position) { position && (this.position = position); /* jshint ignore:line */ var _this = this; if (this.getProjection() && typeof this.position.lng == 'function') { void 0; var setPosition = function() { if (!_this.getProjection()) { return; } var posPixel = _this.getProjection().fromLatLngToDivPixel(_this.position); var x = Math.round(posPixel.x - (_this.el.offsetWidth/2)); var y = Math.round(posPixel.y - _this.el.offsetHeight - 10); // 10px for anchor if (supportedTransform) { _this.el.style[supportedTransform] = "translate(" + x + "px, " + y + "px)"; } else { _this.el.style.left = x + "px"; _this.el.style.top = y + "px"; } _this.el.style.visibility = "visible"; }; if (_this.el.offsetWidth && _this.el.offsetHeight) { setPosition(); } else { //delayed left/top calculation when width/height are not set instantly $timeout(setPosition, 300); } } }; CustomMarker.prototype.setZIndex = function(zIndex) { if (zIndex === undefined) return; (this.zIndex !== zIndex) && (this.zIndex = zIndex); /* jshint ignore:line */ (this.el.style.zIndex !== this.zIndex) && (this.el.style.zIndex = this.zIndex); }; CustomMarker.prototype.getVisible = function() { return this.visible; }; CustomMarker.prototype.setVisible = function(visible) { if (this.el.style.display === 'none' && visible) { this.el.style.display = 'block'; } else if (this.el.style.display !== 'none' && !visible) { this.el.style.display = 'none'; } this.visible = visible; }; CustomMarker.prototype.addClass = function(className) { var classNames = this.el.className.trim().split(' '); (classNames.indexOf(className) == -1) && classNames.push(className); /* jshint ignore:line */ this.el.className = classNames.join(' '); }; CustomMarker.prototype.removeClass = function(className) { var classNames = this.el.className.split(' '); var index = classNames.indexOf(className); (index > -1) && classNames.splice(index, 1); /* jshint ignore:line */ this.el.className = classNames.join(' '); }; CustomMarker.prototype.onAdd = function() { this.getPanes().overlayMouseTarget.appendChild(this.el); }; CustomMarker.prototype.draw = function() { this.setPosition(); this.setZIndex(this.zIndex); this.setVisible(this.visible); }; CustomMarker.prototype.onRemove = function() { this.el.parentNode.removeChild(this.el); //this.el = null; }; }; var linkFunc = function(orgHtml, varsToWatch) { //console.log('orgHtml', orgHtml, 'varsToWatch', varsToWatch); return function(scope, element, attrs, mapController) { mapController = mapController[0]||mapController[1]; var orgAttrs = parser.orgAttributes(element); var filtered = parser.filter(attrs); var options = parser.getOptions(filtered, {scope: scope}); var events = parser.getEvents(scope, filtered); /** * build a custom marker element */ element[0].style.display = 'none'; void 0; var customMarker = new CustomMarker(options); // Do we really need a timeout with $scope.$apply() here? setTimeout(function() { //apply contents, class, and location after it is compiled scope.$watch('[' + varsToWatch.join(',') + ']', function(newVal, oldVal) { customMarker.setContent(orgHtml, scope); }, true); customMarker.setContent(element[0].innerHTML, scope); var classNames = (element[0].firstElementChild) && (element[0].firstElementChild.className || ''); customMarker.class && (classNames += " " + customMarker.class); customMarker.addClass('custom-marker'); classNames && customMarker.addClass(classNames); void 0; if (!(options.position instanceof google.maps.LatLng)) { NgMap.getGeoLocation(options.position).then( function(latlng) { customMarker.setPosition(latlng); } ); } }); void 0; for (var eventName in events) { /* jshint ignore:line */ google.maps.event.addDomListener( customMarker.el, eventName, events[eventName]); } mapController.addObject('customMarkers', customMarker); //set observers mapController.observeAttrSetObj(orgAttrs, attrs, customMarker); element.bind('$destroy', function() { //Is it required to remove event listeners when DOM is removed? mapController.deleteObject('customMarkers', customMarker); }); }; // linkFunc }; var customMarkerDirective = function( _$timeout_, _$compile_, $interpolate, Attr2MapOptions, _NgMap_, escapeRegExp ) { parser = Attr2MapOptions; $timeout = _$timeout_; $compile = _$compile_; NgMap = _NgMap_; var exprStartSymbol = $interpolate.startSymbol(); var exprEndSymbol = $interpolate.endSymbol(); var exprRegExp = new RegExp(escapeRegExp(exprStartSymbol) + '([^' + exprEndSymbol.substring(0, 1) + ']+)' + escapeRegExp(exprEndSymbol), 'g'); return { restrict: 'E', require: ['?^map','?^ngMap'], compile: function(element) { void 0; setCustomMarker(); element[0].style.display ='none'; var orgHtml = element.html(); var matches = orgHtml.match(exprRegExp); var varsToWatch = []; //filter out that contains '::', 'this.' (matches || []).forEach(function(match) { var toWatch = match.replace(exprStartSymbol,'').replace(exprEndSymbol,''); if (match.indexOf('::') == -1 && match.indexOf('this.') == -1 && varsToWatch.indexOf(toWatch) == -1) { varsToWatch.push(match.replace(exprStartSymbol,'').replace(exprEndSymbol,'')); } }); return linkFunc(orgHtml, varsToWatch); } }; // return };// function customMarkerDirective.$inject = ['$timeout', '$compile', '$interpolate', 'Attr2MapOptions', 'NgMap', 'escapeRegexpFilter']; angular.module('ngMap').directive('customMarker', customMarkerDirective); })(); /** * @ngdoc directive * @name directions * @description * Enable directions on map. * e.g., origin, destination, draggable, waypoints, etc * * Requires: map directive * * Restrict To: Element * * @attr {String} DirectionsRendererOptions * [Any DirectionsRendererOptions](https://developers.google.com/maps/documentation/javascript/reference#DirectionsRendererOptions) * @attr {String} DirectionsRequestOptions * [Any DirectionsRequest options](https://developers.google.com/maps/documentation/javascript/reference#DirectionsRequest) * @example * <map zoom="14" center="37.7699298, -122.4469157"> * <directions * draggable="true" * panel="directions-panel" * travel-mode="{{travelMode}}" * waypoints="[{location:'kingston', stopover:true}]" * origin="{{origin}}" * destination="{{destination}}"> * </directions> * </map> */ /* global document */ (function() { 'use strict'; var NgMap, $timeout, NavigatorGeolocation; var requestTimeout, routeRequest; // Delay for each route render to accumulate all requests into a single one // This is required for simultaneous origin\waypoints\destination change // 20ms should be enough to merge all request data var routeRenderDelay = 20; var getDirectionsRenderer = function(options, events) { if (options.panel) { options.panel = document.getElementById(options.panel) || document.querySelector(options.panel); } var renderer = new google.maps.DirectionsRenderer(options); for (var eventName in events) { google.maps.event.addListener(renderer, eventName, events[eventName]); } return renderer; }; var updateRoute = function(renderer, options) { var directionsService = new google.maps.DirectionsService(); /* filter out valid keys only for DirectionsRequest object*/ var request = options; request.travelMode = request.travelMode || 'DRIVING'; var validKeys = [ 'origin', 'destination', 'travelMode', 'transitOptions', 'unitSystem', 'durationInTraffic', 'waypoints', 'optimizeWaypoints', 'provideRouteAlternatives', 'avoidHighways', 'avoidTolls', 'region' ]; if (request) { for(var key in request) { if (request.hasOwnProperty(key)) { (validKeys.indexOf(key) === -1) && (delete request[key]); } } } if(request.waypoints) { // Check for acceptable values if(!Array.isArray(request.waypoints)) { delete request.waypoints; } } var showDirections = function(request) { if (requestTimeout && request) { if (!routeRequest) { routeRequest = request; } else { for (var attr in request) { if (request.hasOwnProperty(attr)) { routeRequest[attr] = request[attr]; } } } } else { requestTimeout = $timeout(function() { if (!routeRequest) { routeRequest = request; } directionsService.route(routeRequest, function(response, status) { if (status == google.maps.DirectionsStatus.OK) { renderer.setDirections(response); // Unset request for the next call routeRequest = undefined; } }); $timeout.cancel(requestTimeout); // Unset expired timeout for the next call requestTimeout = undefined; }, routeRenderDelay); } }; if (request && request.origin && request.destination) { if (request.origin == 'current-location') { NavigatorGeolocation.getCurrentPosition().then(function(ll) { request.origin = new google.maps.LatLng(ll.coords.latitude, ll.coords.longitude); showDirections(request); }); } else if (request.destination == 'current-location') { NavigatorGeolocation.getCurrentPosition().then(function(ll) { request.destination = new google.maps.LatLng(ll.coords.latitude, ll.coords.longitude); showDirections(request); }); } else { showDirections(request); } } }; var directions = function( Attr2MapOptions, _$timeout_, _NavigatorGeolocation_, _NgMap_) { var parser = Attr2MapOptions; NgMap = _NgMap_; $timeout = _$timeout_; NavigatorGeolocation = _NavigatorGeolocation_; var linkFunc = function(scope, element, attrs, mapController) { mapController = mapController[0]||mapController[1]; var orgAttrs = parser.orgAttributes(element); var filtered = parser.filter(attrs); var options = parser.getOptions(filtered, {scope: scope}); var events = parser.getEvents(scope, filtered); var attrsToObserve = parser.getAttrsToObserve(orgAttrs); var attrsToObserve = []; if (!filtered.noWatcher) { attrsToObserve = parser.getAttrsToObserve(orgAttrs); } var renderer = getDirectionsRenderer(options, events); mapController.addObject('directionsRenderers', renderer); attrsToObserve.forEach(function(attrName) { (function(attrName) { attrs.$observe(attrName, function(val) { if (attrName == 'panel') { $timeout(function(){ var panel = document.getElementById(val) || document.querySelector(val); void 0; panel && renderer.setPanel(panel); }); } else if (options[attrName] !== val) { //apply only if changed var optionValue = parser.toOptionValue(val, {key: attrName}); void 0; options[attrName] = optionValue; updateRoute(renderer, options); } }); })(attrName); }); NgMap.getMap().then(function() { updateRoute(renderer, options); }); element.bind('$destroy', function() { mapController.deleteObject('directionsRenderers', renderer); }); }; return { restrict: 'E', require: ['?^map','?^ngMap'], link: linkFunc }; }; // var directions directions.$inject = ['Attr2MapOptions', '$timeout', 'NavigatorGeolocation', 'NgMap']; angular.module('ngMap').directive('directions', directions); })(); /** * @ngdoc directive * @name drawing-manager * @param Attr2Options {service} convert html attribute to Google map api options * @description * Requires: map directive * Restrict To: Element * * @example * Example: * * <map zoom="13" center="37.774546, -122.433523" map-type-id="SATELLITE"> * <drawing-manager * on-overlaycomplete="onMapOverlayCompleted()" * position="ControlPosition.TOP_CENTER" * drawingModes="POLYGON,CIRCLE" * drawingControl="true" * circleOptions="fillColor: '#FFFF00';fillOpacity: 1;strokeWeight: 5;clickable: false;zIndex: 1;editable: true;" > * </drawing-manager> * </map> * * TODO: Add remove button. * currently, for our solution, we have the shapes/markers in our own * controller, and we use some css classes to change the shape button * to a remove button (<div>X</div>) and have the remove operation in our own controller. */ (function() { 'use strict'; angular.module('ngMap').directive('drawingManager', [ 'Attr2MapOptions', function(Attr2MapOptions) { var parser = Attr2MapOptions; return { restrict: 'E', require: ['?^map','?^ngMap'], link: function(scope, element, attrs, mapController) { mapController = mapController[0]||mapController[1]; var filtered = parser.filter(attrs); var options = parser.getOptions(filtered, {scope: scope}); var controlOptions = parser.getControlOptions(filtered); var events = parser.getEvents(scope, filtered); /** * set options */ var drawingManager = new google.maps.drawing.DrawingManager({ drawingMode: options.drawingmode, drawingControl: options.drawingcontrol, drawingControlOptions: controlOptions.drawingControlOptions, circleOptions:options.circleoptions, markerOptions:options.markeroptions, polygonOptions:options.polygonoptions, polylineOptions:options.polylineoptions, rectangleOptions:options.rectangleoptions }); //Observers attrs.$observe('drawingControlOptions', function (newValue) { drawingManager.drawingControlOptions = parser.getControlOptions({drawingControlOptions: newValue}).drawingControlOptions; drawingManager.setDrawingMode(null); drawingManager.setMap(mapController.map); }); /** * set events */ for (var eventName in events) { google.maps.event.addListener(drawingManager, eventName, events[eventName]); } mapController.addObject('mapDrawingManager', drawingManager); element.bind('$destroy', function() { mapController.deleteObject('mapDrawingManager', drawingManager); }); } }; // return }]); })(); /** * @ngdoc directive * @name dynamic-maps-engine-layer * @description * Requires: map directive * Restrict To: Element * * @example * Example: * <map zoom="14" center="[59.322506, 18.010025]"> * <dynamic-maps-engine-layer * layer-id="06673056454046135537-08896501997766553811"> * </dynamic-maps-engine-layer> * </map> */ (function() { 'use strict'; angular.module('ngMap').directive('dynamicMapsEngineLayer', [ 'Attr2MapOptions', function(Attr2MapOptions) { var parser = Attr2MapOptions; var getDynamicMapsEngineLayer = function(options, events) { var layer = new google.maps.visualization.DynamicMapsEngineLayer(options); for (var eventName in events) { google.maps.event.addListener(layer, eventName, events[eventName]); } return layer; }; return { restrict: 'E', require: ['?^map','?^ngMap'], link: function(scope, element, attrs, mapController) { mapController = mapController[0]||mapController[1]; var filtered = parser.filter(attrs); var options = parser.getOptions(filtered, {scope: scope}); var events = parser.getEvents(scope, filtered, events); var layer = getDynamicMapsEngineLayer(options, events); mapController.addObject('mapsEngineLayers', layer); } }; // return }]); })(); /** * @ngdoc directive * @name fusion-tables-layer * @description * Requires: map directive * Restrict To: Element * * @example * Example: * <map zoom="11" center="41.850033, -87.6500523"> * <fusion-tables-layer query="{ * select: 'Geocodable address', * from: '1mZ53Z70NsChnBMm-qEYmSDOvLXgrreLTkQUvvg'}"> * </fusion-tables-layer> * </map> */ (function() { 'use strict'; angular.module('ngMap').directive('fusionTablesLayer', [ 'Attr2MapOptions', function(Attr2MapOptions) { var parser = Attr2MapOptions; var getLayer = function(options, events) { var layer = new google.maps.FusionTablesLayer(options); for (var eventName in events) { google.maps.event.addListener(layer, eventName, events[eventName]); } return layer; }; return { restrict: 'E', require: ['?^map','?^ngMap'], link: function(scope, element, attrs, mapController) { mapController = mapController[0]||mapController[1]; var filtered = parser.filter(attrs); var options = parser.getOptions(filtered, {scope: scope}); var events = parser.getEvents(scope, filtered, events); void 0; var layer = getLayer(options, events); mapController.addObject('fusionTablesLayers', layer); element.bind('$destroy', function() { mapController.deleteObject('fusionTablesLayers', layer); }); } }; // return }]); })(); /** * @ngdoc directive * @name heatmap-layer * @param Attr2Options {service} convert html attribute to Google map api options * @description * Requires: map directive * Restrict To: Element * * @example * * <map zoom="11" center="[41.875696,-87.624207]"> * <heatmap-layer data="taxiData"></heatmap-layer> * </map> */ (function() { 'use strict'; angular.module('ngMap').directive('heatmapLayer', [ 'Attr2MapOptions', '$window', function(Attr2MapOptions, $window) { var parser = Attr2MapOptions; return { restrict: 'E', require: ['?^map','?^ngMap'], link: function(scope, element, attrs, mapController) { mapController = mapController[0]||mapController[1]; var filtered = parser.filter(attrs); /** * set options */ var options = parser.getOptions(filtered, {scope: scope}); options.data = $window[attrs.data] || parseScope(attrs.data, scope); if (options.data instanceof Array) { options.data = new google.maps.MVCArray(options.data); } else { throw "invalid heatmap data"; } var layer = new google.maps.visualization.HeatmapLayer(options); /** * set events */ var events = parser.getEvents(scope, filtered); void 0; mapController.addObject('heatmapLayers', layer); //helper get nexted path function parseScope( path, obj ) { return path.split('.').reduce( function( prev, curr ) { return prev[curr]; }, obj || this ); } } }; // return }]); })(); /** * @ngdoc directive * @name info-window * @param Attr2MapOptions {service} * convert html attribute to Google map api options * @param $compile {service} $compile service * @description * Defines infoWindow and provides compile method * * Requires: map directive * * Restrict To: Element * * NOTE: this directive should **NOT** be used with `ng-repeat` * because InfoWindow itself is a template, and a template must be * reused by each marker, thus, should not be redefined repeatedly * by `ng-repeat`. * * @attr {Boolean} visible * Indicates to show it when map is initialized * @attr {Boolean} visible-on-marker * Indicates to show it on a marker when map is initialized * @attr {Expression} geo-callback * if position is an address, the expression is will be performed * when geo-lookup is successful. e.g., geo-callback="showDetail()" * @attr {String} &lt;InfoWindowOption> Any InfoWindow options, * https://developers.google.com/maps/documentation/javascript/reference?csw=1#InfoWindowOptions * @attr {String} &lt;InfoWindowEvent> Any InfoWindow events, * https://developers.google.com/maps/documentation/javascript/reference * @example * Usage: * <map MAP_ATTRIBUTES> * <info-window id="foo" ANY_OPTIONS ANY_EVENTS"></info-window> * </map> * * Example: * <map center="41.850033,-87.6500523" zoom="3"> * <info-window id="1" position="41.850033,-87.6500523" > * <div ng-non-bindable> * Chicago, IL<br/> * LatLng: {{chicago.lat()}}, {{chicago.lng()}}, <br/> * World Coordinate: {{worldCoordinate.x}}, {{worldCoordinate.y}}, <br/> * Pixel Coordinate: {{pixelCoordinate.x}}, {{pixelCoordinate.y}}, <br/> * Tile Coordinate: {{tileCoordinate.x}}, {{tileCoordinate.y}} at Zoom Level {{map.getZoom()}} * </div> * </info-window> * </map> */ /* global google */ (function() { 'use strict'; var infoWindow = function(Attr2MapOptions, $compile, $q, $templateRequest, $timeout, $parse, NgMap) { var parser = Attr2MapOptions; var getInfoWindow = function(options, events, element) { var infoWindow; /** * set options */ if (options.position && !(options.position instanceof google.maps.LatLng)) { delete options.position; } infoWindow = new google.maps.InfoWindow(options); /** * set events */ for (var eventName in events) { if (eventName) { google.maps.event.addListener(infoWindow, eventName, events[eventName]); } } /** * set template and template-related functions * it must have a container element with ng-non-bindable */ var templatePromise = $q(function(resolve) { if (angular.isString(element)) { $templateRequest(element).then(function (requestedTemplate) { resolve(angular.element(requestedTemplate).wrap('<div>').parent()); }, function(message) { throw "info-window template request failed: " + message; }); } else { resolve(element); } }).then(function(resolvedTemplate) { var template = resolvedTemplate.html().trim(); if (angular.element(template).length != 1) { throw "info-window working as a template must have a container"; } infoWindow.__template = template.replace(/\s?ng-non-bindable[='"]+/,""); }); infoWindow.__open = function(map, scope, anchor) { templatePromise.then(function() { $timeout(function() { anchor && (scope.anchor = anchor); var el = $compile(infoWindow.__template)(scope); infoWindow.setContent(el[0]); scope.$apply(); if (anchor && anchor.getPosition) { infoWindow.open(map, anchor); } else if (anchor && anchor instanceof google.maps.LatLng) { infoWindow.open(map); infoWindow.setPosition(anchor); } else { infoWindow.open(map); } $timeout(function() { // to avoid racing condition var infoWindowContainerEl = infoWindow.content.parentElement.parentElement.parentElement; infoWindowContainerEl.className = "ng-map-info-window"; }); }); }); }; return infoWindow; }; var linkFunc = function(scope, element, attrs, mapController) { mapController = mapController[0]||mapController[1]; element.css('display','none'); var orgAttrs = parser.orgAttributes(element); var filtered = parser.filter(attrs); var options = parser.getOptions(filtered, {scope: scope}); var events = parser.getEvents(scope, filtered); var infoWindow = getInfoWindow(options, events, options.template || element); var address; if (options.position && !(options.position instanceof google.maps.LatLng)) { address = options.position; } if (address) { NgMap.getGeoLocation(address).then(function(latlng) { infoWindow.setPosition(latlng); infoWindow.__open(mapController.map, scope, latlng); var geoCallback = attrs.geoCallback; geoCallback && $parse(geoCallback)(scope); }); } mapController.addObject('infoWindows', infoWindow); mapController.observeAttrSetObj(orgAttrs, attrs, infoWindow); mapController.showInfoWindow = mapController.map.showInfoWindow = mapController.showInfoWindow || function(p1, p2, p3) { //event, id, marker var id = typeof p1 == 'string' ? p1 : p2; var marker = typeof p1 == 'string' ? p2 : p3; if (typeof marker == 'string') { //Check if markers if defined to avoid odd 'undefined' errors if ( typeof mapController.map.markers != "undefined" && typeof mapController.map.markers[marker] != "undefined") { marker = mapController.map.markers[marker]; } else if ( //additionally check if that marker is a custom marker typeof mapController.map.customMarkers !== "undefined" && typeof mapController.map.customMarkers[marker] !== "undefined") { marker = mapController.map.customMarkers[marker]; } else { //Better error output if marker with that id is not defined throw new Error("Cant open info window for id " + marker + ". Marker or CustomMarker is not defined") } } var infoWindow = mapController.map.infoWindows[id]; var anchor = marker ? marker : (this.getPosition ? this : null); infoWindow.__open(mapController.map, scope, anchor); if(mapController.singleInfoWindow) { if(mapController.lastInfoWindow) { scope.hideInfoWindow(mapController.lastInfoWindow); } mapController.lastInfoWindow = id; } }; mapController.hideInfoWindow = mapController.map.hideInfoWindow = mapController.hideInfoWindow || function(p1, p2) { var id = typeof p1 == 'string' ? p1 : p2; var infoWindow = mapController.map.infoWindows[id]; infoWindow.close(); }; //TODO DEPRECATED scope.showInfoWindow = mapController.map.showInfoWindow; scope.hideInfoWindow = mapController.map.hideInfoWindow; var map = infoWindow.mapId ? {id:infoWindow.mapId} : 0; NgMap.getMap(map).then(function(map) { infoWindow.visible && infoWindow.__open(map, scope); if (infoWindow.visibleOnMarker) { var markerId = infoWindow.visibleOnMarker; infoWindow.__open(map, scope, map.markers[markerId]); } }); }; //link return { restrict: 'E', require: ['?^map','?^ngMap'], link: linkFunc }; }; // infoWindow infoWindow.$inject = ['Attr2MapOptions', '$compile', '$q', '$templateRequest', '$timeout', '$parse', 'NgMap']; angular.module('ngMap').directive('infoWindow', infoWindow); })(); /** * @ngdoc directive * @name kml-layer * @param Attr2MapOptions {service} convert html attribute to Google map api options * @description * renders Kml layer on a map * Requires: map directive * Restrict To: Element * * @attr {Url} url url of the kml layer * @attr {KmlLayerOptions} KmlLayerOptions * (https://developers.google.com/maps/documentation/javascript/reference#KmlLayerOptions) * @attr {String} &lt;KmlLayerEvent> Any KmlLayer events, * https://developers.google.com/maps/documentation/javascript/reference * @example * Usage: * <map MAP_ATTRIBUTES> * <kml-layer ANY_KML_LAYER ANY_KML_LAYER_EVENTS"></kml-layer> * </map> * * Example: * * <map zoom="11" center="[41.875696,-87.624207]"> * <kml-layer url="https://gmaps-samples.googlecode.com/svn/trunk/ggeoxml/cta.kml" > * </kml-layer> * </map> */ (function() { 'use strict'; angular.module('ngMap').directive('kmlLayer', [ 'Attr2MapOptions', function(Attr2MapOptions) { var parser = Attr2MapOptions; var getKmlLayer = function(options, events) { var kmlLayer = new google.maps.KmlLayer(options); for (var eventName in events) { google.maps.event.addListener(kmlLayer, eventName, events[eventName]); } return kmlLayer; }; return { restrict: 'E', require: ['?^map','?^ngMap'], link: function(scope, element, attrs, mapController) { mapController = mapController[0]||mapController[1]; var orgAttrs = parser.orgAttributes(element); var filtered = parser.filter(attrs); var options = parser.getOptions(filtered, {scope: scope}); var events = parser.getEvents(scope, filtered); void 0; var kmlLayer = getKmlLayer(options, events); mapController.addObject('kmlLayers', kmlLayer); mapController.observeAttrSetObj(orgAttrs, attrs, kmlLayer); //observers element.bind('$destroy', function() { mapController.deleteObject('kmlLayers', kmlLayer); }); } }; // return }]); })(); /** * @ngdoc directive * @name map-data * @param Attr2MapOptions {service} * convert html attribute to Google map api options * @description * set map data * Requires: map directive * Restrict To: Element * * @wn {String} method-name, run map.data[method-name] with attribute value * @example * Example: * * <map zoom="11" center="[41.875696,-87.624207]"> * <map-data load-geo-json="https://storage.googleapis.com/maps-devrel/google.json"></map-data> * </map> */ (function() { 'use strict'; angular.module('ngMap').directive('mapData', [ 'Attr2MapOptions', 'NgMap', function(Attr2MapOptions, NgMap) { var parser = Attr2MapOptions; return { restrict: 'E', require: ['?^map','?^ngMap'], link: function(scope, element, attrs, mapController) { mapController = mapController[0] || mapController[1]; var filtered = parser.filter(attrs); var options = parser.getOptions(filtered, {scope: scope}); var events = parser.getEvents(scope, filtered, events); void 0; NgMap.getMap(mapController.map.id).then(function(map) { //options for (var key in options) { var val = options[key]; if (typeof scope[val] === "function") { map.data[key](scope[val]); } else { map.data[key](val); } } //events for (var eventName in events) { map.data.addListener(eventName, events[eventName]); } }); } }; // return }]); })(); /** * @ngdoc directive * @name map-lazy-load * @param Attr2Options {service} convert html attribute to Google map api options * @description * Requires: Delay the initialization of map directive * until the map is ready to be rendered * Restrict To: Attribute * * @attr {String} map-lazy-load * Maps api script source file locat