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