JAVASCRIPT   12

gmap3.js

Guest on 6th May 2021 03:03:14 PM

  1. /*!
  2.  *  GMAP3 Plugin for JQuery
  3.  *  Version   : 5.1.1
  4.  *  Licence   : GPL v3 : http://www.gnu.org/licenses/gpl.html
  5.  *  Author    : DEMONTE Jean-Baptiste
  6.  *  Contact   : jbdemonte@gmail.com
  7.  *  Web site  : http://gmap3.net
  8.  *
  9.  *  Copyright (c) 2010-2012 Jean-Baptiste DEMONTE
  10.  *  All rights reserved.
  11.  *
  12.  * Redistribution and use in source and binary forms, with or without
  13.  * modification, are permitted provided that the following conditions are met:
  14.  *
  15.  *   - Redistributions of source code must retain the above copyright
  16.  *     notice, this list of conditions and the following disclaimer.
  17.  *   - Redistributions in binary form must reproduce the above
  18.  *     copyright notice, this list of conditions and the following
  19.  *     disclaimer in the documentation and/or other materials provided
  20.  *     with the distribution.
  21.  *   - Neither the name of the author nor the names of its contributors
  22.  *     may be used to endorse or promote products derived from this
  23.  *     software without specific prior written permission.
  24.  *
  25.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  26.  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  27.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  28.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
  29.  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  30.  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  31.  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  32.  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  33.  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  34.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  35.  * POSSIBILITY OF SUCH DAMAGE.
  36.  */
  37. ;(function ($, undef) {
  38.  
  39.   /***************************************************************************/
  40.   /*                           GMAP3 DEFAULTS                                */
  41.   /***************************************************************************/
  42.   // defaults are defined later in the code to pass the rails asset pipeline and
  43.   //jasmine while google library is not loaded
  44.   var defaults, gId = 0;
  45.  
  46.   function initDefaults() {
  47.     if (!defaults) {
  48.       defaults = {
  49.         verbose: false,
  50.         queryLimit: {
  51.           attempt: 5,
  52.           delay: 250, // setTimeout(..., delay + random);
  53.           random: 250
  54.         },
  55.         classes: {
  56.           Map               : google.maps.Map,
  57.           Marker            : google.maps.Marker,
  58.           InfoWindow        : google.maps.InfoWindow,
  59.           Circle            : google.maps.Circle,
  60.           Rectangle         : google.maps.Rectangle,
  61.           OverlayView       : google.maps.OverlayView,
  62.           StreetViewPanorama: google.maps.StreetViewPanorama,
  63.           KmlLayer          : google.maps.KmlLayer,
  64.           TrafficLayer      : google.maps.TrafficLayer,
  65.           BicyclingLayer    : google.maps.BicyclingLayer,
  66.           GroundOverlay     : google.maps.GroundOverlay,
  67.           StyledMapType     : google.maps.StyledMapType,
  68.           ImageMapType      : google.maps.ImageMapType
  69.         },
  70.         map: {
  71.           mapTypeId : google.maps.MapTypeId.ROADMAP,
  72.           center: [46.578498, 2.457275],
  73.           zoom: 2
  74.         },
  75.         overlay: {
  76.           pane: "floatPane",
  77.           content: "",
  78.           offset: {
  79.             x: 0,
  80.             y: 0
  81.           }
  82.         },
  83.         geoloc: {
  84.           getCurrentPosition: {
  85.             maximumAge: 60000,
  86.             timeout: 5000
  87.           }
  88.         }
  89.       }
  90.     }
  91.   }
  92.  
  93.   function globalId(id, simulate){
  94.     return id !== undef ? id : "gmap3_" + (simulate ? gId + 1 : ++gId);
  95.   }
  96.  
  97.   /**
  98.    * Return true if current version of Google Maps is equal or above to these in parameter
  99.    * @param version {string} Minimal version required
  100.    * @return {Boolean}
  101.    */
  102.   function googleVersionMin(version) {
  103.     var toInt = function(v){return parseInt(v, 10);},
  104.         // extract the google map version
  105.         gmVersion = google.maps.version.split(".").map(toInt),
  106.         i;
  107.     version = version.split(".").map(toInt);
  108.     for(i = 0; i < version.length; i++) {
  109.         if (gmVersion.hasOwnProperty(i)) {
  110.             if (gmVersion[i] < version[i]) {
  111.                 return false;
  112.             }
  113.         } else {
  114.             return false;
  115.         }
  116.     }
  117.     return true;
  118.   }
  119.  
  120.   /**
  121.    * attach events from a container to a sender
  122.    * todo[
  123.    *  events => { eventName => function, }
  124.    *  onces  => { eventName => function, }  
  125.    *  data   => mixed data        
  126.    * ]
  127.    **/
  128.   function attachEvents($container, args, sender, id, senders){
  129.     if (args.todo.events || args.todo.onces) {
  130.       var context = {
  131.         id: id,
  132.         data: args.todo.data,
  133.         tag: args.todo.tag
  134.       };
  135.       if (args.todo.events){
  136.         $.each(args.todo.events, function(name, f){
  137.           var that = $container, fn = f;
  138.           if ($.isArray(f)) {
  139.             that = f[0];
  140.             fn = f[1]
  141.           }
  142.           google.maps.event.addListener(sender, name, function(event) {
  143.             fn.apply(that, [senders ? senders : sender, event, context]);
  144.           });
  145.         });
  146.       }
  147.       if (args.todo.onces){
  148.         $.each(args.todo.onces, function(name, f){
  149.           var that = $container, fn = f;
  150.           if ($.isArray(f)) {
  151.             that = f[0];
  152.             fn = f[1]
  153.           }
  154.           google.maps.event.addListenerOnce(sender, name, function(event) {
  155.             fn.apply(that, [senders ? senders : sender, event, context]);
  156.           });
  157.         });
  158.       }
  159.     }
  160.   }
  161.  
  162.   /***************************************************************************/
  163.   /*                                STACK                                    */
  164.   /***************************************************************************/
  165.  
  166.   function Stack (){
  167.     var st = [];
  168.     this.empty = function (){
  169.       return !st.length;
  170.     };
  171.     this.add = function(v){
  172.       st.push(v);
  173.     };
  174.     this.get = function (){
  175.       return st.length ? st[0] : false;
  176.     };
  177.     this.ack = function (){
  178.       st.shift();
  179.     };
  180.   }
  181.  
  182.   /***************************************************************************/
  183.   /*                                TASK                                     */
  184.   /***************************************************************************/
  185.  
  186.   function Task(ctx, onEnd, todo){
  187.     var session = {},
  188.       that = this,
  189.       current,
  190.       resolve = {
  191.         latLng:{ // function => bool (=> address = latLng)
  192.           map:false,
  193.           marker:false,
  194.           infowindow:false,
  195.           circle:false,
  196.           overlay: false,
  197.           getlatlng: false,
  198.           getmaxzoom: false,
  199.           getelevation: false,
  200.           streetviewpanorama: false,
  201.           getaddress: true
  202.         },
  203.         geoloc:{
  204.           getgeoloc: true
  205.         }
  206.       };
  207.      
  208.     if (typeof todo === "string"){
  209.       todo =  unify(todo);
  210.     }
  211.  
  212.     function unify(todo){
  213.       var result = {};
  214.       result[todo] = {};
  215.       return result;
  216.     }
  217.    
  218.     function next(){
  219.       var k;
  220.       for(k in todo){
  221.         if (k in session){ // already run
  222.           continue;
  223.         }
  224.         return k;
  225.       }
  226.     }
  227.    
  228.     this.run = function (){
  229.       var k, opts;
  230.       while(k = next()){
  231.         if (typeof ctx[k] === "function"){
  232.           current = k;
  233.           opts = $.extend(true, {}, defaults[k] || {}, todo[k].options || {});
  234.           if (k in resolve.latLng){
  235.             if (todo[k].values){
  236.               resolveAllLatLng(todo[k].values, ctx, ctx[k], {todo:todo[k], opts:opts, session:session});
  237.             } else {
  238.               resolveLatLng(ctx, ctx[k], resolve.latLng[k], {todo:todo[k], opts:opts, session:session});
  239.             }
  240.           } else if (k in resolve.geoloc){
  241.             geoloc(ctx, ctx[k], {todo:todo[k], opts:opts, session:session});
  242.           } else {
  243.             ctx[k].apply(ctx, [{todo:todo[k], opts:opts, session:session}]);
  244.           }
  245.           return; // wait until ack
  246.         } else {
  247.           session[k] = null;
  248.         }
  249.       }
  250.       onEnd.apply(ctx, [todo, session]);
  251.     };
  252.    
  253.     this.ack = function(result){
  254.       session[current] = result;
  255.       that.run.apply(that, []);
  256.     };
  257.   }
  258.  
  259.   function getKeys(obj){
  260.     var k, keys = [];
  261.     for(k in obj){
  262.       keys.push(k);
  263.     }
  264.     return keys;
  265.   }
  266.  
  267.   function tuple(args, value){
  268.     var todo = {};
  269.    
  270.     // "copy" the common data
  271.     if (args.todo){
  272.       for(var k in args.todo){
  273.         if ((k !== "options") && (k !== "values")){
  274.           todo[k] = args.todo[k];
  275.         }
  276.       }
  277.     }
  278.     // "copy" some specific keys from value first else args.todo
  279.     var i, keys = ["data", "tag", "id", "events",  "onces"];
  280.     for(i=0; i<keys.length; i++){
  281.       copyKey(todo, keys[i], value, args.todo);
  282.     }
  283.    
  284.     // create an extended options
  285.     todo.options = $.extend({}, args.opts || {}, value.options || {});
  286.    
  287.     return todo;
  288.   }
  289.  
  290.   /**
  291.    * copy a key content
  292.    **/
  293.   function copyKey(target, key){
  294.     for(var i=2; i<arguments.length; i++){
  295.       if (key in arguments[i]){
  296.         target[key] = arguments[i][key];
  297.         return;
  298.       }
  299.     }
  300.   }
  301.  
  302.   /***************************************************************************/
  303.   /*                             GEOCODERCACHE                               */
  304.   /***************************************************************************/
  305.  
  306.   function GeocoderCache(){
  307.     var cache = [];
  308.    
  309.     this.get = function(request){
  310.       if (cache.length){
  311.         var i, j, k, item, eq,
  312.           keys = getKeys(request);
  313.         for(i=0; i<cache.length; i++){
  314.           item = cache[i];
  315.           eq = keys.length == item.keys.length;
  316.           for(j=0; (j<keys.length) && eq; j++){
  317.             k = keys[j];
  318.             eq = k in item.request;
  319.             if (eq){
  320.               if ((typeof request[k] === "object") && ("equals" in request[k]) && (typeof request[k] === "function")){
  321.                 eq = request[k].equals(item.request[k]);
  322.               } else{
  323.                 eq = request[k] === item.request[k];
  324.               }
  325.             }
  326.           }
  327.           if (eq){
  328.             return item.results;
  329.           }
  330.         }
  331.       }
  332.     };
  333.    
  334.     this.store = function(request, results){
  335.       cache.push({request:request, keys:getKeys(request), results:results});
  336.     };
  337.   }
  338.  
  339.   /***************************************************************************/
  340.   /*                                OVERLAYVIEW                              */
  341.   /***************************************************************************/
  342.   function OverlayView(map, opts, latLng, $div) {
  343.     var that = this, listeners = [];
  344.    
  345.     defaults.classes.OverlayView.call(this);
  346.     this.setMap(map);
  347.    
  348.     this.onAdd = function() {
  349.         var panes = this.getPanes();
  350.         if (opts.pane in panes) {
  351.             $(panes[opts.pane]).append($div);
  352.         }
  353.         $.each("dblclick click mouseover mousemove mouseout mouseup mousedown".split(" "), function(i, name){
  354.             listeners.push(
  355.                 google.maps.event.addDomListener($div[0], name, function(e) {
  356.                     $.Event(e).stopPropagation();
  357.                     google.maps.event.trigger(that, name, [e]);
  358.                     that.draw();
  359.                 })
  360.             );
  361.         });
  362.         listeners.push(
  363.             google.maps.event.addDomListener($div[0], "contextmenu", function(e) {
  364.                 $.Event(e).stopPropagation();
  365.                 google.maps.event.trigger(that, "rightclick", [e]);
  366.                 that.draw();
  367.             })
  368.         );
  369.     };
  370.     this.getPosition = function(){
  371.         return latLng;
  372.     };
  373.     this.draw = function() {
  374.         var ps = this.getProjection().fromLatLngToDivPixel(latLng);
  375.         $div
  376.             .css("left", (ps.x+opts.offset.x) + "px")
  377.             .css("top" , (ps.y+opts.offset.y) + "px");
  378.     };
  379.     this.onRemove = function() {
  380.       for (var i = 0; i < listeners.length; i++) {
  381.         google.maps.event.removeListener(listeners[i]);
  382.       }
  383.       $div.remove();
  384.     };
  385.     this.hide = function() {
  386.       $div.hide();
  387.     };
  388.     this.show = function() {
  389.       $div.show();
  390.     };
  391.     this.toggle = function() {
  392.       if ($div) {
  393.         if ($div.is(":visible")){
  394.           this.show();
  395.         } else {
  396.           this.hide();
  397.         }
  398.       }
  399.     };
  400.     this.toggleDOM = function() {
  401.       if (this.getMap()) {
  402.         this.setMap(null);
  403.       } else {
  404.         this.setMap(map);
  405.       }
  406.     };
  407.     this.getDOMElement = function() {
  408.       return $div[0];
  409.     };
  410.   }
  411.  
  412.   /***************************************************************************/
  413.   /*                              CLUSTERING                                 */
  414.   /***************************************************************************/
  415.      
  416.   /**
  417.    * Usefull to get a projection
  418.    * => done in a function, to let dead-code analyser works without google library loaded    
  419.    **/
  420.   function newEmptyOverlay(map, radius){
  421.     function Overlay(){
  422.       this.onAdd = function(){};
  423.       this.onRemove = function(){};
  424.       this.draw = function(){};
  425.       return defaults.classes.OverlayView.apply(this, []);
  426.     }
  427.     Overlay.prototype = defaults.classes.OverlayView.prototype;
  428.     var obj = new Overlay();
  429.     obj.setMap(map);
  430.     return obj;
  431.   }
  432.  
  433.   /**
  434.    * Class InternalClusterer
  435.    * This class manage clusters thanks to "todo" objects
  436.    *
  437.    * Note:
  438.    * Individuals marker are created on the fly thanks to the todo objects, they are
  439.    * first set to null to keep the indexes synchronised with the todo list
  440.    * This is the "display" function, set by the gmap3 object, which uses theses data
  441.    * to create markers when clusters are not required
  442.    * To remove a marker, the objects are deleted and set not null in arrays
  443.    *    markers[key]
  444.    *      = null : marker exist but has not been displayed yet
  445.    *      = false : marker has been removed      
  446.    **/
  447.   function InternalClusterer($container, map, raw){
  448.     var updating = false,
  449.       updated = false,
  450.       redrawing = false,
  451.       ready = false,
  452.       enabled = true,
  453.       that = this,
  454.       events =  [],
  455.       store = {},   // combin of index (id1-id2-...) => object
  456.       ids = {},     // unique id => index
  457.       idxs = {},    // index => unique id
  458.       markers = [], // index => marker
  459.       todos = [],   // index => todo or null if removed
  460.       values = [],  // index => value
  461.       overlay = newEmptyOverlay(map, raw.radius),
  462.       timer, projection,
  463.       ffilter, fdisplay, ferror; // callback function
  464.      
  465.     main();
  466.  
  467.     function prepareMarker(index) {
  468.       if (!markers[index]) {
  469.         delete todos[index].options.map;
  470.         markers[index] = new defaults.classes.Marker(todos[index].options);
  471.         attachEvents($container, {todo: todos[index]}, markers[index], todos[index].id);
  472.       }
  473.     }
  474.  
  475.     /**
  476.      * return a marker by its id, null if not yet displayed and false if no exist or removed
  477.      **/
  478.     this.getById = function(id){
  479.       if (id in ids) {
  480.         prepareMarker(ids[id]);
  481.         return  markers[ids[id]];
  482.       }
  483.       return false;
  484.     };
  485.  
  486.     /**
  487.      * remove one object from the store
  488.      **/
  489.     this.rm = function (id) {
  490.       var index = ids[id];
  491.       if (markers[index]){ // can be null
  492.         markers[index].setMap(null);
  493.       }
  494.       delete markers[index];
  495.       markers[index] = false;
  496.  
  497.       delete todos[index];
  498.       todos[index] = false;
  499.  
  500.       delete values[index];
  501.       values[index] = false;
  502.  
  503.       delete ids[id];
  504.       delete idxs[index];
  505.       updated = true;
  506.     };
  507.    
  508.     /**
  509.      * remove a marker by its id
  510.      **/
  511.     this.clearById = function(id){
  512.       if (id in ids){
  513.         this.rm(id);
  514.         return true;
  515.       }
  516.     };
  517.  
  518.     /**
  519.      * remove objects from the store
  520.      **/
  521.     this.clear = function(last, first, tag){
  522.       var start, stop, step, index, i,
  523.           list = [],
  524.           check = ftag(tag);
  525.       if (last) {
  526.         start = todos.length - 1;
  527.         stop = -1;
  528.         step = -1;
  529.       } else {
  530.         start = 0;
  531.         stop =  todos.length;
  532.         step = 1;
  533.       }
  534.       for (index = start; index != stop; index += step) {
  535.         if (todos[index]) {
  536.           if (!check || check(todos[index].tag)){
  537.             list.push(idxs[index]);
  538.             if (first || last) {
  539.               break;
  540.             }
  541.           }
  542.         }
  543.       }
  544.       for (i = 0; i < list.length; i++) {
  545.         this.rm(list[i]);
  546.       }
  547.     };
  548.    
  549.     // add a "marker todo" to the cluster
  550.     this.add = function(todo, value){
  551.       todo.id = globalId(todo.id);
  552.       this.clearById(todo.id);
  553.       ids[todo.id] = markers.length;
  554.       idxs[markers.length] = todo.id;
  555.       markers.push(null); // null = marker not yet created / displayed
  556.       todos.push(todo);
  557.       values.push(value);
  558.       updated = true;
  559.     };
  560.    
  561.     // add a real marker to the cluster
  562.     this.addMarker = function(marker, todo){
  563.       todo = todo || {};
  564.       todo.id = globalId(todo.id);
  565.       this.clearById(todo.id);
  566.       if (!todo.options){
  567.         todo.options = {};
  568.       }
  569.       todo.options.position = marker.getPosition();
  570.       attachEvents($container, {todo:todo}, marker, todo.id);
  571.       ids[todo.id] = markers.length;
  572.       idxs[markers.length] = todo.id;
  573.       markers.push(marker);
  574.       todos.push(todo);
  575.       values.push(todo.data || {});
  576.       updated = true;
  577.     };
  578.    
  579.     // return a "marker todo" by its index
  580.     this.todo = function(index){
  581.       return todos[index];
  582.     };
  583.    
  584.     // return a "marker value" by its index
  585.     this.value = function(index){
  586.       return values[index];
  587.     };
  588.  
  589.     // return a marker by its index
  590.     this.marker = function(index){
  591.       if (index in markers) {
  592.         prepareMarker(index);
  593.         return  markers[index];
  594.       }
  595.       return false;
  596.     };
  597.  
  598.     // return a marker by its index
  599.     this.markerIsSet = function(index){
  600.       return Boolean(markers[index]);
  601.     };
  602.    
  603.     // store a new marker instead if the default "false"
  604.     this.setMarker = function(index, marker){
  605.       markers[index] = marker;
  606.     };
  607.    
  608.     // link the visible overlay to the logical data (to hide overlays later)
  609.     this.store = function(cluster, obj, shadow){
  610.       store[cluster.ref] = {obj:obj, shadow:shadow};
  611.     };
  612.    
  613.     // free all objects
  614.     this.free = function(){
  615.       for(var i = 0; i < events.length; i++){
  616.         google.maps.event.removeListener(events[i]);
  617.       }
  618.       events = [];
  619.      
  620.       $.each(store, function(key){
  621.         flush(key);
  622.       });
  623.       store = {};
  624.      
  625.       $.each(todos, function(i){
  626.         todos[i] = null;
  627.       });
  628.       todos = [];
  629.      
  630.       $.each(markers, function(i){
  631.         if (markers[i]){ // false = removed
  632.           markers[i].setMap(null);
  633.           delete markers[i];
  634.         }
  635.       });
  636.       markers = [];
  637.      
  638.       $.each(values, function(i){
  639.         delete values[i];
  640.       });
  641.       values = [];
  642.      
  643.       ids = {};
  644.       idxs = {};
  645.     };
  646.    
  647.     // link the display function
  648.     this.filter = function(f){
  649.       ffilter = f;
  650.       redraw();
  651.     };
  652.    
  653.     // enable/disable the clustering feature
  654.     this.enable = function(value){
  655.       if (enabled != value){
  656.         enabled = value;
  657.         redraw();
  658.       }
  659.     };
  660.    
  661.     // link the display function
  662.     this.display = function(f){
  663.       fdisplay = f;
  664.     };
  665.    
  666.     // link the errorfunction
  667.     this.error = function(f){
  668.       ferror = f;
  669.     };
  670.    
  671.     // lock the redraw
  672.     this.beginUpdate = function(){
  673.       updating = true;
  674.     };
  675.    
  676.     // unlock the redraw
  677.     this.endUpdate = function(){
  678.       updating = false;
  679.       if (updated){
  680.         redraw();
  681.       }
  682.     };
  683.  
  684.     // extends current bounds with internal markers
  685.     this.autofit = function(bounds){
  686.       for(var i=0; i<todos.length; i++){
  687.         if (todos[i]){
  688.           bounds.extend(todos[i].options.position);
  689.         }
  690.       }
  691.     };
  692.    
  693.     // bind events
  694.     function main(){
  695.       projection = overlay.getProjection();
  696.       if (!projection){
  697.         setTimeout(function(){
  698.           main.apply(that, []);
  699.         },
  700.         25);
  701.         return;
  702.       }
  703.       ready = true;
  704.       events.push(google.maps.event.addListener(map, "zoom_changed", function(){delayRedraw();}));
  705.       events.push(google.maps.event.addListener(map, "bounds_changed", function(){delayRedraw();}));
  706.       redraw();
  707.     }
  708.    
  709.     // flush overlays
  710.     function flush(key){
  711.       if (typeof store[key] === "object"){ // is overlay
  712.         if (typeof(store[key].obj.setMap) === "function") {
  713.           store[key].obj.setMap(null);
  714.         }
  715.         if (typeof(store[key].obj.remove) === "function") {
  716.           store[key].obj.remove();
  717.         }
  718.         if (typeof(store[key].shadow.remove) === "function") {
  719.           store[key].obj.remove();
  720.         }
  721.         if (typeof(store[key].shadow.setMap) === "function") {
  722.           store[key].shadow.setMap(null);
  723.         }
  724.         delete store[key].obj;
  725.         delete store[key].shadow;
  726.       } else if (markers[key]){ // marker not removed
  727.         markers[key].setMap(null);
  728.         // don't remove the marker object, it may be displayed later
  729.       }
  730.       delete store[key];
  731.     }
  732.    
  733.     /**
  734.      * return the distance between 2 latLng couple into meters
  735.      * Params :  
  736.      *  Lat1, Lng1, Lat2, Lng2
  737.      *  LatLng1, Lat2, Lng2
  738.      *  Lat1, Lng1, LatLng2
  739.      *  LatLng1, LatLng2
  740.      **/
  741.     function distanceInMeter(){
  742.       var lat1, lat2, lng1, lng2, e, f, g, h;
  743.       if (arguments[0] instanceof google.maps.LatLng){
  744.         lat1 = arguments[0].lat();
  745.         lng1 = arguments[0].lng();
  746.         if (arguments[1] instanceof google.maps.LatLng){
  747.           lat2 = arguments[1].lat();
  748.           lng2 = arguments[1].lng();
  749.         } else {
  750.           lat2 = arguments[1];
  751.           lng2 = arguments[2];
  752.         }
  753.       } else {
  754.         lat1 = arguments[0];
  755.         lng1 = arguments[1];
  756.         if (arguments[2] instanceof google.maps.LatLng){
  757.           lat2 = arguments[2].lat();
  758.           lng2 = arguments[2].lng();
  759.         } else {
  760.           lat2 = arguments[2];
  761.           lng2 = arguments[3];
  762.         }
  763.       }
  764.       e = Math.PI*lat1/180;
  765.       f = Math.PI*lng1/180;
  766.       g = Math.PI*lat2/180;
  767.       h = Math.PI*lng2/180;
  768.       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));
  769.     }
  770.    
  771.     // extend the visible bounds
  772.     function extendsMapBounds(){
  773.       var radius = distanceInMeter(map.getCenter(), map.getBounds().getNorthEast()),
  774.         circle = new google.maps.Circle({
  775.           center: map.getCenter(),
  776.           radius: 1.25 * radius // + 25%
  777.         });
  778.       return circle.getBounds();
  779.     }
  780.    
  781.     // return an object where keys are store keys
  782.     function getStoreKeys(){
  783.       var keys = {}, k;
  784.       for(k in store){
  785.         keys[k] = true;
  786.       }
  787.       return keys;
  788.     }
  789.    
  790.     // async the delay function
  791.     function delayRedraw(){
  792.       clearTimeout(timer);
  793.       timer = setTimeout(function(){
  794.         redraw();
  795.       },
  796.       25);
  797.     }
  798.    
  799.     // generate bounds extended by radius
  800.     function extendsBounds(latLng) {
  801.       var p = projection.fromLatLngToDivPixel(latLng),
  802.         ne = projection.fromDivPixelToLatLng(new google.maps.Point(p.x+raw.radius, p.y-raw.radius)),
  803.         sw = projection.fromDivPixelToLatLng(new google.maps.Point(p.x-raw.radius, p.y+raw.radius));
  804.       return new google.maps.LatLngBounds(sw, ne);
  805.     }
  806.    
  807.     // run the clustering process and call the display function
  808.     function redraw(){
  809.       if (updating || redrawing || !ready){
  810.         return;
  811.       }
  812.  
  813.       var keys = [], used = {},
  814.         zoom = map.getZoom(),
  815.         forceDisabled = ("maxZoom" in raw) && (zoom > raw.maxZoom),
  816.         previousKeys = getStoreKeys(),
  817.         i, j, k, indexes, check = false, bounds, cluster, position, previous, lat, lng, loop;
  818.  
  819.       // reset flag
  820.       updated = false;
  821.  
  822.       if (zoom > 3){
  823.         // extend the bounds of the visible map to manage clusters near the boundaries
  824.         bounds = extendsMapBounds();
  825.  
  826.         // check contain only if boundaries are valid
  827.         check = bounds.getSouthWest().lng() < bounds.getNorthEast().lng();
  828.       }
  829.  
  830.       // calculate positions of "visibles" markers (in extended bounds)
  831.       for(i=0; i<todos.length; i++){
  832.         if (todos[i] && (!check || bounds.contains(todos[i].options.position)) && (!ffilter || ffilter(values[i]))){
  833.           keys.push(i);
  834.         }
  835.       }
  836.  
  837.       // for each "visible" marker, search its neighbors to create a cluster
  838.       // we can't do a classical "for" loop, because, analysis can bypass a marker while focusing on cluster
  839.       while(1){
  840.         i=0;
  841.         while(used[i] && (i<keys.length)){ // look for the next marker not used
  842.           i++;
  843.         }
  844.         if (i == keys.length){
  845.           break;
  846.         }
  847.  
  848.         indexes = [];
  849.  
  850.         if (enabled && !forceDisabled){
  851.           loop = 10;
  852.           do{
  853.             previous = indexes;
  854.             indexes = [];
  855.             loop--;
  856.  
  857.             if (previous.length){
  858.               position = bounds.getCenter()
  859.             } else {
  860.               position = todos[ keys[i] ].options.position;
  861.             }
  862.             bounds = extendsBounds(position);
  863.  
  864.             for(j=i; j<keys.length; j++){
  865.               if (used[j]){
  866.                 continue;
  867.               }
  868.               if (bounds.contains(todos[ keys[j] ].options.position)){
  869.                 indexes.push(j);
  870.               }
  871.             }
  872.           } while( (previous.length < indexes.length) && (indexes.length > 1) && loop);
  873.         } else {
  874.           for(j=i; j<keys.length; j++){
  875.             if (used[j]){
  876.               continue;
  877.             }
  878.             indexes.push(j);
  879.             break;
  880.           }
  881.         }
  882.  
  883.         cluster = {indexes:[], ref:[]};
  884.         lat = lng = 0;
  885.         for(k=0; k<indexes.length; k++){
  886.           used[ indexes[k] ] = true;
  887.           cluster.indexes.push(keys[indexes[k]]);
  888.           cluster.ref.push(keys[indexes[k]]);
  889.           lat += todos[ keys[indexes[k]] ].options.position.lat();
  890.           lng += todos[ keys[indexes[k]] ].options.position.lng();
  891.         }
  892.         lat /= indexes.length;
  893.         lng /= indexes.length;
  894.         cluster.latLng = new google.maps.LatLng(lat, lng);
  895.  
  896.         cluster.ref = cluster.ref.join("-");
  897.  
  898.         if (cluster.ref in previousKeys){ // cluster doesn't change
  899.           delete previousKeys[cluster.ref]; // remove this entry, these still in this array will be removed
  900.         } else { // cluster is new
  901.           if (indexes.length === 1){ // alone markers are not stored, so need to keep the key (else, will be displayed every time and marker will blink)
  902.             store[cluster.ref] = true;
  903.           }
  904.           fdisplay(cluster);
  905.         }
  906.       }
  907.  
  908.       // flush the previous overlays which are not still used
  909.       $.each(previousKeys, function(key){
  910.         flush(key);
  911.       });
  912.       redrawing = false;
  913.     }
  914.   }
  915.  
  916.   /**
  917.    * Class Clusterer
  918.    * a facade with limited method for external use
  919.    **/
  920.   function Clusterer(id, internalClusterer){
  921.     this.id = function(){
  922.       return id;
  923.     };
  924.     this.filter = function(f){
  925.       internalClusterer.filter(f);
  926.     };
  927.     this.enable = function(){
  928.       internalClusterer.enable(true);
  929.     };
  930.     this.disable = function(){
  931.       internalClusterer.enable(false);
  932.     };
  933.     this.add = function(marker, todo, lock){
  934.       if (!lock) {
  935.         internalClusterer.beginUpdate();
  936.       }
  937.       internalClusterer.addMarker(marker, todo);
  938.       if (!lock) {
  939.         internalClusterer.endUpdate();
  940.       }
  941.     };
  942.     this.getById = function(id){
  943.       return internalClusterer.getById(id);
  944.     };
  945.     this.clearById = function(id, lock){
  946.       var result;
  947.       if (!lock) {
  948.         internalClusterer.beginUpdate();
  949.       }
  950.       result = internalClusterer.clearById(id);
  951.       if (!lock) {
  952.         internalClusterer.endUpdate();
  953.       }
  954.       return result;
  955.     };
  956.     this.clear = function(last, first, tag, lock){
  957.       if (!lock) {
  958.         internalClusterer.beginUpdate();
  959.       }
  960.       internalClusterer.clear(last, first, tag);
  961.       if (!lock) {
  962.         internalClusterer.endUpdate();
  963.       }
  964.     };
  965.   }
  966.   /***************************************************************************/
  967.   /*                                STORE                                    */
  968.   /***************************************************************************/
  969.  
  970.   function Store(){
  971.     var store = {}, // name => [id, ...]
  972.       objects = {}; // id => object
  973.  
  974.     function normalize(res) {
  975.       return {
  976.         id: res.id,
  977.         name: res.name,
  978.         object:res.obj,
  979.         tag:res.tag,
  980.         data:res.data
  981.       };
  982.     }
  983.    
  984.     /**
  985.      * add a mixed to the store
  986.      **/
  987.     this.add = function(args, name, obj, sub){
  988.       var todo = args.todo || {},
  989.         id = globalId(todo.id);
  990.       if (!store[name]){
  991.         store[name] = [];
  992.       }
  993.       if (id in objects){ // object already exists: remove it
  994.         this.clearById(id);
  995.       }
  996.       objects[id] = {obj:obj, sub:sub, name:name, id:id, tag:todo.tag, data:todo.data};
  997.       store[name].push(id);
  998.       return id;
  999.     };
  1000.    
  1001.     /**
  1002.      * return a stored object by its id
  1003.      **/
  1004.     this.getById = function(id, sub, full){
  1005.       if (id in objects){
  1006.           if (sub) {
  1007.             return objects[id].sub
  1008.           } else if (full) {
  1009.             return normalize(objects[id]);
  1010.           }
  1011.           return objects[id].obj;
  1012.  
  1013.       }
  1014.       return false;
  1015.     };
  1016.    
  1017.     /**
  1018.      * return a stored value
  1019.      **/
  1020.     this.get = function(name, last, tag, full){
  1021.       var n, id, check = ftag(tag);
  1022.       if (!store[name] || !store[name].length){
  1023.         return null;
  1024.       }
  1025.       n = store[name].length;
  1026.       while(n){
  1027.         n--;
  1028.         id = store[name][last ? n : store[name].length - n - 1];
  1029.         if (id && objects[id]){
  1030.           if (check && !check(objects[id].tag)){
  1031.             continue;
  1032.           }
  1033.           return full ? normalize(objects[id]) : objects[id].obj;
  1034.         }
  1035.       }
  1036.       return null;
  1037.     };
  1038.    
  1039.     /**
  1040.      * return all stored values
  1041.      **/
  1042.     this.all = function(name, tag, full){
  1043.       var result = [],
  1044.           check = ftag(tag),
  1045.           find = function(n){
  1046.             var i, id;
  1047.             for(i=0; i<store[n].length; i++){
  1048.               id = store[n][i];
  1049.               if (id && objects[id]){
  1050.                 if (check && !check(objects[id].tag)){
  1051.                   continue;
  1052.                 }
  1053.                 result.push(full ? normalize(objects[id]) : objects[id].obj);
  1054.               }
  1055.             }
  1056.           };
  1057.       if (name in store){
  1058.         find(name);
  1059.       } else if (name === undef){ // internal use only
  1060.         for(name in store){
  1061.           find(name);
  1062.         }
  1063.       }
  1064.       return result;
  1065.     };
  1066.    
  1067.     /**
  1068.      * hide and remove an object
  1069.      **/
  1070.     function rm(obj){
  1071.       // Google maps element
  1072.       if (typeof(obj.setMap) === "function") {
  1073.         obj.setMap(null);
  1074.       }
  1075.       // jQuery
  1076.       if (typeof(obj.remove) === "function") {
  1077.         obj.remove();
  1078.       }
  1079.       // internal (cluster)
  1080.       if (typeof(obj.free) === "function") {
  1081.         obj.free();
  1082.       }
  1083.       obj = null;
  1084.     }
  1085.  
  1086.     /**
  1087.      * remove one object from the store
  1088.      **/
  1089.     this.rm = function(name, check, pop){
  1090.       var idx, id;
  1091.       if (!store[name]) {
  1092.         return false;
  1093.       }
  1094.       if (check){
  1095.         if (pop){
  1096.           for(idx = store[name].length - 1; idx >= 0; idx--){
  1097.             id = store[name][idx];
  1098.             if ( check(objects[id].tag) ){
  1099.               break;
  1100.             }
  1101.           }
  1102.         } else {
  1103.           for(idx = 0; idx < store[name].length; idx++){
  1104.             id = store[name][idx];
  1105.             if (check(objects[id].tag)){
  1106.               break;
  1107.             }
  1108.           }
  1109.         }
  1110.       } else {
  1111.         idx = pop ? store[name].length - 1 : 0;
  1112.       }
  1113.       if ( !(idx in store[name]) ) {
  1114.         return false;
  1115.       }
  1116.       return this.clearById(store[name][idx], idx);
  1117.     };
  1118.    
  1119.     /**
  1120.      * remove object from the store by its id
  1121.      **/
  1122.     this.clearById = function(id, idx){
  1123.       if (id in objects){
  1124.         var i, name = objects[id].name;
  1125.         for(i=0; idx === undef && i<store[name].length; i++){
  1126.           if (id === store[name][i]){
  1127.             idx = i;
  1128.           }
  1129.         }
  1130.         rm(objects[id].obj);
  1131.         if(objects[id].sub){
  1132.           rm(objects[id].sub);
  1133.         }
  1134.         delete objects[id];
  1135.         store[name].splice(idx, 1);
  1136.         return true;
  1137.       }
  1138.       return false;
  1139.     };
  1140.    
  1141.     /**
  1142.      * return an object from a container object in the store by its id
  1143.      * ! for now, only cluster manage this feature
  1144.      **/
  1145.     this.objGetById = function(id){
  1146.       var result;
  1147.       if (store["clusterer"]) {
  1148.         for(var idx in store["clusterer"]){
  1149.           if ((result = objects[store["clusterer"][idx]].obj.getById(id)) !== false){
  1150.             return result;
  1151.           }
  1152.         }
  1153.       }
  1154.       return false;
  1155.     };
  1156.    
  1157.     /**
  1158.      * remove object from a container object in the store by its id
  1159.      * ! for now, only cluster manage this feature
  1160.      **/
  1161.     this.objClearById = function(id){
  1162.       if (store["clusterer"]) {
  1163.         for(var idx in store["clusterer"]){
  1164.           if (objects[store["clusterer"][idx]].obj.clearById(id)){
  1165.             return true;
  1166.           }
  1167.         }
  1168.       }
  1169.       return null;
  1170.     };
  1171.    
  1172.     /**
  1173.      * remove objects from the store
  1174.      **/
  1175.     this.clear = function(list, last, first, tag){
  1176.       var k, i, name, check = ftag(tag);
  1177.       if (!list || !list.length){
  1178.         list = [];
  1179.         for(k in store){
  1180.           list.push(k);
  1181.         }
  1182.       } else {
  1183.         list = array(list);
  1184.       }
  1185.       for(i=0; i<list.length; i++){
  1186.         name = list[i];
  1187.         if (last){
  1188.           this.rm(name, check, true);
  1189.         } else if (first){
  1190.           this.rm(name, check, false);
  1191.         } else { // all
  1192.           while(this.rm(name, check, false));
  1193.         }
  1194.       }
  1195.     };
  1196.  
  1197.     /**
  1198.      * remove object from a container object in the store by its tags
  1199.      * ! for now, only cluster manage this feature
  1200.      **/
  1201.     this.objClear = function(list, last, first, tag){
  1202.       if (store["clusterer"] && ($.inArray("marker", list) >= 0 || !list.length)) {
  1203.         for(var idx in store["clusterer"]){
  1204.           objects[store["clusterer"][idx]].obj.clear(last, first, tag);
  1205.         }
  1206.       }
  1207.     };
  1208.   }
  1209.  
  1210.   /***************************************************************************/
  1211.   /*                           GMAP3 GLOBALS                                 */
  1212.   /***************************************************************************/
  1213.  
  1214.   var services = {},
  1215.     geocoderCache = new GeocoderCache();
  1216.    
  1217.   //-----------------------------------------------------------------------//
  1218.   // Service tools
  1219.   //-----------------------------------------------------------------------//
  1220.  
  1221.   function geocoder(){
  1222.     if (!services.geocoder) {
  1223.       services.geocoder = new google.maps.Geocoder();
  1224.     }
  1225.     return services.geocoder;
  1226.   }
  1227.  
  1228.   function directionsService(){
  1229.     if (!services.directionsService) {
  1230.       services.directionsService = new google.maps.DirectionsService();
  1231.     }
  1232.     return services.directionsService;
  1233.   }
  1234.  
  1235.   function elevationService(){
  1236.     if (!services.elevationService) {
  1237.       services.elevationService = new google.maps.ElevationService();
  1238.     }
  1239.     return services.elevationService;
  1240.   }
  1241.  
  1242.   function maxZoomService(){
  1243.     if (!services.maxZoomService) {
  1244.       services.maxZoomService = new google.maps.MaxZoomService();
  1245.     }
  1246.     return services.maxZoomService;
  1247.   }
  1248.  
  1249.   function distanceMatrixService(){
  1250.     if (!services.distanceMatrixService) {
  1251.       services.distanceMatrixService = new google.maps.DistanceMatrixService();
  1252.     }
  1253.     return services.distanceMatrixService;
  1254.   }
  1255.  
  1256.   //-----------------------------------------------------------------------//
  1257.   // Unit tools
  1258.   //-----------------------------------------------------------------------//
  1259.  
  1260.   function error(){
  1261.     if (defaults.verbose){
  1262.       var i, err = [];
  1263.       if (window.console && (typeof console.error === "function") ){
  1264.         for(i=0; i<arguments.length; i++){
  1265.           err.push(arguments[i]);
  1266.         }
  1267.         console.error.apply(console, err);
  1268.       } else {
  1269.         err = "";
  1270.         for(i=0; i<arguments.length; i++){
  1271.           err += arguments[i].toString() + " " ;
  1272.         }
  1273.         alert(err);
  1274.       }
  1275.     }
  1276.   }
  1277.  
  1278.   /**
  1279.    * return true if mixed is usable as number
  1280.    **/
  1281.   function numeric(mixed){
  1282.     return (typeof(mixed) === "number" || typeof(mixed) === "string") && mixed !== "" && !isNaN(mixed);
  1283.   }
  1284.  
  1285.   /**
  1286.    * convert data to array
  1287.    **/
  1288.   function array(mixed){
  1289.     var k, a = [];
  1290.     if (mixed !== undef){
  1291.       if (typeof(mixed) === "object"){
  1292.         if (typeof(mixed.length) === "number") {
  1293.           a = mixed;
  1294.         } else {
  1295.           for(k in mixed) {
  1296.             a.push(mixed[k]);
  1297.           }
  1298.         }
  1299.       } else{
  1300.         a.push(mixed);
  1301.       }
  1302.     }
  1303.     return a;
  1304.   }
  1305.  
  1306.   /**
  1307.    * create a function to check a tag
  1308.    */
  1309.   function ftag(tag){
  1310.     if (tag){
  1311.       if (typeof tag === "function"){
  1312.         return tag;
  1313.       }
  1314.       tag = array(tag);
  1315.       return function(val){
  1316.         if (val === undef){
  1317.           return false;
  1318.         }
  1319.         if (typeof val === "object"){
  1320.           for(var i=0; i<val.length; i++){
  1321.             if($.inArray(val[i], tag) >= 0){
  1322.               return true;
  1323.             }
  1324.           }
  1325.           return false;
  1326.         }
  1327.         return $.inArray(val, tag) >= 0;
  1328.       }
  1329.     }
  1330.   }
  1331.  
  1332.   /**
  1333.    * convert mixed [ lat, lng ] objet to google.maps.LatLng
  1334.    **/
  1335.   function toLatLng (mixed, emptyReturnMixed, noFlat){
  1336.     var empty = emptyReturnMixed ? mixed : null;
  1337.     if (!mixed || (typeof mixed === "string")){
  1338.       return empty;
  1339.     }
  1340.     // defined latLng
  1341.     if (mixed.latLng) {
  1342.       return toLatLng(mixed.latLng);
  1343.     }
  1344.     // google.maps.LatLng object
  1345.     if (mixed instanceof google.maps.LatLng) {
  1346.       return mixed;
  1347.     }
  1348.     // {lat:X, lng:Y} object
  1349.     else if ( numeric(mixed.lat) ) {
  1350.       return new google.maps.LatLng(mixed.lat, mixed.lng);
  1351.     }
  1352.     // [X, Y] object
  1353.     else if ( !noFlat && $.isArray(mixed)){
  1354.       if ( !numeric(mixed[0]) || !numeric(mixed[1]) ) {
  1355.         return empty;
  1356.       }
  1357.       return new google.maps.LatLng(mixed[0], mixed[1]);
  1358.     }
  1359.     return empty;
  1360.   }
  1361.  
  1362.   /**
  1363.    * convert mixed [ sw, ne ] object by google.maps.LatLngBounds
  1364.    **/
  1365.   function toLatLngBounds(mixed){
  1366.     var ne, sw;
  1367.     if (!mixed || mixed instanceof google.maps.LatLngBounds) {
  1368.       return mixed || null;
  1369.     }
  1370.     if ($.isArray(mixed)){
  1371.       if (mixed.length == 2){
  1372.         ne = toLatLng(mixed[0]);
  1373.         sw = toLatLng(mixed[1]);
  1374.       } else if (mixed.length == 4){
  1375.         ne = toLatLng([mixed[0], mixed[1]]);
  1376.         sw = toLatLng([mixed[2], mixed[3]]);
  1377.       }
  1378.     } else {
  1379.       if ( ("ne" in mixed) && ("sw" in mixed) ){
  1380.         ne = toLatLng(mixed.ne);
  1381.         sw = toLatLng(mixed.sw);
  1382.       } else if ( ("n" in mixed) && ("e" in mixed) && ("s" in mixed) && ("w" in mixed) ){
  1383.         ne = toLatLng([mixed.n, mixed.e]);
  1384.         sw = toLatLng([mixed.s, mixed.w]);
  1385.       }
  1386.     }
  1387.     if (ne && sw){
  1388.       return new google.maps.LatLngBounds(sw, ne);
  1389.     }
  1390.     return null;
  1391.   }
  1392.  
  1393.   /**
  1394.    * resolveLatLng      
  1395.    **/
  1396.   function resolveLatLng(ctx, method, runLatLng, args, attempt){
  1397.     var latLng = runLatLng ? toLatLng(args.todo, false, true) : false,
  1398.       conf = latLng ?  {latLng:latLng} : (args.todo.address ? (typeof(args.todo.address) === "string" ? {address:args.todo.address} : args.todo.address) : false),
  1399.       cache = conf ? geocoderCache.get(conf) : false,
  1400.       that = this;
  1401.     if (conf){
  1402.       attempt = attempt || 0; // convert undefined to int
  1403.       if (cache){
  1404.         args.latLng = cache.results[0].geometry.location;
  1405.         args.results = cache.results;
  1406.         args.status = cache.status;
  1407.         method.apply(ctx, [args]);
  1408.       } else {
  1409.         if (conf.location){
  1410.           conf.location = toLatLng(conf.location);
  1411.         }
  1412.         if (conf.bounds){
  1413.           conf.bounds = toLatLngBounds(conf.bounds);
  1414.         }
  1415.         geocoder().geocode(
  1416.           conf,
  1417.           function(results, status) {
  1418.             if (status === google.maps.GeocoderStatus.OK){
  1419.               geocoderCache.store(conf, {results:results, status:status});
  1420.               args.latLng = results[0].geometry.location;
  1421.               args.results = results;
  1422.               args.status = status;
  1423.               method.apply(ctx, [args]);
  1424.             } else if ( (status === google.maps.GeocoderStatus.OVER_QUERY_LIMIT) && (attempt < defaults.queryLimit.attempt) ){
  1425.               setTimeout(
  1426.                 function(){
  1427.                   resolveLatLng.apply(that, [ctx, method, runLatLng, args, attempt+1]);
  1428.                 },
  1429.                 defaults.queryLimit.delay + Math.floor(Math.random() * defaults.queryLimit.random)
  1430.               );
  1431.             } else {
  1432.               error("geocode failed", status, conf);
  1433.               args.latLng = args.results = false;
  1434.               args.status = status;
  1435.               method.apply(ctx, [args]);
  1436.             }
  1437.           }
  1438.         );
  1439.       }
  1440.     } else {
  1441.       args.latLng = toLatLng(args.todo, false, true);
  1442.       method.apply(ctx, [args]);
  1443.     }
  1444.   }
  1445.  
  1446.   function resolveAllLatLng(list, ctx, method, args){
  1447.     var that = this, i = -1;
  1448.    
  1449.     function resolve(){
  1450.       // look for next address to resolve
  1451.       do{
  1452.         i++;
  1453.       }while( (i < list.length) && !("address" in list[i]) );
  1454.      
  1455.       // no address found, so run method
  1456.       if (i >= list.length){
  1457.         method.apply(ctx, [args]);
  1458.         return;
  1459.       }
  1460.      
  1461.       resolveLatLng(
  1462.         that,
  1463.         function(args){
  1464.           delete args.todo;
  1465.           $.extend(list[i], args);
  1466.           resolve.apply(that, []); // resolve next (using apply avoid too much recursion)
  1467.         },
  1468.         true,
  1469.         {todo:list[i]}
  1470.       );
  1471.     }
  1472.     resolve();
  1473.   }
  1474.    
  1475.   /**
  1476.    * geolocalise the user and return a LatLng
  1477.    **/
  1478.   function geoloc(ctx, method, args){
  1479.     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
  1480.     if (navigator && navigator.geolocation){
  1481.        navigator.geolocation.getCurrentPosition(
  1482.         function(pos) {
  1483.           if (is_echo){
  1484.             return;
  1485.           }
  1486.           is_echo = true;
  1487.           args.latLng = new google.maps.LatLng(pos.coords.latitude,pos.coords.longitude);
  1488.           method.apply(ctx, [args]);
  1489.         },
  1490.         function() {
  1491.           if (is_echo){
  1492.             return;
  1493.           }
  1494.           is_echo = true;
  1495.           args.latLng = false;
  1496.           method.apply(ctx, [args]);
  1497.         },
  1498.         args.opts.getCurrentPosition
  1499.       );
  1500.     } else {
  1501.       args.latLng = false;
  1502.       method.apply(ctx, [args]);
  1503.     }
  1504.   }
  1505.  
  1506.   /***************************************************************************/
  1507.   /*                                GMAP3                                    */
  1508.   /***************************************************************************/
  1509.  
  1510.   function Gmap3($this){
  1511.     var that = this,
  1512.       stack = new Stack(),
  1513.       store = new Store(),
  1514.       map = null,
  1515.       task;
  1516.    
  1517.     //-----------------------------------------------------------------------//
  1518.     // Stack tools
  1519.     //-----------------------------------------------------------------------//
  1520.  
  1521.     /**
  1522.      * store actions to execute in a stack manager
  1523.      **/
  1524.     this._plan = function(list){
  1525.       for(var k = 0; k < list.length; k++) {
  1526.         stack.add(new Task(that, end, list[k]));
  1527.       }
  1528.       run();
  1529.     };
  1530.    
  1531.     /**
  1532.      * if not running, start next action in stack
  1533.      **/
  1534.     function run(){
  1535.       if (!task && (task = stack.get())){
  1536.         task.run();
  1537.       }
  1538.     }
  1539.    
  1540.     /**
  1541.      * called when action in finished, to acknoledge the current in stack and start next one
  1542.      **/
  1543.      function end(){
  1544.       task = null;
  1545.       stack.ack();
  1546.       run.call(that); // restart to high level scope
  1547.     }
  1548.    
  1549.     //-----------------------------------------------------------------------//
  1550.     // Tools
  1551.     //-----------------------------------------------------------------------//
  1552.    
  1553.     /**
  1554.      * execute callback functions
  1555.      **/
  1556.     function callback(args){
  1557.       if (args.todo.callback) {
  1558.         var params = Array.prototype.slice.call(arguments, 1);
  1559.         if (typeof args.todo.callback === "function") {
  1560.           args.todo.callback.apply($this, params);
  1561.         } else if ($.isArray(args.todo.callback)) {
  1562.           if (typeof args.todo.callback[1] === "function") {
  1563.             args.todo.callback[1].apply(args.todo.callback[0], params);
  1564.           }
  1565.         }
  1566.       }
  1567.     }
  1568.    
  1569.     /**
  1570.      * execute ending functions
  1571.      **/
  1572.     function manageEnd(args, obj, id){
  1573.       if (id){
  1574.         attachEvents($this, args, obj, id);
  1575.       }
  1576.       callback(args, obj);
  1577.       task.ack(obj);
  1578.     }
  1579.    
  1580.     /**
  1581.      * initialize the map if not yet initialized
  1582.      **/
  1583.     function newMap(latLng, args){
  1584.       args = args || {};
  1585.       if (map) {
  1586.         if (args.todo && args.todo.options){
  1587.           if (args.todo.options.center) {
  1588.             args.todo.options.center = toLatLng(args.todo.options.center);
  1589.           }
  1590.           map.setOptions(args.todo.options);
  1591.         }
  1592.       } else {
  1593.         var opts = args.opts || $.extend(true, {}, defaults.map, args.todo && args.todo.options ? args.todo.options : {});
  1594.         opts.center = latLng || toLatLng(opts.center);
  1595.         map = new defaults.classes.Map($this.get(0), opts);
  1596.       }
  1597.     }
  1598.      
  1599.     /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  1600.     => function with latLng resolution
  1601.     = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
  1602.    
  1603.     /**
  1604.      * Initialize google.maps.Map object
  1605.      **/
  1606.     this.map = function(args){
  1607.       newMap(args.latLng, args);
  1608.       attachEvents($this, args, map);
  1609.       manageEnd(args, map);
  1610.     };
  1611.    
  1612.     /**
  1613.      * destroy an existing instance
  1614.      **/
  1615.     this.destroy = function(args){
  1616.       store.clear();
  1617.       $this.empty();
  1618.       if (map){
  1619.         map = null;
  1620.       }
  1621.       manageEnd(args, true);
  1622.     };
  1623.    
  1624.     /**
  1625.      * add an infowindow
  1626.      **/
  1627.     this.infowindow = function(args){
  1628.       var objs = [], multiple = "values" in args.todo;
  1629.       if (!multiple){
  1630.         if (args.latLng) {
  1631.           args.opts.position = args.latLng;
  1632.         }
  1633.         args.todo.values = [{options:args.opts}];
  1634.       }
  1635.       $.each(args.todo.values, function(i, value){
  1636.         var id, obj, todo = tuple(args, value);
  1637.         todo.options.position = todo.options.position ? toLatLng(todo.options.position) : toLatLng(value.latLng);
  1638.         if (!map){
  1639.           newMap(todo.options.position);
  1640.         }
  1641.         obj = new defaults.classes.InfoWindow(todo.options);
  1642.         if (obj && ((todo.open === undef) || todo.open)){
  1643.           if (multiple){
  1644.             obj.open(map, todo.anchor ? todo.anchor : undef);
  1645.           } else {
  1646.             obj.open(map, todo.anchor ? todo.anchor : (args.latLng ? undef : (args.session.marker ? args.session.marker : undef)));
  1647.           }
  1648.         }
  1649.         objs.push(obj);
  1650.         id = store.add({todo:todo}, "infowindow", obj);
  1651.         attachEvents($this, {todo:todo}, obj, id);
  1652.       });
  1653.       manageEnd(args, multiple ? objs : objs[0]);
  1654.     };
  1655.    
  1656.     /**
  1657.      * add a circle
  1658.      **/
  1659.     this.circle = function(args){
  1660.       var objs = [], multiple = "values" in args.todo;
  1661.       if (!multiple){
  1662.         args.opts.center = args.latLng || toLatLng(args.opts.center);
  1663.         args.todo.values = [{options:args.opts}];
  1664.       }
  1665.       if (!args.todo.values.length){
  1666.         manageEnd(args, false);
  1667.         return;
  1668.       }
  1669.       $.each(args.todo.values, function(i, value){
  1670.         var id, obj, todo = tuple(args, value);
  1671.         todo.options.center = todo.options.center ? toLatLng(todo.options.center) : toLatLng(value);
  1672.         if (!map){
  1673.           newMap(todo.options.center);
  1674.         }
  1675.         todo.options.map = map;
  1676.         obj = new defaults.classes.Circle(todo.options);
  1677.         objs.push(obj);
  1678.         id = store.add({todo:todo}, "circle", obj);
  1679.         attachEvents($this, {todo:todo}, obj, id);
  1680.       });
  1681.       manageEnd(args, multiple ? objs : objs[0]);
  1682.     };
  1683.    
  1684.     /**
  1685.      * add an overlay
  1686.      **/
  1687.     this.overlay = function(args, internal){
  1688.       var objs = [], multiple = "values" in args.todo;
  1689.       if (!multiple){
  1690.         args.todo.values = [{latLng: args.latLng, options: args.opts}];
  1691.       }
  1692.       if (!args.todo.values.length){
  1693.         manageEnd(args, false);
  1694.         return;
  1695.       }
  1696.       if (!OverlayView.__initialised) {
  1697.         OverlayView.prototype = new defaults.classes.OverlayView();
  1698.         OverlayView.__initialised = true;
  1699.       }
  1700.       $.each(args.todo.values, function(i, value){
  1701.         var id, obj, todo = tuple(args, value),
  1702.             $div = $(document.createElement("div")).css({
  1703.               border: "none",
  1704.               borderWidth: "0px",
  1705.               position: "absolute"
  1706.             });
  1707.         $div.append(todo.options.content);
  1708.         obj = new OverlayView(map, todo.options, toLatLng(todo) || toLatLng(value), $div);
  1709.         objs.push(obj);
  1710.         $div = null; // memory leak
  1711.         if (!internal){
  1712.           id = store.add(args, "overlay", obj);
  1713.           attachEvents($this, {todo:todo}, obj, id);
  1714.         }
  1715.       });
  1716.       if (internal){
  1717.         return objs[0];
  1718.       }
  1719.       manageEnd(args, multiple ? objs : objs[0]);
  1720.     };
  1721.    
  1722.     /**
  1723.      * returns address structure from latlng        
  1724.      **/
  1725.     this.getaddress = function(args){
  1726.       callback(args, args.results, args.status);
  1727.       task.ack();
  1728.     };
  1729.    
  1730.     /**
  1731.      * returns latlng from an address
  1732.      **/
  1733.     this.getlatlng = function(args){
  1734.       callback(args, args.results, args.status);
  1735.       task.ack();
  1736.     };
  1737.    
  1738.     /**
  1739.      * return the max zoom of a location
  1740.      **/
  1741.     this.getmaxzoom = function(args){
  1742.       maxZoomService().getMaxZoomAtLatLng(
  1743.         args.latLng,
  1744.         function(result) {
  1745.           callback(args, result.status === google.maps.MaxZoomStatus.OK ? result.zoom : false, status);
  1746.           task.ack();
  1747.         }
  1748.       );
  1749.     };
  1750.    
  1751.     /**
  1752.      * return the elevation of a location
  1753.      **/
  1754.     this.getelevation = function(args){
  1755.       var i, locations = [],
  1756.         f = function(results, status){
  1757.           callback(args, status === google.maps.ElevationStatus.OK ? results : false, status);
  1758.           task.ack();
  1759.         };
  1760.      
  1761.       if (args.latLng){
  1762.         locations.push(args.latLng);
  1763.       } else {
  1764.         locations = array(args.todo.locations || []);
  1765.         for(i=0; i<locations.length; i++){
  1766.           locations[i] = toLatLng(locations[i]);
  1767.         }
  1768.       }
  1769.       if (locations.length){
  1770.         elevationService().getElevationForLocations({locations:locations}, f);
  1771.       } else {
  1772.         if (args.todo.path && args.todo.path.length){
  1773.           for(i=0; i<args.todo.path.length; i++){
  1774.             locations.push(toLatLng(args.todo.path[i]));
  1775.           }
  1776.         }
  1777.         if (locations.length){
  1778.           elevationService().getElevationAlongPath({path:locations, samples:args.todo.samples}, f);
  1779.         } else {
  1780.           task.ack();
  1781.         }
  1782.       }
  1783.     };
  1784.    
  1785.     /* = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  1786.     => function without latLng resolution
  1787.     = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = */
  1788.    
  1789.     /**
  1790.      * define defaults values
  1791.      **/
  1792.     this.defaults = function(args){
  1793.       $.each(args.todo, function(name, value){
  1794.         if (typeof defaults[name] === "object"){
  1795.           defaults[name] = $.extend({}, defaults[name], value);
  1796.         } else {
  1797.           defaults[name] = value;
  1798.         }
  1799.       });
  1800.       task.ack(true);
  1801.     };
  1802.    
  1803.     /**
  1804.      * add a rectangle
  1805.      **/
  1806.     this.rectangle = function(args){
  1807.       var objs = [], multiple = "values" in args.todo;
  1808.       if (!multiple){
  1809.         args.todo.values = [{options:args.opts}];
  1810.       }
  1811.       if (!args.todo.values.length){
  1812.         manageEnd(args, false);
  1813.         return;
  1814.       }
  1815.       $.each(args.todo.values, function(i, value){
  1816.         var id, obj, todo = tuple(args, value);
  1817.         todo.options.bounds = todo.options.bounds ? toLatLngBounds(todo.options.bounds) : toLatLngBounds(value);
  1818.         if (!map){
  1819.           newMap(todo.options.bounds.getCenter());
  1820.         }
  1821.         todo.options.map = map;
  1822.        
  1823.         obj = new defaults.classes.Rectangle(todo.options);
  1824.         objs.push(obj);
  1825.         id = store.add({todo:todo}, "rectangle", obj);
  1826.         attachEvents($this, {todo:todo}, obj, id);
  1827.       });
  1828.       manageEnd(args, multiple ? objs : objs[0]);
  1829.     };
  1830.    
  1831.     /**
  1832.      * add a polygone / polyline
  1833.      **/
  1834.     function poly(args, poly, path){
  1835.       var objs = [], multiple = "values" in args.todo;
  1836.       if (!multiple){
  1837.         args.todo.values = [{options:args.opts}];
  1838.       }
  1839.       if (!args.todo.values.length){
  1840.         manageEnd(args, false);
  1841.         return;
  1842.       }
  1843.       newMap();
  1844.       $.each(args.todo.values, function(_, value){
  1845.         var id, i, j, obj, todo = tuple(args, value);
  1846.         if (todo.options[path]){
  1847.           if (todo.options[path][0][0] && $.isArray(todo.options[path][0][0])){
  1848.             for(i=0; i<todo.options[path].length; i++){
  1849.               for(j=0; j<todo.options[path][i].length; j++){
  1850.                 todo.options[path][i][j] = toLatLng(todo.options[path][i][j]);
  1851.               }
  1852.             }
  1853.           } else {
  1854.             for(i=0; i<todo.options[path].length; i++){
  1855.               todo.options[path][i] = toLatLng(todo.options[path][i]);
  1856.             }
  1857.           }
  1858.         }
  1859.         todo.options.map = map;
  1860.         obj = new google.maps[poly](todo.options);
  1861.         objs.push(obj);
  1862.         id = store.add({todo:todo}, poly.toLowerCase(), obj);
  1863.         attachEvents($this, {todo:todo}, obj, id);
  1864.       });
  1865.       manageEnd(args, multiple ? objs : objs[0]);
  1866.     }
  1867.    
  1868.     this.polyline = function(args){
  1869.       poly(args, "Polyline", "path");
  1870.     };
  1871.    
  1872.     this.polygon = function(args){
  1873.       poly(args, "Polygon", "paths");
  1874.     };
  1875.    
  1876.     /**
  1877.      * add a traffic layer
  1878.      **/
  1879.     this.trafficlayer = function(args){
  1880.       newMap();
  1881.       var obj = store.get("trafficlayer");
  1882.       if (!obj){
  1883.         obj = new defaults.classes.TrafficLayer();
  1884.         obj.setMap(map);
  1885.         store.add(args, "trafficlayer", obj);
  1886.       }
  1887.       manageEnd(args, obj);
  1888.     };
  1889.    
  1890.     /**
  1891.      * add a bicycling layer
  1892.      **/
  1893.     this.bicyclinglayer = function(args){
  1894.       newMap();
  1895.       var obj = store.get("bicyclinglayer");
  1896.       if (!obj){
  1897.         obj = new defaults.classes.BicyclingLayer();
  1898.         obj.setMap(map);
  1899.         store.add(args, "bicyclinglayer", obj);
  1900.       }
  1901.       manageEnd(args, obj);
  1902.     };
  1903.    
  1904.     /**
  1905.      * add a ground overlay
  1906.      **/
  1907.     this.groundoverlay = function(args){
  1908.       args.opts.bounds = toLatLngBounds(args.opts.bounds);
  1909.       if (args.opts.bounds){
  1910.         newMap(args.opts.bounds.getCenter());
  1911.       }
  1912.       var id, obj = new defaults.classes.GroundOverlay(args.opts.url, args.opts.bounds, args.opts.opts);
  1913.       obj.setMap(map);
  1914.       id = store.add(args, "groundoverlay", obj);
  1915.       manageEnd(args, obj, id);
  1916.     };
  1917.    
  1918.     /**
  1919.      * set a streetview
  1920.      **/
  1921.     this.streetviewpanorama = function(args){
  1922.       if (!args.opts.opts){
  1923.         args.opts.opts = {};
  1924.       }
  1925.       if (args.latLng){
  1926.         args.opts.opts.position = args.latLng;
  1927.       } else if (args.opts.opts.position){
  1928.         args.opts.opts.position = toLatLng(args.opts.opts.position);
  1929.       }
  1930.       if (args.todo.divId){
  1931.         args.opts.container = document.getElementById(args.todo.divId)
  1932.       } else if (args.opts.container){
  1933.         args.opts.container = $(args.opts.container).get(0);
  1934.       }
  1935.       var id, obj = new defaults.classes.StreetViewPanorama(args.opts.container, args.opts.opts);
  1936.       if (obj){
  1937.         map.setStreetView(obj);
  1938.       }
  1939.       id = store.add(args, "streetviewpanorama", obj);
  1940.       manageEnd(args, obj, id);
  1941.     };
  1942.    
  1943.     this.kmllayer = function(args){
  1944.       var objs = [], multiple = "values" in args.todo;
  1945.       if (!multiple){
  1946.         args.todo.values = [{options:args.opts}];
  1947.       }
  1948.       if (!args.todo.values.length){
  1949.         manageEnd(args, false);
  1950.         return;
  1951.       }
  1952.       $.each(args.todo.values, function(i, value){
  1953.         var id, obj, options, todo = tuple(args, value);
  1954.         if (!map){
  1955.           newMap();
  1956.         }
  1957.         options = todo.options;
  1958.         // compatibility 5.0-
  1959.         if (todo.options.opts) {
  1960.             options = todo.options.opts;
  1961.             if (todo.options.url) {
  1962.                 options.url = todo.options.url;
  1963.             }
  1964.         }
  1965.         // -- end --
  1966.         options.map = map;
  1967.         if (googleVersionMin("3.10")) {
  1968.             obj = new defaults.classes.KmlLayer(options);
  1969.         } else {
  1970.             obj = new defaults.classes.KmlLayer(options.url, options);
  1971.         }
  1972.         objs.push(obj);
  1973.         id = store.add({todo:todo}, "kmllayer", obj);
  1974.         attachEvents($this, {todo:todo}, obj, id);
  1975.       });
  1976.       manageEnd(args, multiple ? objs : objs[0]);
  1977.     };
  1978.    
  1979.     /**
  1980.      * add a fix panel
  1981.      **/
  1982.      this.panel = function(args){
  1983.       newMap();
  1984.       var id, x= 0, y=0, $content,
  1985.         $div = $(document.createElement("div"));
  1986.      
  1987.       $div.css({
  1988.         position: "absolute",
  1989.         zIndex: 1000,
  1990.         visibility: "hidden"
  1991.       });
  1992.        
  1993.       if (args.opts.content){
  1994.         $content = $(args.opts.content);
  1995.         $div.append($content);
  1996.         $this.first().prepend($div);
  1997.        
  1998.         if (args.opts.left !== undef){
  1999.           x = args.opts.left;
  2000.         } else if (args.opts.right !== undef){
  2001.           x = $this.width() - $content.width() - args.opts.right;
  2002.         } else if (args.opts.center){
  2003.           x = ($this.width() - $content.width()) / 2;
  2004.         }
  2005.        
  2006.         if (args.opts.top !== undef){
  2007.           y = args.opts.top;
  2008.         } else if (args.opts.bottom !== undef){
  2009.           y = $this.height() - $content.height() - args.opts.bottom;
  2010.         } else if (args.opts.middle){
  2011.           y = ($this.height() - $content.height()) / 2
  2012.         }
  2013.      
  2014.         $div.css({
  2015.             top: y,
  2016.             left: x,
  2017.             visibility: "visible"
  2018.         });
  2019.       }
  2020.  
  2021.       id = store.add(args, "panel", $div);
  2022.       manageEnd(args, $div, id);
  2023.       $div = null; // memory leak
  2024.     };
  2025.    
  2026.     /**
  2027.      * Create an InternalClusterer object
  2028.      **/
  2029.     function createClusterer(raw){
  2030.       var internalClusterer = new InternalClusterer($this, map, raw),
  2031.         todo = {},
  2032.         styles = {},
  2033.         thresholds = [],
  2034.         isInt = /^[0-9]+$/,
  2035.         calculator,
  2036.         k;
  2037.  
  2038.       for(k in raw){
  2039.         if (isInt.test(k)){
  2040.           thresholds.push(1*k); // cast to int
  2041.           styles[k] = raw[k];
  2042.           styles[k].width = styles[k].width || 0;
  2043.           styles[k].height = styles[k].height || 0;
  2044.         } else {
  2045.           todo[k] = raw[k];
  2046.         }
  2047.       }
  2048.       thresholds.sort(function (a, b) { return a > b});
  2049.      
  2050.       // external calculator
  2051.       if (todo.calculator){
  2052.         calculator = function(indexes){
  2053.           var data = [];
  2054.           $.each(indexes, function(i, index){
  2055.             data.push(internalClusterer.value(index));
  2056.           });
  2057.           return todo.calculator.apply($this, [data]);
  2058.         };
  2059.       } else {
  2060.         calculator = function(indexes){
  2061.           return indexes.length;
  2062.         };
  2063.       }
  2064.      
  2065.       // set error function
  2066.       internalClusterer.error(function(){
  2067.         error.apply(that, arguments);
  2068.       });
  2069.      
  2070.       // set display function
  2071.       internalClusterer.display(function(cluster){
  2072.         var i, style, atodo, obj, offset,
  2073.           cnt = calculator(cluster.indexes);
  2074.        
  2075.         // look for the style to use
  2076.         if (raw.force || cnt > 1) {
  2077.           for(i = 0; i < thresholds.length; i++) {
  2078.             if (thresholds[i] <= cnt) {
  2079.               style = styles[thresholds[i]];
  2080.             }
  2081.           }
  2082.         }
  2083.        
  2084.         if (style){
  2085.           offset = style.offset || [-style.width/2, -style.height/2];
  2086.           // create a custom overlay command
  2087.           // nb: 2 extends are faster that a deeper extend
  2088.           atodo = $.extend({}, todo);
  2089.           atodo.options = $.extend({
  2090.             pane: "overlayLayer",
  2091.             content:style.content ? style.content.replace("CLUSTER_COUNT", cnt) : "",
  2092.             offset:{
  2093.               x: ("x" in offset ? offset.x : offset[0]) || 0,
  2094.               y: ("y" in offset ? offset.y : offset[1]) || 0
  2095.             }
  2096.           },
  2097.           todo.options || {});
  2098.          
  2099.           obj = that.overlay({todo:atodo, opts:atodo.options, latLng:toLatLng(cluster)}, true);
  2100.          
  2101.           atodo.options.pane = "floatShadow";
  2102.           atodo.options.content = $(document.createElement("div")).width(style.width+"px").height(style.height+"px").css({cursor:"pointer"});
  2103.           shadow = that.overlay({todo:atodo, opts:atodo.options, latLng:toLatLng(cluster)}, true);
  2104.          
  2105.           // store data to the clusterer
  2106.           todo.data = {
  2107.             latLng: toLatLng(cluster),
  2108.             markers:[]
  2109.           };
  2110.           $.each(cluster.indexes, function(i, index){
  2111.             todo.data.markers.push(internalClusterer.value(index));
  2112.             if (internalClusterer.markerIsSet(index)){
  2113.               internalClusterer.marker(index).setMap(null);
  2114.             }
  2115.           });
  2116.           attachEvents($this, {todo:todo}, shadow, undef, {main:obj, shadow:shadow});
  2117.           internalClusterer.store(cluster, obj, shadow);
  2118.         } else {
  2119.           $.each(cluster.indexes, function(i, index){
  2120.             internalClusterer.marker(index).setMap(map);
  2121.           });
  2122.         }
  2123.       });
  2124.      
  2125.       return internalClusterer;
  2126.     }
  2127.     /**
  2128.      *  add a marker
  2129.      **/
  2130.     this.marker = function(args){
  2131.       var multiple = "values" in args.todo,
  2132.         init = !map;
  2133.       if (!multiple){
  2134.         args.opts.position = args.latLng || toLatLng(args.opts.position);
  2135.         args.todo.values = [{options:args.opts}];
  2136.       }
  2137.       if (!args.todo.values.length){
  2138.         manageEnd(args, false);
  2139.         return;
  2140.       }
  2141.       if (init){
  2142.         newMap();
  2143.       }
  2144.      
  2145.       if (args.todo.cluster && !map.getBounds()){ // map not initialised => bounds not available : wait for map if clustering feature is required
  2146.         google.maps.event.addListenerOnce(map, "bounds_changed", function() { that.marker.apply(that, [args]); });
  2147.         return;
  2148.       }
  2149.       if (args.todo.cluster){
  2150.         var clusterer, internalClusterer;
  2151.         if (args.todo.cluster instanceof Clusterer){
  2152.           clusterer = args.todo.cluster;
  2153.           internalClusterer = store.getById(clusterer.id(), true);
  2154.         } else {
  2155.           internalClusterer = createClusterer(args.todo.cluster);
  2156.           clusterer = new Clusterer(globalId(args.todo.id, true), internalClusterer);
  2157.           store.add(args, "clusterer", clusterer, internalClusterer);
  2158.         }
  2159.         internalClusterer.beginUpdate();
  2160.        
  2161.         $.each(args.todo.values, function(i, value){
  2162.           var todo = tuple(args, value);
  2163.           todo.options.position = todo.options.position ? toLatLng(todo.options.position) : toLatLng(value);
  2164.           todo.options.map = map;
  2165.           if (init){
  2166.             map.setCenter(todo.options.position);
  2167.             init = false;
  2168.           }
  2169.           internalClusterer.add(todo, value);
  2170.         });
  2171.        
  2172.         internalClusterer.endUpdate();
  2173.         manageEnd(args, clusterer);
  2174.        
  2175.       } else {
  2176.         var objs = [];
  2177.         $.each(args.todo.values, function(i, value){
  2178.           var id, obj, todo = tuple(args, value);
  2179.           todo.options.position = todo.options.position ? toLatLng(todo.options.position) : toLatLng(value);
  2180.           todo.options.map = map;
  2181.           if (init){
  2182.             map.setCenter(todo.options.position);
  2183.             init = false;
  2184.           }
  2185.           obj = new defaults.classes.Marker(todo.options);
  2186.           objs.push(obj);
  2187.           id = store.add({todo:todo}, "marker", obj);
  2188.           attachEvents($this, {todo:todo}, obj, id);
  2189.         });
  2190.         manageEnd(args, multiple ? objs : objs[0]);
  2191.       }
  2192.     };
  2193.    
  2194.     /**
  2195.      * return a route
  2196.      **/
  2197.     this.getroute = function(args){
  2198.       args.opts.origin = toLatLng(args.opts.origin, true);
  2199.       args.opts.destination = toLatLng(args.opts.destination, true);
  2200.       directionsService().route(
  2201.         args.opts,
  2202.         function(results, status) {
  2203.           callback(args, status == google.maps.DirectionsStatus.OK ? results : false, status);
  2204.           task.ack();
  2205.         }
  2206.       );
  2207.     };
  2208.    
  2209.     /**
  2210.      * add a direction renderer
  2211.      **/
  2212.     this.directionsrenderer = function(args){
  2213.       args.opts.map = map;
  2214.       var id, obj = new google.maps.DirectionsRenderer(args.opts);
  2215.       if (args.todo.divId){
  2216.         obj.setPanel(document.getElementById(args.todo.divId));
  2217.       } else if (args.todo.container){
  2218.         obj.setPanel($(args.todo.container).get(0));
  2219.       }
  2220.       id = store.add(args, "directionsrenderer", obj);
  2221.       manageEnd(args, obj, id);
  2222.     };
  2223.    
  2224.     /**
  2225.      * returns latLng of the user        
  2226.      **/
  2227.     this.getgeoloc = function(args){
  2228.       manageEnd(args, args.latLng);
  2229.     };
  2230.    
  2231.     /**
  2232.      * add a style
  2233.      **/
  2234.     this.styledmaptype = function(args){
  2235.       newMap();
  2236.       var obj = new defaults.classes.StyledMapType(args.todo.styles, args.opts);
  2237.       map.mapTypes.set(args.todo.id, obj);
  2238.       manageEnd(args, obj);
  2239.     };
  2240.    
  2241.     /**
  2242.      * add an imageMapType
  2243.      **/
  2244.     this.imagemaptype = function(args){
  2245.       newMap();
  2246.       var obj = new defaults.classes.ImageMapType(args.opts);
  2247.       map.mapTypes.set(args.todo.id, obj);
  2248.       manageEnd(args, obj);
  2249.     };
  2250.    
  2251.     /**
  2252.      * autofit a map using its overlays (markers, rectangles ...)
  2253.      **/
  2254.     this.autofit = function(args){
  2255.       var bounds = new google.maps.LatLngBounds();
  2256.       $.each(store.all(), function(i, obj){
  2257.         if (obj.getPosition){
  2258.           bounds.extend(obj.getPosition());
  2259.         } else if (obj.getBounds){
  2260.           bounds.extend(obj.getBounds().getNorthEast());
  2261.           bounds.extend(obj.getBounds().getSouthWest());
  2262.         } else if (obj.getPaths){
  2263.           obj.getPaths().forEach(function(path){
  2264.             path.forEach(function(latLng){
  2265.               bounds.extend(latLng);
  2266.             });
  2267.           });
  2268.         } else if (obj.getPath){
  2269.           obj.getPath().forEach(function(latLng){
  2270.             bounds.extend(latLng);""
  2271.           });
  2272.         } else if (obj.getCenter){
  2273.           bounds.extend(obj.getCenter());
  2274.         } else if (obj instanceof Clusterer){
  2275.           obj = store.getById(obj.id(), true);
  2276.           if (obj){
  2277.             obj.autofit(bounds);
  2278.           }
  2279.         }
  2280.       });
  2281.  
  2282.       if (!bounds.isEmpty() && (!map.getBounds() || !map.getBounds().equals(bounds))){
  2283.         if ("maxZoom" in args.todo){
  2284.           // fitBouds Callback event => detect zoom level and check maxZoom
  2285.           google.maps.event.addListenerOnce(
  2286.             map,
  2287.             "bounds_changed",
  2288.             function() {
  2289.               if (this.getZoom() > args.todo.maxZoom){
  2290.                 this.setZoom(args.todo.maxZoom);
  2291.               }
  2292.             }
  2293.           );
  2294.         }
  2295.         map.fitBounds(bounds);
  2296.       }
  2297.       manageEnd(args, true);
  2298.     };
  2299.    
  2300.     /**
  2301.      * remove objects from a map
  2302.      **/
  2303.     this.clear = function(args){
  2304.       if (typeof args.todo === "string"){
  2305.         if (store.clearById(args.todo) || store.objClearById(args.todo)){
  2306.           manageEnd(args, true);
  2307.           return;
  2308.         }
  2309.         args.todo = {name:args.todo};
  2310.       }
  2311.       if (args.todo.id){
  2312.         $.each(array(args.todo.id), function(i, id){
  2313.           store.clearById(id) || store.objClearById(id);
  2314.         });
  2315.       } else {
  2316.         store.clear(array(args.todo.name), args.todo.last, args.todo.first, args.todo.tag);
  2317.         store.objClear(array(args.todo.name), args.todo.last, args.todo.first, args.todo.tag);
  2318.       }
  2319.       manageEnd(args, true);
  2320.     };
  2321.    
  2322.     /**
  2323.      * run a function on each items selected
  2324.      **/
  2325.     this.exec = function(args){
  2326.       var that = this;
  2327.       $.each(array(args.todo.func), function(i, func){
  2328.         $.each(that.get(args.todo, true, args.todo.hasOwnProperty("full") ? args.todo.full : true), function(j, res){
  2329.           func.call($this, res);
  2330.         });
  2331.       });
  2332.       manageEnd(args, true);
  2333.     };
  2334.    
  2335.     /**
  2336.      * return objects previously created
  2337.      **/
  2338.     this.get = function(args, direct, full){
  2339.       var name, res,
  2340.           todo = direct ? args : args.todo;
  2341.       if (!direct) {
  2342.         full = todo.full;
  2343.       }
  2344.       if (typeof todo === "string"){
  2345.         res = store.getById(todo, false, full) || store.objGetById(todo);
  2346.         if (res === false){
  2347.           name = todo;
  2348.           todo = {};
  2349.         }
  2350.       } else {
  2351.         name = todo.name;
  2352.       }
  2353.       if (name === "map"){
  2354.         res = map;
  2355.       }
  2356.       if (!res){
  2357.         res = [];
  2358.         if (todo.id){
  2359.             $.each(array(todo.id), function(i, id) {
  2360.                 res.push(store.getById(id, false, full) || store.objGetById(id));
  2361.             });
  2362.             if (!$.isArray(todo.id)) {
  2363.               res = res[0];
  2364.             }
  2365.         } else {
  2366.           $.each(name ? array(name) : [undef], function(i, aName) {
  2367.             var result;
  2368.             if (todo.first){
  2369.                 result = store.get(aName, false, todo.tag, full);
  2370.                 if (result) res.push(result);
  2371.             } else if (todo.all){
  2372.                 $.each(store.all(aName, todo.tag, full), function(i, result){
  2373.                   res.push(result);
  2374.                 });
  2375.             } else {
  2376.                 result = store.get(aName, true, todo.tag, full);
  2377.                 if (result) res.push(result);
  2378.             }
  2379.           });
  2380.           if (!todo.all && !$.isArray(name)) {
  2381.             res = res[0];
  2382.           }
  2383.         }
  2384.       }
  2385.       res = $.isArray(res) || !todo.all ? res : [res];
  2386.       if (direct){
  2387.         return res;
  2388.       } else {
  2389.         manageEnd(args, res);
  2390.       }
  2391.     };
  2392.  
  2393.     /**
  2394.      * return the distance between an origin and a destination
  2395.      *      
  2396.      **/
  2397.     this.getdistance = function(args){
  2398.       var i;
  2399.       args.opts.origins = array(args.opts.origins);
  2400.       for(i=0; i<args.opts.origins.length; i++){
  2401.         args.opts.origins[i] = toLatLng(args.opts.origins[i], true);
  2402.       }
  2403.       args.opts.destinations = array(args.opts.destinations);
  2404.       for(i=0; i<args.opts.destinations.length; i++){
  2405.         args.opts.destinations[i] = toLatLng(args.opts.destinations[i], true);
  2406.       }
  2407.       distanceMatrixService().getDistanceMatrix(
  2408.         args.opts,
  2409.         function(results, status) {
  2410.           callback(args, status === google.maps.DistanceMatrixStatus.OK ? results : false, status);
  2411.           task.ack();
  2412.         }
  2413.       );
  2414.     };
  2415.    
  2416.     /**
  2417.      * trigger events on the map
  2418.      **/
  2419.     this.trigger = function(args){
  2420.       if (typeof args.todo === "string"){
  2421.         google.maps.event.trigger(map, args.todo);
  2422.       } else {
  2423.         var options = [map, args.todo.eventName];
  2424.         if (args.todo.var_args) {
  2425.             $.each(args.todo.var_args, function(i, v){
  2426.               options.push(v);
  2427.             });
  2428.         }
  2429.         google.maps.event.trigger.apply(google.maps.event, options);
  2430.       }
  2431.       callback(args);
  2432.       task.ack();
  2433.     };
  2434.   }
  2435.  
  2436.   /**
  2437.    * Return true if get is a direct call
  2438.    * it means :
  2439.    *   - get is the only key
  2440.    *   - get has no callback
  2441.    * @param obj {Object} The request to check
  2442.    * @return {Boolean}
  2443.    */
  2444.   function isDirectGet(obj) {
  2445.     var k;
  2446.     if (!typeof obj === "object" || !obj.hasOwnProperty("get")){
  2447.       return false;
  2448.     }
  2449.     for(k in obj) {
  2450.       if (k !== "get") {
  2451.         return false;
  2452.       }
  2453.     }
  2454.     return !obj.get.hasOwnProperty("callback");
  2455.   }
  2456.  
  2457.   //-----------------------------------------------------------------------//
  2458.   // jQuery plugin
  2459.   //-----------------------------------------------------------------------//
  2460.    
  2461.   $.fn.gmap3 = function(){
  2462.     var i, list = [], empty = true, results = [];
  2463.    
  2464.     // init library
  2465.     initDefaults();
  2466.    
  2467.     // store all arguments in a todo list
  2468.     for(i=0; i<arguments.length; i++){
  2469.       if (arguments[i]){
  2470.         list.push(arguments[i]);
  2471.       }
  2472.     }
  2473.  
  2474.     // resolve empty call - run init
  2475.     if (!list.length) {
  2476.       list.push("map");
  2477.     }
  2478.  
  2479.     // loop on each jQuery object
  2480.     $.each(this, function() {
  2481.       var $this = $(this), gmap3 = $this.data("gmap3");
  2482.       empty = false;
  2483.       if (!gmap3){
  2484.         gmap3 = new Gmap3($this);
  2485.         $this.data("gmap3", gmap3);
  2486.       }
  2487.       if (list.length === 1 && (list[0] === "get" || isDirectGet(list[0]))){
  2488.         if (list[0] === "get") {
  2489.           results.push(gmap3.get("map", true));
  2490.         } else {
  2491.           results.push(gmap3.get(list[0].get, true, list[0].get.full));
  2492.         }
  2493.       } else {
  2494.         gmap3._plan(list);
  2495.       }
  2496.     });
  2497.    
  2498.     // return for direct call only
  2499.     if (results.length){
  2500.       if (results.length === 1){ // 1 css selector
  2501.         return results[0];
  2502.       } else {
  2503.         return results;
  2504.       }
  2505.     }
  2506.    
  2507.     return this;
  2508.   }
  2509.  
  2510. })(jQuery);

Raw Paste


Login or Register to edit or fork this paste. It's free.