PageSourceSearch

https://kyoto-map.com/wp-content/themes/javo-directory/assets/js/gmap3.js

js kyoto-map.com collected 2026-10-08 07:19:48 UTC 62,945 bytes, 2,521 lines download raw bytes

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

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.