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.