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