PageSourceSearch

https://www.cfjacksonhole.org/cpresources/df4c2360/js/dynamicmap.js?v=1758325807

js cfjacksonhole.org collected 2026-09-26 01:35:39 UTC 43,750 bytes, 1,462 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._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.