PageSourceSearch

https://valdeloire.org/modules/contrib/leaflet/js/leaflet.drupal.js?tlvcse

js valdeloire.org collected 2026-09-24 18:16:10 UTC 41,262 bytes, 1,132 lines download raw bytes

1(function($, Drupal, drupalSettings, once) {
2
3  "use strict";
4
5  Drupal.behaviors.leaflet = {
6    attach: function(context, settings) {
7
8      // For each Leaflet Map/id defined process with Leaflet Map and Features
9      // generation.
10      $.each(settings.leaflet, function(mapid, leaflet_settings) {
11
12        // Ensure the Leaflet Behavior is attached only once to each Leaflet map
13        // id element.
14        // @see https://www.drupal.org/project/leaflet/issues/3314762#comment-15044223
15        once('behaviour-leaflet', '#' + mapid).forEach(function (element) {
16          const map_container = $(element);
17
18          // Function to load the Leaflet Map, based on the provided mapid.
19          function loadMap(mapid) {
20            // Process a new Leaflet Map only if the map container is empty.
21            // Avoid reprocessing a Leaflet Map already initialised.
22            if (map_container.data('leaflet') === undefined) {
23              map_container.data('leaflet', new Drupal.Leaflet(L.DomUtil.get(mapid), mapid, leaflet_settings.map));
24              if (leaflet_settings.features.length > 0) {
25                // Add Leaflet Map Features.
26                map_container.data('leaflet').add_features(leaflet_settings.features, true);
27              }
28
29              // Add the Leaflet map to data settings object to make it
30              // accessible and extendable from other Drupal.behaviors attached
31              // functions.
32              // @NOTE: i.e. this is used by the leaflet.widget.js.
33              leaflet_settings.lMap = map_container.data('leaflet').lMap;
34
35              // Add the Leaflet Map Markers to data settings object to make it
36              // also accessible and extendable.
37              leaflet_settings.markers = map_container.data('leaflet').markers;
38
39              // Set initial Map position to wrap its defined bounds.
40              map_container.data('leaflet').fitBounds();
41
42              // Set the start center and the start zoom.
43              if (!map_container.data('leaflet').start_center && !map_container.data('leaflet').start_zoom) {
44                map_container.data('leaflet').start_center = map_container.data('leaflet').lMap.getCenter();
45                map_container.data('leaflet').start_zoom = map_container.data('leaflet').lMap.getZoom();
46              }
47
48              // Define the global Drupal.Leaflet[mapid] object to be accessible
49              // from outside - NOTE: This should always be created after setting
50              // the (above) start center.
51              Drupal.Leaflet[mapid] = map_container.data('leaflet');
52
53              // Add the Map Geocoder Control if requested.
54              if (!Drupal.Leaflet[mapid].geocoder_control && Drupal.Leaflet.prototype.map_geocoder_control) {
55                const mapGeocoderControlDiv = document.createElement('div');
56                Drupal.Leaflet[mapid].geocoder_control = Drupal.Leaflet.prototype.map_geocoder_control(mapGeocoderControlDiv, mapid);
57                Drupal.Leaflet[mapid].geocoder_control.addTo(Drupal.Leaflet[mapid].lMap);
58                const geocoder_settings = drupalSettings.leaflet[mapid].map.settings.geocoder.settings;
59                Drupal.Leaflet.prototype.map_geocoder_control.autocomplete(mapid, geocoder_settings);
60              }
61
62              // Add the Layers Control, if initialised/existing.
63              if (Drupal.Leaflet[mapid].layer_control) {
64                Drupal.Leaflet[mapid].lMap.addControl(Drupal.Leaflet[mapid].layer_control);
65              }
66
67              // Add and Initialise the Map Reset View Control if requested.
68              if (!Drupal.Leaflet[mapid].reset_view_control &&
69                  map_container.data('leaflet').map_settings.reset_map &&
70                  map_container.data('leaflet').map_settings.reset_map.control) {
71                const map_reset_view_options = map_container.data('leaflet').map_settings.reset_map.options ?
72                  JSON.parse(map_container.data('leaflet').map_settings.reset_map.options) : {};
73                map_reset_view_options.latlng = map_container.data('leaflet').start_center;
74                map_reset_view_options.zoom = map_container.data('leaflet').start_zoom;
75                Drupal.Leaflet[mapid].reset_view_control = L.control.resetView(map_reset_view_options)
76                  .addTo(map_container.data('leaflet').lMap);
77              }
78
79              // Add and Initialise the Map Scale Control if requested.
80              if (!Drupal.Leaflet[mapid].map_scale_control &&
81                  map_container.data('leaflet').map_settings.map_scale &&
82                  map_container.data('leaflet').map_settings.map_scale.control) {
83                const map_scale_options = map_container.data('leaflet').map_settings.map_scale.options ?
84                  JSON.parse(map_container.data('leaflet').map_settings.map_scale.options) : {};
85                Drupal.Leaflet[mapid].map_scale_control = L.control.scale(map_scale_options)
86                  .addTo(map_container.data('leaflet').lMap);
87              }
88
89              // Add the Locate Control if requested.
90              if (!Drupal.Leaflet[mapid].locate_control &&
91                  map_container.data('leaflet').map_settings.locate &&
92                  map_container.data('leaflet').map_settings.locate.control) {
93                const locate_options = map_container.data('leaflet').map_settings.locate.options ?
94                  JSON.parse(map_container.data('leaflet').map_settings.locate.options) : {};
95                Drupal.Leaflet[mapid].locate_control = L.control.locate(locate_options)
96                  .addTo(map_container.data('leaflet').lMap);
97
98                // In case this Leaflet Map is not in a Widget Context, eventually perform the Automatic User Locate, if requested.
99                if (!leaflet_settings.hasOwnProperty('leaflet_widget') &&
100                    map_container.data('leaflet').map_settings.hasOwnProperty('locate') &&
101                    map_container.data('leaflet').map_settings.locate.automatic) {
102                  Drupal.Leaflet[mapid].locate_control.start();
103                }
104              }
105
106              // Add Fullscreen Control, if requested.
107              if (!Drupal.Leaflet[mapid].fullscreen_control &&
108                  map_container.data('leaflet').map_settings.fullscreen &&
109                  map_container.data('leaflet').map_settings.fullscreen.control) {
110                const map_fullscreen_options = map_container.data('leaflet').map_settings.fullscreen.options ?
111                  JSON.parse(map_container.data('leaflet').map_settings.fullscreen.options) : {};
112                Drupal.Leaflet[mapid].fullscreen_control = L.control.fullscreen(map_fullscreen_options)
113                  .addTo(map_container.data('leaflet').lMap);
114              }
115
116              // Attach Leaflet Map listeners On Popup Open.
117              leaflet_settings.lMap.on('popupopen', function(e) {
118                // On leaflet-ajax-popup selector, fetch and set Ajax content.
119                const element = e.popup._contentNode;
120                const content = $('*[data-leaflet-ajax-popup]', element);
121                if (content.length) {
122                  const url = content.data('leaflet-ajax-popup');
123                  Drupal.ajax({url: url}).execute()
124                    .done(function(data) {
125                      // Copy the html we received via AJAX to the popup, so we won't
126                      // have to make another AJAX call (#see 3258780).
127                      e.popup.setContent(data[0].data);
128
129                      // Attach drupal behaviors on new content.
130                      Drupal.attachBehaviors(element, drupalSettings);
131                    })
132                    .fail(function() {
133                      // In case of failing fetching data.
134                      e.popup.close();
135                    });
136                }
137
138                // Make the (eventually present) Tooltip disappear on Popup Open
139                // in case the Popup is generated from a _source.
140                if (e.popup._source) {
141                  const tooltip = e.popup._source.getTooltip();
142                  // not all features will have tooltips!
143                  if (tooltip) {
144                    // use opacity to make the tooltip disappear.
145                    tooltip.setOpacity(0);
146                  }
147                }
148              });
149
150              // Attach Leaflet Map listeners On Popup Close.
151              leaflet_settings.lMap.on('popupclose', function(e) {
152                // Make the (eventually present) Tooltip re-appear on Popup Close.
153                // in case the Popup is generated from a _source.
154                if (e.popup._source) {
155                  const tooltip = e.popup._source.getTooltip();
156                  if (tooltip) {
157                    tooltip.setOpacity(0.9);
158                  }
159                }
160              });
161
162              // Define and Trigger 'leafletMapInit' event to let other modules
163              // and custom js libraries to bind & rect on Leaflet Map
164              // generation and extend & interact with its definition,
165              // properties and contents / features.
166              // NOTE: don't change this trigger arguments,
167              // to preserve backwards compatibility.
168              $(document).trigger('leafletMapInit', [leaflet_settings.map, leaflet_settings.lMap, mapid, leaflet_settings.markers]);
169              // NOTE: Keep also this pre-existing event for backwards
170              // compatibility with Leaflet < 2.1.0.
171              $(document).trigger('leaflet.map', [leaflet_settings.map, leaflet_settings.lMap, mapid, leaflet_settings.markers]);
172            }
173          }
174
175          // If the IntersectionObserver API is available, create an observer to load the map when it enters the viewport
176          // It will be used to handle map loading instead of displaying the map on page load.
177          let mapObserver = null;
178          if ('IntersectionObserver' in window) {
179            mapObserver = new IntersectionObserver(function(entries, observer) {
180              for (let i = 0; i < entries.length; i++) {
181                if (entries[i].isIntersecting) {
182                  const mapid = entries[i].target.id;
183                  loadMap(mapid);
184                }
185              }
186            });
187          }
188
189          // Load the Leaflet Map, lazy based on the mapObserver, or not.
190          if (mapObserver && leaflet_settings.map.settings.map_lazy_load?.lazy_load) {
191            mapObserver.observe(element);
192          } else {
193            loadMap(mapid);
194          }
195        });
196      });
197    }
198  };
199
200  /**
201   * Define a main Drupal.Leaflet function being generated/triggered on each
202   * Leaflet Map map_container element load.
203   *
204   * @param map_container
205   *   The Leaflet Map map_container.
206   * @param mapid
207   *   The Leaflet Map id.
208   * @param map_definition
209   *   The Leaflet Map definition.
210   * @constructor
211   */
212  Drupal.Leaflet = function(map_container, mapid, map_definition) {
213    this.mapid = mapid;
214    this.map_definition = map_definition;
215    this.map_settings = this.map_definition.settings;
216    this.bounds = [];
217    this.base_layers = {};
218    this.overlays = {};
219    this.lMap = null;
220    this.start_center = null;
221    this.start_zoom = null;
222    this.layer_control = null;
223    this.markers = {};
224    this.features = {};
225    this.initialise(mapid);
226  };
227
228  /**
229   * Initialise the specific Leaflet Map
230   *
231   * @param mapid
232   *   The dom element #id to inject the Leaflet Map into.
233   */
234  Drupal.Leaflet.prototype.initialise = function(mapid) {
235    // Instantiate a new Leaflet map.
236    this.lMap = new L.Map(mapid, this.map_settings);
237
238    // Add map layers (base and overlay layers).
239    let i = 0;
240    for (const key in this.map_definition.layers) {
241      const layer = this.map_definition.layers[key];
242      // Distinguish between "base" and "overlay" layers.
243      // Default to "base" in case "layer_type" has not been defined in hook_leaflet_map_info().
244      layer.layer_type = (typeof layer.layer_type === 'undefined') ? 'base' : layer.layer_type;
245
246      switch (layer.layer_type) {
247        case 'overlay':
248          const overlay_layer = this.create_layer(layer, key);
249          const layer_hidden = (typeof layer.layer_hidden === "undefined") ? false : layer.layer_hidden;
250          this.add_overlay(key, overlay_layer, layer_hidden);
251          break;
252
253        default:
254          this.add_base_layer(key, layer, i);
255          // Only the first base layer needs to be added to the map - all the
256          // others are accessed via the layer switcher.
257          if (i === 0) {
258            i++;
259          }
260          break;
261      }
262      i++;
263    }
264
265    // Set initial view, fallback to displaying the whole world.
266    if (this.map_settings.center && this.map_settings.zoom) {
267      this.lMap.setView(
268        new L.LatLng(this.map_settings.center.lat, this.map_settings.center.lon),
269        this.map_settings.zoom
270      );
271    }
272    else {
273      this.lMap.fitWorld();
274    }
275
276    // Set the position of the Zoom Control, if enabled.
277    if (this.lMap.zoomControl) {
278      this.lMap.zoomControl.setPosition(this.map_settings.zoomControlPosition);
279    }
280
281    // Set to refresh when first in viewport to avoid missing visibility.
282    new IntersectionObserver((entries, observer) => {
283      entries.forEach(entry => {
284        if (entry.intersectionRatio > 0) {
285          this.lMap.invalidateSize();
286          observer.disconnect();
287        }
288      });
289    }).observe(this.lMap._container);
290  };
291
292  /**
293   * Initialise the Leaflet Map Layers (Overlays) Control
294   */
295  Drupal.Leaflet.prototype.initialise_layers_control = function() {
296    const count_layers = function(obj) {
297      // Browser compatibility: Chrome, IE 9+, FF 4+, or Safari 5+.
298      // @see http://kangax.github.com/es5-compat-table/
299      return Object.keys(obj).length;
300    };
301
302    // Only add a layer switcher if it is enabled in settings, and we have
303    // at least two base layers or at least one overlay.
304    if (this.layer_control == null &&
305        ((this.map_settings.layerControl && count_layers(this.base_layers) > 1) ||
306         count_layers(this.overlays) > 0)) {
307      const base_layers = count_layers(this.base_layers) > 1 ? this.base_layers : [];
308      // Instantiate layer control, using settings.layerControl as settings.
309      this.layer_control = new L.Control.Layers(
310        base_layers,
311        [],
312        this.map_settings.layerControlOptions
313      );
314    }
315  };
316
317  /**
318   * Create & Add a Base Layer to the Leaflet Map and Layers Control.
319   *
320   * @param key
321   *   The Layer index/label.
322   * @param definition
323   *   The Layer definition,
324   * @param i
325   *   The layers progressive counter.
326   */
327  Drupal.Leaflet.prototype.add_base_layer = function(key, definition, i) {
328    const base_layer = this.create_layer(definition, key);
329    this.base_layers[key] = base_layer;
330
331    // Only the first base layer needs to be added to the map - all the others are accessed via the layer switcher.
332    if (i === 0) {
333      this.lMap.addLayer(base_layer);
334    }
335
336    // Initialise the Layers Control, if not yet.
337    if (this.layer_control == null) {
338      this.initialise_layers_control();
339    } else {
340      // Add the new base layer to layer_control.
341      this.layer_control.addBaseLayer(base_layer, key);
342    }
343  };
344
345  /**
346   * Adds a Specific Layer and related Overlay to the Leaflet Map.
347   *
348   * @param {string} label
349   *   The Overlay Layer Label.
350   * @param layer
351   *   The Leaflet Overlay.
352   * @param {boolean} hidden_layer
353   *   The flag to disable the Layer from the Over Layers Control.
354   */
355  Drupal.Leaflet.prototype.add_overlay = function(label, layer, hidden_layer) {
356    if (!hidden_layer) {
357      this.lMap.addLayer(layer);
358    }
359
360    // Add the Overlay to the Drupal.Leaflet object.
361    this.overlays[label] = layer;
362
363    // Initialise the Layers Control, if it is not.
364    if (label && this.layer_control == null) {
365      this.initialise_layers_control();
366    }
367
368    // Add the Overlay to the Layer Control only if there is a Label.
369    if (label && this.layer_control) {
370      // If we already have a layer control, add the new overlay to it.
371      this.layer_control.addOverlay(layer, label);
372    }
373  };
374
375  /**
376   * Add Leaflet Features to the Leaflet Map.
377   *
378   * @param {Array} features
379   *   Features List definition.
380   * @param {boolean} initial
381   *   Boolean to identify initial status.
382   */
383  Drupal.Leaflet.prototype.add_features = function(features, initial) {
384    // Define Map Layers holder.
385    const layers = {};
386
387    for (let i = 0; i < features.length; i++) {
388      const feature = features[i];
389      let lFeature;
390
391      // In case of a Features Group.
392      if (feature.group) {
393        // Define a named Layer Group
394        layers[feature.group_label] = this.create_feature_group();
395        for (const groupKey in feature.features) {
396          const groupFeature = feature.features[groupKey];
397          lFeature = this.create_feature(groupFeature);
398          if (lFeature !== undefined) {
399            // Add the lFeature to the lGroup.
400            layers[feature.group_label].addLayer(lFeature);
401
402            // Allow others to do something with the feature that was just added to the map.
403            $(document).trigger('leaflet.feature', [lFeature, groupFeature, this, layers]);
404          }
405        }
406
407        // Add the group to the layer switcher.
408        this.add_overlay(feature.group_label, layers[feature.group_label], feature.disabled);
409      }
410      else {
411        lFeature = this.create_feature(feature);
412        if (lFeature !== undefined) {
413          // Add the Leaflet Feature to the Map.
414          this.lMap.addLayer(lFeature);
415
416          // Allow others to do something with the feature that was just added to the map.
417          $(document).trigger('leaflet.feature', [lFeature, feature, this]);
418        }
419      }
420    }
421
422    // Allow plugins to do things after features have been added.
423    $(document).trigger('leaflet.features', [initial || false, this]);
424  };
425
426  /**
427   * Create a Leaflet Feature Group.
428   *
429   * @returns {Object}
430   *   A Leaflet feature group.
431   */
432  Drupal.Leaflet.prototype.create_feature_group = function() {
433    return new L.featureGroup();
434  };
435
436  /**
437   * Add Leaflet Popup to the Leaflet Feature.
438   *
439   * @param {Object} lFeature
440   *   The Leaflet Feature.
441   * @param {Object} feature
442   *   The Feature coming from Drupal settings.
443   */
444  Drupal.Leaflet.prototype.feature_bind_popup = function(lFeature, feature) {
445    // Attach the Popup only if supported and a value is set for it.
446    if (typeof lFeature.bindPopup !== "undefined" && feature.popup && feature.popup.value) {
447      const popup_options = feature.popup.options ? JSON.parse(feature.popup.options) : {};
448      lFeature.bindPopup(feature.popup.value, popup_options);
449    }
450  };
451
452  /**
453   * Add Leaflet Tooltip to the Leaflet Feature.
454   *
455   * @param {Object} lFeature
456   *   The Leaflet Feature.
457   * @param {Object} feature
458   *   The Feature coming from Drupal settings.
459   */
460  Drupal.Leaflet.prototype.feature_bind_tooltip = function(lFeature, feature) {
461    // Set the Leaflet Tooltip, with its options (if the stripped value is not null).
462    if (feature.tooltip && feature.tooltip.value.replace(/(<([^>]+)>)/gi, "").trim().length > 0) {
463      const tooltip_options = feature.tooltip.options ? JSON.parse(feature.tooltip.options) : {};
464
465      // Cleanup tooltip className from commas, to support multivalue class fields.
466      if (tooltip_options.hasOwnProperty('className') && tooltip_options.className.length > 0) {
467        tooltip_options.className = tooltip_options.className.replaceAll(",", "");
468      }
469
470      // Need to more correctly set the tooltip_options.permanent option.
471      tooltip_options.permanent = tooltip_options.permanent === true || tooltip_options.permanent === "true";
472
473      lFeature.bindTooltip(feature.tooltip.value, tooltip_options);
474    }
475  };
476
477  /**
478   * Set Leaflet Feature path style.
479   *
480   * @param {Object} lFeature
481   *   The Leaflet Feature.
482   * @param {Object} feature
483   *   The Feature coming from Drupal settings.
484   */
485  Drupal.Leaflet.prototype.set_feature_path_style = function(lFeature, feature) {
486    let lFeature_path_style;
487    try {
488      lFeature_path_style = feature.path ?
489        (feature.path instanceof Object ? feature.path : JSON.parse(feature.path)) : {};
490    }
491    catch (e) {
492      lFeature_path_style = {};
493    }
494
495    // Make sure that the weight property is cast into integer, for avoiding
496    // polygons eventually disappearing with pan and zooming.
497    // @see: https://stackoverflow.com/a/65892728/5451394
498    if (lFeature_path_style.hasOwnProperty('weight')) {
499      lFeature_path_style.weight = parseInt(lFeature_path_style.weight);
500    }
501    lFeature.setStyle(lFeature_path_style);
502  };
503
504  /**
505   * Extend Map Bounds with new lFeature/feature.
506   *
507   * @param {Object} lFeature
508   *   The Leaflet Feature.
509   * @param {Object} feature
510   *   The Feature coming from Drupal settings.
511   *   (this parameter should be kept to eventually extend this method with
512   *   conditional logics on feature properties)
513   */
514  Drupal.Leaflet.prototype.extend_map_bounds = function(lFeature, feature) {
515    if (feature.type === 'point') {
516      this.bounds.push([feature.lat, feature.lon]);
517    } else if (lFeature.getBounds) {
518      this.bounds.push(lFeature.getBounds().getSouthWest(), lFeature.getBounds().getNorthEast());
519    }
520  };
521
522  /**
523   * Add Marker and Feature to the Drupal.Leaflet object.
524   *
525   * @param {Object} lFeature
526   *   The Leaflet Feature.
527   * @param {Object} feature
528   *   The Feature coming from Drupal settings.
529   */
530  Drupal.Leaflet.prototype.push_markers_features = function(lFeature, feature) {
531    if (feature.entity_id) {
532      // Generate the markers object index based on entity id (and geofield
533      // cardinality), and add the marker to the markers object.
534      const entity_id = feature.entity_id;
535      if (this.map_definition.geofield_cardinality && this.map_definition.geofield_cardinality !== 1) {
536        let i = 0;
537        while (this.markers[entity_id + '-' + i]) {
538          i++;
539        }
540        this.markers[entity_id + '-' + i] = lFeature;
541        this.features[entity_id + '-' + i] = feature;
542      }
543      else {
544        this.markers[entity_id] = lFeature;
545        this.features[entity_id] = feature;
546      }
547    }
548  };
549
550  /**
551   * Generates a Leaflet Geometry (Point or Geometry).
552   *
553   * @param {Object} feature
554   *   The feature definition coming from Drupal backend.
555   * @param {Object|boolean} map_settings
556   *   The map_settings if defined, false otherwise.
557   *
558   * @returns {Object}
559   *   The generated Leaflet Geometry.
560   */
561  Drupal.Leaflet.prototype.create_geometry = function(feature, map_settings = false) {
562    let lFeature;
563    switch (feature.type) {
564      case 'point':
565        lFeature = this.create_point(feature);
566        break;
567
568      case 'linestring':
569        lFeature = this.create_linestring(
570          feature,
571          map_settings?.leaflet_markercluster.include_path ?? false
572        );
573        break;
574
575      case 'polygon':
576        lFeature = this.create_polygon(
577          feature,
578          map_settings?.leaflet_markercluster.include_path ?? false
579        );
580        break;
581
582      case 'multipolygon':
583        lFeature = this.create_multipolygon(
584          feature,
585          map_settings?.leaflet_markercluster.include_path ?? false
586        );
587        break;
588
589      case 'multipolyline':
590        lFeature = this.create_multipoly(
591          feature,
592          map_settings?.leaflet_markercluster.include_path ?? false
593        );
594        break;
595
596      // In case of singular cases where feature.type is json we use this.create_json method.
597      // @see https://www.drupal.org/project/leaflet/issues/3377403
598      // @see https://www.drupal.org/project/leaflet/issues/3186029
599      case 'json':
600        lFeature = this.create_json(feature.json, feature.options, feature.events);
601        break;
602
603      case 'multipoint':
604      case 'geometrycollection':
605        lFeature = this.create_collection(feature);
606        break;
607
608      default:
609        lFeature = {};
610    }
611    return lFeature;
612  };
613
614  /**
615   * Generates a Leaflet Feature (Point or Geometry)
616   * with Leaflet adds on (Tooltip, Popup, Path Styles, etc.)
617   *
618   * @param {Object} feature
619   *   The feature definition coming from Drupal backend.
620   * @returns {Object}
621   *   The generated Leaflet Feature.
622   */
623  Drupal.Leaflet.prototype.create_feature = function(feature) {
624    const map_settings = this.map_settings ?? null;
625    const lFeature = this.create_geometry(feature, map_settings);
626
627    // Eventually add Tooltip to the lFeature.
628    this.feature_bind_tooltip(lFeature, feature);
629
630    // Eventually add Popup to the lFeature.
631    this.feature_bind_popup(lFeature, feature);
632
633    // Eventually Set Style for Path/Geometry lFeature.
634    if (lFeature.setStyle) {
635      this.set_feature_path_style(lFeature, feature);
636    }
637
638    // Eventually extend Map Bounds with new lFeature.
639    this.extend_map_bounds(lFeature, feature);
640
641    // Add Marker and Feature to the Drupal.Leaflet object.
642    this.push_markers_features(lFeature, feature);
643
644    return lFeature;
645  };
646
647  /**
648   * Generate a Leaflet Layer.
649   *
650   * @param {Object} layer
651   *   The Layer definition.
652   * @param {string} key
653   *   The Layer index/label.
654   *
655   * @returns {Object}
656   *   The Leaflet layer object.
657   */
658  Drupal.Leaflet.prototype.create_layer = function(layer, key) {
659    const self = this;
660    let map_layer;
661    const layer_type = layer.type ?? 'base';
662    const urlTemplate = layer.urlTemplate ?? '';
663    const layer_options = layer.options ?? {};
664
665    switch (layer_type) {
666      case 'wms':
667        map_layer = new L.tileLayer.wms(urlTemplate, layer_options);
668        break;
669
670      case 'vector':
671        map_layer = new L.maplibreGL({
672          style: urlTemplate,
673          attribution: layer_options.attribution ?? '',
674          pitch: layer_options.pitch ?? '',
675          bearing: layer_options.bearing ?? ''
676        });
677        break;
678
679      default:
680        map_layer = new L.tileLayer(urlTemplate, layer_options);
681    }
682
683    map_layer._leaflet_id = key;
684
685    // Layers served from TileStream need this correction in the y coordinates.
686    // TODO: Need to explore this more and find a more elegant solution.
687    if (layer.type === 'tilestream') {
688      map_layer.getTileUrl = function(tilePoint) {
689        self._adjustTilePoint(tilePoint);
690        const zoom = self._getZoomForUrl();
691        return L.Util.template(self._url, L.Util.extend({
692          s: self._getSubdomain(tilePoint),
693          z: zoom,
694          x: tilePoint.x,
695          y: Math.pow(2, zoom) - tilePoint.y - 1
696        }, self.options));
697      };
698    }
699    return map_layer;
700  };
701
702  /**
703   * Leaflet Icon creator.
704   *
705   * @param {Object} options
706   *   The Icon options.
707   *
708   * @returns {Object}
709   *   Leaflet Icon object.
710   */
711  Drupal.Leaflet.prototype.create_icon = function(options) {
712    const icon_options = {
713      iconUrl: options.iconUrl,
714    };
715
716    // Apply Icon properties
717    // @see https://leafletjs.com/reference.html#icon
718
719    // Icon Size.
720    if (options.iconSize) {
721      icon_options.iconSize = new L.Point(
722        parseInt(options.iconSize.x),
723        parseInt(options.iconSize.y)
724      );
725    }
726
727    // Icon Anchor.
728    if (options.iconAnchor && options.iconAnchor.x && options.iconAnchor.y) {
729      icon_options.iconAnchor = new L.Point(
730        parseInt(options.iconAnchor.x),
731        parseInt(options.iconAnchor.y)
732      );
733    }
734
735    // Popup Anchor.
736    if (options.popupAnchor && options.popupAnchor.x && options.popupAnchor.y) {
737      icon_options.popupAnchor = new L.Point(
738        parseInt(options.popupAnchor.x),
739        parseInt(options.popupAnchor.y)
740      );
741    }
742
743    // Popup ShadowUrl.
744    if (options.shadowUrl) {
745      icon_options.shadowUrl = options.shadowUrl;
746    }
747
748    // Popup ShadowSize.
749    if (options.shadowSize && options.shadowSize.x && options.shadowSize.y) {
750      icon_options.shadowSize = new L.Point(
751        parseInt(options.shadowSize.x),
752        parseInt(options.shadowSize.y)
753      );
754    }
755
756    // Popup ShadowAnchor.
757    if (options.shadowAnchor && options.shadowAnchor.x && options.shadowAnchor.y) {
758      icon_options.shadowAnchor = new L.Point(
759        parseInt(options.shadowAnchor.x),
760        parseInt(options.shadowAnchor.y)
761      );
762    }
763
764    if (options.className) {
765      icon_options.className = options.className;
766    }
767
768    // Popup IconRetinaUrl.
769    // @see https://www.drupal.org/project/leaflet/issues/3268023
770    if (options.iconRetinaUrl) {
771      icon_options.iconRetinaUrl = options.iconRetinaUrl;
772    }
773
774    return new L.Icon(icon_options);
775  };
776
777  /**
778   * Leaflet DIV Icon creator.
779   *
780   * @param options
781   *   The Icon options.
782   *
783   * @returns {*}
784   */
785  Drupal.Leaflet.prototype.create_divicon = function (options) {
786    let html_class = options['html_class'] || '';
787    let icon = new L.DivIcon({html: options.html, className: html_class});
788
789    // Apply Icon properties
790    // @see https://leafletjs.com/reference.html#icon
791
792    // Icon Size.
793    if (options.iconSize) {
794      icon.options.iconSize = new L.Point(parseInt(options.iconSize.x, 10), parseInt(options.iconSize.y, 10));
795    }
796    // Icon Anchor.
797    if (options.iconAnchor && options.iconAnchor.x && options.iconAnchor.y) {
798      icon.options.iconAnchor = new L.Point(parseInt(options.iconAnchor.x), parseInt(options.iconAnchor.y));
799    }
800    // Popup Anchor.
801    if (options.popupAnchor && options.popupAnchor.x && options.popupAnchor.y) {
802      icon.options.popupAnchor = new L.Point(parseInt(options.popupAnchor.x), parseInt(options.popupAnchor.y));
803    }
804
805    return icon;
806  };
807
808  /**
809   * Return Geometry Construction Base Options.
810   *
811   * @param feature
812   *   The feature definition.
813   *
814   * @returns {*}
815   */
816  Drupal.Leaflet.prototype.create_geometry_base_options = function(feature) {
817    // Assign the marker title value depending if a Marker simple title or a
818    // Leaflet tooltip was set.
819    let marker_title = '';
820    if (feature.title) {
821      marker_title = feature.title.replace(/<[^>]*>/g, '').trim()
822    }
823    return {
824      // Title only set in case of a Marker simple title.
825      title: marker_title ?? "",
826      className: feature.icon && feature.icon.className ? feature.icon.className.replaceAll(",", "") : '',
827      // Alt property always set matching Simple Title or Feature Tooltip.
828      alt: feature.tooltip && feature.tooltip.value ? feature.tooltip.value.replace(/<[^>]*>/g, '').trim() : marker_title,
829      group_label: feature.group_label ?? '',
830    };
831  }
832
833  /**
834   * Leaflet Point (Marker) creator.
835   *
836   * @param feature
837   *   The feature definition.
838   *
839   * @returns {*}
840   */
841  Drupal.Leaflet.prototype.create_point = function(feature) {
842
843    const latLng = new L.LatLng(feature.lat, feature.lon);
844    let options = this.create_geometry_base_options(feature);
845    let lMarker = new L.Marker(latLng, options);
846
847    if (feature.icon) {
848      if (feature.icon.iconType && feature.icon.iconType === 'html' && feature.icon.html) {
849        let icon = this.create_divicon(feature.icon);
850        lMarker.setIcon(icon);
851      }
852      else if (feature.icon.iconType && feature.icon.iconType === 'circle_marker') {
853        try {
854          // Extend the options with circle marker specific properties,
855          Object.assign(options , feature.icon.circle_marker_options ? JSON.parse(feature.icon.circle_marker_options) : {})
856          options.radius = options.radius ? parseInt(options['radiu
856s']) : 10;
857        }
858        catch (e) {
859          options = {};
860        }
861        lMarker = new L.CircleMarker(latLng, options);
862      }
863      else if (feature.icon.iconUrl) {
864        feature.icon.iconSize = feature.icon.iconSize || {};
865        feature.icon.iconSize.x = feature.icon.iconSize.x || this.naturalWidth;
866        feature.icon.iconSize.y = feature.icon.iconSize.y || this.naturalHeight;
867        if (feature.icon.shadowUrl) {
868          feature.icon.shadowSize = feature.icon.shadowSize || {};
869          feature.icon.shadowSize.x = feature.icon.shadowSize.x || this.naturalWidth;
870          feature.icon.shadowSize.y = feature.icon.shadowSize.y || this.naturalHeight;
871        }
872        let icon = this.create_icon(feature.icon);
873        lMarker.setIcon(icon);
874      }
875    }
876
877    return lMarker;
878  };
879
880  /**
881   * Leaflet Linestring creator.
882   *
883   * @param polyline
884   *   The Polyline definition.
885   * @param clusterable
886   *   Clusterable bool option.
887   *
888   * @returns {*}
889   */
890  Drupal.Leaflet.prototype.create_linestring = function(polyline, clusterable = false) {
891    let latlngs = [];
892    for (let i = 0; i < polyline.points.length; i++) {
893      let latlng = new L.LatLng(polyline.points[i].lat, polyline.points[i].lon);
894      latlngs.push(latlng);
895    }
896    const options = this.create_geometry_base_options(polyline);
897    return clusterable ? new L.PolylineClusterable(latlngs, options) : new L.Polyline(latlngs, options);
898  };
899
900  /**
901   * Leaflet Polygon creator.
902   *
903   * @param polygon
904   *   The polygon definition,
905   * @param clusterable
906   *   Clusterable bool option.
907   *
908   * @returns {*}
909   */
910  Drupal.Leaflet.prototype.create_polygon = function(polygon, clusterable = false) {
911    const coordinates = polygon.points ?? [];
912    const options = this.create_geometry_base_options(polygon);
913    return clusterable ? new L.PolygonClusterable(coordinates, options) : new L.Polygon(coordinates, options);
914  };
915
916  /**
917   * Leaflet Multi-Polygon creator.
918   *
919   * @param multipolygon
920   *   The polygon definition,
921   * @param clusterable
922   *   Clusterable bool option.
923   *
924   * @returns {*}
925   */
926  Drupal.Leaflet.prototype.create_multipolygon = function(multipolygon, clusterable = false) {
927    const coordinates = multipolygon.points ?? [];
928    const options = this.create_geometry_base_options(multipolygon);
929    return clusterable ? new L.PolygonClusterable(coordinates, options) : new L.Polygon(coordinates, options);
930  };
931
932  /**
933   * Leaflet Multi-Poly creator (both Polygons & Poly-lines)
934   *
935   * @param multipoly
936   *   The multipoly definition,
937   * @param clusterable
938   *   Clusterable bool option.
939   *
940   * @returns {*}
941   */
942  Drupal.Leaflet.prototype.create_multipoly = function(multipoly, clusterable = false) {
943    let polygons = [];
944    for (let x = 0; x < multipoly.component.length; x++) {
945      let latlngs = [];
946      let polygon = multipoly.component[x];
947      for (let i = 0; i < polygon.points.length; i++) {
948        let latlng = new L.LatLng(polygon.points[i].lat, polygon.points[i].lon);
949        latlngs.push(latlng);
950      }
951      polygons.push(latlngs);
952    }
953    const options = this.create_geometry_base_options(multipoly);
954    if (multipoly.multipolyline) {
955      return clusterable ? new L.PolylineClusterable(polygons, options) : new L.Polyline(polygons, options);
956    }
957    else {
958      return clusterable ? new L.PolygonClusterable(polygons, options) : new L.Polygon(polygons, options);
959    }
960  };
961
962  /**
963   *  Leaflet Collection creator.
964   *
965   * @param collection
966   *   The collection definition.
967   *
968   * @returns {*}
969   */
970  Drupal.Leaflet.prototype.create_collection = function(collection) {
971    let layers = new L.featureGroup();
972    for (let x = 0; x < collection.component.length; x++) {
973      let feature = { ...collection, ...collection.component[x]};
974      layers.addLayer(this.create_feature(feature));
975    }
976    return layers;
977  };
978
979  /**
980   * Leaflet Geo JSON Creator.
981   *
982   * In case of singular cases where feature type is json we use this.create_json method.
983   * @see https://www.drupal.org/project/leaflet/issues/3377403
984   * @see https://www.drupal.org/project/leaflet/issues/3186029
985   *
986   * @param json
987   *   The json input.
988   * @param options
989   *   The options array,
990   *   that would reflect the GeoJSON Leaflet Js library options
991   *   https://leafletjs.com/reference.html#geojson
992   * @param events
993   *   The events array
994   *
995   * @returns {*}
996   */
997  Drupal.Leaflet.prototype.create_json = function(json, options = [], events = []) {
998    let lJSON = new L.GeoJSON();
999    const self = this;
1000
1001    lJSON.options.onEachFeature = function(feature, layer) {
1002      for (let layer_id in layer._layers) {
1003        for (let i in layer._layers[layer_id]._latlngs) {
1004        }
1005      }
1006      if (feature.properties.style) {
1007        layer.setStyle(feature.properties.style);
1008      }
1009      if (feature.properties.leaflet_id) {
1010        layer._leaflet_id = feature.properties.leaflet_id;
1011      }
1012
1013      // Eventually add Tooltip to the lFeature.
1014      self.feature_bind_tooltip(layer, feature.properties);
1015
1016      // Eventually add Popup to the Layer.
1017      self.feature_bind_popup(layer, feature.properties);
1018
1019      for (const e in events) {
1020        let layerParam = {};
1021        layerParam[e] = eval(events[e]);
1022        layer.on(layerParam);
1023      }
1024    };
1025
1026    for (const option in options) {
1027      if (Object.prototype.hasOwnProperty.call(options, option)) {
1028        lJSON.options[option] = eval(options[option]);
1029      }
1030    }
1031
1032    lJSON.addData(json);
1033    return lJSON;
1034  };
1035
1036
1037  // Set Map initial map position and Zoom. Different scenarios:
1038  //  1)  Force the initial map center and zoom to values provided by input settings
1039  //  2)  Fit multiple features onto map using Leaflet's fitBounds method
1040  //  3)  Fit a single polygon onto map using Leaflet's fitBounds method
1041  //  4)  Display a single marker using the specified zoom
1042  //  5)  Adjust the initial zoom using zoomFiner, if specified
1043  //  6)  Cater for a map with no features (use input settings for Zoom and Center, if supplied)
1044  //
1045  // @NOTE: This method used by Leaflet Markecluster module (don't remove/rename)
1046  Drupal.Leaflet.prototype.fitBounds = function() {
1047    let start_zoom = this.map_settings.zoom ? this.map_settings.zoom : 12;
1048    // Note: this.map_settings.center might not be defined in case of Leaflet widget and Automatically locate user current position.
1049    let start_center = this.map_settings.center ? new L.LatLng(this.map_settings.center.lat, this.map_settings.center.lon) : new L.LatLng(0,0);
1050
1051    //  Check whether the Zoom and Center are to be forced to use the input settings
1052    if (this.map_settings.map_position_force) {
1053      //  Set the Zoom and Center to values provided by the input settings
1054      this.lMap.setView(start_center, start_zoom);
1055    } else {
1056      if (this.bounds.length === 0) {
1057        //  No features - set the Zoom and Center to values provided by the input settings, if specified
1058        this.lMap.setView(start_center, start_zoom);
1059      } else {
1060        //  Set the Zoom and Center by using the Leaflet fitBounds function
1061        const bounds = new L.LatLngBounds(this.bounds);
1062        const fitbounds_options = this.map_settings.fitbounds_options ? JSON.parse(this.map_settings.fitbounds_options) : {};
1063        this.lMap.fitBounds(bounds, fitbounds_options);
1064        start_center = bounds.getCenter();
1065        start_zoom = this.lMap.getBoundsZoom(bounds);
1066
1067        if (this.bounds.length === 1) {
1068          //  Single marker - set zoom to input settings
1069          this.lMap.setZoom(this.map_settings.zoom);
1070          start_zoom = this.map_settings.zoom;
1071        }
1072      }
1073
1074      // In case of map initial position not forced, and zooFiner not null/neutral,
1075      // adapt the Map Zoom and the Start Zoom accordingly.
1076      if (this.map_settings.hasOwnProperty('zoomFiner') && parseInt(this.map_settings.zoomFiner)) {
1077        start_zoom += parseFloat(this.map_settings.zoomFiner);
1078        this.lMap.setView(start_center, start_zoom);
1079      }
1080
1081      // Set the map start zoom and center.
1082      this.start_zoom = start_zoom;
1083      this.start_center = start_center;
1084    }
1085
1086  };
1087
1088  /**
1089   * Triggers a Leaflet Map Reset View action.
1090   *
1091   * @param mapid
1092   *   The Map identifier, to apply the rest to.
1093   */
1094  Drupal.Leaflet.prototype.map_reset = function(mapid) {
1095    Drupal.Leaflet[mapid].reset_view_control._resetView();
1096  };
1097
1098  // Extend the L.Polyline to make it clustarable.
1099  // @see https://gis.stackexchange.com/questions/197882/is-it-possible-to-cluster-polygons-in-leaflet
1100  L.PolylineClusterable = L.Polyline.extend({
1101    _originalInitialize: L.Polyline.prototype.initialize,
1102
1103    initialize: function (bounds, options) {
1104      this._originalInitialize(bounds, options);
1105      this._latlng = this.getBounds().getCenter();
1106    },
1107
1108    getLatLng: function () {
1109      return this._latlng;
1110    },
1111
1112    setLatLng: function () {}
1113  });
1114
1115  // Extend the L.Polygon to make it clustarable.
1116  // @see https://gis.stackexchange.com/questions/197882/is-it-possible-to-cluster-polygons-in-leaflet
1117  L.PolygonClusterable = L.Polygon.extend({
1118    _originalInitialize: L.Polygon.prototype.initialize,
1119
1120    initialize: function (bounds, options) {
1121      this._originalInitialize(bounds, options);
1122      this._latlng = this.getBounds().getCenter();
1123    },
1124
1125    getLatLng: function () {
1126      return this._latlng;
1127    },
1128
1129    setLatLng: function () {}
1130  });
1131
1132})(jQuery, Drupal, drupalSettings, once);

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.