PageSourceSearch

https://www.gtb.be/cpresources/5dce324f/js/dynamicmap.js?v=1650086257

js gtb.be collected 2026-10-02 04:54:27 UTC 37,066 bytes, 1,241 lines download raw bytes

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.