<html ng-app="Maptesting">
<body ng-controller="CtrlGMap">
<google-map center="center"
zoom="zoom"
markers="markers"
style="height: 400px; width: 100%; display: block;">
</google-map>
<script src="http://code.angularjs.org/1.1.5/angular.js"></script>
<script type="text/javascript" src="angular.js"></script>
<script type="text/javascript" src="angular-google-maps.js"></script>
<script src="http://maps.googleapis.com/maps/api/js?sensor=false&language=en"></script>
</script>
</body>
</html>
angular.module('Maptesting', ['google-maps'])
.controller('CtrlGMap', ['$scope', function($scope) {
$scope.center = {
latitude: 45,
longitude: -73
};
$scope.markers = [];
$scope.zoom = 8;
}])
/**!
* 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 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);
}
});
// 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();
});
}
};
}]);
}());