jquery-gmap3-rails 0.1.1 → 0.2.0

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