Advertisement
Guest User

GMAP 3

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