1 (function(root, factory) {
2 if(typeof exports === 'object') {
3 module.exports = factory();
5 else if(typeof define === 'function' && define.amd) {
6 define('GMaps', [], factory);
9 root.GMaps = factory();
15 * http://hpneo.github.com/gmaps/
17 * Copyright 2013, Gustavo Leon
18 * Released under the MIT License.
21 if (!(typeof window.google === 'object' && window.google.maps)) {
22 throw 'Google Maps API is required. Please register the following JavaScript library http://maps.google.com/maps/api/js?sensor=true.'
25 var extend_object = function(obj, new_obj) {
28 if (obj === new_obj) {
32 for (name in new_obj) {
33 obj[name] = new_obj[name];
39 var replace_object = function(obj, replace) {
42 if (obj === replace) {
46 for (name in replace) {
47 if (obj[name] != undefined) {
48 obj[name] = replace[name];
55 var array_map = function(array, callback) {
56 var original_callback_params = Array.prototype.slice.call(arguments, 2),
58 array_length = array.length,
61 if (Array.prototype.map && array.map === Array.prototype.map) {
62 array_return = Array.prototype.map.call(array, function(item) {
63 callback_params = original_callback_params;
64 callback_params.splice(0, 0, item);
66 return callback.apply(this, callback_params);
70 for (i = 0; i < array_length; i++) {
71 callback_params = original_callback_params;
72 callback_params.splice(0, 0, array[i]);
73 array_return.push(callback.apply(this, callback_params));
80 var array_flat = function(array) {
84 for (i = 0; i < array.length; i++) {
85 new_array = new_array.concat(array[i]);
91 var coordsToLatLngs = function(coords, useGeoJSON) {
92 var first_coord = coords[0],
93 second_coord = coords[1];
96 first_coord = coords[1];
97 second_coord = coords[0];
100 return new google.maps.LatLng(first_coord, second_coord);
103 var arrayToLatLng = function(coords, useGeoJSON) {
106 for (i = 0; i < coords.length; i++) {
107 if (coords[i].length > 0 && typeof(coords[i][0]) == "object") {
108 coords[i] = arrayToLatLng(coords[i], useGeoJSON);
111 coords[i] = coordsToLatLngs(coords[i], useGeoJSON);
118 var getElementById = function(id, context) {
120 id = id.replace('#', '');
122 if ('jQuery' in this && context) {
123 element = $("#" + id, context)[0];
125 element = document.getElementById(id);
131 var findAbsolutePosition = function(obj) {
135 if (obj.offsetParent) {
137 curleft += obj.offsetLeft;
138 curtop += obj.offsetTop;
139 } while (obj = obj.offsetParent);
142 return [curleft, curtop];
145 var GMaps = (function(global) {
150 var GMaps = function(options) {
151 if (!this) return new GMaps(options);
153 options.zoom = options.zoom || 15;
154 options.mapType = options.mapType || 'roadmap';
158 events_that_hide_context_menu = ['bounds_changed', 'center_changed', 'click', 'dblclick', 'drag', 'dragend', 'dragstart', 'idle', 'maptypeid_changed', 'projection_changed', 'resize', 'tilesloaded', 'zoom_changed'],
159 events_that_doesnt_hide_context_menu = ['mousemove', 'mouseout', 'mouseover'],
160 options_to_be_deleted = ['el', 'lat', 'lng', 'mapType', 'width', 'height', 'markerClusterer', 'enableNewStyle'],
161 container_id = options.el || options.div,
162 markerClustererFunction = options.markerClusterer,
163 mapType = google.maps.MapTypeId[options.mapType.toUpperCase()],
164 map_center = new google.maps.LatLng(options.lat, options.lng),
165 zoomControl = options.zoomControl || true,
166 zoomControlOpt = options.zoomControlOpt || {
170 zoomControlStyle = zoomControlOpt.style || 'DEFAULT',
171 zoomControlPosition = zoomControlOpt.position || 'TOP_LEFT',
172 panControl = options.panControl || true,
173 mapTypeControl = options.mapTypeControl || true,
174 scaleControl = options.scaleControl || true,
175 streetViewControl = options.streetViewControl || true,
176 overviewMapControl = overviewMapControl || true,
183 map_controls_options = {
184 panControl: panControl,
185 zoomControl: zoomControl,
186 zoomControlOptions: {
187 style: google.maps.ZoomControlStyle[zoomControlStyle],
188 position: google.maps.ControlPosition[zoomControlPosition]
190 mapTypeControl: mapTypeControl,
191 scaleControl: scaleControl,
192 streetViewControl: streetViewControl,
193 overviewMapControl: overviewMapControl
196 if (typeof(options.el) === 'string' || typeof(options.div) === 'string') {
197 this.el = getElementById(container_id, options.context);
199 this.el = container_id;
202 if (typeof(this.el) === 'undefined' || this.el === null) {
203 throw 'No element defined.';
206 window.context_menu = window.context_menu || {};
207 window.context_menu[self.el.id] = {};
211 this.layers = []; // array with kml/georss and fusiontables layers, can be as many
212 this.singleLayers = {}; // object with the other layers, only one per layer
217 this.infoWindow = null;
218 this.overlay_el = null;
219 this.zoom = options.zoom;
220 this.registered_events = {};
222 this.el.style.width = options.width || this.el.scrollWidth || this.el.offsetWidth;
223 this.el.style.height = options.height || this.el.scrollHeight || this.el.offsetHeight;
225 google.maps.visualRefresh = options.enableNewStyle;
227 for (i = 0; i < options_to_be_deleted.length; i++) {
228 delete options[options_to_be_deleted[i]];
231 if(options.disableDefaultUI != true) {
232 map_base_options = extend_object(map_base_options, map_controls_options);
235 map_options = extend_object(map_base_options, options);
237 for (i = 0; i < events_that_hide_context_menu.length; i++) {
238 delete map_options[events_that_hide_context_menu[i]];
241 for (i = 0; i < events_that_doesnt_hide_context_menu.length; i++) {
242 delete map_options[events_that_doesnt_hide_context_menu[i]];
245 this.map = new google.maps.Map(this.el, map_options);
247 if (markerClustererFunction) {
248 this.markerClusterer = markerClustererFunction.apply(this, [this.map]);
251 var buildContextMenuHTML = function(control, e) {
253 options = window.context_menu[self.el.id][control];
255 for (var i in options){
256 if (options.hasOwnProperty(i)) {
257 var option = options[i];
259 html += '<li><a id="' + control + '_' + i + '" href="#">' + option.title + '</a></li>';
263 if (!getElementById('gmaps_context_menu')) return;
265 var context_menu_element = getElementById('gmaps_context_menu');
267 context_menu_element.innerHTML = html;
269 var context_menu_items = context_menu_element.getElementsByTagName('a'),
270 context_menu_items_count = context_menu_items.length
273 for (i = 0; i < context_menu_items_count; i++) {
274 var context_menu_item = context_menu_items[i];
276 var assign_menu_item_action = function(ev){
279 options[this.id.replace(control + '_', '')].action.apply(self, [e]);
280 self.hideContextMenu();
283 google.maps.event.clearListeners(context_menu_item, 'click');
284 google.maps.event.addDomListenerOnce(context_menu_item, 'click', assign_menu_item_action, false);
287 var position = findAbsolutePosition.apply(this, [self.el]),
288 left = position[0] + e.pixel.x - 15,
289 top = position[1] + e.pixel.y- 15;
291 context_menu_element.style.left = left + "px";
292 context_menu_element.style.top = top + "px";
294 context_menu_element.style.display = 'block';
297 this.buildContextMenu = function(control, e) {
298 if (control === 'marker') {
301 var overlay = new google.maps.OverlayView();
302 overlay.setMap(self.map);
304 overlay.draw = function() {
305 var projection = overlay.getProjection(),
306 position = e.marker.getPosition();
308 e.pixel = projection.fromLatLngToContainerPixel(position);
310 buildContextMenuHTML(control, e);
314 buildContextMenuHTML(control, e);
318 this.setContextMenu = function(options) {
319 window.context_menu[self.el.id][options.control] = {};
322 ul = doc.createElement('ul');
324 for (i in options.options) {
325 if (options.options.hasOwnProperty(i)) {
326 var option = options.options[i];
328 window.context_menu[self.el.id][options.control][option.name] = {
330 action: option.action
335 ul.id = 'gmaps_context_menu';
336 ul.style.display = 'none';
337 ul.style.position = 'absolute';
338 ul.style.minWidth = '100px';
339 ul.style.background = 'white';
340 ul.style.listStyle = 'none';
341 ul.style.padding = '8px';
342 ul.style.boxShadow = '2px 2px 6px #ccc';
344 doc.body.appendChild(ul);
346 var context_menu_element = getElementById('gmaps_context_menu')
348 google.maps.event.addDomListener(context_menu_element, 'mouseout', function(ev) {
349 if (!ev.relatedTarget || !this.contains(ev.relatedTarget)) {
350 window.setTimeout(function(){
351 context_menu_element.style.display = 'none';
357 this.hideContextMenu = function() {
358 var context_menu_element = getElementById('gmaps_context_menu');
360 if (context_menu_element) {
361 context_menu_element.style.display = 'none';
365 var setupListener = function(object, name) {
366 google.maps.event.addListener(object, name, function(e){
367 if (e == undefined) {
371 options[name].apply(this, [e]);
373 self.hideContextMenu();
377 for (var ev = 0; ev < events_that_hide_context_menu.length; ev++) {
378 var name = events_that_hide_context_menu[ev];
380 if (name in options) {
381 setupListener(this.map, name);
385 for (var ev = 0; ev < events_that_doesnt_hide_context_menu.length; ev++) {
386 var name = events_that_doesnt_hide_context_menu[ev];
388 if (name in options) {
389 setupListener(this.map, name);
393 google.maps.event.addListener(this.map, 'rightclick', function(e) {
394 if (options.rightclick) {
395 options.rightclick.apply(this, [e]);
398 if(window.context_menu[self.el.id]['map'] != undefined) {
399 self.buildContextMenu('map', e);
403 this.refresh = function() {
404 google.maps.event.trigger(this.map, 'resize');
407 this.fitZoom = function() {
409 markers_length = this.markers.length,
412 for (i = 0; i < markers_length; i++) {
413 if(typeof(this.markers[i].visible) === 'boolean' && this.markers[i].visible) {
414 latLngs.push(this.markers[i].getPosition());
418 this.fitLatLngBounds(latLngs);
421 this.fitLatLngBounds = function(latLngs) {
422 var total = latLngs.length;
423 var bounds = new google.maps.LatLngBounds();
425 for(var i=0; i < total; i++) {
426 bounds.extend(latLngs[i]);
429 this.map.fitBounds(bounds);
432 this.setCenter = function(lat, lng, callback) {
433 this.map.panTo(new google.maps.LatLng(lat, lng));
440 this.getElement = function() {
444 this.zoomIn = function(value) {
447 this.zoom = this.map.getZoom() + value;
448 this.map.setZoom(this.zoom);
451 this.zoomOut = function(value) {
454 this.zoom = this.map.getZoom() - value;
455 this.map.setZoom(this.zoom);
458 var native_methods = [],
461 for (method in this.map) {
462 if (typeof(this.map[method]) == 'function' && !this[method]) {
463 native_methods.push(method);
467 for (i=0; i < native_methods.length; i++) {
468 (function(gmaps, scope, method_name) {
469 gmaps[method_name] = function(){
470 return scope[method_name].apply(scope, arguments);
472 })(this, this.map, native_methods[i]);
479 GMaps.prototype.createControl = function(options) {
480 var control = document.createElement('div');
482 control.style.cursor = 'pointer';
483 control.style.fontFamily = 'Arial, sans-serif';
484 control.style.fontSize = '13px';
485 control.style.boxShadow = 'rgba(0, 0, 0, 0.398438) 0px 2px 4px';
487 for (var option in options.style) {
488 control.style[option] = options.style[option];
492 control.id = options.id;
495 if (options.classes) {
496 control.className = options.classes;
499 if (options.content) {
500 control.innerHTML = options.content;
503 for (var ev in options.events) {
504 (function(object, name) {
505 google.maps.event.addDomListener(object, name, function(){
506 options.events[name].apply(this, [this]);
516 GMaps.prototype.addControl = function(options) {
517 var position = google.maps.ControlPosition[options.position.toUpperCase()];
519 delete options.position;
521 var control = this.createControl(options);
522 this.controls.push(control);
524 this.map.controls[position].push(control);
529 GMaps.prototype.createMarker = function(options) {
530 if (options.lat == undefined && options.lng == undefined && options.position == undefined) {
531 throw 'No latitude or longitude defined.';
535 details = options.details,
536 fences = options.fences,
537 outside = options.outside,
539 position: new google.maps.LatLng(options.lat, options.lng),
545 delete options.fences;
546 delete options.outside;
548 var marker_options = extend_object(base_options, options),
549 marker = new google.maps.Marker(marker_options);
551 marker.fences = fences;
553 if (options.infoWindow) {
554 marker.infoWindow = new google.maps.InfoWindow(options.infoWindow);
556 var info_window_events = ['closeclick', 'content_changed', 'domready', 'position_changed', 'zindex_changed'];
558 for (var ev = 0; ev < info_window_events.length; ev++) {
559 (function(object, name) {
560 if (options.infoWindow[name]) {
561 google.maps.event.addListener(object, name, function(e){
562 options.infoWindow[name].apply(this, [e]);
565 })(marker.infoWindow, info_window_events[ev]);
569 var marker_events = ['animation_changed', 'clickable_changed', 'cursor_changed', 'draggable_changed', 'flat_changed', 'icon_changed', 'position_changed', 'shadow_changed', 'shape_changed', 'title_changed', 'visible_changed', 'zindex_changed'];
571 var marker_events_with_mouse = ['dblclick', 'drag', 'dragend', 'dragstart', 'mousedown', 'mouseout', 'mouseover', 'mouseup'];
573 for (var ev = 0; ev < marker_events.length; ev++) {
574 (function(object, name) {
576 google.maps.event.addListener(object, name, function(){
577 options[name].apply(this, [this]);
580 })(marker, marker_events[ev]);
583 for (var ev = 0; ev < marker_events_with_mouse.length; ev++) {
584 (function(map, object, name) {
586 google.maps.event.addListener(object, name, function(me){
588 me.pixel = map.getProjection().fromLatLngToPoint(me.latLng)
591 options[name].apply(this, [me]);
594 })(this.map, marker, marker_events_with_mouse[ev]);
597 google.maps.event.addListener(marker, 'click', function() {
598 this.details = details;
601 options.click.apply(this, [this]);
604 if (marker.infoWindow) {
605 self.hideInfoWindows();
606 marker.infoWindow.open(self.map, marker);
610 google.maps.event.addListener(marker, 'rightclick', function(e) {
613 if (options.rightclick) {
614 options.rightclick.apply(this, [e]);
617 if (window.context_menu[self.el.id]['marker'] != undefined) {
618 self.buildContextMenu('marker', e);
623 google.maps.event.addListener(marker, 'dragend', function() {
624 self.checkMarkerGeofence(marker, function(m, f) {
633 GMaps.prototype.addMarker = function(options) {
635 if(options.hasOwnProperty('gm_accessors_')) {
636 // Native google.maps.Marker object
640 if ((options.hasOwnProperty('lat') && options.hasOwnProperty('lng')) || options.position) {
641 marker = this.createMarker(options);
644 throw 'No latitude or longitude defined.';
648 marker.setMap(this.map);
650 if(this.markerClusterer) {
651 this.markerClusterer.addMarker(marker);
654 this.markers.push(marker);
656 GMaps.fire('marker_added', marker, this);
661 GMaps.prototype.addMarkers = function(array) {
662 for (var i = 0, marker; marker=array[i]; i++) {
663 this.addMarker(marker);
669 GMaps.prototype.hideInfoWindows = function() {
670 for (var i = 0, marker; marker = this.markers[i]; i++){
671 if (marker.infoWindow){
672 marker.infoWindow.close();
677 GMaps.prototype.removeMarker = function(marker) {
678 for (var i = 0; i < this.markers.length; i++) {
679 if (this.markers[i] === marker) {
680 this.markers[i].setMap(null);
681 this.markers.splice(i, 1);
683 if(this.markerClusterer) {
684 this.markerClusterer.removeMarker(marker);
687 GMaps.fire('marker_removed', marker, this);
696 GMaps.prototype.removeMarkers = function(collection) {
697 var collection = (collection || this.markers);
699 for (var i = 0;i < this.markers.length; i++) {
700 if(this.markers[i] === collection[i]) {
701 this.markers[i].setMap(null);
705 var new_markers = [];
707 for (var i = 0;i < this.markers.length; i++) {
708 if(this.markers[i].getMap() != null) {
709 new_markers.push(this.markers[i]);
713 this.markers = new_markers;
716 GMaps.prototype.drawOverlay = function(options) {
717 var overlay = new google.maps.OverlayView(),
720 overlay.setMap(this.map);
722 if (options.auto_show != null) {
723 auto_show = options.auto_show;
726 overlay.onAdd = function() {
727 var el = document.createElement('div');
729 el.style.borderStyle = "none";
730 el.style.borderWidth = "0px";
731 el.style.position = "absolute";
732 el.style.zIndex = 100;
733 el.innerHTML = options.content;
737 if (!options.layer) {
738 options.layer = 'overlayLayer';
741 var panes = this.getPanes(),
742 overlayLayer = panes[options.layer],
743 stop_overlay_events = ['contextmenu', 'DOMMouseScroll', 'dblclick', 'mousedown'];
745 overlayLayer.appendChild(el);
747 for (var ev = 0; ev < stop_overlay_events.length; ev++) {
748 (function(object, name) {
749 google.maps.event.addDomListener(object, name, function(e){
750 if (navigator.userAgent.toLowerCase().indexOf('msie') != -1 && document.all) {
751 e.cancelBubble = true;
752 e.returnValue = false;
758 })(el, stop_overlay_events[ev]);
761 google.maps.event.trigger(this, 'ready');
764 overlay.draw = function() {
765 var projection = this.getProjection(),
766 pixel = projection.fromLatLngToDivPixel(new google.maps.LatLng(options.lat, options.lng));
768 options.horizontalOffset = options.horizontalOffset || 0;
769 options.verticalOffset = options.verticalOffset || 0;
772 content = el.children[0],
773 content_height = content.clientHeight,
774 content_width = content.clientWidth;
776 switch (options.verticalAlign) {
778 el.style.top = (pixel.y - content_height + options.verticalOffset) + 'px';
782 el.style.top = (pixel.y - (content_height / 2) + options.verticalOffset) + 'px';
785 el.style.top = (pixel.y + options.verticalOffset) + 'px';
789 switch (options.horizontalAlign) {
791 el.style.left = (pixel.x - content_width + options.horizontalOffset) + 'px';
795 el.style.left = (pixel.x - (content_width / 2) + options.horizontalOffset) + 'px';
798 el.style.left = (pixel.x + options.horizontalOffset) + 'px';
802 el.style.display = auto_show ? 'block' : 'none';
805 options.show.apply(this, [el]);
809 overlay.onRemove = function() {
812 if (options.remove) {
813 options.remove.apply(this, [el]);
816 overlay.el.parentNode.removeChild(overlay.el);
821 this.overlays.push(overlay);
825 GMaps.prototype.removeOverlay = function(overlay) {
826 for (var i = 0; i < this.overlays.length; i++) {
827 if (this.overlays[i] === overlay) {
828 this.overlays[i].setMap(null);
829 this.overlays.splice(i, 1);
836 GMaps.prototype.removeOverlays = function() {
837 for (var i = 0, item; item = this.overlays[i]; i++) {
844 GMaps.prototype.drawPolyline = function(options) {
846 points = options.path;
849 if (points[0][0] === undefined) {
853 for (var i=0, latlng; latlng=points[i]; i++) {
854 path.push(new google.maps.LatLng(latlng[0], latlng[1]));
859 var polyline_options = {
862 strokeColor: options.strokeColor,
863 strokeOpacity: options.strokeOpacity,
864 strokeWeight: options.strokeWeight,
865 geodesic: options.geodesic,
871 if (options.hasOwnProperty("clickable")) {
872 polyline_options.clickable = options.clickable;
875 if (options.hasOwnProperty("editable")) {
876 polyline_options.editable = options.editable;
879 if (options.hasOwnProperty("icons")) {
880 polyline_options.icons = options.icons;
883 if (options.hasOwnProperty("zIndex")) {
884 polyline_options.zIndex = options.zIndex;
887 var polyline = new google.maps.Polyline(polyline_options);
889 var polyline_events = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick'];
891 for (var ev = 0; ev < polyline_events.length; ev++) {
892 (function(object, name) {
894 google.maps.event.addListener(object, name, function(e){
895 options[name].apply(this, [e]);
898 })(polyline, polyline_events[ev]);
901 this.polylines.push(polyline);
903 GMaps.fire('polyline_added', polyline, this);
908 GMaps.prototype.removePolyline = function(polyline) {
909 for (var i = 0; i < this.polylines.length; i++) {
910 if (this.polylines[i] === polyline) {
911 this.polylines[i].setMap(null);
912 this.polylines.splice(i, 1);
914 GMaps.fire('polyline_removed', polyline, this);
921 GMaps.prototype.removePolylines = function() {
922 for (var i = 0, item; item = this.polylines[i]; i++) {
929 GMaps.prototype.drawCircle = function(options) {
930 options = extend_object({
932 center: new google.maps.LatLng(options.lat, options.lng)
938 var polygon = new google.maps.Circle(options),
939 polygon_events = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick'];
941 for (var ev = 0; ev < polygon_events.length; ev++) {
942 (function(object, name) {
944 google.maps.event.addListener(object, name, function(e){
945 options[name].apply(this, [e]);
948 })(polygon, polygon_events[ev]);
951 this.polygons.push(polygon);
956 GMaps.prototype.drawRectangle = function(options) {
957 options = extend_object({
961 var latLngBounds = new google.maps.LatLngBounds(
962 new google.maps.LatLng(options.bounds[0][0], options.bounds[0][1]),
963 new google.maps.LatLng(options.bounds[1][0], options.bounds[1][1])
966 options.bounds = latLngBounds;
968 var polygon = new google.maps.Rectangle(options),
969 polygon_events = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick'];
971 for (var ev = 0; ev < polygon_events.length; ev++) {
972 (function(object, name) {
974 google.maps.event.addListener(object, name, function(e){
975 options[name].apply(this, [e]);
978 })(polygon, polygon_events[ev]);
981 this.polygons.push(polygon);
986 GMaps.prototype.drawPolygon = function(options) {
987 var useGeoJSON = false;
989 if(options.hasOwnProperty("useGeoJSON")) {
990 useGeoJSON = options.useGeoJSON;
993 delete options.useGeoJSON;
995 options = extend_object({
999 if (useGeoJSON == false) {
1000 options.paths = [options.paths.slice(0)];
1003 if (options.paths.length > 0) {
1004 if (options.paths[0].length > 0) {
1005 options.paths = array_flat(array_map(options.paths, arrayToLatLng, useGeoJSON));
1009 var polygon = new google.maps.Polygon(options),
1010 polygon_events = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick'];
1012 for (var ev = 0; ev < polygon_events.length; ev++) {
1013 (function(object, name) {
1014 if (options[name]) {
1015 google.maps.event.addListener(object, name, function(e){
1016 options[name].apply(this, [e]);
1019 })(polygon, polygon_events[ev]);
1022 this.polygons.push(polygon);
1024 GMaps.fire('polygon_added', polygon, this);
1029 GMaps.prototype.removePolygon = function(polygon) {
1030 for (var i = 0; i < this.polygons.length; i++) {
1031 if (this.polygons[i] === polygon) {
1032 this.polygons[i].setMap(null);
1033 this.polygons.splice(i, 1);
1035 GMaps.fire('polygon_removed', polygon, this);
1042 GMaps.prototype.removePolygons = function() {
1043 for (var i = 0, item; item = this.polygons[i]; i++) {
1050 GMaps.prototype.getFromFusionTables = function(options) {
1051 var events = options.events;
1053 delete options.events;
1055 var fusion_tables_options = options,
1056 layer = new google.maps.FusionTablesLayer(fusion_tables_options);
1058 for (var ev in events) {
1059 (function(object, name) {
1060 google.maps.event.addListener(object, name, function(e) {
1061 events[name].apply(this, [e]);
1066 this.layers.push(layer);
1071 GMaps.prototype.loadFromFusionTables = function(options) {
1072 var layer = this.getFromFusionTables(options);
1073 layer.setMap(this.map);
1078 GMaps.prototype.getFromKML = function(options) {
1079 var url = options.url,
1080 events = options.events;
1083 delete options.events;
1085 var kml_options = options,
1086 layer = new google.maps.KmlLayer(url, kml_options);
1088 for (var ev in events) {
1089 (function(object, name) {
1090 google.maps.event.addListener(object, name, function(e) {
1091 events[name].apply(this, [e]);
1096 this.layers.push(layer);
1101 GMaps.prototype.loadFromKML = function(options) {
1102 var layer = this.getFromKML(options);
1103 layer.setMap(this.map);
1108 GMaps.prototype.addLayer = function(layerName, options) {
1109 //var default_layers = ['weather', 'clouds', 'traffic', 'transit', 'bicycling', 'panoramio', 'places'];
1110 options = options || {};
1114 case 'weather': this.singleLayers.weather = layer = new google.maps.weather.WeatherLayer();
1116 case 'clouds': this.singleLayers.clouds = layer = new google.maps.weather.CloudLayer();
1118 case 'traffic': this.singleLayers.traffic = layer = new google.maps.TrafficLayer();
1120 case 'transit': this.singleLayers.transit = layer = new google.maps.TransitLayer();
1122 case 'bicycling': this.singleLayers.bicycling = layer = new google.maps.BicyclingLayer();
1125 this.singleLayers.panoramio = layer = new google.maps.panoramio.PanoramioLayer();
1126 layer.setTag(options.filter);
1127 delete options.filter;
1130 if (options.click) {
1131 google.maps.event.addListener(layer, 'click', function(event) {
1132 options.click(event);
1133 delete options.click;
1138 this.singleLayers.places = layer = new google.maps.places.PlacesService(this.map);
1140 //search and nearbySearch callback, Both are the same
1141 if (options.search || options.nearbySearch) {
1142 var placeSearchRequest = {
1143 bounds : options.bounds || null,
1144 keyword : options.keyword || null,
1145 location : options.location || null,
1146 name : options.name || null,
1147 radius : options.radius || null,
1148 rankBy : options.rankBy || null,
1149 types : options.types || null
1152 if (options.search) {
1153 layer.search(placeSearchRequest, options.search);
1156 if (options.nearbySearch) {
1157 layer.nearbySearch(placeSearchRequest, options.nearbySearch);
1161 //textSearch callback
1162 if (options.textSearch) {
1163 var textSearchRequest = {
1164 bounds : options.bounds || null,
1165 location : options.location || null,
1166 query : options.query || null,
1167 radius : options.radius || null
1170 layer.textSearch(textSearchRequest, options.textSearch);
1175 if (layer !== undefined) {
1176 if (typeof layer.setOptions == 'function') {
1177 layer.setOptions(options);
1179 if (typeof layer.setMap == 'function') {
1180 layer.setMap(this.map);
1187 GMaps.prototype.removeLayer = function(layer) {
1188 if (typeof(layer) == "string" && this.singleLayers[layer] !== undefined) {
1189 this.singleLayers[layer].setMap(null);
1191 delete this.singleLayers[layer];
1194 for (var i = 0; i < this.layers.length; i++) {
1195 if (this.layers[i] === layer) {
1196 this.layers[i].setMap(null);
1197 this.layers.splice(i, 1);
1205 var travelMode, unitSystem;
1207 GMaps.prototype.getRoutes = function(options) {
1208 switch (options.travelMode) {
1210 travelMode = google.maps.TravelMode.BICYCLING;
1213 travelMode = google.maps.TravelMode.TRANSIT;
1216 travelMode = google.maps.TravelMode.DRIVING;
1219 travelMode = google.maps.TravelMode.WALKING;
1223 if (options.unitSystem === 'imperial') {
1224 unitSystem = google.maps.UnitSystem.IMPERIAL;
1227 unitSystem = google.maps.UnitSystem.METRIC;
1230 var base_options = {
1231 avoidHighways: false,
1233 optimizeWaypoints: false,
1236 request_options = extend_object(base_options, options);
1238 request_options.origin = /string/.test(typeof options.origin) ? options.origin : new google.maps.LatLng(options.origin[0], options.origin[1]);
1239 request_options.destination = /string/.test(typeof options.destination) ? options.destination : new google.maps.LatLng(options.destination[0], options.destination[1]);
1240 request_options.travelMode = travelMode;
1241 request_options.unitSystem = unitSystem;
1243 delete request_options.callback;
1244 delete request_options.error;
1247 service = new google.maps.DirectionsService();
1249 service.route(request_options, function(result, status) {
1250 if (status === google.maps.DirectionsStatus.OK) {
1251 for (var r in result.routes) {
1252 if (result.routes.hasOwnProperty(r)) {
1253 self.routes.push(result.routes[r]);
1257 if (options.callback) {
1258 options.callback(self.routes);
1262 if (options.error) {
1263 options.error(result, status);
1269 GMaps.prototype.removeRoutes = function() {
1273 GMaps.prototype.getElevations = function(options) {
1274 options = extend_object({
1280 if (options.locations.length > 0) {
1281 if (options.locations[0].length > 0) {
1282 options.locations = array_flat(array_map([options.locations], arrayToLatLng, false));
1286 var callback = options.callback;
1287 delete options.callback;
1289 var service = new google.maps.ElevationService();
1292 if (!options.path) {
1293 delete options.path;
1294 delete options.samples;
1296 service.getElevationForLocations(options, function(result, status) {
1297 if (callback && typeof(callback) === "function") {
1298 callback(result, status);
1304 path : options.locations,
1305 samples : options.samples
1308 service.getElevationAlongPath(pathRequest, function(result, status) {
1309 if (callback && typeof(callback) === "function") {
1310 callback(result, status);
1316 GMaps.prototype.cleanRoute = GMaps.prototype.removePolylines;
1318 GMaps.prototype.drawRoute = function(options) {
1322 origin: options.origin,
1323 destination: options.destination,
1324 travelMode: options.travelMode,
1325 waypoints: options.waypoints,
1326 unitSystem: options.unitSystem,
1327 error: options.error,
1328 callback: function(e) {
1331 path: e[e.length - 1].overview_path,
1332 strokeColor: options.strokeColor,
1333 strokeOpacity: options.strokeOpacity,
1334 strokeWeight: options.strokeWeight
1337 if (options.callback) {
1338 options.callback(e[e.length - 1]);
1345 GMaps.prototype.travelRoute = function(options) {
1346 if (options.origin && options.destination) {
1348 origin: options.origin,
1349 destination: options.destination,
1350 travelMode: options.travelMode,
1351 waypoints : options.waypoints,
1352 error: options.error,
1353 callback: function(e) {
1355 if (e.length > 0 && options.start) {
1356 options.start(e[e.length - 1]);
1360 if (e.length > 0 && options.step) {
1361 var route = e[e.length - 1];
1362 if (route.legs.length > 0) {
1363 var steps = route.legs[0].steps;
1364 for (var i=0, step; step=steps[i]; i++) {
1365 step.step_number = i;
1366 options.step(step, (route.legs[0].steps.length - 1));
1372 if (e.length > 0 && options.end) {
1373 options.end(e[e.length - 1]);
1378 else if (options.route) {
1379 if (options.route.legs.length > 0) {
1380 var steps = options.route.legs[0].steps;
1381 for (var i=0, step; step=steps[i]; i++) {
1382 step.step_number = i;
1389 GMaps.prototype.drawSteppedRoute = function(options) {
1392 if (options.origin && options.destination) {
1394 origin: options.origin,
1395 destination: options.destination,
1396 travelMode: options.travelMode,
1397 waypoints : options.waypoints,
1398 error: options.error,
1399 callback: function(e) {
1401 if (e.length > 0 && options.start) {
1402 options.start(e[e.length - 1]);
1406 if (e.length > 0 && options.step) {
1407 var route = e[e.length - 1];
1408 if (route.legs.length > 0) {
1409 var steps = route.legs[0].steps;
1410 for (var i=0, step; step=steps[i]; i++) {
1411 step.step_number = i;
1414 strokeColor: options.strokeColor,
1415 strokeOpacity: options.strokeOpacity,
1416 strokeWeight: options.strokeWeight
1418 options.step(step, (route.legs[0].steps.length - 1));
1424 if (e.length > 0 && options.end) {
1425 options.end(e[e.length - 1]);
1430 else if (options.route) {
1431 if (options.route.legs.length > 0) {
1432 var steps = options.route.legs[0].steps;
1433 for (var i=0, step; step=steps[i]; i++) {
1434 step.step_number = i;
1437 strokeColor: options.strokeColor,
1438 strokeOpacity: options.strokeOpacity,
1439 strokeWeight: options.strokeWeight
1447 GMaps.Route = function(options) {
1448 this.origin = options.origin;
1449 this.destination = options.destination;
1450 this.waypoints = options.waypoints;
1452 this.map = options.map;
1453 this.route = options.route;
1454 this.step_count = 0;
1455 this.steps = this.route.legs[0].steps;
1456 this.steps_length = this.steps.length;
1458 this.polyline = this.map.drawPolyline({
1459 path: new google.maps.MVCArray(),
1460 strokeColor: options.strokeColor,
1461 strokeOpacity: options.strokeOpacity,
1462 strokeWeight: options.strokeWeight
1466 GMaps.Route.prototype.getRoute = function(options) {
1469 this.map.getRoutes({
1470 origin : this.origin,
1471 destination : this.destination,
1472 travelMode : options.travelMode,
1473 waypoints : this.waypoints || [],
1474 error: options.error,
1475 callback : function() {
1478 if (options.callback) {
1479 options.callback.call(self);
1485 GMaps.Route.prototype.back = function() {
1486 if (this.step_count > 0) {
1488 var path = this.route.legs[0].steps[this.step_count].path;
1490 for (var p in path){
1491 if (path.hasOwnProperty(p)){
1492 this.polyline.pop();
1498 GMaps.Route.prototype.forward = function() {
1499 if (this.step_count < this.steps_length) {
1500 var path = this.route.legs[0].steps[this.step_count].path;
1502 for (var p in path){
1503 if (path.hasOwnProperty(p)){
1504 this.polyline.push(path[p]);
1511 GMaps.prototype.checkGeofence = function(lat, lng, fence) {
1512 return fence.containsLatLng(new google.maps.LatLng(lat, lng));
1515 GMaps.prototype.checkMarkerGeofence = function(marker, outside_callback) {
1516 if (marker.fences) {
1517 for (var i = 0, fence; fence = marker.fences[i]; i++) {
1518 var pos = marker.getPosition();
1519 if (!this.checkGeofence(pos.lat(), pos.lng(), fence)) {
1520 outside_callback(marker, fence);
1526 GMaps.prototype.toImage = function(options) {
1527 var options = options || {},
1528 static_map_options = {};
1530 static_map_options['size'] = options['size'] || [this.el.clientWidth, this.el.clientHeight];
1531 static_map_options['lat'] = this.getCenter().lat();
1532 static_map_options['lng'] = this.getCenter().lng();
1534 if (this.markers.length > 0) {
1535 static_map_options['markers'] = [];
1537 for (var i = 0; i < this.markers.length; i++) {
1538 static_map_options['markers'].push({
1539 lat: this.markers[i].getPosition().lat(),
1540 lng: this.markers[i].getPosition().lng()
1545 if (this.polylines.length > 0) {
1546 var polyline = this.polylines[0];
1548 static_map_options['polyline'] = {};
1549 static_map_options['polyline']['path'] = google.maps.geometry.encoding.encodePath(polyline.getPath());
1550 static_map_options['polyline']['strokeColor'] = polyline.strokeColor
1551 static_map_options['polyline']['strokeOpacity'] = polyline.strokeOpacity
1552 static_map_options['polyline']['strokeWeight'] = polyline.strokeWeight
1555 return GMaps.staticMapURL(static_map_options);
1558 GMaps.staticMapURL = function(options){
1559 var parameters = [],
1561 static_root = 'http://maps.googleapis.com/maps/api/staticmap';
1564 static_root = options.url;
1570 var markers = options.markers;
1572 delete options.markers;
1574 if (!markers && options.marker) {
1575 markers = [options.marker];
1576 delete options.marker;
1579 var styles = options.styles;
1581 delete options.styles;
1583 var polyline = options.polyline;
1584 delete options.polyline;
1587 if (options.center) {
1588 parameters.push('center=' + options.center);
1589 delete options.center;
1591 else if (options.address) {
1592 parameters.push('center=' + options.address);
1593 delete options.address;
1595 else if (options.lat) {
1596 parameters.push(['center=', options.lat, ',', options.lng].join(''));
1600 else if (options.visible) {
1601 var visible = encodeURI(options.visible.join('|'));
1602 parameters.push('visible=' + visible);
1605 var size = options.size;
1608 size = size.join('x');
1610 delete options.size;
1615 parameters.push('size=' + size);
1617 if (!options.zoom && options.zoom !== false) {
1621 var sensor = options.hasOwnProperty('sensor') ? !!options.sensor : true;
1622 delete options.sensor;
1623 parameters.push('sensor=' + sensor);
1625 for (var param in options) {
1626 if (options.hasOwnProperty(param)) {
1627 parameters.push(param + '=' + options[param]);
1635 for (var i=0; data=markers[i]; i++) {
1638 if (data.size && data.size !== 'normal') {
1639 marker.push('size:' + data.size);
1642 else if (data.icon) {
1643 marker.push('icon:' + encodeURI(data.icon));
1648 marker.push('color:' + data.color.replace('#', '0x'));
1653 marker.push('label:' + data.label[0].toUpperCase());
1657 loc = (data.address ? data.address : data.lat + ',' + data.lng);
1658 delete data.address;
1662 for(var param in data){
1663 if (data.hasOwnProperty(param)) {
1664 marker.push(param + ':' + data[param]);
1668 if (marker.length || i === 0) {
1670 marker = marker.join('|');
1671 parameters.push('markers=' + encodeURI(marker));
1673 // New marker without styles
1675 marker = parameters.pop() + encodeURI('|' + loc);
1676 parameters.push(marker);
1683 for (var i = 0; i < styles.length; i++) {
1685 if (styles[i].featureType && styles[i].featureType != 'all' ) {
1686 styleRule.push('feature:' + styles[i].featureType);
1689 if (styles[i].elementType && styles[i].elementType != 'all') {
1690 styleRule.push('element:' + styles[i].elementType);
1693 for (var j = 0; j < styles[i].stylers.length; j++) {
1694 for (var p in styles[i].stylers[j]) {
1695 var ruleArg = styles[i].stylers[j][p];
1696 if (p == 'hue' || p == 'color') {
1697 ruleArg = '0x' + ruleArg.substring(1);
1699 styleRule.push(p + ':' + ruleArg);
1703 var rule = styleRule.join('|');
1705 parameters.push('style=' + rule);
1711 function parseColor(color, opacity) {
1712 if (color[0] === '#'){
1713 color = color.replace('#', '0x');
1716 opacity = parseFloat(opacity);
1717 opacity = Math.min(1, Math.max(opacity, 0));
1718 if (opacity === 0) {
1719 return '0x00000000';
1721 opacity = (opacity * 255).toString(16);
1722 if (opacity.length === 1) {
1726 color = color.slice(0,8) + opacity;
1736 if (data.strokeWeight) {
1737 polyline.push('weight:' + parseInt(data.strokeWeight, 10));
1740 if (data.strokeColor) {
1741 var color = parseColor(data.strokeColor, data.strokeOpacity);
1742 polyline.push('color:' + color);
1745 if (data.fillColor) {
1746 var fillcolor = parseColor(data.fillColor, data.fillOpacity);
1747 polyline.push('fillcolor:' + fillcolor);
1750 var path = data.path;
1752 for (var j=0, pos; pos=path[j]; j++) {
1753 polyline.push(pos.join(','));
1757 polyline.push('enc:' + path);
1760 polyline = polyline.join('|');
1761 parameters.push('path=' + encodeURI(polyline));
1764 /** Retina support **/
1765 var dpi = window.devicePixelRatio || 1;
1766 parameters.push('scale=' + dpi);
1768 parameters = parameters.join('&');
1769 return static_root + parameters;
1772 GMaps.prototype.addMapType = function(mapTypeId, options) {
1773 if (options.hasOwnProperty("getTileUrl") && typeof(options["getTileUrl"]) == "function") {
1774 options.tileSize = options.tileSize || new google.maps.Size(256, 256);
1776 var mapType = new google.maps.ImageMapType(options);
1778 this.map.mapTypes.set(mapTypeId, mapType);
1781 throw "'getTileUrl' function required.";
1785 GMaps.prototype.addOverlayMapType = function(options) {
1786 if (options.hasOwnProperty("getTile") && typeof(options["getTile"]) == "function") {
1787 var overlayMapTypeIndex = options.index;
1789 delete options.index;
1791 this.map.overlayMapTypes.insertAt(overlayMapTypeIndex, options);
1794 throw "'getTile' function required.";
1798 GMaps.prototype.removeOverlayMapType = function(overlayMapTypeIndex) {
1799 this.map.overlayMapTypes.removeAt(overlayMapTypeIndex);
1802 GMaps.prototype.addStyle = function(options) {
1803 var styledMapType = new google.maps.StyledMapType(options.styles, { name: options.styledMapName });
1805 this.map.mapTypes.set(options.mapTypeId, styledMapType);
1808 GMaps.prototype.setStyle = function(mapTypeId) {
1809 this.map.setMapTypeId(mapTypeId);
1812 GMaps.prototype.createPanorama = function(streetview_options) {
1813 if (!streetview_options.hasOwnProperty('lat') || !streetview_options.hasOwnProperty('lng')) {
1814 streetview_options.lat = this.getCenter().lat();
1815 streetview_options.lng = this.getCenter().lng();
1818 this.panorama = GMaps.createPanorama(streetview_options);
1820 this.map.setStreetView(this.panorama);
1822 return this.panorama;
1825 GMaps.createPanorama = function(options) {
1826 var el = getElementById(options.el, options.context);
1828 options.position = new google.maps.LatLng(options.lat, options.lng);
1831 delete options.context;
1835 var streetview_events = ['closeclick', 'links_changed', 'pano_changed', 'position_changed', 'pov_changed', 'resize', 'visible_changed'],
1836 streetview_options = extend_object({visible : true}, options);
1838 for (var i = 0; i < streetview_events.length; i++) {
1839 delete streetview_options[streetview_events[i]];
1842 var panorama = new google.maps.StreetViewPanorama(el, streetview_options);
1844 for (var i = 0; i < streetview_events.length; i++) {
1845 (function(object, name) {
1846 if (options[name]) {
1847 google.maps.event.addListener(object, name, function(){
1848 options[name].apply(this);
1851 })(panorama, streetview_events[i]);
1857 GMaps.prototype.on = function(event_name, handler) {
1858 return GMaps.on(event_name, this, handler);
1861 GMaps.prototype.off = function(event_name) {
1862 GMaps.off(event_name, this);
1865 GMaps.custom_events = ['marker_added', 'marker_removed', 'polyline_added', 'polyline_removed', 'polygon_added', 'polygon_removed', 'geolocated', 'geolocation_failed'];
1867 GMaps.on = function(event_name, object, handler) {
1868 if (GMaps.custom_events.indexOf(event_name) == -1) {
1869 return google.maps.event.addListener(object, event_name, handler);
1872 var registered_event = {
1874 eventName : event_name
1877 object.registered_events[event_name] = object.registered_events[event_name] || [];
1878 object.registered_events[event_name].push(registered_event);
1880 return registered_event;
1884 GMaps.off = function(event_name, object) {
1885 if (GMaps.custom_events.indexOf(event_name) == -1) {
1886 google.maps.event.clearListeners(object, event_name);
1889 object.registered_events[event_name] = [];
1893 GMaps.fire = function(event_name, object, scope) {
1894 if (GMaps.custom_events.indexOf(event_name) == -1) {
1895 google.maps.event.trigger(object, event_name, Array.prototype.slice.apply(arguments).slice(2));
1898 if(event_name in scope.registered_events) {
1899 var firing_events = scope.registered_events[event_name];
1901 for(var i = 0; i < firing_events.length; i++) {
1902 (function(handler, scope, object) {
1903 handler.apply(scope, [object]);
1904 })(firing_events[i]['handler'], scope, object);
1910 GMaps.geolocate = function(options) {
1911 var complete_callback = options.always || options.complete;
1913 if (navigator.geolocation) {
1914 navigator.geolocation.getCurrentPosition(function(position) {
1915 options.success(position);
1917 if (complete_callback) {
1918 complete_callback();
1920 }, function(error) {
1921 options.error(error);
1923 if (complete_callback) {
1924 complete_callback();
1926 }, options.options);
1929 options.not_supported();
1931 if (complete_callback) {
1932 complete_callback();
1937 GMaps.geocode = function(options) {
1938 this.geocoder = new google.maps.Geocoder();
1939 var callback = options.callback;
1940 if (options.hasOwnProperty('lat') && options.hasOwnProperty('lng')) {
1941 options.latLng = new google.maps.LatLng(options.lat, options.lng);
1946 delete options.callback;
1948 this.geocoder.geocode(options, function(results, status) {
1949 callback(results, status);
1953 //==========================
1954 // Polygon containsLatLng
1955 // https://github.com/tparkin/Google-Maps-Point-in-Polygon
1956 // Poygon getBounds extension - google-maps-extensions
1957 // http://code.google.com/p/google-maps-extensions/source/browse/google.maps.Polygon.getBounds.js
1958 if (!google.maps.Polygon.prototype.getBounds) {
1959 google.maps.Polygon.prototype.getBounds = function(latLng) {
1960 var bounds = new google.maps.LatLngBounds();
1961 var paths = this.getPaths();
1964 for (var p = 0; p < paths.getLength(); p++) {
1965 path = paths.getAt(p);
1966 for (var i = 0; i < path.getLength(); i++) {
1967 bounds.extend(path.getAt(i));
1975 if (!google.maps.Polygon.prototype.containsLatLng) {
1976 // Polygon containsLatLng - method to determine if a latLng is within a polygon
1977 google.maps.Polygon.prototype.containsLatLng = function(latLng) {
1978 // Exclude points outside of bounds as there is no way they are in the poly
1979 var bounds = this.getBounds();
1981 if (bounds !== null && !bounds.contains(latLng)) {
1985 // Raycast point in polygon method
1988 var numPaths = this.getPaths().getLength();
1989 for (var p = 0; p < numPaths; p++) {
1990 var path = this.getPaths().getAt(p);
1991 var numPoints = path.getLength();
1992 var j = numPoints - 1;
1994 for (var i = 0; i < numPoints; i++) {
1995 var vertex1 = path.getAt(i);
1996 var vertex2 = path.getAt(j);
1998 if (vertex1.lng() < latLng.lng() && vertex2.lng() >= latLng.lng() || vertex2.lng() < latLng.lng() && vertex1.lng() >= latLng.lng()) {
1999 if (vertex1.lat() + (latLng.lng() - vertex1.lng()) / (vertex2.lng() - vertex1.lng()) * (vertex2.lat() - vertex1.lat()) < latLng.lat()) {
2012 google.maps.LatLngBounds.prototype.containsLatLng = function(latLng) {
2013 return this.contains(latLng);
2016 google.maps.Marker.prototype.setFences = function(fences) {
2017 this.fences = fences;
2020 google.maps.Marker.prototype.addFence = function(fence) {
2021 this.fences.push(fence);
2024 google.maps.Marker.prototype.getId = function() {
2025 return this['__gm_id'];
2028 //==========================
2030 // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf
2031 if (!Array.prototype.indexOf) {
2032 Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
2035 throw new TypeError();
2037 var t = Object(this);
2038 var len = t.length >>> 0;
2043 if (arguments.length > 1) {
2044 n = Number(arguments[1]);
2045 if (n != n) { // shortcut for verifying if it's NaN
2047 } else if (n != 0 && n != Infinity && n != -Infinity) {
2048 n = (n > 0 || -1) * Math.floor(Math.abs(n));
2054 var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
2055 for (; k < len; k++) {
2056 if (k in t && t[k] === searchElement) {