PageSourceSearch

https://wonderfm.com/wp-content/themes/clubber-php85/js/gmap.js?ver=dd9142e185a17290317387c2454a2201

js wonderfm.com collected 2026-10-02 00:35:00 UTC 71,769 bytes, 2,361 lines download raw bytes

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