1// Dynamic Map model for Google Maps plugin 2function DynamicMap(locations, options) { 3 4 // Safely omit both parameters 5 locations = locations || []; 6 options = options || {}; 7 8 // Initialize properties 9 this.id = null; 10 this.div = null; 11 this._map = null; 12 13 // Initialize collections 14 this._markers = {}; 15 this._infoWindows = {}; 16 this._circles = {}; 17 this._kmls = {}; 18 19 // Initialize marker cluster 20 this._cluster = false; 21 22 // Initialize defaults 23 this._d = {}; 24 25 // Set a comfortable default zoom level 26 this._comfortableZoom = 11; 27 28 // ========================================================================= // 29 30 // Create a new map object 31 this.__construct = function(locations, options) { 32 33 // Ensure options are valid 34 options = options || {}; 35 36 // If no map ID was specified, generate one 37 this.id = options.id || this._generateId('map'); 38 39 // Get map container 40 this.div = document.getElementById(options.id); 41 42 // If container does not exist 43 if (!this.div) { 44 // Create new container from scratch 45 this.div = document.createElement('div'); 46 } 47 48 // Configure map container 49 this.div.id = this.id; 50 this.div.classList.add('gm-map'); 51 this.div.style.display = 'block'; 52 53 // Set defaults (with fallbacks) 54 this._d.zoom = options.zoom || null; 55 this._d.center = options.center || null; 56 this._d.markerOptions = options.markerOptions || {}; 57 this._d.circleOptions = options.circleOptions || {}; 58 this._d.infoWindowOptions = options.infoWindowOptions || {}; 59 this._d.markerLink = options.markerLink || null; 60 this._d.markerClick = options.markerClick || null; 61 62 // Internalize clustering preference 63 this._cluster = options.cluster || false; 64 65 // Optionally set container height 66 if (options.height) { 67 this.div.style.height = `${options.height}px`; 68 } 69 70 // Optionally set width of container 71 if (options.width) { 72 this.div.style.width = `${options.width}px`; 73 } 74 75 // Create a new Google Map object 76 this._createMap(options.mapOptions || {}); 77 78 // Optionally set styles of map 79 if (options.styles) { 80 this.styles(options.styles); 81 } 82 83 // If locations were specified 84 if (locations && locations.length > 0) { 85 // Add markers (using default markerOptions & infoWindowOptions) 86 this.markers(locations); 87 } 88 }; 89 90 // ========================================================================= // 91 92 // Add a set of markers to the map 93 this.markers = function(locations, options) { 94 95 // If no locations, bail 96 if (!locations) { 97 return; 98 } 99 100 // Ensure options are valid 101 options = options || {}; 102 103 // Ensure marker options are valid 104 options.markerOptions = options.markerOptions || this._d.markerOptions; 105 106 // Ensure info window options are valid 107 options.infoWindowOptions = options.infoWindowOptions || this._d.infoWindowOptions; 108 109 // Ensure marker link is valid 110 options.markerLink = options.markerLink || this._d.markerLink; 111 112 // Ensure marker click event is valid 113 options.markerClick = options.markerClick || this._d.markerClick; 114 115 // Set map 116 options.markerOptions.map = this._map; 117 118 // If icon specified, copy into markerOptions 119 if (options.icon) { 120 options.markerOptions.icon = options.icon; 121 } 122 123 // Force locations to be an array structure 124 if (!Array.isArray(locations)) { 125 locations = [locations]; 126 } 127 128 // Loop through all locations 129 for (var i in locations) { 130 131 // Get individual coordinates 132 var coords = locations[i]; 133 134 // If an error is present, show in console 135 if (coords.error){ 136 console.error(`[GM] ${coords.error}`); 137 } 138 139 // If coordinates are not valid, skip 140 if (!coords.hasOwnProperty('lat') || !coords.hasOwnProperty('lng')) { 141 continue; 142 } 143 144 // Get marker ID or generate a random one 145 var markerId = options.id || coords.id || this._generateId('marker'); 146 147 // Set marker ID back to coordinates object 148 coords.id = markerId; 149 150 // Create a new marker 151 this._createMarker(coords, options.markerOptions); 152 153 // If content provided, create a new info window 154 if (options.infoWindowOptions.content) { 155 this._initInfoWindow(coords.id, options.infoWindowOptions); 156 } 157 158 // If callback is a boolean true 159 if (true === options.markerClick) { 160 // Suppress click and link 161 options.markerClick = false;
162 options.markerLink = false; 163 } 164 165 // If click event specified 166 if (options.markerClick) { 167 168 // Attach listener for specified function 169 this._initMarkerClick(markerId, options.markerClick); 170 171 // Else if marker link specified and valid 172 } else if (options.markerLink && 'string' === typeof options.markerLink) { 173 174 // Attach listener which directs to specified link 175 this._initMarkerClick(markerId, function () { 176 window.location.href = options.markerLink; 177 }); 178 179 } 180 181 } 182 183 // Keep the party going 184 return this; 185 }; 186 187 // Add a set of circles to the map 188 this.circles = function(locations, options) { 189 190 // If no locations, bail 191 if (!locations) { 192 return; 193 } 194 // Ensure options are valid 195 options = options || {}; 196 197 // Ensure circle options are valid 198 options.circleOptions = options.circleOptions || this._d.circleOptions; 199 200 // Ensure radius is valid (default to 50 kilometers) 201 options.circleOptions.radius = options.circleOptions.radius || 50000; 202 203 // Set map 204 options.circleOptions.map = this._map; 205 206 // Force locations to be an array structure 207 if (!Array.isArray(locations)) { 208 locations = [locations]; 209 } 210 211 // Loop through all locations 212 for (var i in locations) { 213 214 // Get individual coordinates 215 var coords = locations[i]; 216 217 // If coordinates are not valid, skip 218 if (!coords.hasOwnProperty('lat') || !coords.hasOwnProperty('lng')) { 219 continue; 220 } 221 222 // Get circle ID or generate a random one 223 var circleId = options.id || coords.id || this._generateId('circle'); 224 225 // Set circle ID back to coordinates object 226 coords.id = circleId; 227 228 // Create a new circle 229 this._createCircle(coords, options.circleOptions); 230 } 231 232 // Keep the party going 233 return this; 234 }; 235 236 // Add a KML layer to the map 237 this.kml = function(url, options) { 238 239 // Ensure options are valid 240 options = options || {}; 241 242 // Apply file URL 243 options.url = options.url || url; 244 245 // Set map 246 options.map = this._map; 247 248 // Create a new KML layer 249 this._createKml(url, options); 250 251 // Keep the party going 252 return this; 253 }; 254 255 // ========================================================================= // 256 257 // Apply a batch of styles to the map 258 this.styles = function(styleSet) { 259 260 // Ensure styles are valid 261 styleSet = styleSet || {}; 262 263 // Log status 264 if (googleMaps.log) { 265 console.log(`[${this.id}] Styling map`); 266 } 267 268 // Apply collection of styles 269 this._map.setOptions({styles: styleSet}); 270 271 // Keep the party going 272 return this; 273 }; 274 275 // Zoom map to specified level 276 this.zoom = function(level, assumeSuccess) { 277 278 // Ensure level is valid 279 level = level || this._d.zoom; 280 281 // Update default zoom level 282 this._d.zoom = level; 283 284 // Log status (if success is not assumed) 285 if (googleMaps.log && !assumeSuccess) { 286 console.log(`[${this.id}] Zooming map to level`, level); 287 } 288 289 // Pass object to callback function 290 var mapObject = this; 291 292 // Set zoom level of current map 293 mapObject._map.setZoom(level); 294 295 // Repeat after fitbounds has finished (if it's running) 296 google.maps.event.addListenerOnce(this._map, 'bounds_changed', function() { 297 mapObject._map.setZoom(level); 298 }); 299 300 // Keep the party going 301 return this; 302 }; 303 304 // Center the map on a set of coordinates 305 this.center = function(coords, assumeSuccess) { 306 307 // Ensure coordinates are valid 308 coords = coords || this._d.center; 309 310 // Log status (if success is not assumed) 311 if (googleMaps.log && !assumeSuccess) { 312 console.log(`[${this.id}] Centering map on coordinates`, coords); 313 } 314 315 // If coordinates do not exist, emit warning and bail 316 if (!coords) { 317 console.warn(`[GM] Unable to center map, invalid coordinates:`, coords); 318 return this; 319 } 320 321 // Ensure coordinates are float values, fallback to zero 322 coords.lat = (coords.lat ? parseFloat(coords.lat) : 0); 323 coords.lng = (coords.lng ? parseFloat(coords.lng) : 0); 324 325 // Update default center coordinates 326 this._d.center = coords; 327 328 // Pass object to callback function 329 var mapObject = this; 330 331 // Re-center current map 332 mapObject._map.setCenter(coords); 333 334 // Repeat after bounds have changed (if they are still changing) 335 google.maps.event.addListenerOnce(mapObject._map, 'bounds_changed', function() { 336 mapObject._map.setCenter(coords); 337 }); 338 339 // Keep the party going 340 return this; 341 }; 342
343 // Fit map according to bounds 344 this.fit = function(assumeSuccess) { 345 346 // Log status (if success is not assumed) 347 if (googleMaps.log && !assumeSuccess) { 348 console.log(`[${this.id}] Fitting map to existing boundaries`); 349 } 350 351 // Get bounds 352 var bounds = this._determineBounds(); 353 354 // If bounds are empty 355 if (bounds.isEmpty()) { 356 // Example coordinates 357 const zeroZero = {'lat':0,'lng':0}; 358 // Log error message 359 console.error(`[GM] No items on the map, it will be centered in the middle of the ocean! ð`, zeroZero); 360 // Center the map 361 this.center(zeroZero, true); 362 // We are in the ocean, zoom out 363 this.zoom(2, true); 364 // Bail 365 return this; 366 } 367 368 // If bounds are just a single point 369 if (bounds.getNorthEast().equals(bounds.getSouthWest())) { 370 // Center the map 371 this.center(bounds.getCenter().toJSON(), true); 372 // Quietly zoom to a comfortable level 373 this.zoom(this._comfortableZoom, true); 374 // Bail 375 return this; 376 } 377 378 // Fit bounds of current map 379 this._map.fitBounds(bounds); 380 381 // Keep the party going 382 return this; 383 }; 384 385 // Refresh the map 386 this.refresh = function() { 387 388 // Log status 389 if (googleMaps.log) { 390 console.log(`[${this.id}] Refreshing map`); 391 } 392 393 // Refresh the current map 394 google.maps.event.trigger(this._map, 'resize'); 395 396 // Keep the party going 397 return this; 398 }; 399 400 // ========================================================================= // 401 402 // Pan map to center on a specific marker 403 this.panToMarker = function(markerId) { 404 405 // Get specified marker 406 var marker = this.getMarker(markerId); 407 408 // Log status 409 if (googleMaps.log) { 410 console.log(`[${this.id}] Panning to marker "${markerId}"`); 411 } 412 413 // If invalid marker, bail 414 if (!marker) { 415 console.warn(`[GM] Unable to pan to marker "${markerId}"`); 416 return this; 417 } 418 419 // Pass object to callback function 420 var mapObject = this; 421 422 // Pan map to marker position 423 mapObject._map.panTo(marker.position); 424 425 // Repeat after bounds have changed (if they are still changing) 426 google.maps.event.addListenerOnce(mapObject._map, 'bounds_changed', function() { 427 mapObject._map.panTo(marker.position); 428 }); 429 430 // Keep the party going 431 return this; 432 }; 433 434 // Set the icon of an existing marker 435 this.setMarkerIcon = function(markerId, icon, assumeSuccess) { 436 437 // If setting icon for multiple markers 438 if (Array.isArray(markerId)) { 439 // Log status (if success is not assumed) 440 if (googleMaps.log && !assumeSuccess) { 441 console.log(`[${this.id}] Setting icon for multiple markers`); 442 } 443 // Set each marker icon individually 444 for (var i in markerId) { 445 this.setMarkerIcon(markerId[i], icon); 446 } 447 // Our work here is done 448 return this; 449 } 450 451 // If setting icon for all markers 452 if ('*' === markerId) { 453 // Log status (if success is not assumed) 454 if (googleMaps.log && !assumeSuccess) { 455 console.log(`[${this.id}] Setting icon for all markers:`, icon); 456 } 457 // Set each marker icon individually 458 for (var key in this._markers) { 459 this.setMarkerIcon(key, icon, true); 460 } 461 // Our work here is done 462 return this; 463 } 464 465 // Log status (if success is not assumed) 466 if (googleMaps.log && !assumeSuccess) { 467 console.log(`[${this.id}] Setting icon for marker "${markerId}":`, icon); 468 } 469 470 // Get specified marker 471 var marker = this.getMarker(markerId, true); 472 473 // If invalid marker, bail 474 if (!marker) { 475 console.warn(`[GM] Unable to set icon of marker "${markerId}"`); 476 return this; 477 } 478 479 // Set marker icon 480 marker.setIcon(icon); 481 482 // Keep the party going 483 return this; 484 }; 485 486 // ========================================================================= // 487 488 // Hide a marker 489 this.hideMarker = function(markerId, assumeSuccess) { 490 491 // If hiding multiple markers 492 if (Array.isArray(markerId)) { 493 // Log status 494 if (googleMaps.log) { 495 console.log(`[${this.id}] Hiding multiple markers`); 496 } 497 // Hide each marker individually 498 for (var i in markerId) { 499 this.hideMarker(markerId[i]); 500 } 501 // Our work here is done 502 return this; 503 } 504 505 // If hiding all markers 506 if ('*' === markerId) { 507 // Log status 508 if (googleMaps.log && !assumeSuccess) { 509 console.log(`[${this.id}] Hiding all markers`); 510 } 511 // Hide each marker individually 512 for (var key in this._markers) { 513 this.hideMarker(key, true); 514 } 515 // Our work here is done 516 return this; 517 } 518 519 // Log status (if success is not assumed) 520 if (googleMaps.log && !assumeSuccess) { 521 console.log(`[${this.id}] Hiding marker "${markerId}"`); 522 } 523 524 // Get specified marker 525 var marker = this.getMarker(markerId); 526 527 // If invalid marker, bail 528 if (!marker) { 529 console.warn(`[GM] Unable to hide marker "${markerId}"`); 530 return this; 531 } 532 533 // If clustering 534 if (this._cluster) {
535 // Remove marker from clusters 536 this._cluster.removeMarker(marker); 537 } else { 538 // Remove marker from map 539 marker.setMap(null); 540 } 541 542 // Keep the party going 543 return this; 544 }; 545 546 // Show a marker 547 this.showMarker = function(markerId, assumeSuccess) { 548 549 // If showing multiple markers 550 if (Array.isArray(markerId)) { 551 // Log status 552 if (googleMaps.log) { 553 console.log(`[${this.id}] Showing multiple markers`); 554 } 555 // Show each marker individually 556 for (var i in markerId) { 557 this.showMarker(markerId[i]); 558 } 559 // Our work here is done 560 return this; 561 } 562 563 // If showing all markers 564 if ('*' === markerId) { 565 // Log status 566 if (googleMaps.log && !assumeSuccess) { 567 console.log(`[${this.id}] Showing all markers`); 568 } 569 // Show each marker individually 570 for (var key in this._markers) { 571 this.showMarker(key, true); 572 } 573 // Our work here is done 574 return this; 575 } 576 577 // Log status (if success is not assumed) 578 if (googleMaps.log && !assumeSuccess) { 579 console.log(`[${this.id}] Showing marker "${markerId}"`); 580 } 581 582 // Get specified marker 583 var marker = this.getMarker(markerId); 584 585 // If invalid marker, bail 586 if (!marker) { 587 console.warn(`[GM] Unable to show marker "${markerId}"`); 588 return this; 589 } 590 591 // If clustering 592 if (this._cluster) { 593 // Add marker to clusters 594 this._cluster.addMarker(marker); 595 } else { 596 // Add marker directly to map 597 marker.setMap(this._map); 598 } 599 600 // Keep the party going 601 return this; 602 }; 603 604 // Open an info window 605 this.openInfoWindow = function(markerId, assumeSuccess) { 606 607 // If opening multiple info windows 608 if (Array.isArray(markerId)) { 609 // Log status 610 if (googleMaps.log) { 611 console.log(`[${this.id}] Opening multiple info windows`); 612 } 613 // Open each info window individually 614 for (var i in markerId) { 615 this.openInfoWindow(markerId[i]); 616 } 617 // Our work here is done 618 return this; 619 } 620 621 // If opening all info windows 622 if ('*' === markerId) { 623 // Log status 624 if (googleMaps.log && !assumeSuccess) { 625 console.log(`[${this.id}] Opening all info windows`); 626 } 627 // Open each info window individually 628 for (var key in this._infoWindows) { 629 this.openInfoWindow(key, true); 630 } 631 // Our work here is done 632 return this; 633 } 634 635 // Log status (if success is not assumed) 636 if (googleMaps.log && !assumeSuccess) { 637 console.log(`[${this.id}] Opening info window "${markerId}"`); 638 } 639 640 // Get marker and info window objects 641 var marker = this.getMarker(markerId, true); 642 var infoWindow = this.getInfoWindow(markerId, true); 643 644 // If invalid marker, bail 645 if (!marker) { 646 console.warn(`[GM] Unable to find marker "${markerId}"`); 647 return this; 648 } 649 650 // If invalid info window, bail 651 if (!infoWindow) { 652 console.warn(`[GM] Unable to open info window "${markerId}"`); 653 return this; 654 } 655 656 // Open the specified info window 657 infoWindow.open({ 658 'map': this._map, 659 'anchor': marker 660 }); 661 662 // Keep the party going 663 return this; 664 }; 665 666 // Close an info window 667 this.closeInfoWindow = function(markerId, assumeSuccess) { 668 669 // If closing multiple info windows 670 if (Array.isArray(markerId)) { 671 // Log status 672 if (googleMaps.log) { 673 console.log(`[${this.id}] Closing multiple info windows`); 674 } 675 // Close each info window individually 676 for (var i in markerId) { 677 this.closeInfoWindow(markerId[i]); 678 } 679 // Our work here is done 680 return this; 681 } 682 683 // If closing all info windows 684 if ('*' === markerId) { 685 // Log status 686 if (googleMaps.log && !assumeSuccess) { 687 console.log(`[${this.id}] Closing all info windows`); 688 } 689 // Close each info window individually 690 for (var key in this._infoWindows) { 691 this.closeInfoWindow(key, true); 692 } 693 // Our work here is done 694 return this; 695 } 696 697 // Log status (if success is not assumed) 698 if (googleMaps.log && !assumeSuccess) { 699 console.log(`[${this.id}
699] Closing info window "${markerId}"`); 700 } 701 702 // Get info window object 703 var infoWindow = this.getInfoWindow(markerId, true); 704 705 // If invalid info window, bail 706 if (!infoWindow) { 707 console.warn(`[GM] Unable to close info window "${markerId}"`); 708 return this; 709 } 710 711 // Close info window 712 infoWindow.close(); 713 714 // Keep the party going 715 return this; 716 }; 717 718 // Hide a circle 719 this.hideCircle = function(circleId, assumeSuccess) { 720 721 // If hiding multiple circles 722 if (Array.isArray(circleId)) { 723 // Log status 724 if (googleMaps.log) { 725 console.log(`[${this.id}] Hiding multiple circles`); 726 } 727 // Hide each circle individually 728 for (var i in circleId) { 729 this.hideCircle(circleId[i]); 730 } 731 // Our work here is done 732 return this; 733 } 734 735 // If hiding all circles 736 if ('*' === circleId) { 737 // Log status 738 if (googleMaps.log && !assumeSuccess) { 739 console.log(`[${this.id}] Hiding all circles`); 740 } 741 // Hide each circle individually 742 for (var key in this._circles) { 743 this.hideCircle(key, true); 744 } 745 // Our work here is done 746 return this; 747 } 748 749 // Log status (if success is not assumed) 750 if (googleMaps.log && !assumeSuccess) { 751 console.log(`[${this.id}] Hiding circle "${circleId}"`); 752 } 753 754 // Get specified circle 755 var circle = this.getCircle(circleId, true); 756 757 // If invalid circle, bail 758 if (!circle) { 759 console.warn(`[GM] Unable to hide circle "${circleId}"`); 760 return this; 761 } 762 763 // Detach circle from map 764 circle.setMap(null); 765 766 // Keep the party going 767 return this; 768 }; 769 770 // Show a circle 771 this.showCircle = function(circleId, assumeSuccess) { 772 773 // If showing multiple circles 774 if (Array.isArray(circleId)) { 775 // Log status 776 if (googleMaps.log) { 777 console.log(`[${this.id}] Showing multiple circles`); 778 } 779 // Show each circle individually 780 for (var i in circleId) { 781 this.showCircle(circleId[i]); 782 } 783 // Our work here is done 784 return this; 785 } 786 787 // If showing all circles 788 if ('*' === circleId) { 789 // Log status 790 if (googleMaps.log && !assumeSuccess) { 791 console.log(`[${this.id}] Showing all circles`); 792 } 793 // Show each circle individually 794 for (var key in this._circles) { 795 this.showCircle(key, true); 796 } 797 // Our work here is done 798 return this; 799 } 800 801 // Log status (if success is not assumed) 802 if (googleMaps.log && !assumeSuccess) { 803 console.log(`[${this.id}] Showing circle "${circleId}"`); 804 } 805 806 // Get specified circle 807 var circle = this.getCircle(circleId, true); 808 809 // If invalid circle, bail 810 if (!circle) { 811 console.warn(`[GM] Unable to show circle "${circleId}"`); 812 return this; 813 } 814 815 // Attach circle to current map 816 circle.setMap(this._map); 817 818 // Keep the party going 819 return this; 820 }; 821 822 // Hide a KML layer 823 this.hideKml = function(kmlId, assumeSuccess) { 824 825 // If hiding multiple KML layers 826 if (Array.isArray(kmlId)) { 827 // Log status 828 if (googleMaps.log) { 829 console.log(`[${this.id}] Hiding multiple KML layers`); 830 } 831 // Hide each KML layer individually 832 for (var i in kmlId) { 833 this.hideKml(kmlId[i]); 834 } 835 // Our work here is done 836 return this; 837 } 838 839 // If hiding all KML layers 840 if ('*' === kmlId) { 841 // Log status 842 if (googleMaps.log && !assumeSuccess) { 843 console.log(`[${this.id}] Hiding all KML layers`); 844 } 845 // Hide each KML layer individually 846 for (var key in this._kmls) { 847 this.hideKml(key, true); 848 } 849 // Our work here is done 850 return this; 851 } 852 853 // Log status (if success is not assumed) 854 if (googleMaps.log && !assumeSuccess) { 855 console.log(`[${this.id}] Hiding KML layer "${kmlId}"`); 856 } 857 858 // Get specified KML layer 859 var kml = this.getKml(kmlId, true); 860 861 // If invalid KML layer, bail 862 if (!kml) { 863 console.warn(`[GM] Unable to hide KML layer "${kmlId}"`); 864 return this; 865 } 866 867 // Detach KML layer from map 868 kml.setMap(null); 869 870 // Keep the party going 871 return this; 872 }; 873 874 // Show a KML layer 875 this.showKml = function(kmlId, assumeSuccess) { 876 877 // If showing multiple KML layers 878 if (Array.isArray(kmlId)) { 879 // Log status 880 if (googleMaps.log) { 881 console.log(`[${this.id}] Showing multiple KML layers`); 882 } 883 // Show each KML layer individually 884 for (var i in kmlId) { 885 this.showKml(kmlId[i]); 886 } 887 // Our work here is done 888 return this; 889 } 890 891 // If showing all KML layers 892 if ('*' === kmlId) { 893 // Log status 894 if (googleMaps.log && !assumeSuccess) { 895 console.log(`[${this.id}] Showing all KML layers`); 896 } 897 // Show each KML layer individually 898 for (var key in this._kmls) { 899 this.showKml(key, true); 900 } 901 // Our work here is done 902 return this; 903 } 904 905 // Log status (if success is not assumed) 906 if (googleMaps.log && !assumeSuccess) { 907 console.log(`[${this.id}] Showing KML layer "${kmlId}"`); 908 } 909 910 // Get specified KML layer 911 var kml = this.getKml(kmlId, true); 912 913 // If invalid KML layer, bail 914 if (!kml) { 915 console.warn(`[GM] Unable to show KML layer "${kmlId}"`); 916 return this; 917 } 918 919 // Attach KML layer to current map 920 kml.setMap(this._map); 921 922 // Keep the party going 923 return this; 924 }; 925 926 // ========================================================================= // 927 928 // Get a specific Google Maps marker object 929 this.getMarker = function(markerId, assumeSuccess) { 930 931 // Log status (if success is not assumed) 932 if (googleMaps.log && !assumeSuccess) { 933 console.log(`[${this.id}] Getting existing marker "${markerId}"`); 934 } 935 936 // Get existing marker 937 var marker = this._markers[markerId]; 938 939 // If marker does not exist, emit warning 940 if (!marker) { 941 console.warn(`[GM] Unable to find marker "${markerId}"`); 942 } 943 944 // Return marker 945 return marker; 946 }; 947 948 // Get a specific Google Maps info window object 949 this.getInfoWindow = function(markerId, assumeSuccess) { 950 951 // Log status (if success is not assumed) 952 if (googleMaps.log && !assumeSuccess) { 953 console.log(`[${this.id}
953] Getting existing info window "${markerId}"`); 954 } 955 956 // Get existing info window 957 var infoWindow = this._infoWindows[markerId]; 958 959 // If info window does not exist, emit warning 960 if (!infoWindow) { 961 console.warn(`[GM] Unable to find info window "${markerId}"`); 962 } 963 964 // Return info window 965 return infoWindow; 966 }; 967 968 // Get a specific Google Maps circle object 969 this.getCircle = function(circleId, assumeSuccess) { 970 971 // Log status (if success is not assumed) 972 if (googleMaps.log && !assumeSuccess) { 973 console.log(`[${this.id}] Getting existing circle "${circleId}"`); 974 } 975 976 // Get existing circle 977 var circle = this._circles[circleId]; 978 979 // If circle does not exist, emit warning 980 if (!circle) { 981 console.warn(`[GM] Unable to find circle "${circleId}"`); 982 } 983 984 // Return circle 985 return circle; 986 }; 987 988 // Get a specific Google Maps KML layer object 989 this.getKml = function(kmlId, assumeSuccess) { 990 991 // Log status (if success is not assumed) 992 if (googleMaps.log && !assumeSuccess) { 993 console.log(`[${this.id}] Getting existing KML layer "${kmlId}"`); 994 } 995 996 // Get existing KML layer 997 var kml = this._kmls[kmlId]; 998 999 // If KML layer does not exist, emit warning 1000 if (!kml) { 1001 console.warn(`[GM] Unable to find KML layer "${kmlId}"`); 1002 } 1003 1004 // Return KML layer 1005 return kml; 1006 }; 1007 1008 // Get the internal marker clustering object 1009 this.getMarkerClusterer = function() { 1010 1011 // Log status 1012 if (googleMaps.log) { 1013 console.log(`[${this.id}] Getting the marker clustering object`); 1014 } 1015 1016 // Return the MarkerClusterer object 1017 return this._cluster; 1018 }; 1019 1020 // Get the current zoom level of the map 1021 this.getZoom = function(assumeSuccess) { 1022 1023 // Log status (if success is not assumed) 1024 if (googleMaps.log && !assumeSuccess) { 1025 console.log(`[${this.id}] Getting the current zoom level of the map`); 1026 } 1027 1028 // Return zoom level 1029 return this._map.getZoom(); 1030 }; 1031 1032 // Get the current center point of the map 1033 this.getCenter = function(assumeSuccess) { 1034 1035 // Log status (if success is not assumed) 1036 if (googleMaps.log && !assumeSuccess) { 1037 console.log(`[${this.id}] Getting the current center point of the map`); 1038 } 1039 1040 // Return the center coordinates 1041 return this._map.getCenter(); 1042 }; 1043 1044 // Get the current bounds of the map 1045 this.getBounds = function() { 1046 1047 // Log status 1048 if (googleMaps.log) { 1049 console.log(`[${this.id}] Getting the current bounds of the map`); 1050 } 1051 1052 // Return a pair of bounds coordinates 1053 return this._map.getBounds(); 1054 }; 1055 1056 // ========================================================================= // 1057 1058 // Generate a complete map element 1059 this.tag = function(options) { 1060 1061 // Log status 1062 if (googleMaps.log) { 1063 console.log(`[${this.id}] Rendering map`); 1064 } 1065 1066 // Ensure options are valid 1067 options = options || {}; 1068 1069 // Get the ID of the parent container 1070 var parentId = (options.parentId || null); 1071 1072 // If no valid parent container specified 1073 if (!parentId || 'string' !== typeof parentId) { 1074 // Check to ensure the map is visible 1075 this._checkMapVisibility(); 1076 // Log status 1077 if (googleMaps.log) { 1078 console.log(`[${this.id}] Finished initializing map as a detached element ð`); 1079 } 1080 // Return the element as-is 1081 return this.div; 1082 } 1083 1084 // Get specified parent container 1085 var parent = document.getElementById(parentId); 1086 1087 // If parent container exists, populate it 1088 if (parent) { 1089 parent.appendChild(this.div); 1090 } else { 1091 console.warn(`[GM] Unable to find target container #${parentId}`); 1092 } 1093 1094 // Check to ensure the map is visible 1095 this._checkMapVisibility(); 1096 1097 // Log status 1098 if (googleMaps.log) { 1099 console.log(`[${this.id}
1099] Finished initializing map in container "${parentId}" ð`); 1100 } 1101 1102 // Return map container 1103 return this.div; 1104 }; 1105 1106 // ========================================================================= // 1107 1108 // Create a new map object 1109 this._createMap = function(mapOptions) { 1110 1111 // Log status 1112 if (googleMaps.log) { 1113 console.log(`[${this.id}] Creating map`); 1114 } 1115 1116 // Initialize map data 1117 this._map = new google.maps.Map(this.div, mapOptions); 1118 1119 // Optionally cluster markers 1120 this._clusterMarkers(); 1121 }; 1122 1123 // Create a new marker object 1124 this._createMarker = function(coords, markerOptions) { 1125 1126 // Log status 1127 if (googleMaps.log) { 1128 console.log(`[${this.id}] Adding marker "${coords.id}"`); 1129 } 1130 1131 // Set marker position based on coordinates 1132 markerOptions.position = coords; 1133 1134 // Initialize marker object 1135 this._markers[coords.id] = new google.maps.Marker(markerOptions); 1136 1137 // If clustering markers, add new marker to group 1138 if (this._cluster) { 1139 this._cluster.addMarker(this._markers[coords.id]); 1140 } 1141 1142 }; 1143 1144 // Create a new circle object 1145 this._createCircle = function(coords, circleOptions) { 1146 1147 // Log status 1148 if (googleMaps.log) { 1149 console.log(`[${this.id}] Adding circle "${coords.id}"`); 1150 } 1151 1152 // Set circle center based on coordinates 1153 circleOptions.center = coords; 1154 1155 // Initialize circle object 1156 this._circles[coords.id] = new google.maps.Circle(circleOptions); 1157 1158 }; 1159 1160 // Create a new KML layer 1161 this._createKml = function(url, options) { 1162 1163 // Get KML ID or generate a random one 1164 var kmlId = options.id || this._generateId('kml'); 1165 1166 // Log status 1167 if (googleMaps.log) { 1168 console.log(`[${this.id}] Adding KML layer "${kmlId}"`); 1169 } 1170 1171 // Initialize KML object 1172 this._kmls[kmlId] = new google.maps.KmlLayer(options); 1173 1174 }; 1175 1176 // ========================================================================= // 1177 1178 // Set info window for each marker 1179 this._initInfoWindow = function(markerId, infoWindowOptions) { 1180 1181 // Log status 1182 if (googleMaps.log) { 1183 console.log(`[${this.id}] Adding info window to marker "${markerId}"`); 1184 } 1185 1186 // Get related marker 1187 var marker = this.getMarker(markerId, true); 1188 1189 // If no related marker exists, bail 1190 if (!marker) { 1191 return; 1192 } 1193 1194 // Initialize info window object 1195 this._infoWindows[markerId] = new google.maps.InfoWindow(infoWindowOptions); 1196 1197 // Pass map object into event listener 1198 var map = this; 1199 1200 // Add click event to marker 1201 google.maps.event.addListener(marker, 'click', function() { 1202 // Close all info windows 1203 map.closeInfoWindow('*', true); 1204 // Open info window for this marker 1205 map.openInfoWindow(markerId); 1206 }); 1207 1208 }; 1209 1210 // Set click event for each marker 1211 this._initMarkerClick = function(markerId, callback) { 1212 1213 // Log status 1214 if (googleMaps.log) { 1215 console.log(`[${this.id}] Adding click event callback to marker "${markerId}":`, callback); 1216 } 1217 1218 // Get related marker 1219 var marker = this.getMarker(markerId, true); 1220 1221 // If no related marker exists, bail 1222 if (!marker) { 1223 return; 1224 } 1225 1226 // If callback is a boolean true, bail 1227 if (true === callback) { 1228 return; 1229 } 1230 1231 // If callback is not a function, warn and bail 1232 if ('function' !== typeof callback) { 1233 console.warn('[GM] Invalid callback function:', callback); 1234 return; 1235 } 1236 1237 // When marker is clicked, trigger the callback 1238 google.maps.event.addListener(marker, 'click', callback); 1239 1240 }; 1241 1242 // ========================================================================= // 1243 1244 // Get the functional boundaries of the current map 1245 this._determineBounds = function() { 1246 1247 // Create a set of map bounds 1248 var bounds = new google.maps.LatLngBounds(); 1249 1250 // Initialize loop variables 1251 var key, marker, circle, cluster; 1252 1253 // Loop through all markers 1254 for (key in this._markers) { 1255 // Get each marker 1256 marker = this._markers[key]; 1257 // If marker is not tied to a map, skip it 1258 if (null === marker.map) { 1259 continue; 1260 } 1261 // Extend map boundaries to include marker 1262 bounds.extend(marker.getPosition()); 1263 } 1264 1265 // Loop through all circles 1266 for (key in this._circles) { 1267 // Get each circle 1268 circle = this._circles[key]; 1269 // If circle is not tied to a map, skip it 1270 if (null === circle.map) { 1271 continue; 1272 } 1273 // Extend map boundaries to include circle 1274 bounds.extend(circle.getCenter()); 1275 } 1276 1277 // If map uses clustering 1278 if (this._cluster) {
1279 // Loop through all clusters 1280 for (key in this._cluster.clusters) { 1281 // Get each cluster 1282 cluster = this._cluster.clusters[key]; 1283 // Join map boundaries with cluster boundaries 1284 bounds.union(cluster.bounds); 1285 } 1286 } 1287 1288 // Return a set of map bounds 1289 return bounds; 1290 }; 1291 1292 // Optionally apply marker clustering 1293 this._clusterMarkers = function() { 1294 1295 // Whether to use default clustering 1296 const clusterDefault = (true === this._cluster); 1297 1298 // Get clustering options data 1299 let _cluster = (window._gmData ? window._gmData.cluster : []); 1300 1301 // Get custom Twig/PHP options, fallback to null 1302 const twigOptions = _cluster[this.id] || null; 1303 1304 // If provided, get options from Twig/PHP data 1305 if (clusterDefault && twigOptions) { 1306 this._cluster = twigOptions; 1307 } 1308 1309 // Whether to use custom clustering 1310 const clusterCustom = ('object' === typeof this._cluster); 1311 1312 // If not clustering (neither default nor custom), bail 1313 if (!clusterDefault && !clusterCustom) { 1314 return; 1315 } 1316 1317 // Set clustering options 1318 const options = { 1319 'map': this._map, 1320 'markers': [], 1321 }; 1322 1323 // If customized clustering 1324 if (clusterCustom) { 1325 // If algorithm specified, add to options 1326 if (this._cluster.algorithm || null) { 1327 options.algorithm = this._cluster.algorithm; 1328 } 1329 // If renderer specified, add to options 1330 if (this._cluster.renderer || null) { 1331 options.renderer = this._cluster.renderer; 1332 } 1333 // If onClusterClick specified, add to options 1334 if (this._cluster.onClusterClick || null) { 1335 options.onClusterClick = this._cluster.onClusterClick; 1336 } 1337 } 1338 1339 // If no onClusterClick defined, use default (native) function 1340 const onClusterClick = 1341 options.onClusterClick || 1342 markerClusterer.defaultOnClusterClickHandler; 1343 1344 // Set parent map object 1345 const parent = this; 1346 1347 // Wrap onClusterClick with a safeguard function 1348 options.onClusterClick = function (event, cluster, map) { 1349 // Log status 1350 if (googleMaps.log) { 1351 console.log(`[${parent.id}] Opening cluster of ${cluster.markers.length} markers`); 1352 } 1353 // After a brief pause 1354 setTimeout(() => { 1355 // Close all info windows 1356 parent.closeInfoWindow('*', true); 1357 }, 100); 1358 // Run original onClusterClick function 1359 onClusterClick(event, cluster, map); 1360 } 1361 1362 // Create the marker clustering object 1363 this._cluster = new markerClusterer.MarkerClusterer(options); 1364 }; 1365 1366 // ========================================================================= // 1367 1368 // Check to ensure the map is visible, emit warnings if necessary 1369 this._checkMapVisibility = function() { 1370 // Check the container height 1371 this._checkHeight(); 1372 // Prevent the map from appearing as a grey box 1373 this._preventGreyBox(); 1374 }; 1375 1376 // Check the container height, emit warning if necessary 1377 this._checkHeight = function() { 1378 1379 // If not logging, skip check 1380 if (!googleMaps.log) { 1381 return; 1382 } 1383 1384 // Get current height of container div 1385 var height = this.div.clientHeight; 1386 1387 // If height is a positive number, check is successful 1388 if (0 < height) { 1389 return; 1390 } 1391 1392 // Zero pixels tall, emit warning 1393 var url = 'https://plugins.doublesecretagency.com/google-maps/guides/setting-map-height/'; 1394 console.warn(`[GM] The map is not visible because its parent container is zero pixels tall. More info: ${url}`); 1395 1396 }; 1397 1398 // ========================================================================= // 1399 1400 // Prevent the map from appearing as a grey box, emit warnings if necessary 1401 this._preventGreyBox = function() { 1402 1403 // Get current zoom & center 1404 let zoom = this.getZoom(true); 1405 let center = this.getCenter(true); 1406 1407 // Fallback to default values 1408 zoom = zoom || this._d.zoom; 1409 center = center || this._d.center; 1410 1411 // If center was specified 1412 if (center) { 1413 1414 // Get coordinates 1415 let lat = center.lat ?? center.lat(); 1416 let lng = center.lng ?? center.lng(); 1417 1418 // Set sanitized center 1419 this.center({ 1420 'lat': (isNaN(lat) ? 0 : lat), 1421 'lng': (isNaN(lng) ? 0 : lng) 1422 }); 1423 1424 } else { 1425 1426 // Fit to the existing items 1427 this.fit(); 1428 1429 } 1430 1431 // If zoom is specified, set it 1432 if (zoom) { 1433 this.zoom(zoom); 1434 } 1435 }; 1436 1437 // ========================================================================= // 1438 1439 // Generate a random ID 1440 this._generateId = function(prefix) { 1441 1442 // Initialize random ID 1443 var randomId = ''; 1444 1445 // Create an array of the alphabet 1446 var alpha = 'abcdefghijklmnopqrstuvwxyz'; 1447 var alphabet = alpha.split(''); 1448 1449 // Add six randomly selected characters 1450 for (char = 0; char < 6; char++) { 1451 var i = Math.floor(Math.random() * 25); 1452 randomId += alphabet[i]; 1453 } 1454 1455 // Return new ID (with optional prefix) 1456 return (prefix ? `${prefix}-${randomId}` : randomId); 1457 }; 1458 1459 // Prepare the object 1460 this.__construct(locations, options); 1461 1462}
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.