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