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