/* * GMAP3 Plugin for JQuery * Version : 4.1 * Date : 2011-11-18 * 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, maxZoom, 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(), noClusters = maxZoom && (maxZoom <= map.getZoom()), chkContain = map.getZoom() > 2; cnt = 0; keys = {}; for(i = 0; i < markers.length; i++){ if (chkContain && !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 && !noClusters){ 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 { 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 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'); 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'), that = this; 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]); that._end(); } ); } else { 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(), maxZoom = ival(todo, 'maxZoom', null); names = store.names(); for(i=0; i detect zoom level and check maxZoom google.maps.event.addListenerOnce( map, 'bounds_changed', function() { if (this.getZoom() > maxZoom){ this.setZoom(maxZoom); } } ); } map.fitBounds(bounds); } if (!internal){ this._manageEnd(empty ? false : bounds, todo, internal); } } }; //-----------------------------------------------------------------------// // jQuery plugin //-----------------------------------------------------------------------// $.fn.gmap3 = function(){ var i, args, list = [], empty = true, results = []; // store all arguments in a todo list for(i=0; i