(function(root, factory) { if(typeof exports === 'object') { module.exports = factory(); } else if(typeof define === 'function' && define.amd) { define('GMaps', [], factory); } root.GMaps = factory(); }(this, function() { if (!(typeof window.google === 'object' && window.google.maps)) { throw 'Google Maps API is required. Please register the following JavaScript library http://maps.google.com/maps/api/js?sensor=true.' } var extend_object = function(obj, new_obj) { var name; if (obj === new_obj) { return obj; } for (name in new_obj) { obj[name] = new_obj[name]; } return obj; }; var replace_object = function(obj, replace) { var name; if (obj === replace) { return obj; } for (name in replace) { if (obj[name] != undefined) { obj[name] = replace[name]; } } return obj; }; var array_map = function(array, callback) { var original_callback_params = Array.prototype.slice.call(arguments, 2), array_return = [], array_length = array.length, i; if (Array.prototype.map && array.map === Array.prototype.map) { array_return = Array.prototype.map.call(array, function(item) { callback_params = original_callback_params; callback_params.splice(0, 0, item); return callback.apply(this, callback_params); }); } else { for (i = 0; i < array_length; i++) { callback_params = original_callback_params; callback_params.splice(0, 0, array[i]); array_return.push(callback.apply(this, callback_params)); } } return array_return; }; var array_flat = function(array) { var new_array = [], i; for (i = 0; i < array.length; i++) { new_array = new_array.concat(array[i]); } return new_array; }; var coordsToLatLngs = function(coords, useGeoJSON) { var first_coord = coords[0], second_coord = coords[1]; if (useGeoJSON) { first_coord = coords[1]; second_coord = coords[0]; } return new google.maps.LatLng(first_coord, second_coord); }; var arrayToLatLng = function(coords, useGeoJSON) { var i; for (i = 0; i < coords.length; i++) { if (coords[i].length > 0 && typeof(coords[i][0]) == "object") { coords[i] = arrayToLatLng(coords[i], useGeoJSON); } else { coords[i] = coordsToLatLngs(coords[i], useGeoJSON); } } return coords; }; var getElementById = function(id, context) { var element, id = id.replace('#', ''); if ('jQuery' in this && context) { element = $("#" + id, context)[0]; } else { element = document.getElementById(id); }; return element; }; var findAbsolutePosition = function(obj) { var curleft = 0, curtop = 0; if (obj.offsetParent) { do { curleft += obj.offsetLeft; curtop += obj.offsetTop; } while (obj = obj.offsetParent); } return [curleft, curtop]; }; var GMaps = (function(global) { "use strict"; var doc = document; var GMaps = function(options) { if (!this) return new GMaps(options); options.zoom = options.zoom || 15; options.mapType = options.mapType || 'roadmap'; var self = this, i, events_that_hide_context_menu = ['bounds_changed', 'center_changed', 'click', 'dblclick', 'drag', 'dragend', 'dragstart', 'idle', 'maptypeid_changed', 'projection_changed', 'resize', 'tilesloaded', 'zoom_changed'], events_that_doesnt_hide_context_menu = ['mousemove', 'mouseout', 'mouseover'], options_to_be_deleted = ['el', 'lat', 'lng', 'mapType', 'width', 'height', 'markerClusterer', 'enableNewStyle'], container_id = options.el || options.div, markerClustererFunction = options.markerClusterer, mapType = google.maps.MapTypeId[options.mapType.toUpperCase()], map_center = new google.maps.LatLng(options.lat, options.lng), zoomControl = options.zoomControl || true, zoomControlOpt = options.zoomControlOpt || { style: 'DEFAULT', position: 'TOP_LEFT' }, zoomControlStyle = zoomControlOpt.style || 'DEFAULT', zoomControlPosition = zoomControlOpt.position || 'TOP_LEFT', panControl = options.panControl || true, mapTypeControl = options.mapTypeControl || true, scaleControl = options.scaleControl || true, streetViewControl = options.streetViewControl || true, overviewMapControl = overviewMapControl || true, map_options = {}, map_base_options = { zoom: this.zoom, center: map_center, mapTypeId: mapType }, map_controls_options = { panControl: panControl, zoomControl: zoomControl, zoomControlOptions: { style: google.maps.ZoomControlStyle[zoomControlStyle], position: google.maps.ControlPosition[zoomControlPosition] }, mapTypeControl: mapTypeControl, scaleControl: scaleControl, streetViewControl: streetViewControl, overviewMapControl: overviewMapControl }; if (typeof(options.el) === 'string' || typeof(options.div) === 'string') { this.el = getElementById(container_id, options.context); } else { this.el = container_id; } if (typeof(this.el) === 'undefined' || this.el === null) { throw 'No element defined.'; } window.context_menu = window.context_menu || {}; window.context_menu[self.el.id] = {}; this.controls = []; this.overlays = []; this.layers = []; // array with kml/georss and fusiontables layers, can be as many this.singleLayers = {}; // object with the other layers, only one per layer this.markers = []; this.polylines = []; this.routes = []; this.polygons = []; this.infoWindow = null; this.overlay_el = null; this.zoom = options.zoom; this.registered_events = {}; this.el.style.width = options.width || this.el.scrollWidth || this.el.offsetWidth; this.el.style.height = options.height || this.el.scrollHeight || this.el.offsetHeight; google.maps.visualRefresh = options.enableNewStyle; for (i = 0; i < options_to_be_deleted.length; i++) { delete options[options_to_be_deleted[i]]; } if(options.disableDefaultUI != true) { map_base_options = extend_object(map_base_options, map_controls_options); } map_options = extend_object(map_base_options, options); for (i = 0; i < events_that_hide_context_menu.length; i++) { delete map_options[events_that_hide_context_menu[i]]; } for (i = 0; i < events_that_doesnt_hide_context_menu.length; i++) { delete map_options[events_that_doesnt_hide_context_menu[i]]; } this.map = new google.maps.Map(this.el, map_options); if (markerClustererFunction) { this.markerClusterer = markerClustererFunction.apply(this, [this.map]); } var buildContextMenuHTML = function(control, e) { var html = '', options = window.context_menu[self.el.id][control]; for (var i in options){ if (options.hasOwnProperty(i)) { var option = options[i]; html += '
  • ' + option.title + '
  • '; } } if (!getElementById('gmaps_context_menu')) return; var context_menu_element = getElementById('gmaps_context_menu'); context_menu_element.innerHTML = html; var context_menu_items = context_menu_element.getElementsByTagName('a'), context_menu_items_count = context_menu_items.length, i; for (i = 0; i < context_menu_items_count; i++) { var context_menu_item = context_menu_items[i]; var assign_menu_item_action = function(ev){ ev.preventDefault(); options[this.id.replace(control + '_', '')].action.apply(self, [e]); self.hideContextMenu(); }; google.maps.event.clearListeners(context_menu_item, 'click'); google.maps.event.addDomListenerOnce(context_menu_item, 'click', assign_menu_item_action, false); } var position = findAbsolutePosition.apply(this, [self.el]), left = position[0] + e.pixel.x - 15, top = position[1] + e.pixel.y- 15; context_menu_element.style.left = left + "px"; context_menu_element.style.top = top + "px"; context_menu_element.style.display = 'block'; }; this.buildContextMenu = function(control, e) { if (control === 'marker') { e.pixel = {}; var overlay = new google.maps.OverlayView(); overlay.setMap(self.map); overlay.draw = function() { var projection = overlay.getProjection(), position = e.marker.getPosition(); e.pixel = projection.fromLatLngToContainerPixel(position); buildContextMenuHTML(control, e); }; } else { buildContextMenuHTML(control, e); } }; this.setContextMenu = function(options) { window.context_menu[self.el.id][options.control] = {}; var i, ul = doc.createElement('ul'); for (i in options.options) { if (options.options.hasOwnProperty(i)) { var option = options.options[i]; window.context_menu[self.el.id][options.control][option.name] = { title: option.title, action: option.action }; } } ul.id = 'gmaps_context_menu'; ul.style.display = 'none'; ul.style.position = 'absolute'; ul.style.minWidth = '100px'; ul.style.background = 'white'; ul.style.listStyle = 'none'; ul.style.padding = '8px'; ul.style.boxShadow = '2px 2px 6px #ccc'; doc.body.appendChild(ul); var context_menu_element = getElementById('gmaps_context_menu') google.maps.event.addDomListener(context_menu_element, 'mouseout', function(ev) { if (!ev.relatedTarget || !this.contains(ev.relatedTarget)) { window.setTimeout(function(){ context_menu_element.style.display = 'none'; }, 400); } }, false); }; this.hideContextMenu = function() { var context_menu_element = getElementById('gmaps_context_menu'); if (context_menu_element) { context_menu_element.style.display = 'none'; } }; var setupListener = function(object, name) { google.maps.event.addListener(object, name, function(e){ if (e == undefined) { e = this; } options[name].apply(this, [e]); self.hideContextMenu(); }); }; for (var ev = 0; ev < events_that_hide_context_menu.length; ev++) { var name = events_that_hide_context_menu[ev]; if (name in options) { setupListener(this.map, name); } } for (var ev = 0; ev < events_that_doesnt_hide_context_menu.length; ev++) { var name = events_that_doesnt_hide_context_menu[ev]; if (name in options) { setupListener(this.map, name); } } google.maps.event.addListener(this.map, 'rightclick', function(e) { if (options.rightclick) { options.rightclick.apply(this, [e]); } if(window.context_menu[self.el.id]['map'] != undefined) { self.buildContextMenu('map', e); } }); this.refresh = function() { google.maps.event.trigger(this.map, 'resize'); }; this.fitZoom = function() { var latLngs = [], markers_length = this.markers.length, i; for (i = 0; i < markers_length; i++) { if(typeof(this.markers[i].visible) === 'boolean' && this.markers[i].visible) { latLngs.push(this.markers[i].getPosition()); } } this.fitLatLngBounds(latLngs); }; this.fitLatLngBounds = function(latLngs) { var total = latLngs.length; var bounds = new google.maps.LatLngBounds(); for(var i=0; i < total; i++) { bounds.extend(latLngs[i]); } this.map.fitBounds(bounds); }; this.setCenter = function(lat, lng, callback) { this.map.panTo(new google.maps.LatLng(lat, lng)); if (callback) { callback(); } }; this.getElement = function() { return this.el; }; this.zoomIn = function(value) { value = value || 1; this.zoom = this.map.getZoom() + value; this.map.setZoom(this.zoom); }; this.zoomOut = function(value) { value = value || 1; this.zoom = this.map.getZoom() - value; this.map.setZoom(this.zoom); }; var native_methods = [], method; for (method in this.map) { if (typeof(this.map[method]) == 'function' && !this[method]) { native_methods.push(method); } } for (i=0; i < native_methods.length; i++) { (function(gmaps, scope, method_name) { gmaps[method_name] = function(){ return scope[method_name].apply(scope, arguments); }; })(this, this.map, native_methods[i]); } }; return GMaps; })(this); GMaps.prototype.createControl = function(options) { var control = document.createElement('div'); control.style.cursor = 'pointer'; if (options.disableDefaultStyles !== true) { control.style.fontFamily = 'Roboto, Arial, sans-serif'; control.style.fontSize = '11px'; control.style.boxShadow = 'rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px'; } for (var option in options.style) { control.style[option] = options.style[option]; } if (options.id) { control.id = options.id; } if (options.classes) { control.className = options.classes; } if (options.content) { control.innerHTML = options.content; } if (options.position) { control.position = google.maps.ControlPosition[options.position.toUpperCase()]; } for (var ev in options.events) { (function(object, name) { google.maps.event.addDomListener(object, name, function(){ options.events[name].apply(this, [this]); }); })(control, ev); } control.index = 1; return control; }; GMaps.prototype.addControl = function(options) { var control = this.createControl(options); this.controls.push(control); this.map.controls[control.position].push(control); return control; }; GMaps.prototype.removeControl = function(control) { var position = null; for (var i = 0; i < this.controls.length; i++) { if (this.controls[i] == control) { position = this.controls[i].position; this.controls.splice(i, 1); } } if (position) { for (i = 0; i < this.map.controls.length; i++) { var controlsForPosition = this.map.controls[control.position] if (controlsForPosition.getAt(i) == control) { controlsForPosition.removeAt(i); break; } } } return control; }; GMaps.prototype.createMarker = function(options) { if (options.lat == undefined && options.lng == undefined && options.position == undefined) { throw 'No latitude or longitude defined.'; } var self = this, details = options.details, fences = options.fences, outside = options.outside, base_options = { position: new google.maps.LatLng(options.lat, options.lng), map: null }, marker_options = extend_object(base_options, options); delete marker_options.lat; delete marker_options.lng; delete marker_options.fences; delete marker_options.outside; var marker = new google.maps.Marker(marker_options); marker.fences = fences; if (options.infoWindow) { marker.infoWindow = new google.maps.InfoWindow(options.infoWindow); var info_window_events = ['closeclick', 'content_changed', 'domready', 'position_changed', 'zindex_changed']; for (var ev = 0; ev < info_window_events.length; ev++) { (function(object, name) { if (options.infoWindow[name]) { google.maps.event.addListener(object, name, function(e){ options.infoWindow[name].apply(this, [e]); }); } })(marker.infoWindow, info_window_events[ev]); } } 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']; var marker_events_with_mouse = ['dblclick', 'drag', 'dragend', 'dragstart', 'mousedown', 'mouseout', 'mouseover', 'mouseup']; for (var ev = 0; ev < marker_events.length; ev++) { (function(object, name) { if (options[name]) { google.maps.event.addListener(object, name, function(){ options[name].apply(this, [this]); }); } })(marker, marker_events[ev]); } for (var ev = 0; ev < marker_events_with_mouse.length; ev++) { (function(map, object, name) { if (options[name]) { google.maps.event.addListener(object, name, function(me){ if(!me.pixel){ me.pixel = map.getProjection().fromLatLngToPoint(me.latLng) } options[name].apply(this, [me]); }); } })(this.map, marker, marker_events_with_mouse[ev]); } google.maps.event.addListener(marker, 'click', function() { this.details = details; if (options.click) { options.click.apply(this, [this]); } if (marker.infoWindow) { self.hideInfoWindows(); marker.infoWindow.open(self.map, marker); } }); google.maps.event.addListener(marker, 'rightclick', function(e) { e.marker = this; if (options.rightclick) { options.rightclick.apply(this, [e]); } if (window.context_menu[self.el.id]['marker'] != undefined) { self.buildContextMenu('marker', e); } }); if (marker.fences) { google.maps.event.addListener(marker, 'dragend', function() { self.checkMarkerGeofence(marker, function(m, f) { outside(m, f); }); }); } return marker; }; GMaps.prototype.addMarker = function(options) { var marker; if(options.hasOwnProperty('gm_accessors_')) { // Native google.maps.Marker object marker = options; } else { if ((options.hasOwnProperty('lat') && options.hasOwnProperty('lng')) || options.position) { marker = this.createMarker(options); } else { throw 'No latitude or longitude defined.'; } } marker.setMap(this.map); if(this.markerClusterer) { this.markerClusterer.addMarker(marker); } this.markers.push(marker); GMaps.fire('marker_added', marker, this); return marker; }; GMaps.prototype.addMarkers = function(array) { for (var i = 0, marker; marker=array[i]; i++) { this.addMarker(marker); } return this.markers; }; GMaps.prototype.hideInfoWindows = function() { for (var i = 0, marker; marker = this.markers[i]; i++){ if (marker.infoWindow) { marker.infoWindow.close(); } } }; GMaps.prototype.removeMarker = function(marker) { for (var i = 0; i < this.markers.length; i++) { if (this.markers[i] === marker) { this.markers[i].setMap(null); this.markers.splice(i, 1); if(this.markerClusterer) { this.markerClusterer.removeMarker(marker); } GMaps.fire('marker_removed', marker, this); break; } } return marker; }; GMaps.prototype.removeMarkers = function (collection) { var new_markers = []; if (typeof collection == 'undefined') { for (var i = 0; i < this.markers.length; i++) { this.markers[i].setMap(null); } this.markers = new_markers; } else { for (var i = 0; i < collection.length; i++) { if (this.markers.indexOf(collection[i]) > -1) { this.markers[i].setMap(null); } } for (var i = 0; i < this.markers.length; i++) { if (this.markers[i].getMap() != null) { new_markers.push(this.markers[i]); } } this.markers = new_markers; } }; GMaps.prototype.drawOverlay = function(options) { var overlay = new google.maps.OverlayView(), auto_show = true; overlay.setMap(this.map); if (options.auto_show != null) { auto_show = options.auto_show; } overlay.onAdd = function() { var el = document.createElement('div'); el.style.borderStyle = "none"; el.style.borderWidth = "0px"; el.style.position = "absolute"; el.style.zIndex = 100; el.innerHTML = options.content; overlay.el = el; if (!options.layer) { options.layer = 'overlayLayer'; } var panes = this.getPanes(), overlayLayer = panes[options.layer], stop_overlay_events = ['contextmenu', 'DOMMouseScroll', 'dblclick', 'mousedown']; overlayLayer.appendChild(el); for (var ev = 0; ev < stop_overlay_events.length; ev++) { (function(object, name) { google.maps.event.addDomListener(object, name, function(e){ if (navigator.userAgent.toLowerCase().indexOf('msie') != -1 && document.all) { e.cancelBubble = true; e.returnValue = false; } else { e.stopPropagation(); } }); })(el, stop_overlay_events[ev]); } if (options.click) { google.maps.event.addDomListener(overlay.el, 'click', function() { options.click.apply(overlay, [overlay]); }); } google.maps.event.trigger(this, 'ready'); }; overlay.draw = function() { var projection = this.getProjection(), pixel = projection.fromLatLngToDivPixel(new google.maps.LatLng(options.lat, options.lng)); options.horizontalOffset = options.horizontalOffset || 0; options.verticalOffset = options.verticalOffset || 0; var el = overlay.el, content = el.children[0], content_height = content.clientHeight, content_width = content.clientWidth; switch (options.verticalAlign) { case 'top': el.style.top = (pixel.y - content_height + options.verticalOffset) + 'px'; break; default: case 'middle': el.style.top = (pixel.y - (content_height / 2) + options.verticalOffset) + 'px'; break; case 'bottom': el.style.top = (pixel.y + options.verticalOffset) + 'px'; break; } switch (options.horizontalAlign) { case 'left': el.style.left = (pixel.x - content_width + options.horizontalOffset) + 'px'; break; default: case 'center': el.style.left = (pixel.x - (content_width / 2) + options.horizontalOffset) + 'px'; break; case 'right': el.style.left = (pixel.x + options.horizontalOffset) + 'px'; break; } el.style.display = auto_show ? 'block' : 'none'; if (!auto_show) { options.show.apply(this, [el]); } }; overlay.onRemove = function() { var el = overlay.el; if (options.remove) { options.remove.apply(this, [el]); } else { overlay.el.parentNode.removeChild(overlay.el); overlay.el = null; } }; this.overlays.push(overlay); return overlay; }; GMaps.prototype.removeOverlay = function(overlay) { for (var i = 0; i < this.overlays.length; i++) { if (this.overlays[i] === overlay) { this.overlays[i].setMap(null); this.overlays.splice(i, 1); break; } } }; GMaps.prototype.removeOverlays = function() { for (var i = 0, item; item = this.overlays[i]; i++) { item.setMap(null); } this.overlays = []; }; GMaps.prototype.drawPolyline = function(options) { var path = [], points = options.path; if (points.length) { if (points[0][0] === undefined) { path = points; } else { for (var i=0, latlng; latlng=points[i]; i++) { path.push(new google.maps.LatLng(latlng[0], latlng[1])); } } } var polyline_options = { map: this.map, path: path, strokeColor: options.strokeColor, strokeOpacity: options.strokeOpacity, strokeWeight: options.strokeWeight, geodesic: options.geodesic, clickable: true, editable: false, visible: true }; if (options.hasOwnProperty("clickable")) { polyline_options.clickable = options.clickable; } if (options.hasOwnProperty("editable")) { polyline_options.editable = options.editable; } if (options.hasOwnProperty("icons")) { polyline_options.icons = options.icons; } if (options.hasOwnProperty("zIndex")) { polyline_options.zIndex = options.zIndex; } var polyline = new google.maps.Polyline(polyline_options); var polyline_events = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick']; for (var ev = 0; ev < polyline_events.length; ev++) { (function(object, name) { if (options[name]) { google.maps.event.addListener(object, name, function(e){ options[name].apply(this, [e]); }); } })(polyline, polyline_events[ev]); } this.polylines.push(polyline); GMaps.fire('polyline_added', polyline, this); return polyline; }; GMaps.prototype.removePolyline = function(polyline) { for (var i = 0; i < this.polylines.length; i++) { if (this.polylines[i] === polyline) { this.polylines[i].setMap(null); this.polylines.splice(i, 1); GMaps.fire('polyline_removed', polyline, this); break; } } }; GMaps.prototype.removePolylines = function() { for (var i = 0, item; item = this.polylines[i]; i++) { item.setMap(null); } this.polylines = []; }; GMaps.prototype.drawCircle = function(options) { options = extend_object({ map: this.map, center: new google.maps.LatLng(options.lat, options.lng) }, options); delete options.lat; delete options.lng; var polygon = new google.maps.Circle(options), polygon_events = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick']; for (var ev = 0; ev < polygon_events.length; ev++) { (function(object, name) { if (options[name]) { google.maps.event.addListener(object, name, function(e){ options[name].apply(this, [e]); }); } })(polygon, polygon_events[ev]); } this.polygons.push(polygon); return polygon; }; GMaps.prototype.drawRectangle = function(options) { options = extend_object({ map: this.map }, options); var latLngBounds = new google.maps.LatLngBounds( new google.maps.LatLng(options.bounds[0][0], options.bounds[0][1]), new google.maps.LatLng(options.bounds[1][0], options.bounds[1][1]) ); options.bounds = latLngBounds; var polygon = new google.maps.Rectangle(options), polygon_events = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick']; for (var ev = 0; ev < polygon_events.length; ev++) { (function(object, name) { if (options[name]) { google.maps.event.addListener(object, name, function(e){ options[name].apply(this, [e]); }); } })(polygon, polygon_events[ev]); } this.polygons.push(polygon); return polygon; }; GMaps.prototype.drawPolygon = function(options) { var useGeoJSON = false; if(options.hasOwnProperty("useGeoJSON")) { useGeoJSON = options.useGeoJSON; } delete options.useGeoJSON; options = extend_object({ map: this.map }, options); if (useGeoJSON == false) { options.paths = [options.paths.slice(0)]; } if (options.paths.length > 0) { if (options.paths[0].length > 0) { options.paths = array_flat(array_map(options.paths, arrayToLatLng, useGeoJSON)); } } var polygon = new google.maps.Polygon(options), polygon_events = ['click', 'dblclick', 'mousedown', 'mousemove', 'mouseout', 'mouseover', 'mouseup', 'rightclick']; for (var ev = 0; ev < polygon_events.length; ev++) { (function(object, name) { if (options[name]) { google.maps.event.addListener(object, name, function(e){ options[name].apply(this, [e]); }); } })(polygon, polygon_events[ev]); } this.polygons.push(polygon); GMaps.fire('polygon_added', polygon, this); return polygon; }; GMaps.prototype.removePolygon = function(polygon) { for (var i = 0; i < this.polygons.length; i++) { if (this.polygons[i] === polygon) { this.polygons[i].setMap(null); this.polygons.splice(i, 1); GMaps.fire('polygon_removed', polygon, this); break; } } }; GMaps.prototype.removePolygons = function() { for (var i = 0, item; item = this.polygons[i]; i++) { item.setMap(null); } this.polygons = []; }; GMaps.prototype.getFromFusionTables = function(options) { var events = options.events; delete options.events; var fusion_tables_options = options, layer = new google.maps.FusionTablesLayer(fusion_tables_options); for (var ev in events) { (function(object, name) { google.maps.event.addListener(object, name, function(e) { events[name].apply(this, [e]); }); })(layer, ev); } this.layers.push(layer); return layer; }; GMaps.prototype.loadFromFusionTables = function(options) { var layer = this.getFromFusionTables(options); layer.setMap(this.map); return layer; }; GMaps.prototype.getFromKML = function(options) { var url = options.url, events = options.events; delete options.url; delete options.events; var kml_options = options, layer = new google.maps.KmlLayer(url, kml_options); for (var ev in events) { (function(object, name) { google.maps.event.addListener(object, name, function(e) { events[name].apply(this, [e]); }); })(layer, ev); } this.layers.push(layer); return layer; }; GMaps.prototype.loadFromKML = function(options) { var layer = this.getFromKML(options); layer.setMap(this.map); return layer; }; GMaps.prototype.addLayer = function(layerName, options) { //var default_layers = ['weather', 'clouds', 'traffic', 'transit', 'bicycling', 'panoramio', 'places']; options = options || {}; var layer; switch(layerName) { case 'weather': this.singleLayers.weather = layer = new google.maps.weather.WeatherLayer(); break; case 'clouds': this.singleLayers.clouds = layer = new google.maps.weather.CloudLayer(); break; case 'traffic': this.singleLayers.traffic = layer = new google.maps.TrafficLayer(); break; case 'transit': this.singleLayers.transit = layer = new google.maps.TransitLayer(); break; case 'bicycling': this.singleLayers.bicycling = layer = new google.maps.BicyclingLayer(); break; case 'panoramio': this.singleLayers.panoramio = layer = new google.maps.panoramio.PanoramioLayer(); layer.setTag(options.filter); delete options.filter; //click event if (options.click) { google.maps.event.addListener(layer, 'click', function(event) { options.click(event); delete options.click; }); } break; case 'places': this.singleLayers.places = layer = new google.maps.places.PlacesService(this.map); //search, nearbySearch, radarSearch callback, Both are the same if (options.search || options.nearbySearch || options.radarSearch) { var placeSearchRequest = { bounds : options.bounds || null, keyword : options.keyword || null, location : options.location || null, name : options.name || null, radius : options.radius || null, rankBy : options.rankBy || null, types : options.types || null }; if (options.radarSearch) { layer.radarSearch(placeSearchRequest, options.radarSearch); } if (options.search) { layer.search(placeSearchRequest, options.search); } if (options.nearbySearch) { layer.nearbySearch(placeSearchRequest, options.nearbySearch); } } //textSearch callback if (options.textSearch) { var textSearchRequest = { bounds : options.bounds || null, location : options.location || null, query : options.query || null, radius : options.radius || null }; layer.textSearch(textSearchRequest, options.textSearch); } break; } if (layer !== undefined) { if (typeof layer.setOptions == 'function') { layer.setOptions(options); } if (typeof layer.setMap == 'function') { layer.setMap(this.map); } return layer; } }; GMaps.prototype.removeLayer = function(layer) { if (typeof(layer) == "string" && this.singleLayers[layer] !== undefined) { this.singleLayers[layer].setMap(null); delete this.singleLayers[layer]; } else { for (var i = 0; i < this.layers.length; i++) { if (this.layers[i] === layer) { this.layers[i].setMap(null); this.layers.splice(i, 1); break; } } } }; var travelMode, unitSystem; GMaps.prototype.getRoutes = function(options) { switch (options.travelMode) { case 'bicycling': travelMode = google.maps.TravelMode.BICYCLING; break; case 'transit': travelMode = google.maps.TravelMode.TRANSIT; break; case 'driving': travelMode = google.maps.TravelMode.DRIVING; break; default: travelMode = google.maps.TravelMode.WALKING; break; } if (options.unitSystem === 'imperial') { unitSystem = google.maps.UnitSystem.IMPERIAL; } else { unitSystem = google.maps.UnitSystem.METRIC; } var base_options = { avoidHighways: false, avoidTolls: false, optimizeWaypoints: false, waypoints: [] }, request_options = extend_object(base_options, options); request_options.origin = /string/.test(typeof options.origin) ? options.origin : new google.maps.LatLng(options.origin[0], options.origin[1]); request_options.destination = /string/.test(typeof options.destination) ? options.destination : new google.maps.LatLng(options.destination[0], options.destination[1]); request_options.travelMode = travelMode; request_options.unitSystem = unitSystem; delete request_options.callback; delete request_options.error; var self = this, service = new google.maps.DirectionsService(); service.route(request_options, function(result, status) { if (status === google.maps.DirectionsStatus.OK) { for (var r in result.routes) { if (result.routes.hasOwnProperty(r)) { self.routes.push(result.routes[r]); } } if (options.callback) { options.callback(self.routes); } } else { if (options.error) { options.error(result, status); } } }); }; GMaps.prototype.removeRoutes = function() { this.routes = []; }; GMaps.prototype.getElevations = function(options) { options = extend_object({ locations: [], path : false, samples : 256 }, options); if (options.locations.length > 0) { if (options.locations[0].length > 0) { options.locations = array_flat(array_map([options.locations], arrayToLatLng, false)); } } var callback = options.callback; delete options.callback; var service = new google.maps.ElevationService(); //location request if (!options.path) { delete options.path; delete options.samples; service.getElevationForLocations(options, function(result, status) { if (callback && typeof(callback) === "function") { callback(result, status); } }); //path request } else { var pathRequest = { path : options.locations, samples : options.samples }; service.getElevationAlongPath(pathRequest, function(result, status) { if (callback && typeof(callback) === "function") { callback(result, status); } }); } }; GMaps.prototype.cleanRoute = GMaps.prototype.removePolylines; GMaps.prototype.drawRoute = function(options) { var self = this; this.getRoutes({ origin: options.origin, destination: options.destination, travelMode: options.travelMode, waypoints: options.waypoints, unitSystem: options.unitSystem, error: options.error, callback: function(e) { if (e.length > 0) { self.drawPolyline({ path: e[e.length - 1].overview_path, strokeColor: options.strokeColor, strokeOpacity: options.strokeOpacity, strokeWeight: options.strokeWeight }); if (options.callback) { options.callback(e[e.length - 1]); } } } }); }; GMaps.prototype.travelRoute = function(options) { if (options.origin && options.destination) { this.getRoutes({ origin: options.origin, destination: options.destination, travelMode: options.travelMode, waypoints : options.waypoints, unitSystem: options.unitSystem, error: options.error, callback: function(e) { //start callback if (e.length > 0 && options.start) { options.start(e[e.length - 1]); } //step callback if (e.length > 0 && options.step) { var route = e[e.length - 1]; if (route.legs.length > 0) { var steps = route.legs[0].steps; for (var i=0, step; step=steps[i]; i++) { step.step_number = i; options.step(step, (route.legs[0].steps.length - 1)); } } } //end callback if (e.length > 0 && options.end) { options.end(e[e.length - 1]); } } }); } else if (options.route) { if (options.route.legs.length > 0) { var steps = options.route.legs[0].steps; for (var i=0, step; step=steps[i]; i++) { step.step_number = i; options.step(step); } } } }; GMaps.prototype.drawSteppedRoute = function(options) { var self = this; if (options.origin && options.destination) { this.getRoutes({ origin: options.origin, destination: options.destination, travelMode: options.travelMode, waypoints : options.waypoints, error: options.error, callback: function(e) { //start callback if (e.length > 0 && options.start) { options.start(e[e.length - 1]); } //step callback if (e.length > 0 && options.step) { var route = e[e.length - 1]; if (route.legs.length > 0) { var steps = route.legs[0].steps; for (var i=0, step; step=steps[i]; i++) { step.step_number = i; self.drawPolyline({ path: step.path, strokeColor: options.strokeColor, strokeOpacity: options.strokeOpacity, strokeWeight: options.strokeWeight }); options.step(step, (route.legs[0].steps.length - 1)); } } } //end callback if (e.length > 0 && options.end) { options.end(e[e.length - 1]); } } }); } else if (options.route) { if (options.route.legs.length > 0) { var steps = options.route.legs[0].steps; for (var i=0, step; step=steps[i]; i++) { step.step_number = i; self.drawPolyline({ path: step.path, strokeColor: options.strokeColor, strokeOpacity: options.strokeOpacity, strokeWeight: options.strokeWeight }); options.step(step); } } } }; GMaps.Route = function(options) { this.origin = options.origin; this.destination = options.destination; this.waypoints = options.waypoints; this.map = options.map; this.route = options.route; this.step_count = 0; this.steps = this.route.legs[0].steps; this.steps_length = this.steps.length; this.polyline = this.map.drawPolyline({ path: new google.maps.MVCArray(), strokeColor: options.strokeColor, strokeOpacity: options.strokeOpacity, strokeWeight: options.strokeWeight }).getPath(); }; GMaps.Route.prototype.getRoute = function(options) { var self = this; this.map.getRoutes({ origin : this.origin, destination : this.destination, travelMode : options.travelMode, waypoints : this.waypoints || [], error: options.error, callback : function() { self.route = e[0]; if (options.callback) { options.callback.call(self); } } }); }; GMaps.Route.prototype.back = function() { if (this.step_count > 0) { this.step_count--; var path = this.route.legs[0].steps[this.step_count].path; for (var p in path){ if (path.hasOwnProperty(p)){ this.polyline.pop(); } } } }; GMaps.Route.prototype.forward = function() { if (this.step_count < this.steps_length) { var path = this.route.legs[0].steps[this.step_count].path; for (var p in path){ if (path.hasOwnProperty(p)){ this.polyline.push(path[p]); } } this.step_count++; } }; GMaps.prototype.checkGeofence = function(lat, lng, fence) { return fence.containsLatLng(new google.maps.LatLng(lat, lng)); }; GMaps.prototype.checkMarkerGeofence = function(marker, outside_callback) { if (marker.fences) { for (var i = 0, fence; fence = marker.fences[i]; i++) { var pos = marker.getPosition(); if (!this.checkGeofence(pos.lat(), pos.lng(), fence)) { outside_callback(marker, fence); } } } }; GMaps.prototype.toImage = function(options) { var options = options || {}, static_map_options = {}; static_map_options['size'] = options['size'] || [this.el.clientWidth, this.el.clientHeight]; static_map_options['lat'] = this.getCenter().lat(); static_map_options['lng'] = this.getCenter().lng(); if (this.markers.length > 0) { static_map_options['markers'] = []; for (var i = 0; i < this.markers.length; i++) { static_map_options['markers'].push({ lat: this.markers[i].getPosition().lat(), lng: this.markers[i].getPosition().lng() }); } } if (this.polylines.length > 0) { var polyline = this.polylines[0]; static_map_options['polyline'] = {}; static_map_options['polyline']['path'] = google.maps.geometry.encoding.encodePath(polyline.getPath()); static_map_options['polyline']['strokeColor'] = polyline.strokeColor static_map_options['polyline']['strokeOpacity'] = polyline.strokeOpacity static_map_options['polyline']['strokeWeight'] = polyline.strokeWeight } return GMaps.staticMapURL(static_map_options); }; GMaps.staticMapURL = function(options){ var parameters = [], data, static_root = 'http://maps.googleapis.com/maps/api/staticmap'; if (options.url) { static_root = options.url; delete options.url; } static_root += '?'; var markers = options.markers; delete options.markers; if (!markers && options.marker) { markers = [options.marker]; delete options.marker; } var styles = options.styles; delete options.styles; var polyline = options.polyline; delete options.polyline; /** Map options **/ if (options.center) { parameters.push('center=' + options.center); delete options.center; } else if (options.address) { parameters.push('center=' + options.address); delete options.address; } else if (options.lat) { parameters.push(['center=', options.lat, ',', options.lng].join('')); delete options.lat; delete options.lng; } else if (options.visible) { var visible = encodeURI(options.visible.join('|')); parameters.push('visible=' + visible); } var size = options.size; if (size) { if (size.join) { size = size.join('x'); } delete options.size; } else { size = '630x300'; } parameters.push('size=' + size); if (!options.zoom && options.zoom !== false) { options.zoom = 15; } var sensor = options.hasOwnProperty('sensor') ? !!options.sensor : true; delete options.sensor; parameters.push('sensor=' + sensor); for (var param in options) { if (options.hasOwnProperty(param)) { parameters.push(param + '=' + options[param]); } } /** Markers **/ if (markers) { var marker, loc; for (var i=0; data=markers[i]; i++) { marker = []; if (data.size && data.size !== 'normal') { marker.push('size:' + data.size); delete data.size; } else if (data.icon) { marker.push('icon:' + encodeURI(data.icon)); delete data.icon; } if (data.color) { marker.push('color:' + data.color.replace('#', '0x')); delete data.color; } if (data.label) { marker.push('label:' + data.label[0].toUpperCase()); delete data.label; } loc = (data.address ? data.address : data.lat + ',' + data.lng); delete data.address; delete data.lat; delete data.lng; for(var param in data){ if (data.hasOwnProperty(param)) { marker.push(param + ':' + data[param]); } } if (marker.length || i === 0) { marker.push(loc); marker = marker.join('|'); parameters.push('markers=' + encodeURI(marker)); } // New marker without styles else { marker = parameters.pop() + encodeURI('|' + loc); parameters.push(marker); } } } /** Map Styles **/ if (styles) { for (var i = 0; i < styles.length; i++) { var styleRule = []; if (styles[i].featureType){ styleRule.push('feature:' + styles[i].featureType.toLowerCase()); } if (styles[i].elementType) { styleRule.push('element:' + styles[i].elementType.toLowerCase()); } for (var j = 0; j < styles[i].stylers.length; j++) { for (var p in styles[i].stylers[j]) { var ruleArg = styles[i].stylers[j][p]; if (p == 'hue' || p == 'color') { ruleArg = '0x' + ruleArg.substring(1); } styleRule.push(p + ':' + ruleArg); } } var rule = styleRule.join('|'); if (rule != '') { parameters.push('style=' + rule); } } } /** Polylines **/ function parseColor(color, opacity) { if (color[0] === '#'){ color = color.replace('#', '0x'); if (opacity) { opacity = parseFloat(opacity); opacity = Math.min(1, Math.max(opacity, 0)); if (opacity === 0) { return '0x00000000'; } opacity = (opacity * 255).toString(16); if (opacity.length === 1) { opacity += opacity; } color = color.slice(0,8) + opacity; } } return color; } if (polyline) { data = polyline; polyline = []; if (data.strokeWeight) { polyline.push('weight:' + parseInt(data.strokeWeight, 10)); } if (data.strokeColor) { var color = parseColor(data.strokeColor, data.strokeOpacity); polyline.push('color:' + color); } if (data.fillColor) { var fillcolor = parseColor(data.fillColor, data.fillOpacity); polyline.push('fillcolor:' + fillcolor); } var path = data.path; if (path.join) { for (var j=0, pos; pos=path[j]; j++) { polyline.push(pos.join(',')); } } else { polyline.push('enc:' + path); } polyline = polyline.join('|'); parameters.push('path=' + encodeURI(polyline)); } /** Retina support **/ var dpi = window.devicePixelRatio || 1; parameters.push('scale=' + dpi); parameters = parameters.join('&'); return static_root + parameters; }; GMaps.prototype.addMapType = function(mapTypeId, options) { if (options.hasOwnProperty("getTileUrl") && typeof(options["getTileUrl"]) == "function") { options.tileSize = options.tileSize || new google.maps.Size(256, 256); var mapType = new google.maps.ImageMapType(options); this.map.mapTypes.set(mapTypeId, mapType); } else { throw "'getTileUrl' function required."; } }; GMaps.prototype.addOverlayMapType = function(options) { if (options.hasOwnProperty("getTile") && typeof(options["getTile"]) == "function") { var overlayMapTypeIndex = options.index; delete options.index; this.map.overlayMapTypes.insertAt(overlayMapTypeIndex, options); } else { throw "'getTile' function required."; } }; GMaps.prototype.removeOverlayMapType = function(overlayMapTypeIndex) { this.map.overlayMapTypes.removeAt(overlayMapTypeIndex); }; GMaps.prototype.addStyle = function(options) { var styledMapType = new google.maps.StyledMapType(options.styles, { name: options.styledMapName }); this.map.mapTypes.set(options.mapTypeId, styledMapType); }; GMaps.prototype.setStyle = function(mapTypeId) { this.map.setMapTypeId(mapTypeId); }; GMaps.prototype.createPanorama = function(streetview_options) { if (!streetview_options.hasOwnProperty('lat') || !streetview_options.hasOwnProperty('lng')) { streetview_options.lat = this.getCenter().lat(); streetview_options.lng = this.getCenter().lng(); } this.panorama = GMaps.createPanorama(streetview_options); this.map.setStreetView(this.panorama); return this.panorama; }; GMaps.createPanorama = function(options) { var el = getElementById(options.el, options.context); options.position = new google.maps.LatLng(options.lat, options.lng); delete options.el; delete options.context; delete options.lat; delete options.lng; var streetview_events = ['closeclick', 'links_changed', 'pano_changed', 'position_changed', 'pov_changed', 'resize', 'visible_changed'], streetview_options = extend_object({visible : true}, options); for (var i = 0; i < streetview_events.length; i++) { delete streetview_options[streetview_events[i]]; } var panorama = new google.maps.StreetViewPanorama(el, streetview_options); for (var i = 0; i < streetview_events.length; i++) { (function(object, name) { if (options[name]) { google.maps.event.addListener(object, name, function(){ options[name].apply(this); }); } })(panorama, streetview_events[i]); } return panorama; }; GMaps.prototype.on = function(event_name, handler) { return GMaps.on(event_name, this, handler); }; GMaps.prototype.off = function(event_name) { GMaps.off(event_name, this); }; GMaps.custom_events = ['marker_added', 'marker_removed', 'polyline_added', 'polyline_removed', 'polygon_added', 'polygon_removed', 'geolocated', 'geolocation_failed']; GMaps.on = function(event_name, object, handler) { if (GMaps.custom_events.indexOf(event_name) == -1) { if(object instanceof GMaps) object = object.map; return google.maps.event.addListener(object, event_name, handler); } else { var registered_event = { handler : handler, eventName : event_name }; object.registered_events[event_name] = object.registered_events[event_name] || []; object.registered_events[event_name].push(registered_event); return registered_event; } }; GMaps.off = function(event_name, object) { if (GMaps.custom_events.indexOf(event_name) == -1) { if(object instanceof GMaps) object = object.map; google.maps.event.clearListeners(object, event_name); } else { object.registered_events[event_name] = []; } }; GMaps.fire = function(event_name, object, scope) { if (GMaps.custom_events.indexOf(event_name) == -1) { google.maps.event.trigger(object, event_name, Array.prototype.slice.apply(arguments).slice(2)); } else { if(event_name in scope.registered_events) { var firing_events = scope.registered_events[event_name]; for(var i = 0; i < firing_events.length; i++) { (function(handler, scope, object) { handler.apply(scope, [object]); })(firing_events[i]['handler'], scope, object); } } } }; GMaps.geolocate = function(options) { var complete_callback = options.always || options.complete; if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(function(position) { options.success(position); if (complete_callback) { complete_callback(); } }, function(error) { options.error(error); if (complete_callback) { complete_callback(); } }, options.options); } else { options.not_supported(); if (complete_callback) { complete_callback(); } } }; GMaps.geocode = function(options) { this.geocoder = new google.maps.Geocoder(); var callback = options.callback; if (options.hasOwnProperty('lat') && options.hasOwnProperty('lng')) { options.latLng = new google.maps.LatLng(options.lat, options.lng); } delete options.lat; delete options.lng; delete options.callback; this.geocoder.geocode(options, function(results, status) { callback(results, status); }); }; //========================== // Polygon containsLatLng // https://github.com/tparkin/Google-Maps-Point-in-Polygon // Poygon getBounds extension - google-maps-extensions // http://code.google.com/p/google-maps-extensions/source/browse/google.maps.Polygon.getBounds.js if (!google.maps.Polygon.prototype.getBounds) { google.maps.Polygon.prototype.getBounds = function(latLng) { var bounds = new google.maps.LatLngBounds(); var paths = this.getPaths(); var path; for (var p = 0; p < paths.getLength(); p++) { path = paths.getAt(p); for (var i = 0; i < path.getLength(); i++) { bounds.extend(path.getAt(i)); } } return bounds; }; } if (!google.maps.Polygon.prototype.containsLatLng) { // Polygon containsLatLng - method to determine if a latLng is within a polygon google.maps.Polygon.prototype.containsLatLng = function(latLng) { // Exclude points outside of bounds as there is no way they are in the poly var bounds = this.getBounds(); if (bounds !== null && !bounds.contains(latLng)) { return false; } // Raycast point in polygon method var inPoly = false; var numPaths = this.getPaths().getLength(); for (var p = 0; p < numPaths; p++) { var path = this.getPaths().getAt(p); var numPoints = path.getLength(); var j = numPoints - 1; for (var i = 0; i < numPoints; i++) { var vertex1 = path.getAt(i); var vertex2 = path.getAt(j); if (vertex1.lng() < latLng.lng() && vertex2.lng() >= latLng.lng() || vertex2.lng() < latLng.lng() && vertex1.lng() >= latLng.lng()) { if (vertex1.lat() + (latLng.lng() - vertex1.lng()) / (vertex2.lng() - vertex1.lng()) * (vertex2.lat() - vertex1.lat()) < latLng.lat()) { inPoly = !inPoly; } } j = i; } } return inPoly; }; } google.maps.LatLngBounds.prototype.containsLatLng = function(latLng) { return this.contains(latLng); }; google.maps.Marker.prototype.setFences = function(fences) { this.fences = fences; }; google.maps.Marker.prototype.addFence = function(fence) { this.fences.push(fence); }; google.maps.Marker.prototype.getId = function() { return this['__gm_id']; }; //========================== // Array indexOf // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf if (!Array.prototype.indexOf) { Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { "use strict"; if (this == null) { throw new TypeError(); } var t = Object(this); var len = t.length >>> 0; if (len === 0) { return -1; } var n = 0; if (arguments.length > 1) { n = Number(arguments[1]); if (n != n) { // shortcut for verifying if it's NaN n = 0; } else if (n != 0 && n != Infinity && n != -Infinity) { n = (n > 0 || -1) * Math.floor(Math.abs(n)); } } if (n >= len) { return -1; } var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); for (; k < len; k++) { if (k in t && t[k] === searchElement) { return k; } } return -1; } } return GMaps; }));