/* * GMAP3 Plugin for JQuery * Version : 4.0 * Date : 2011-08-23 * Licence : GPL v3 : http://www.gnu.org/licenses/gpl.html * Author : DEMONTE Jean-Baptiste * Contact : jbdemonte@gmail.com * Web site : http://gmap3.net * * Copyright (c) 2010-2011 Jean-Baptiste DEMONTE * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * - Neither the name of the author nor the names of its contributors * may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ (function ($) { /***************************************************************************/ /* STACK */ /***************************************************************************/ function Stack (){ var st = []; this.empty = function (){ for(var i = 0; i < st.length; i++){ if (st[i]){ return false } } return true; } this.add = function(v){ st.push(v); } this.addNext = function ( v){ var t=[], i, k = 0; for(i = 0; i < st.length; i++){ if (!st[i]){ continue; } if (k == 1) { t.push(v); } t.push(st[i]); k++; } if (k < 2) { t.push(v); } st = t; } this.get = function (){ for(var i = 0; i < st.length; i++){ if (st[i]) { return st[i]; } } return false; } this.ack = function (){ for(var i = 0; i < st.length; i++){ if (st[i]) { delete st[i]; break; } } if (this.empty()){ st = []; } } } /***************************************************************************/ /* STORE */ /***************************************************************************/ function Store(){ var store = {}; /** * add a mixed to the store **/ this.add = function(name, obj, todo){ name = name.toLowerCase(); if (!store[name]){ store[name] = []; } store[name].push({obj:obj, tag:ival(todo, 'tag')}); return name + '-' + (store[name].length-1); } /** * return a stored mixed **/ this.get = function(name, last, tag){ var i, idx, add; name = name.toLowerCase(); if (!store[name] || !store[name].length){ return null; } idx = last ? store[name].length : -1; add = last ? -1 : 1; for(i=0; i= 0; idx--){ if ( (store[name][idx] !== undefined) && (store[name][idx].tag !== undefined) && ($.inArray(store[name][idx].tag, tag) >= 0) ){ break; } } } else { for(idx = 0; idx < store[name].length; idx++){ if ( (store[name][idx] !== undefined) && (store[name][idx].tag !== undefined) && ($.inArray(store[name][idx].tag, tag) >= 0) ){ break; } } } } else { idx = pop ? store[name].length - 1 : 0; } if ( !(idx in store[name]) ) { return false; } // Google maps element if (typeof(store[name][idx].obj.setMap) === 'function') { store[name][idx].obj.setMap(null); } // jQuery if (typeof(store[name][idx].obj.remove) === 'function') { store[name][idx].obj.remove(); } // internal (cluster) if (typeof(store[name][idx].obj.free) === 'function') { store[name][idx].obj.free(); } delete store[name][idx].obj; if (tag !== undefined){ tmp = []; for(i=0; i= 0){ same[j] = true; } else { this.freeIndex(i); } } return same; } this.add = function(latLng, marker){ markers.push({latLng:latLng, marker:marker}); } this.get = function(i){ return markers[i]; } this.clusters = function(map, radius, force){ var proj = map.getProjection(), nwP = proj.fromLatLngToPoint( new google.maps.LatLng( map.getBounds().getNorthEast().lat(), map.getBounds().getSouthWest().lng() ) ), i, j, j2, p, x, y, k, k2, z = map.getZoom(), pos = {}, saved = {}, unik = {}, clusters = [], cluster, chk, lat, lng, keys, cnt, bounds = map.getBounds(); cnt = 0; keys = {}; for(i = 0; i < markers.length; i++){ if (!bounds.contains(markers[i].latLng)){ continue; } p = proj.fromLatLngToPoint(markers[i].latLng); pos[i] = [ Math.floor((p.x - nwP.x) * Math.pow(2, z)), Math.floor((p.y - nwP.y) * Math.pow(2, z)) ]; keys[i] = true; cnt++; } // check if visible markers have changed if (!force){ for(k = 0; k < latest.length; k++){ if( k in keys ){ cnt--; } else { break; } } if (!cnt){ return false; // no change } } // save current keys to check later if an update has been done latest = keys; keys = []; for(i in pos){ x = pos[i][0]; y = pos[i][1]; if ( !(x in saved) ){ saved[x] = {}; } if (!( y in saved[x]) ) { saved[x][y] = i; unik[i] = {}; keys.push(i); } unik[ saved[x][y] ][i] = true; } radius = Math.pow(radius, 2); delete(saved); k = 0; while(1){ while((k 1; saved = cluster; } else { chk = cluster.idx.length > saved.idx.length; if (chk){ saved = cluster; } } if (chk){ p = proj.fromLatLngToPoint( new google.maps.LatLng(saved.lat, saved.lng) ); lat = Math.floor((p.x - nwP.x) * Math.pow(2, z)); lng = Math.floor((p.y - nwP.y) * Math.pow(2, z)); } } while(chk); for(k2 = 0; k2 < saved.idx.length; k2++){ if (saved.idx[k2] in unik){ delete(unik[saved.idx[k2]]); } } clusters.push(saved); } return clusters; } this.getBounds = function(){ var i, bounds = new google.maps.LatLngBounds(); for(i = 0; i < markers.length; i++){ bounds.extend(markers[i].latLng); } return bounds; } } /***************************************************************************/ /* GMAP3 GLOBALS */ /***************************************************************************/ var _default = { verbose:false, queryLimit:{ attempt:5, delay:250, // setTimeout(..., delay + random); random:250 }, init:{ mapTypeId : google.maps.MapTypeId.ROADMAP, center:[46.578498,2.457275], zoom: 2 }, classes:{ Map : google.maps.Map, Marker : google.maps.Marker, InfoWindow : google.maps.InfoWindow, Circle : google.maps.Circle, Rectangle : google.maps.Rectangle, OverlayView : google.maps.OverlayView, StreetViewPanorama: google.maps.StreetViewPanorama, KmlLayer : google.maps.KmlLayer, TrafficLayer : google.maps.TrafficLayer, BicyclingLayer : google.maps.BicyclingLayer, GroundOverlay : google.maps.GroundOverlay, StyledMapType : google.maps.StyledMapType } }, _properties = ['events','onces','options','apply', 'callback', 'data', 'tag'], _noInit = ['init', 'geolatlng', 'getlatlng', 'getroute', 'getelevation', 'getdistance', 'addstyledmap', 'setdefault', 'destroy'], _directs = ['get'], geocoder = directionsService = elevationService = maxZoomService = distanceMatrixService = null; function setDefault(values){ for(var k in values){ if (typeof(_default[k]) === 'object'){ _default[k] = $.extend({}, _default[k], values[k]); } else { _default[k] = values[k]; } } } function autoInit(iname){ if (!iname){ return true; } for(var i = 0; i < _noInit.length; i++){ if (_noInit[i] === iname) { return false; } } return true; } /** * return true if action has to be executed directly **/ function isDirect (todo){ var action = ival(todo, 'action'); for(var i = 0; i < _directs.length; i++){ if (_directs[i] === action) { return true; } } return false; } //-----------------------------------------------------------------------// // Objects tools //-----------------------------------------------------------------------// /** * return the real key by an insensitive seach **/ function ikey (object, key){ if (key.toLowerCase){ key = key.toLowerCase(); for(var k in object){ if (k.toLowerCase && (k.toLowerCase() == key)) { return k; } } } return false; } /** * return the value of real key by an insensitive seach **/ function ival (object, key, def){ var k = ikey(object, key); return k ? object[k] : def; } /** * return true if at least one key is set in object * nb: keys in lowercase **/ function hasKey (object, keys){ var n, k; if (!object || !keys) { return false; } keys = array(keys); for(n in object){ if (n.toLowerCase){ n = n.toLowerCase(); for(k in keys){ if (n == keys[k]) { return true; } } } } return false; } /** * return a standard object * nb: include in lowercase **/ function extractObject (todo, include, result/* = {} */){ if (hasKey(todo, _properties) || hasKey(todo, include)){ // #1 classical object definition var i, k; // get defined properties values from todo for(i=0; i<_properties.length; i++){ k = ikey(todo, _properties[i]); result[ _properties[i] ] = k ? todo[k] : {}; } if (include && include.length){ for(i=0; i { eventName => function, } * onces => { eventName => function, } * data => mixed data * ] **/ this._attachEvents = function(sender, todo){ var name; if (!todo) { return } if (todo.events){ for(name in todo.events){ if (typeof(todo.events[name]) === 'function'){ this._attachEvent(sender, name, todo.events[name], todo.data, false); } } } if (todo.onces){ for(name in todo.onces){ if (typeof(todo.onces[name]) === 'function'){ this._attachEvent(sender, name, todo.onces[name], todo.data, true); } } } } /** * execute callback functions **/ this._callback = function(result, todo){ if (typeof(todo.callback) === 'function') { todo.callback.apply($this, [result]); } else if (typeof(todo.callback) === 'object') { for(var i=0; i bounds not available // wait for map google.maps.event.addListenerOnce( map, 'bounds_changed', function() { that._addclusteredmarkers(todo); } ); return; } if (typeof(radius) === 'number'){ clusterer = new Clusterer(); for (i = 0 ; i < markers.length; i++){ latLng = toLatLng(markers[i]); clusterer.add(latLng, markers[i]); } storeId = this._initClusters(todo, clusterer, radius, styles); } this._callback(storeId, todo); this._end(); } this._initClusters = function(todo, clusterer, radius, styles){ var that = this; clusterer.setRedraw(function(force){ var same, clusters = clusterer.clusters(map, radius, force); if (clusters){ same = clusterer.freeDiff(clusters); that._displayClusters(todo, clusterer, clusters, same, styles); } }); clusterer.events( google.maps.event.addListener( map, 'zoom_changed', function() { clusterer.redraw(true); } ), google.maps.event.addListener( map, 'bounds_changed', function() { clusterer.redraw(); } ) ); clusterer.redraw(); return store.add('cluster', clusterer, todo); } this._displayClusters = function(todo, clusterer, clusters, same, styles){ var k, i, ii, m, done, obj, shadow, cluster, options, tmp, w, h, atodo, ctodo = hasKey(todo, 'cluster') ? getObject('', ival(todo, 'cluster')) : {}, mtodo = hasKey(todo, 'marker') ? getObject('', ival(todo, 'marker')) : {}; for(i = 0; i < clusters.length; i++){ if (i in same){ continue; } cluster = clusters[i]; done = false; if (cluster.idx.length > 1){ // look for the cluster design to use m = 0; for(k in styles){ if ( (k > m) && (k <= cluster.idx.length) ){ m = k; } } if (styles[m]){ // cluster defined for the current markers count w = ival(styles[m], 'width'); h = ival(styles[m], 'height'); // create a custom _addOverlay command atodo = {}; $.extend( true, atodo, ctodo, { options:{ pane: 'overlayLayer', content:styles[m].content.replace('CLUSTER_COUNT', cluster.idx.length), offset:{ x: -w/2, y: -h/2 } } } ); obj = this._addOverlay(atodo, toLatLng(cluster), true); atodo.options.pane = 'floatShadow'; atodo.options.content = $('
'); atodo.options.content.width(w); atodo.options.content.height(h); shadow = this._addOverlay(atodo, toLatLng(cluster), true); // store data to the clusterer ctodo.data = { latLng: toLatLng(cluster), markers:[] }; for(ii=0; ii'), listeners = []; $div .css('border', 'none') .css('borderWidth', '0px') .css('position', 'absolute'); $div.append(opts.content); function f() { _default.classes.OverlayView.call(this); this.setMap(map); } f.prototype = new _default.classes.OverlayView(); f.prototype.onAdd = function() { var panes = this.getPanes(); if (opts.pane in panes) { $(panes[opts.pane]).append($div); } } f.prototype.draw = function() { var overlayProjection = this.getProjection(), ps = overlayProjection.fromLatLngToDivPixel(latLng), that = this; $div .css('left', (ps.x+opts.offset.x) + 'px') .css('top' , (ps.y+opts.offset.y) + 'px'); $.each( ("dblclick click mouseover mousemove mouseout mouseup mousedown").split(" "), function( i, name ) { listeners.push( google.maps.event.addDomListener($div[0], name, function(e) { google.maps.event.trigger(that, name); }) ); }); listeners.push( google.maps.event.addDomListener($div[0], "contextmenu", function(e) { google.maps.event.trigger(that, "rightclick"); }) ); } f.prototype.onRemove = function() { for (var i = 0; i < listeners.length; i++) { google.maps.event.removeListener(listeners[i]); } $div.remove(); } f.prototype.hide = function() { $div.hide(); } f.prototype.show = function() { $div.show(); } f.prototype.toggle = function() { if ($div) { if ($div.is(':visible')){ this.show(); } else { this.hide(); } } } f.prototype.toggleDOM = function() { if (this.getMap()) { this.setMap(null); } else { this.setMap(map); } } f.prototype.getDOMElement = function() { return $div[0]; } ov = new f(); if (!internal){ store.add('overlay', ov, o); this._manageEnd(ov, o); } return ov; } /** * add a fix panel to a map **/ this.addfixpanel = function(todo){ var o = getObject('fixpanel', todo), x=y=0, $c, $div; if (o.options.content){ $c = $(o.options.content); if (o.options.left !== undefined){ x = o.options.left; } else if (o.options.right !== undefined){ x = $this.width() - $c.width() - o.options.right; } else if (o.options.center){ x = ($this.width() - $c.width()) / 2; } if (o.options.top !== undefined){ y = o.options.top; } else if (o.options.bottom !== undefined){ y = $this.height() - $c.height() - o.options.bottom; } else if (o.options.middle){ y = ($this.height() - $c.height()) / 2 } $div = $('
') .css('position', 'absolute') .css('top', y+'px') .css('left', x+'px') .css('z-index', '1000') .append($c); $this.first().prepend($div); this._attachEvents(map, o); store.add('fixpanel', $div, o); this._callback($div, o); } this._end(); } /** * add a direction renderer to a map **/ this.adddirectionsrenderer = function(todo, internal){ var dr, o = getObject('directionrenderer', todo, 'panelId'); store.rm('directionrenderer'); o.options.map = map; dr = new google.maps.DirectionsRenderer(o.options); if (o.panelId) { dr.setPanel(document.getElementById(o.panelId)); } store.add('directionrenderer', dr, o); this._manageEnd(dr, o, internal); return dr; } /** * set a direction panel to a dom element from its ID **/ this.setdirectionspanel = function(todo){ var dr = store.get('directionrenderer'), o = getObject('directionpanel', todo, 'id'); if (dr && o.id) { dr.setPanel(document.getElementById(o.id)); } this._manageEnd(dr, o); } /** * set directions on a map (create Direction Renderer if needed) **/ this.setdirections = function(todo){ var dr = store.get('directionrenderer'), o = getObject('directions', todo); if (todo) { o.options.directions = todo.directions ? todo.directions : (todo.options && todo.options.directions ? todo.options.directions : null); } if (o.options.directions) { if (!dr) { dr = this.adddirectionsrenderer(o, true); } else { dr.setDirections(o.options.directions); } } this._manageEnd(dr, o); } /** * set a streetview to a map **/ this.setstreetview = function(todo){ var panorama, o = getObject('streetview', todo, 'id'); if (o.options.position){ o.options.position = toLatLng(o.options.position); } panorama = new _default.classes.StreetViewPanorama(document.getElementById(o.id),o.options); if (panorama){ map.setStreetView(panorama); } this._manageEnd(panorama, o); } /** * add a kml layer to a map **/ this.addkmllayer = function(todo){ var kml, o = getObject('kmllayer', todo, 'url'); o.options.map = map; if (typeof(o.url) === 'string'){ kml = new _default.classes.KmlLayer(o.url, o.options); } store.add('kmllayer', kml, o); this._manageEnd(kml, o); } /** * add a traffic layer to a map **/ this.addtrafficlayer = function(todo){ var o = getObject('trafficlayer', todo), tl = store.get('trafficlayer'); if (!tl){ tl = new _default.classes.TrafficLayer(); tl.setMap(map); store.add('trafficlayer', tl, o); } this._manageEnd(tl, o); } /** * add a bicycling layer to a map **/ this.addbicyclinglayer = function(todo){ var o = getObject('bicyclinglayer', todo), bl = store.get('bicyclinglayer'); if (!bl){ bl = new _default.classes.BicyclingLayer(); bl.setMap(map); store.add('bicyclinglayer', bl, o); } this._manageEnd(bl, o); } /** * add a ground overlay to a map **/ this.addgroundoverlay = function(todo){ var ov, o = getObject('groundoverlay', todo, ['bounds', 'url']); o.bounds = toLatLngBounds(o.bounds); if (o.bounds && (typeof(o.url) === 'string')){ ov = new _default.classes.GroundOverlay(o.url, o.bounds); ov.setMap(map); store.add('groundoverlay', ov, o); } this._manageEnd(ov, o); } /** * geolocalise the user and return a LatLng **/ this.geolatlng = function(todo){ var callback = ival(todo, 'callback'); if (typeof(callback) === 'function') { if(navigator.geolocation) { navigator.geolocation.getCurrentPosition( function(position) { var out = new google.maps.LatLng(position.coords.latitude,position.coords.longitude); callback.apply($this, [out]); }, function() { var out = false; callback.apply($this, [out]); } ); } else if (google.gears) { google.gears.factory.create('beta.geolocation').getCurrentPosition( function(position) { var out = new google.maps.LatLng(position.latitude,position.longitude); callback.apply($this, [out]); }, function() { out = false; callback.apply($this, [out]); } ); } else { callback.apply($this, [false]); } } this._end(); } /** * add a style to a map **/ this.addstyledmap = function(todo, internal){ var o = getObject('styledmap', todo, ['id', 'style']); if (o.style && o.id && !styles[o.id]) { styles[o.id] = new _default.classes.StyledMapType(o.style, o.options); if (map) { map.mapTypes.set(o.id, styles[o.id]); } } this._manageEnd(styles[o.id], o, internal); } /** * set a style to a map (add it if needed) **/ this.setstyledmap = function(todo){ var o = getObject('styledmap', todo, ['id', 'style']); if (o.id) { this.addstyledmap(o, true); if (styles[o.id]) { map.setMapTypeId(o.id); this._callback(styles[o.id], todo); } } this._manageEnd(styles[o.id], o); } /** * remove objects from a map **/ this.clear = function(todo){ var list = array(ival(todo, 'list') || ival(todo, 'name')), last = ival(todo, 'last', false), first = ival(todo, 'first', false), tag = ival(todo, 'tag'); if (tag !== undefined){ tag = array(tag); } store.clear(list, last, first, tag); this._end(); } /** * return objects previously created **/ this.get = function(todo){ var name = ival(todo, 'name') || 'map', first= ival(todo, 'first'), all = ival(todo, 'all'), tag = ival(todo, 'tag'); name = name.toLowerCase(); if (name === 'map'){ return map; } if (tag !== undefined){ tag = array(tag); } if (first){ return store.get(name, false, tag); } else if (all){ return store.all(name, tag); } else { return store.get(name, true, tag); } } /** * return the max zoom of a location **/ this.getmaxzoom = function(todo){ this._resolveLatLng(todo, '_getMaxZoom'); } this._getMaxZoom = function(todo, latLng){ var callback = ival(todo, 'callback'); if (callback && typeof(callback) === 'function') { getMaxZoomService().getMaxZoomAtLatLng( latLng, function(result) { var zoom = result.status === google.maps.MaxZoomStatus.OK ? result.zoom : false; callback.apply($this, [zoom, result.status]); } ); } this._end(); } /** * modify default values **/ this.setdefault = function(todo){ setDefault(todo); this._end(); } /** * autofit a map using its overlays (markers, rectangles ...) **/ this.autofit = function(todo, internal){ var names, list, obj, i, j, empty = true, bounds = new google.maps.LatLngBounds(); names = store.names(); for(i=0; i