/*! * GMAP3 Plugin for JQuery * Version : 5.0b * Date : 2012-11-17 * 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-2012 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 ($, undef) { /***************************************************************************/ /* GMAP3 DEFAULTS */ /***************************************************************************/ // defaults are defined later in the code to pass the rails asset pipeline and //jasmine while google library is not loaded var defaults, gId = 0; function initDefaults() { if (!defaults) { defaults = { verbose: false, queryLimit: { attempt: 5, delay: 250, // setTimeout(..., delay + random); random: 250 }, 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, ImageMapType : google.maps.ImageMapType }, map: { mapTypeId : google.maps.MapTypeId.ROADMAP, center: [46.578498, 2.457275], zoom: 2 }, overlay: { pane: "floatPane", content: "", offset: { x: 0, y: 0 } }, geoloc: { getCurrentPosition: { maximumAge: 60000, timeout: 5000 } } } } } function globalId(id, simulate){ return id !== undef ? id : "gmap3_" + (simulate ? gId + 1 : ++gId); } /** * attach events from a container to a sender * todo[ * events => { eventName => function, } * onces => { eventName => function, } * data => mixed data * ] **/ function attachEvents($container, args, sender, id, senders){ if (args.todo.events || args.todo.onces){ var context = { id: id, data: args.todo.data, tag: args.todo.tag }; } if (args.todo.events){ $.each(args.todo.events, function(name, f){ google.maps.event.addListener(sender, name, function(event) { f.apply($container, [senders ? senders : sender, event, context]); }); }); } if (args.todo.onces){ $.each(args.todo.onces, function(name, f){ google.maps.event.addListenerOnce(sender, name, function(event) { f.apply($container, [senders ? senders : sender, event, context]); }); }); } } /***************************************************************************/ /* STACK */ /***************************************************************************/ function Stack (){ var st = []; this.empty = function (){ return !st.length; }; this.add = function(v){ st.push(v); }; this.get = function (){ return st.length ? st[0] : false; }; this.ack = function (){ st.shift(); }; } /***************************************************************************/ /* TASK */ /***************************************************************************/ function Task(ctx, onEnd, todo){ var session = {}, that = this, current, resolve = { latLng:{ // function => bool (=> address = latLng) map:false, marker:false, infowindow:false, circle:false, overlay: false, getlatlng: false, getmaxzoom: false, getelevation: false, streetviewpanorama: false, getaddress: true }, geoloc:{ getgeoloc: true } }; if (typeof todo === "string"){ todo = unify(todo); } function unify(todo){ var result = {}; result[todo] = {}; return result; } function next(){ var k; for(k in todo){ if (k in session){ // already run continue; } return k; } } this.run = function (){ var k, opts; while(k = next()){ if (typeof ctx[k] === "function"){ current = k; opts = $.extend(true, {}, defaults[k] || {}, todo[k].options || {}); if (k in resolve.latLng){ if (todo[k].values){ resolveAllLatLng(todo[k].values, ctx, ctx[k], {todo:todo[k], opts:opts, session:session}); } else { resolveLatLng(ctx, ctx[k], resolve.latLng[k], {todo:todo[k], opts:opts, session:session}); } } else if (k in resolve.geoloc){ geoloc(ctx, ctx[k], {todo:todo[k], opts:opts, session:session}); } else { ctx[k].apply(ctx, [{todo:todo[k], opts:opts, session:session}]); } return; // wait until ack } else { session[k] = null; } } onEnd.apply(ctx, [todo, session]); }; this.ack = function(result){ session[current] = result; that.run.apply(that, []); }; } function getKeys(obj){ var k, keys = []; for(k in obj){ keys.push(k); } return keys; } function tuple(args, value){ var todo = {}; // "copy" the common data if (args.todo){ for(var k in args.todo){ if ((k !== "options") && (k !== "values")){ todo[k] = args.todo[k]; } } } // "copy" some specific keys from value first else args.todo var i, keys = ["data", "tag", "id", "events", "onces"]; for(i=0; i done in a function, to let dead-code analyser works without google library loaded **/ function newEmptyOverlay(map){ function Overlay(){ this.onAdd = function(){}; this.onRemove = function(){}; this.draw = function(){}; return defaults.classes.OverlayView.apply(this, []); } Overlay.prototype = defaults.classes.OverlayView.prototype; var obj = new Overlay(); obj.setMap(map); return obj; } /** * Class InternalClusterer * This class manage clusters thanks to "todo" objects * * Note: * Individuals marker are created on the fly thanks to the todo objects, they are * first set to null to keep the indexes synchronised with the todo list * This is the "display" function, set by the gmap3 object, which uses theses data * to create markers when clusters are not required * To remove a marker, the objects are deleted and set not null in arrays * markers[key] * = null : marker exist but has not been displayed yet * = false : marker has been removed **/ function InternalClusterer($container, map, radius, maxZoom){ var updating = false, updated = false, redrawing = false, ready = false, enabled = true, that = this, events = [], store = {}, // combin of index (id1-id2-...) => object ids = {}, // unique id => index markers = [], // index => marker todos = [], // index => todo or null if removed values = [], // index => value overlay = newEmptyOverlay(map), timer, projection, ffilter, fdisplay, ferror; // callback function main(); /** * return a marker by its id, null if not yet displayed and false if no exist or removed **/ this.getById = function(id){ return id in ids ? markers[ids[id]] : false; }; /** * remove a marker by its id **/ this.clearById = function(id){ if (id in ids){ var index = ids[id]; if (markers[index]){ // can be null markers[index].setMap(null); } delete markers[index]; markers[index] = false; delete todos[index]; todos[index] = false; delete values[index]; values[index] = false; delete ids[id]; updated = true; } }; // add a "marker todo" to the cluster this.add = function(todo, value){ todo.id = globalId(todo.id); this.clearById(todo.id); ids[todo.id] = markers.length; markers.push(null); // null = marker not yet created / displayed todos.push(todo); values.push(value); updated = true; }; // add a real marker to the cluster this.addMarker = function(marker, todo){ todo = todo || {}; todo.id = globalId(todo.id); this.clearById(todo.id); if (!todo.options){ todo.options = {}; } todo.options.position = marker.getPosition(); attachEvents($container, {todo:todo}, marker, todo.id); ids[todo.id] = markers.length; markers.push(marker); todos.push(todo); values.push(todo.data || {}); updated = true; }; // return a "marker todo" by its index this.todo = function(index){ return todos[index]; }; // return a "marker value" by its index this.value = function(index){ return values[index]; }; // return a marker by its index this.marker = function(index){ return markers[index]; }; // store a new marker instead if the default "false" this.setMarker = function(index, marker){ markers[index] = marker; }; // link the visible overlay to the logical data (to hide overlays later) this.store = function(cluster, obj, shadow){ store[cluster.ref] = {obj:obj, shadow:shadow}; }; // free all objects this.free = function(){ for(var i = 0; i < events.length; i++){ google.maps.event.removeListener(events[i]); } events = []; $.each(store, function(key){ flush(key); }); store = {}; $.each(todos, function(i){ todos[i] = null; }); todos = []; $.each(markers, function(i){ if (markers[i]){ // false = removed markers[i].setMap(null); delete markers[i]; } }); markers = []; $.each(values, function(i){ delete values[i]; }); values = []; ids = {}; }; // link the display function this.filter = function(f){ ffilter = f; redraw(); }; // enable/disable the clustering feature this.enable = function(value){ if (enabled != value){ enabled = value; redraw(); } }; // link the display function this.display = function(f){ fdisplay = f; }; // link the errorfunction this.error = function(f){ ferror = f; }; // lock the redraw this.beginUpdate = function () { updating = true; }; // unlock the redraw this.endUpdate = function(){ updating = false; if (updated){ redraw(); } }; // bind events function main(){ projection = overlay.getProjection(); if (!projection){ setTimeout(function(){ main.apply(that, []); }, 25); return; } ready = true; events.push(google.maps.event.addListener(map, "zoom_changed", function(){delayRedraw();})); events.push(google.maps.event.addListener(map, "bounds_changed", function(){delayRedraw();})); redraw(); } // flush overlays function flush(key){ if (typeof store[key] === "object"){ // is overlay if (typeof(store[key].obj.setMap) === "function") { store[key].obj.setMap(null); } if (typeof(store[key].obj.remove) === "function") { store[key].obj.remove(); } if (typeof(store[key].shadow.remove) === "function") { store[key].obj.remove(); } if (typeof(store[key].shadow.setMap) === "function") { store[key].shadow.setMap(null); } delete store[key].obj; delete store[key].shadow; } else if (markers[key]){ // marker not removed markers[key].setMap(null); // don't remove the marker object, it may be displayed later } delete store[key]; } /** * return the distance between 2 latLng couple into meters * Params : * Lat1, Lng1, Lat2, Lng2 * LatLng1, Lat2, Lng2 * Lat1, Lng1, LatLng2 * LatLng1, LatLng2 **/ function distanceInMeter(){ var lat1, lat2, lng1, lng2, e, f, g, h; if (arguments[0] instanceof google.maps.LatLng){ lat1 = arguments[0].lat(); lng1 = arguments[0].lng(); if (arguments[1] instanceof google.maps.LatLng){ lat2 = arguments[1].lat(); lng2 = arguments[1].lng(); } else { lat2 = arguments[1]; lng2 = arguments[2]; } } else { lat1 = arguments[0]; lng1 = arguments[1]; if (arguments[2] instanceof google.maps.LatLng){ lat2 = arguments[2].lat(); lng2 = arguments[2].lng(); } else { lat2 = arguments[2]; lng2 = arguments[3]; } } e = Math.PI*lat1/180; f = Math.PI*lng1/180; g = Math.PI*lat2/180; h = Math.PI*lng2/180; return 1000*6371 * Math.acos(Math.min(Math.cos(e)*Math.cos(g)*Math.cos(f)*Math.cos(h)+Math.cos(e)*Math.sin(f)*Math.cos(g)*Math.sin(h)+Math.sin(e)*Math.sin(g),1)); } // extend the visible bounds function extendsMapBounds(){ var radius = distanceInMeter(map.getCenter(), map.getBounds().getNorthEast()), circle = new google.maps.Circle({ center: map.getCenter(), radius: 1.25 * radius // + 25% }); return circle.getBounds(); } // return an object where keys are store keys function getStoreKeys(){ var keys = {}, k; for(k in store){ keys[k] = true; } return keys; } // async the delay function function delayRedraw(){ clearTimeout(timer); timer = setTimeout(function(){ redraw(); }, 25); } // generate bounds extended by radius function extendsBounds(latLng) { var p = projection.fromLatLngToDivPixel(latLng), ne = projection.fromDivPixelToLatLng(new google.maps.Point(p.x+radius, p.y-radius)), sw = projection.fromDivPixelToLatLng(new google.maps.Point(p.x-radius, p.y+radius)); return new google.maps.LatLngBounds(sw, ne); } // run the clustering process and call the display function function redraw(){ if (updating || redrawing || !ready){ return; } var keys = [], used = {}, zoom = map.getZoom(), forceDisabled = (maxZoom !== undef) && (zoom > maxZoom), previousKeys = getStoreKeys(), i, j, k, lat, lng, indexes, previous, check = false, bounds, cluster; // reset flag updated = false; if (zoom > 3){ // extend the bounds of the visible map to manage clusters near the boundaries bounds = extendsMapBounds(); // check contain only if boundaries are valid check = bounds.getSouthWest().lng() < bounds.getNorthEast().lng(); } // calculate positions of "visibles" markers (in extended bounds) $.each(todos, function(index, todo){ if (!todo){ // marker removed return; } if (check && (!bounds.contains(todo.options.position))){ return; } if (ffilter && !ffilter(values[index])){ return; } keys.push(index); }); // for each "visible" marker, search its neighbors to create a cluster // we can't do a classical "for" loop, because, analysis can bypass a marker while focusing on cluster while(1){ i=0; while(used[i] && (i 1) ); } else { for(j=i; j [id, ...] objects = {}; // id => object function ftag(tag){ if (tag){ if (typeof tag === "function"){ return tag; } tag = array(tag); return function(val){ if (val === undef){ return false; } if (typeof val === "object"){ for(var i=0; i= 0){ return true; } } return false; } return $.inArray(val, tag) >= 0; } } } function normalize(res) { return { id: res.id, name: res.name, object:res.obj, tag:res.tag, data:res.data }; } /** * add a mixed to the store **/ this.add = function(args, name, obj, sub){ var todo = args.todo || {}, id = globalId(todo.id); if (!store[name]){ store[name] = []; } if (id in objects){ // object already exists: remove it this.clearById(id); } objects[id] = {obj:obj, sub:sub, name:name, id:id, tag:todo.tag, data:todo.data}; store[name].push(id); return id; }; /** * return a stored object by its id **/ this.getById = function(id, sub, full){ if (id in objects){ if (sub) { return objects[id].sub } else if (full) { return normalize(objects[id]); } return objects[id].obj; } return false; }; /** * return a stored value **/ this.get = function(name, last, tag, full){ var n, id, check = ftag(tag); if (!store[name] || !store[name].length){ return null; } n = store[name].length; while(n){ n--; id = store[name][last ? n : store[name].length - n - 1]; if (id && objects[id]){ if (check && !check(objects[id].tag)){ continue; } return full ? normalize(objects[id]) : objects[id].obj; } } return null; }; /** * return all stored values **/ this.all = function(name, tag, full){ var result = [], check = ftag(tag), find = function(n){ var i, id; for(i=0; i= 0; idx--){ id = store[name][idx]; if ( check(objects[id].tag) ){ break; } } } else { for(idx = 0; idx < store[name].length; idx++){ id = store[name][idx]; if (check(objects[id].tag)){ break; } } } } else { idx = pop ? store[name].length - 1 : 0; } if ( !(idx in store[name]) ) { return false; } return this.clearById(store[name][idx], idx); }; /** * remove object from the store by its id **/ this.clearById = function(id, idx){ if (id in objects){ var i, name = objects[id].name; for(i=0; idx === undef && i= list.length){ method.apply(ctx, [args]); return; } resolveLatLng( that, function(args){ delete args.todo; $.extend(list[i], args); resolve.apply(that, []); // resolve next (using apply avoid too much recursion) }, true, {todo:list[i]} ); } resolve(); } /** * geolocalise the user and return a LatLng **/ function geoloc(ctx, method, args){ var is_echo = false; // sometime, a kind of echo appear, this trick will notice once the first call is run to ignore the next one if (navigator && navigator.geolocation){ navigator.geolocation.getCurrentPosition( function(pos) { if (is_echo){ return; } is_echo = true; args.latLng = new google.maps.LatLng(pos.coords.latitude,pos.coords.longitude); method.apply(ctx, [args]); }, function() { if (is_echo){ return; } is_echo = true; args.latLng = false; method.apply(ctx, [args]); }, args.opts.getCurrentPosition ); } else { args.latLng = false; method.apply(ctx, [args]); } } /***************************************************************************/ /* GMAP3 */ /***************************************************************************/ function Gmap3($this){ var that = this, stack = new Stack(), store = new Store(), map = null, task; //-----------------------------------------------------------------------// // Stack tools //-----------------------------------------------------------------------// /** * store actions to execute in a stack manager **/ this._plan = function(list){ for(var k = 0; k < list.length; k++) { stack.add(new Task(that, end, list[k])); } run(); }; /** * if not running, start next action in stack **/ function run(){ if (!task && (task = stack.get())){ task.run(); } } /** * called when action in finished, to acknoledge the current in stack and start next one **/ function end(){ task = null; stack.ack(); run.call(that); // restart to high level scope } //-----------------------------------------------------------------------// // Tools //-----------------------------------------------------------------------// /** * execute callback functions **/ function callback(args){ var i, params = []; for(i=1; i function with latLng resolution = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /** * Initialize google.maps.Map object **/ this.map = function(args){ newMap(args.latLng, args); attachEvents($this, args, map); manageEnd(args, map); }; /** * destroy an existing instance **/ this.destroy = function(args){ store.clear(); $this.empty(); if (map){ map = null; } manageEnd(args, true); }; /** * add an infowindow **/ this.infowindow = function(args){ var objs = [], multiple = "values" in args.todo; if (!multiple){ if (args.latLng) { args.opts.position = args.latLng; } else if (args.opts.position){ args.opts.position = toLatLng(args.opts.position); } args.todo.values = [{options:args.opts}]; } $.each(args.todo.values, function(i, value){ var id, obj, todo = tuple(args, value); if (!map){ newMap(todo.options.position); } obj = new defaults.classes.InfoWindow(todo.options); if (obj && ((todo.open === undef) || todo.open)){ if (multiple){ obj.open(map, todo.anchor ? todo.anchor : undef); } else { obj.open(map, todo.anchor ? todo.anchor : (args.latLng ? undef : (args.session.marker ? args.session.marker : undef))); } } objs.push(obj); id = store.add({todo:todo}, "infowindow", obj); attachEvents($this, {todo:todo}, obj, id); }); manageEnd(args, multiple ? objs : objs[0]); }; /** * add a circle **/ this.circle = function(args){ var objs = [], multiple = "values" in args.todo; if (!multiple){ args.opts.center = args.latLng || toLatLng(args.opts.center); args.todo.values = [{options:args.opts}]; } if (!args.todo.values.length){ manageEnd(args, false); return; } $.each(args.todo.values, function(i, value){ var id, obj, todo = tuple(args, value); todo.options.center = todo.options.center ? toLatLng(todo.options.center) : toLatLng(value); if (!map){ newMap(todo.options.center); } todo.options.map = map; obj = new defaults.classes.Circle(todo.options); objs.push(obj); id = store.add({todo:todo}, "circle", obj); attachEvents($this, {todo:todo}, obj, id); }); manageEnd(args, multiple ? objs : objs[0]); }; /** * add an overlay **/ this.overlay = function(args, internal){ var id, obj, $div = $(document.createElement("div")) .css("border", "none") .css("borderWidth", "0px") .css("position", "absolute"); $div.append(args.opts.content); OverlayView.prototype = new defaults.classes.OverlayView(); obj = new OverlayView(map, args.opts, args.latLng, $div); $div = null; // memory leak if (internal){ return obj; } id = store.add(args, "overlay", obj); manageEnd(args, obj, id); }; /** * returns address structure from latlng **/ this.getaddress = function(args){ callback(args, args.results, args.status); task.ack(); }; /** * returns latlng from an address **/ this.getlatlng = function(args){ callback(args, args.results, args.status); task.ack(); }; /** * return the max zoom of a location **/ this.getmaxzoom = function(args){ maxZoomService().getMaxZoomAtLatLng( args.latLng, function(result) { callback(args, result.status === google.maps.MaxZoomStatus.OK ? result.zoom : false, status); task.ack(); } ); }; /** * return the elevation of a location **/ this.getelevation = function(args){ var i, locations = [], f = function(results, status){ callback(args, status === google.maps.ElevationStatus.OK ? results : false, status); task.ack(); }; if (args.latLng){ locations.push(args.latLng); } else { locations = array(args.todo.locations || []); for(i=0; i function without latLng resolution = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */ /** * define defaults values **/ this.defaults = function(args){ $.each(args.todo, function(name, value){ if (typeof defaults[name] === "object"){ defaults[name] = $.extend({}, defaults[name], value); } else { defaults[name] = value; } }); task.ack(true); }; /** * add a rectangle **/ this.rectangle = function(args){ var objs = [], multiple = "values" in args.todo; if (!multiple){ args.todo.values = [{options:args.opts}]; } if (!args.todo.values.length){ manageEnd(args, false); return; } $.each(args.todo.values, function(i, value){ var id, obj, todo = tuple(args, value); todo.options.bounds = todo.options.bounds ? toLatLngBounds(todo.options.bounds) : toLatLngBounds(value); if (!map){ newMap(todo.options.bounds.getCenter()); } todo.options.map = map; obj = new defaults.classes.Rectangle(todo.options); objs.push(obj); id = store.add({todo:todo}, "rectangle", obj); attachEvents($this, {todo:todo}, obj, id); }); manageEnd(args, multiple ? objs : objs[0]); }; /** * add a polygone / polyline **/ function poly(args, poly, path){ var objs = [], multiple = "values" in args.todo; if (!multiple){ args.todo.values = [{options:args.opts}]; } if (!args.todo.values.length){ manageEnd(args, false); return; } newMap(); $.each(args.todo.values, function(_, value){ var id, i, j, obj, todo = tuple(args, value); if (todo.options[path]){ if (todo.options[path][0][0] && $.isArray(todo.options[path][0][0])){ for(i=0; i 1){ for(k in styles){ k = 1 * k; // cast to int if (k > n && k <= cnt){ n = k; } } style = styles[n]; } if (style){ offset = style.offset || [-style.width/2, -style.height/2]; // create a custom overlay command // nb: 2 extends are faster that a deeper extend atodo = $.extend({}, todo); atodo.options = $.extend({ pane: "overlayLayer", content:style.content ? style.content.replace("CLUSTER_COUNT", cnt) : "", offset:{ x: ("x" in offset ? offset.x : offset[0]) || 0, y: ("y" in offset ? offset.y : offset[1]) || 0 } }, todo.options || {}); obj = that.overlay({todo:atodo, opts:atodo.options, latLng:toLatLng(cluster)}, true); atodo.options.pane = "floatShadow"; atodo.options.content = $(document.createElement("div")).width(style.width+"px").height(style.height+"px"); shadow = that.overlay({todo:atodo, opts:atodo.options, latLng:toLatLng(cluster)}, true); // store data to the clusterer todo.data = { latLng: toLatLng(cluster), markers:[] }; $.each(cluster.indexes, function(i, index){ todo.data.markers.push(internalClusterer.value(index)); if (internalClusterer.marker(index)){ internalClusterer.marker(index).setMap(null); } }); attachEvents($this, {todo:todo}, shadow, undef, {main:obj, shadow:shadow}); internalClusterer.store(cluster, obj, shadow); } else { $.each(cluster.indexes, function(i, index){ if (internalClusterer.marker(index)){ internalClusterer.marker(index).setMap(map); } else { var todo = internalClusterer.todo(index), marker = new defaults.classes.Marker(todo.options); internalClusterer.setMarker(index, marker); attachEvents($this, {todo:todo}, marker, todo.id); } }); } }); return internalClusterer; } /** * add a marker **/ this.marker = function(args){ var multiple = "values" in args.todo, init = !map; if (!multiple){ args.opts.position = args.latLng || toLatLng(args.opts.position); args.todo.values = [{options:args.opts}]; } if (!args.todo.values.length){ manageEnd(args, false); return; } if (init){ newMap(); } if (args.todo.cluster && !map.getBounds()){ // map not initialised => bounds not available : wait for map if clustering feature is required google.maps.event.addListenerOnce(map, "bounds_changed", function() { that.marker.apply(that, [args]); }); return; } if (args.todo.cluster){ var clusterer, internalClusterer; if (args.todo.cluster instanceof Clusterer){ clusterer = args.todo.cluster; internalClusterer = store.getById(clusterer.id(), true); } else { internalClusterer = createClusterer(args.todo.cluster); clusterer = new Clusterer(globalId(args.todo.id, true), internalClusterer); store.add(args, "clusterer", clusterer, internalClusterer); } internalClusterer.beginUpdate(); $.each(args.todo.values, function(i, value){ var todo = tuple(args, value); todo.options.position = todo.options.position ? toLatLng(todo.options.position) : toLatLng(value); todo.options.map = map; if (init){ map.setCenter(todo.options.position); init = false; } internalClusterer.add(todo, value); }); internalClusterer.endUpdate(); manageEnd(args, clusterer); } else { var objs = []; $.each(args.todo.values, function(i, value){ var id, obj, todo = tuple(args, value); todo.options.position = todo.options.position ? toLatLng(todo.options.position) : toLatLng(value); todo.options.map = map; if (init){ map.setCenter(todo.options.position); init = false; } obj = new defaults.classes.Marker(todo.options); objs.push(obj); id = store.add({todo:todo}, "marker", obj); attachEvents($this, {todo:todo}, obj, id); }); manageEnd(args, multiple ? objs : objs[0]); } }; /** * return a route **/ this.getroute = function(args){ args.opts.origin = toLatLng(args.opts.origin, true); args.opts.destination = toLatLng(args.opts.destination, true); directionsService().route( args.opts, function(results, status) { callback(args, status == google.maps.DirectionsStatus.OK ? results : false, status); task.ack(); } ); }; /** * add a direction renderer **/ this.directionsrenderer = function(args){ args.opts.map = map; var id, obj = new google.maps.DirectionsRenderer(args.opts); if (args.todo.divId){ obj.setPanel(document.getElementById(args.todo.divId)); } else if (args.todo.container){ obj.setPanel($(args.todo.container).get(0)); } id = store.add(args, "directionrenderer", obj); manageEnd(args, obj, id); }; /** * returns latLng of the user **/ this.getgeoloc = function(args){ manageEnd(args, args.latLng); }; /** * add a style **/ this.styledmaptype = function(args){ newMap(); var obj = new defaults.classes.StyledMapType(args.todo.styles, args.opts); map.mapTypes.set(args.todo.id, obj); manageEnd(args, obj); }; /** * add an imageMapType **/ this.imagemaptype = function(args){ newMap(); var obj = new defaults.classes.ImageMapType(args.opts); map.mapTypes.set(args.todo.id, obj); manageEnd(args, obj); }; /** * autofit a map using its overlays (markers, rectangles ...) **/ this.autofit = function(args){ var bounds = new google.maps.LatLngBounds(); $.each(store.all(), function(i, obj){ if (obj.getPosition){ bounds.extend(obj.getPosition()); } else if (obj.getBounds){ bounds.extend(obj.getBounds().getNorthEast()); bounds.extend(obj.getBounds().getSouthWest()); } else if (obj.getPaths){ obj.getPaths().forEach(function(path){ path.forEach(function(latLng){ bounds.extend(latLng); }); }); } else if (obj.getPath){ obj.getPath().forEach(function(latLng){ bounds.extend(latLng);"" }); } else if (obj.getCenter){ bounds.extend(obj.getCenter()); } }); if (!bounds.isEmpty() && (!map.getBounds() || !map.getBounds().equals(bounds))){ if ("maxZoom" in args.todo){ // fitBouds Callback event => detect zoom level and check maxZoom google.maps.event.addListenerOnce( map, "bounds_changed", function() { if (this.getZoom() > args.todo.maxZoom){ this.setZoom(args.todo.maxZoom); } } ); } map.fitBounds(bounds); } manageEnd(args, true); }; /** * remove objects from a map **/ this.clear = function(args){ if (typeof args.todo === "string"){ if (store.clearById(args.todo) || store.objClearById(args.todo)){ manageEnd(args, true); return; } args.todo = {name:args.todo}; } if (args.todo.id){ $.each(array(args.todo.id), function(i, id){ store.clearById(id); }); } else { store.clear(array(args.todo.name), args.todo.last, args.todo.first, args.todo.tag); } manageEnd(args, true); }; /** * run a function on each items selected **/ this.exec = function(args){ var that = this; $.each(array(args.todo.func), function(i, func){ $.each(that.get(args.todo, true, args.todo.hasOwnProperty("full") ? args.todo.full : true), function(j, res){ func.call($this, res); }); }); manageEnd(args, true); }; /** * return objects previously created **/ this.get = function(args, direct, full){ var name, res, todo = direct ? args : args.todo; if (!direct) { full = todo.full; } if (typeof todo === "string"){ res = store.getById(todo, false, full) || store.objGetById(todo); if (res === false){ name = todo; todo = {}; } } else { name = todo.name; } if (name === "map"){ res = map; } if (!res){ res = []; if (todo.id){ $.each(array(todo.id), function(i, id) { res.push(store.getById(id, false, full) || store.objGetById(id)); }); if (!$.isArray(todo.id)) { res = res[0]; } } else { $.each(name ? array(name) : [undef], function(i, aName) { var result; if (todo.first){ result = store.get(aName, false, todo.tag, full); if (result) res.push(result); } else if (todo.all){ $.each(store.all(aName, todo.tag, full), function(i, result){ res.push(result); }); } else { result = store.get(aName, true, todo.tag, full); if (result) res.push(result); } }); if (!todo.all && !$.isArray(name)) { res = res[0]; } } } res = $.isArray(res) || !todo.all ? res : [res]; if (direct){ return res; } else { manageEnd(args, res); } }; /** * return the distance between an origin and a destination * **/ this.getdistance = function(args){ var i; args.opts.origins = array(args.opts.origins); for(i=0; i