| /** |
| * Modified copy (using MarkerWithLabel instead of google.maps.Marker). |
| */ |
| |
| /**! |
| * The MIT License |
| * |
| * Copyright (c) 2010-2012 Google, Inc. http://angularjs.org |
| * |
| * 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/nlaplante/angular-google-maps |
| * |
| * @author Nicolas Laplante https://plus.google.com/108189012221374960701 |
| */ |
| |
| (function () { |
| |
| "use strict"; |
| |
| /* |
| * Utility functions |
| */ |
| |
| /** |
| * Check if 2 floating point numbers are equal |
| * |
| * @see http://stackoverflow.com/a/588014 |
| */ |
| function floatEqual (f1, f2) { |
| return (Math.abs(f1 - f2) < 0.000001); |
| } |
| |
| /* |
| * Create the model in a self-contained class where map-specific logic is |
| * done. This model will be used in the directive. |
| */ |
| |
| var MapModel = (function () { |
| |
| var _defaults = { |
| zoom: 8, |
| draggable: false, |
| container: null |
| }; |
| |
| /** |
| * |
| */ |
| function PrivateMapModel(opts) { |
| |
| var _instance = null, |
| _markers = [], // caches the instances of google.maps.Marker |
| _handlers = [], // event handlers |
| _windows = [], // InfoWindow objects |
| o = angular.extend({}, _defaults, opts), |
| that = this, |
| currentInfoWindow = null; |
| |
| this.center = opts.center; |
| this.zoom = o.zoom; |
| this.draggable = o.draggable; |
| this.dragging = false; |
| this.selector = o.container; |
| this.markers = []; |
| this.options = o.options; |
| |
| this.draw = function () { |
| |
| if (that.center == null) { |
| // TODO log error |
| return; |
| } |
| |
| if (_instance == null) { |
| |
| // Create a new map instance |
| |
| _instance = new google.maps.Map(that.selector, angular.extend(that.options, { |
| center: that.center, |
| zoom: that.zoom, |
| draggable: that.draggable, |
| mapTypeId : google.maps.MapTypeId.ROADMAP |
| })); |
| |
| google.maps.event.addListener(_instance, "dragstart", |
| |
| function () { |
| that.dragging = true; |
| } |
| ); |
| |
| google.maps.event.addListener(_instance, "idle", |
| |
| function () { |
| that.dragging = false; |
| } |
| ); |
| |
| google.maps.event.addListener(_instance, "drag", |
| |
| function () { |
| that.dragging = true; |
| } |
| ); |
| |
| google.maps.event.addListener(_instance, "zoom_changed", |
| |
| function () { |
| that.zoom = _instance.getZoom(); |
| that.center = _instance.getCenter(); |
| } |
| ); |
| |
| google.maps.event.addListener(_instance, "center_changed", |
| |
| function () { |
| that.center = _instance.getCenter(); |
| } |
| ); |
| |
| // Attach additional event listeners if needed |
| if (_handlers.length) { |
| |
| angular.forEach(_handlers, function (h, i) { |
| |
| google.maps.event.addListener(_instance, |
| h.on, h.handler); |
| }); |
| } |
| } |
| else { |
| |
| // Refresh the existing instance |
| google.maps.event.trigger(_instance, "resize"); |
| |
| var instanceCenter = _instance.getCenter(); |
| |
| if (!floatEqual(instanceCenter.lat(), that.center.lat()) |
| || !floatEqual(instanceCenter.lng(), that.center.lng())) { |
| _instance.setCenter(that.center); |
| } |
| |
| if (_instance.getZoom() != that.zoom) { |
| _instance.setZoom(that.zoom); |
| } |
| } |
| }; |
| |
| this.fit = function () { |
| if (_instance && _markers.length) { |
| |
| var bounds = new google.maps.LatLngBounds(); |
| |
| angular.forEach(_markers, function (m, i) { |
| bounds.extend(m.getPosition()); |
| }); |
| |
| _instance.fitBounds(bounds); |
| } |
| }; |
| |
| this.on = function(event, handler) { |
| _handlers.push({ |
| "on": event, |
| "handler": handler |
| }); |
| }; |
| |
| this.addMarker = function (lat, lng, icon, infoWindowContent, label, url, |
| thumbnail) { |
| |
| if (that.findMarker(lat, lng) != null) { |
| return; |
| } |
| |
| var marker = new MarkerWithLabel({ |
| position: new google.maps.LatLng(lat, lng), |
| draggable: false, |
| raiseOnDrag: true, |
| map: _instance, |
| labelContent: label, |
| labelAnchor: new google.maps.Point(25, 0), |
| labelClass: "marker-label", // the CSS class for the label |
| labelStyle: {opacity: 1.0} |
| }); |
| |
| /* |
| var marker = new google.maps.Marker({ |
| position: new google.maps.LatLng(lat, lng), |
| map: _instance, |
| icon: icon |
| }); |
| */ |
| |
| if (label) { |
| |
| } |
| |
| if (url) { |
| |
| } |
| |
| if (infoWindowContent != null) { |
| var infoWindow = new google.maps.InfoWindow({ |
| content: infoWindowContent |
| }); |
| |
| google.maps.event.addListener(marker, 'click', function() { |
| if (currentInfoWindow != null) { |
| currentInfoWindow.close(); |
| } |
| infoWindow.open(_instance, marker); |
| currentInfoWindow = infoWindow; |
| }); |
| } |
| |
| // Cache marker |
| _markers.unshift(marker); |
| |
| // Cache instance of our marker for scope purposes |
| that.markers.unshift({ |
| "lat": lat, |
| "lng": lng, |
| "draggable": false, |
| "icon": icon, |
| "infoWindowContent": infoWindowContent, |
| "label": label, |
| "url": url, |
| "thumbnail": thumbnail |
| }); |
| |
| // Return marker instance |
| return marker; |
| }; |
| |
| this.findMarker = function (lat, lng) { |
| for (var i = 0; i < _markers.length; i++) { |
| var pos = _markers[i].getPosition(); |
| |
| if (floatEqual(pos.lat(), lat) && floatEqual(pos.lng(), lng)) { |
| return _markers[i]; |
| } |
| } |
| |
| return null; |
| }; |
| |
| this.findMarkerIndex = function (lat, lng) { |
| for (var i = 0; i < _markers.length; i++) { |
| var pos = _markers[i].getPosition(); |
| |
| if (floatEqual(pos.lat(), lat) && floatEqual(pos.lng(), lng)) { |
| return i; |
| } |
| } |
| |
| return -1; |
| }; |
| |
| this.addInfoWindow = function (lat, lng, html) { |
| var win = new google.maps.InfoWindow({ |
| content: html, |
| position: new google.maps.LatLng(lat, lng) |
| }); |
| |
| _windows.push(win); |
| |
| return win; |
| }; |
| |
| this.hasMarker = function (lat, lng) { |
| return that.findMarker(lat, lng) !== null; |
| }; |
| |
| this.getMarkerInstances = function () { |
| return _markers; |
| }; |
| |
| this.removeMarkers = function (markerInstances) { |
| |
| var s = this; |
| |
| angular.forEach(markerInstances, function (v, i) { |
| var pos = v.getPosition(), |
| lat = pos.lat(), |
| lng = pos.lng(), |
| index = s.findMarkerIndex(lat, lng); |
| |
| // Remove from local arrays |
| _markers.splice(index, 1); |
| s.markers.splice(index, 1); |
| |
| // Remove from map |
| v.setMap(null); |
| }); |
| }; |
| } |
| |
| // Done |
| return PrivateMapModel; |
| }()); |
| |
| // End model |
| |
| // Start Angular directive |
| |
| var googleMapsModule = angular.module("google-maps", []); |
| |
| /** |
| * Map directive |
| */ |
| googleMapsModule.directive("googleMap", ["$log", "$timeout", "$filter", function ($log, $timeout, |
| $filter) { |
| |
| var controller = function ($scope, $element) { |
| |
| var _m = $scope.map; |
| |
| self.addInfoWindow = function (lat, lng, content) { |
| _m.addInfoWindow(lat, lng, content); |
| }; |
| }; |
| |
| controller.$inject = ['$scope', '$element']; |
| |
| return { |
| restrict: "ECA", |
| priority: 100, |
| transclude: true, |
| template: "<div class='angular-google-map' ng-transclude></div>", |
| replace: false, |
| scope: { |
| center: "=center", // required |
| markers: "=markers", // optional |
| latitude: "=latitude", // required |
| longitude: "=longitude", // required |
| zoom: "=zoom", // required |
| refresh: "&refresh", // optional |
| windows: "=windows", // optional |
| events: "=events" |
| }, |
| controller: controller, |
| link: function (scope, element, attrs, ctrl) { |
| |
| // Center property must be specified and provide lat & |
| // lng properties |
| if (!angular.isDefined(scope.center) || |
| (!angular.isDefined(scope.center.latitude) || |
| !angular.isDefined(scope.center.longitude))) { |
| |
| $log.error("angular-google-maps: could not find a valid center property"); |
| return; |
| } |
| |
| if (!angular.isDefined(scope.zoom)) { |
| $log.error("angular-google-maps: map zoom property not set"); |
| return; |
| } |
| |
| angular.element(element).addClass("angular-google-map"); |
| |
| // Parse options |
| var opts = {options: {}}; |
| if (attrs.options) { |
| opts.options = angular.fromJson(attrs.options); |
| } |
| |
| // Create our model |
| var _m = new MapModel(angular.extend(opts, { |
| container: element[0], |
| center: new google.maps.LatLng(scope.center.latitude, scope.center.longitude), |
| draggable: attrs.draggable == "true", |
| zoom: scope.zoom |
| })); |
| |
| _m.on("drag", function () { |
| |
| var c = _m.center; |
| |
| $timeout(function () { |
| |
| scope.$apply(function (s) { |
| scope.center.latitude = c.lat(); |
| scope.center.longitude = c.lng(); |
| }); |
| }); |
| }); |
| |
| _m.on("zoom_changed", function () { |
| |
| if (scope.zoom != _m.zoom) { |
| |
| $timeout(function () { |
| |
| scope.$apply(function (s) { |
| scope.zoom = _m.zoom; |
| }); |
| }); |
| } |
| }); |
| |
| _m.on("center_changed", function () { |
| var c = _m.center; |
| |
| $timeout(function () { |
| |
| scope.$apply(function (s) { |
| |
| if (!_m.dragging) { |
| scope.center.latitude = c.lat(); |
| scope.center.longitude = c.lng(); |
| } |
| }); |
| }); |
| }); |
| |
| if (angular.isDefined(scope.events)) { |
| for (var eventName in scope.events) { |
| if (scope.events.hasOwnProperty(eventName) && angular.isFunction(scope.events[eventName])) { |
| _m.on(eventName, function () { |
| scope.events[eventName].apply(scope, [_m, eventName, arguments]); |
| }); |
| } |
| } |
| } |
| |
| if (attrs.markClick == "true") { |
| (function () { |
| var cm = null; |
| |
| _m.on("click", function (e) { |
| if (cm == null) { |
| |
| cm = { |
| latitude: e.latLng.lat(), |
| longitude: e.latLng.lng() |
| }; |
| |
| scope.markers.push(cm); |
| } |
| else { |
| cm.latitude = e.latLng.lat(); |
| cm.longitude = e.latLng.lng(); |
| } |
| |
| |
| $timeout(function () { |
| scope.latitude = cm.latitude; |
| scope.longitude = cm.longitude; |
| scope.$apply(); |
| }); |
| }); |
| }()); |
| } |
| |
| // Put the map into the scope |
| scope.map = _m; |
| |
| // Check if we need to refresh the map |
| if (angular.isUndefined(scope.refresh())) { |
| // No refresh property given; draw the map immediately |
| _m.draw(); |
| } |
| else { |
| scope.$watch("refresh()", function (newValue, oldValue) { |
| if (newValue && !oldValue) { |
| _m.draw(); |
| } |
| }); |
| } |
| |
| // Markers |
| scope.$watch("markers", function (newValue, oldValue) { |
| |
| $timeout(function () { |
| |
| angular.forEach(newValue, function (v, i) { |
| if (!_m.hasMarker(v.latitude, v.longitude)) { |
| _m.addMarker(v.latitude, v.longitude, v.icon, v.infoWindow, v.label); |
| } |
| }); |
| |
| // Clear orphaned markers |
| var orphaned = []; |
| |
| angular.forEach(_m.getMarkerInstances(), function (v, i) { |
| // Check our scope if a marker with equal latitude and longitude. |
| // If not found, then that marker has been removed form the scope. |
| |
| var pos = v.getPosition(), |
| lat = pos.lat(), |
| lng = pos.lng(), |
| found = false; |
| |
| // Test against each marker in the scope |
| for (var si = 0; si < scope.markers.length; si++) { |
| |
| var sm = scope.markers[si]; |
| |
| if (floatEqual(sm.latitude, lat) && floatEqual(sm.longitude, lng)) { |
| // Map marker is present in scope too, don't remove |
| found = true; |
| } |
| } |
| |
| // Marker in map has not been found in scope. Remove. |
| if (!found) { |
| orphaned.push(v); |
| } |
| }); |
| |
| orphaned.length && _m.removeMarkers(orphaned); |
| |
| // Fit map when there are more than one marker. |
| // This will change the map center coordinates |
| if (attrs.fit == "true" && newValue && newValue.length > 1) { |
| _m.fit(); |
| } |
| }); |
| |
| }, true); |
| |
| |
| // Update map when center coordinates change |
| scope.$watch("center", function (newValue, oldValue) { |
| if (newValue === oldValue) { |
| return; |
| } |
| |
| if (!_m.dragging) { |
| _m.center = new google.maps.LatLng(newValue.latitude, |
| newValue.longitude); |
| _m.draw(); |
| } |
| }, true); |
| |
| scope.$watch("zoom", function (newValue, oldValue) { |
| if (newValue === oldValue) { |
| return; |
| } |
| |
| _m.zoom = newValue; |
| _m.draw(); |
| }); |
| } |
| }; |
| }]); |
| }()); |