PageSourceSearch

http://www.blueberrynation.org/wp-content/themes/classiera/js/gmap3.min.js

js blueberrynation.org collected 2026-09-28 11:09:04 UTC 75,627 bytes, 2,497 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.