PageSourceSearch

https://www.gmapswidget.com/wp-content/plugins/google-maps-widget/js/gmwp-core.js?ver=5.52

js gmapswidget.com collected 2026-09-25 04:04:23 UTC 66,156 bytes, 2,558 lines download raw bytes

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