PageSourceSearch

https://vib.be/modules/contrib/leaflet/js/leaflet.drupal.js?tmn11z

js vib.be collected 2026-10-09 23:06:46 UTC 41,175 bytes, 1,133 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    else if (feature.tooltip && feature.tooltip.value) {
824      marker_title = feature.tooltip.value.replace(/<[^>]*>/g, '').trim();
825    }
826    return {
827      title: marker_title ?? "",
828      className: feature.icon && feature.icon.className ? feature.icon.className.replaceAll(",", "") : '',
829      alt: marker_title ?? "",
830      group_label: feature.group_label ?? '',
831    };
832  }
833
834  /**
835   * Leaflet Point (Marker) creator.
836   *
837   * @param feature
838   *   The feature definition.
839   *
840   * @returns {*}
841   */
842  Drupal.Leaflet.prototype.create_point = function(feature) {
843
844    const latLng = new L.LatLng(feature.lat, feature.lon);
845    let options = this.create_geometry_base_options(feature);
846    let lMarker = new L.Marker(latLng, options);
847
848    if (feature.icon) {
849      if (feature.icon.iconType && feature.icon.iconType === 'html' && feature.icon.html) {
850        let icon = this.create_divicon(feature.icon);
851        lMarker.setIcon(icon);
852      }
853      else if (feature.icon.iconType && feature.icon.iconType === 'circle_marker') {
854        try {
855          // Extend the options with circle marker specific properties,
856          Object.assign(options , feature.icon.circle_marker_options ? JSON.parse(feature.icon.circle_marker_options) : {})
857          options.radius = options.radius ? parseInt(options['radiu
857s']) : 10;
858        }
859        catch (e) {
860          options = {};
861        }
862        lMarker = new L.CircleMarker(latLng, options);
863      }
864      else if (feature.icon.iconUrl) {
865        feature.icon.iconSize = feature.icon.iconSize || {};
866        feature.icon.iconSize.x = feature.icon.iconSize.x || this.naturalWidth;
867        feature.icon.iconSize.y = feature.icon.iconSize.y || this.naturalHeight;
868        if (feature.icon.shadowUrl) {
869          feature.icon.shadowSize = feature.icon.shadowSize || {};
870          feature.icon.shadowSize.x = feature.icon.shadowSize.x || this.naturalWidth;
871          feature.icon.shadowSize.y = feature.icon.shadowSize.y || this.naturalHeight;
872        }
873        let icon = this.create_icon(feature.icon);
874        lMarker.setIcon(icon);
875      }
876    }
877
878    return lMarker;
879  };
880
881  /**
882   * Leaflet Linestring creator.
883   *
884   * @param polyline
885   *   The Polyline definition.
886   * @param clusterable
887   *   Clusterable bool option.
888   *
889   * @returns {*}
890   */
891  Drupal.Leaflet.prototype.create_linestring = function(polyline, clusterable = false) {
892    let latlngs = [];
893    for (let i = 0; i < polyline.points.length; i++) {
894      let latlng = new L.LatLng(polyline.points[i].lat, polyline.points[i].lon);
895      latlngs.push(latlng);
896    }
897    const options = this.create_geometry_base_options(polyline);
898    return clusterable ? new L.PolylineClusterable(latlngs, options) : new L.Polyline(latlngs, options);
899  };
900
901  /**
902   * Leaflet Polygon creator.
903   *
904   * @param polygon
905   *   The polygon definition,
906   * @param clusterable
907   *   Clusterable bool option.
908   *
909   * @returns {*}
910   */
911  Drupal.Leaflet.prototype.create_polygon = function(polygon, clusterable = false) {
912    const coordinates = polygon.points ?? [];
913    const options = this.create_geometry_base_options(polygon);
914    return clusterable ? new L.PolygonClusterable(coordinates, options) : new L.Polygon(coordinates, options);
915  };
916
917  /**
918   * Leaflet Multi-Polygon creator.
919   *
920   * @param multipolygon
921   *   The polygon definition,
922   * @param clusterable
923   *   Clusterable bool option.
924   *
925   * @returns {*}
926   */
927  Drupal.Leaflet.prototype.create_multipolygon = function(multipolygon, clusterable = false) {
928    const coordinates = multipolygon.points ?? [];
929    const options = this.create_geometry_base_options(multipolygon);
930    return clusterable ? new L.PolygonClusterable(coordinates, options) : new L.Polygon(coordinates, options);
931  };
932
933  /**
934   * Leaflet Multi-Poly creator (both Polygons & Poly-lines)
935   *
936   * @param multipoly
937   *   The multipoly definition,
938   * @param clusterable
939   *   Clusterable bool option.
940   *
941   * @returns {*}
942   */
943  Drupal.Leaflet.prototype.create_multipoly = function(multipoly, clusterable = false) {
944    let polygons = [];
945    for (let x = 0; x < multipoly.component.length; x++) {
946      let latlngs = [];
947      let polygon = multipoly.component[x];
948      for (let i = 0; i < polygon.points.length; i++) {
949        let latlng = new L.LatLng(polygon.points[i].lat, polygon.points[i].lon);
950        latlngs.push(latlng);
951      }
952      polygons.push(latlngs);
953    }
954    const options = this.create_geometry_base_options(multipoly);
955    if (multipoly.multipolyline) {
956      return clusterable ? new L.PolylineClusterable(polygons, options) : new L.Polyline(polygons, options);
957    }
958    else {
959      return clusterable ? new L.PolygonClusterable(polygons, options) : new L.Polygon(polygons, options);
960    }
961  };
962
963  /**
964   *  Leaflet Collection creator.
965   *
966   * @param collection
967   *   The collection definition.
968   *
969   * @returns {*}
970   */
971  Drupal.Leaflet.prototype.create_collection = function(collection) {
972    let layers = new L.featureGroup();
973    for (let x = 0; x < collection.component.length; x++) {
974      let feature = { ...collection, ...collection.component[x]};
975      layers.addLayer(this.create_feature(feature));
976    }
977    return layers;
978  };
979
980  /**
981   * Leaflet Geo JSON Creator.
982   *
983   * In case of singular cases where feature type is json we use this.create_json method.
984   * @see https://www.drupal.org/project/leaflet/issues/3377403
985   * @see https://www.drupal.org/project/leaflet/issues/3186029
986   *
987   * @param json
988   *   The json input.
989   * @param options
990   *   The options array,
991   *   that would reflect the GeoJSON Leaflet Js library options
992   *   https://leafletjs.com/reference.html#geojson
993   * @param events
994   *   The events array
995   *
996   * @returns {*}
997   */
998  Drupal.Leaflet.prototype.create_json = function(json, options = [], events = []) {
999    let lJSON = new L.GeoJSON();
1000    const self = this;
1001
1002    lJSON.options.onEachFeature = function(feature, layer) {
1003      for (let layer_id in layer._layers) {
1004        for (let i in layer._layers[layer_id]._latlngs) {
1005        }
1006      }
1007      if (feature.properties.style) {
1008        layer.setStyle(feature.properties.style);
1009      }
1010      if (feature.properties.leaflet_id) {
1011        layer._leaflet_id = feature.properties.leaflet_id;
1012      }
1013
1014      // Eventually add Tooltip to the lFeature.
1015      self.feature_bind_tooltip(layer, feature.properties);
1016
1017      // Eventually add Popup to the Layer.
1018      self.feature_bind_popup(layer, feature.properties);
1019
1020      for (const e in events) {
1021        let layerParam = {};
1022        layerParam[e] = eval(events[e]);
1023        layer.on(layerParam);
1024      }
1025    };
1026
1027    for (const option in options) {
1028      if (Object.prototype.hasOwnProperty.call(options, option)) {
1029        lJSON.options[option] = eval(options[option]);
1030      }
1031    }
1032
1033    lJSON.addData(json);
1034    return lJSON;
1035  };
1036
1037
1038  // Set Map initial map position and Zoom. Different scenarios:
1039  //  1)  Force the initial map center and zoom to values provided by input settings
1040  //  2)  Fit multiple features onto map using Leaflet's fitBounds method
1041  //  3)  Fit a single polygon onto map using Leaflet's fitBounds method
1042  //  4)  Display a single marker using the specified zoom
1043  //  5)  Adjust the initial zoom using zoomFiner, if specified
1044  //  6)  Cater for a map with no features (use input settings for Zoom and Center, if supplied)
1045  //
1046  // @NOTE: This method used by Leaflet Markecluster module (don't remove/rename)
1047  Drupal.Leaflet.prototype.fitBounds = function() {
1048    let start_zoom = this.map_settings.zoom ? this.map_settings.zoom : 12;
1049    // Note: this.map_settings.center might not be defined in case of Leaflet widget and Automatically locate user current position.
1050    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);
1051
1052    //  Check whether the Zoom and Center are to be forced to use the input settings
1053    if (this.map_settings.map_position_force) {
1054      //  Set the Zoom and Center to values provided by the input settings
1055      this.lMap.setView(start_center, start_zoom);
1056    } else {
1057      if (this.bounds.length === 0) {
1058        //  No features - set the Zoom and Center to values provided by the input settings, if specified
1059        this.lMap.setView(start_center, start_zoom);
1060      } else {
1061        //  Set the Zoom and Center by using the Leaflet fitBounds function
1062        const bounds = new L.LatLngBounds(this.bounds);
1063        const fitbounds_options = this.map_settings.fitbounds_options ? JSON.parse(this.map_settings.fitbounds_options) : {};
1064        this.lMap.fitBounds(bounds, fitbounds_options);
1065        start_center = bounds.getCenter();
1066        start_zoom = this.lMap.getBoundsZoom(bounds);
1067
1068        if (this.bounds.length === 1) {
1069          //  Single marker - set zoom to input settings
1070          this.lMap.setZoom(this.map_settings.zoom);
1071          start_zoom = this.map_settings.zoom;
1072        }
1073      }
1074
1075      // In case of map initial position not forced, and zooFiner not null/neutral,
1076      // adapt the Map Zoom and the Start Zoom accordingly.
1077      if (this.map_settings.hasOwnProperty('zoomFiner') && parseInt(this.map_settings.zoomFiner)) {
1078        start_zoom += parseFloat(this.map_settings.zoomFiner);
1079        this.lMap.setView(start_center, start_zoom);
1080      }
1081
1082      // Set the map start zoom and center.
1083      this.start_zoom = start_zoom;
1084      this.start_center = start_center;
1085    }
1086
1087  };
1088
1089  /**
1090   * Triggers a Leaflet Map Reset View action.
1091   *
1092   * @param mapid
1093   *   The Map identifier, to apply the rest to.
1094   */
1095  Drupal.Leaflet.prototype.map_reset = function(mapid) {
1096    Drupal.Leaflet[mapid].reset_view_control._resetView();
1097  };
1098
1099  // Extend the L.Polyline to make it clustarable.
1100  // @see https://gis.stackexchange.com/questions/197882/is-it-possible-to-cluster-polygons-in-leaflet
1101  L.PolylineClusterable = L.Polyline.extend({
1102    _originalInitialize: L.Polyline.prototype.initialize,
1103
1104    initialize: function (bounds, options) {
1105      this._originalInitialize(bounds, options);
1106      this._latlng = this.getBounds().getCenter();
1107    },
1108
1109    getLatLng: function () {
1110      return this._latlng;
1111    },
1112
1113    setLatLng: function () {}
1114  });
1115
1116  // Extend the L.Polygon to make it clustarable.
1117  // @see https://gis.stackexchange.com/questions/197882/is-it-possible-to-cluster-polygons-in-leaflet
1118  L.PolygonClusterable = L.Polygon.extend({
1119    _originalInitialize: L.Polygon.prototype.initialize,
1120
1121    initialize: function (bounds, options) {
1122      this._originalInitialize(bounds, options);
1123      this._latlng = this.getBounds().getCenter();
1124    },
1125
1126    getLatLng: function () {
1127      return this._latlng;
1128    },
1129
1130    setLatLng: function () {}
1131  });
1132
1133})(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.