import L from 'leaflet'; import 'leaflet/dist/leaflet.css'; import flip from '@turf/flip'; import axios from '@/axios-client.js'; import 'leaflet.vectorgrid'; let map; let dictLayersToLeaflet = {}; var layerMVT; let statusList = [ { name: 'Brouillon', value: 'draft', }, { name: 'Publié', value: 'published', }, { name: 'Archivé', value: 'archived', }, { name: 'En attente de publication', value: 'pending', }, ]; L.TileLayer.BetterWMS = L.TileLayer.WMS.extend({ onAdd: function (map) { // Triggered when the layer is added to a map. // Register a click listener, then do all the upstream WMS things L.TileLayer.WMS.prototype.onAdd.call(this, map); map.on('click', this.getFeatureInfo, this); }, onRemove: function (map) { // Triggered when the layer is removed from a map. // Unregister a click listener, then do all the upstream WMS things L.TileLayer.WMS.prototype.onRemove.call(this, map); map.off('click', this.getFeatureInfo, this); }, getFeatureInfo: function (evt) { if (this.wmsParams.basemapId != undefined) { const queryableLayerSelected = document.getElementById(`queryable-layers-selector-${this.wmsParams.basemapId}`).getElementsByClassName('selected')[0].textContent; if (queryableLayerSelected.trim() === this.wmsParams.title.trim()) { // Make an AJAX request to the server and hope for the best var params = this.getFeatureInfoUrl(evt.latlng); var showResults = L.Util.bind(this.showGetFeatureInfo, this); axios.get( window.proxy_url, { params: params, } ).then(response => { let data = response.data; var err = typeof data === 'object' ? null : data; if (data.features || err) { showResults(err, evt.latlng, data); } }) .catch(error => { throw (error); } ); } } }, getFeatureInfoUrl: function (latlng) { // Construct a GetFeatureInfo request URL given a point var point = this._map.latLngToContainerPoint(latlng, this._map.getZoom()); var size = this._map.getSize(), params = { url: this._url, request: 'GetFeatureInfo', service: 'WMS', // srs: this.wmsParams.srs, srs: 'EPSG:4326', // styles: this.wmsParams.styles, // transparent: this.wmsParams.transparent, version: this.wmsParams.version, // format: this.wmsParams.format, bbox: this._map.getBounds().toBBoxString(), height: size.y, width: size.x, layers: this.wmsParams.layers, query_layers: this.wmsParams.layers, info_format: 'application/json' }; params[params.version === '1.3.0' ? 'i' : 'x'] = Math.floor(point.x); params[params.version === '1.3.0' ? 'j' : 'y'] = Math.floor(point.y); return params; }, showGetFeatureInfo: function (err, latlng, data) { let content; if (err) { content = ` <h4>${this.options.title}</h4> <p>Données de la couche inaccessibles</p> `; L.popup({ maxWidth: 800 }) .setLatLng(latlng) .setContent(content) .openOn(this._map); } else { // Otherwise show the content in a popup let contentLines = []; let contentTitle; if (data.features.length > 0) { Object.entries(data.features[0].properties).forEach(entry => { const [key, value] = entry; if (key !== 'bbox') { contentLines.push(`<div>${key}: ${value}</div>`); } }); contentTitle = `<h4>${this.options.title}</h4>`; content = contentTitle.concat(contentLines.join('')); L.popup({ maxWidth: 800 }) .setLatLng(latlng) .setContent(content) .openOn(this._map); } } } }); L.tileLayer.betterWms = function (url, options) { return new L.TileLayer.BetterWMS(url, options); }; const mapUtil = { getMap: () => { return map; }, createMap: function (el, options) { const { lat, lng, mapDefaultViewCenter, mapDefaultViewZoom, zoom, zoomControl = true, } = options; map = L.map(el, { maxZoom: 18, minZoom: 1, zoomControl: false, }).setView( [ lat ? lat : mapDefaultViewCenter[0], lng ? lng : mapDefaultViewCenter[1], ], !zoom ? mapDefaultViewZoom : zoom ); map.setMaxBounds( [[-90,-180], [90,180]] ); if (zoomControl) { L.control .zoom({ zoomInTitle: 'Zoomer', zoomOutTitle: 'Dézoomer', position: 'topright', }) .addTo(map); } L.control.scale().addTo(map); return map; }, addGeocoders: function (configuration) { let geocoder; const geocoderLabel = configuration.SELECTED_GEOCODER.PROVIDER; if (geocoderLabel && L.Control.Geocoder) { const LIMIT_RESULTS = 5; if ( geocoderLabel === configuration.GEOCODER_PROVIDERS.ADDOK ) { geocoder = L.Control.Geocoder.addok({ limit: LIMIT_RESULTS }); } else if ( geocoderLabel === configuration.GEOCODER_PROVIDERS.PHOTON ) { geocoder = L.Control.Geocoder.photon(); } else if ( geocoderLabel === configuration.GEOCODER_PROVIDERS.NOMINATIM ) { geocoder = L.Control.Geocoder.nominatim(); } L.Control.geocoder({ placeholder: 'Chercher une adresse...', geocoder: geocoder, }).addTo(map); } }, addLayers: function (layers, serviceMap, optionsMap, schemaType) { if (layers) { //* if admin has defined basemaps for this project layers.forEach((layer) => { if (layer) { let options = layer.options; if (options) { options.noWrap = true; options.opacity = layer.opacity; if (layer.schema_type === 'wms') { let leafletLayer; if (layer.queryable) { options.title = layer.title; leafletLayer = L.tileLayer .betterWms(layer.service, options) .addTo(map); } else { leafletLayer = L.tileLayer .wms(layer.service, options) .addTo(map); } dictLayersToLeaflet[layer.id] = leafletLayer._leaflet_id; } else if (layer.schema_type === 'tms') { const leafletLayer = L.tileLayer(layer.service, options).addTo(map); dictLayersToLeaflet[layer.id] = leafletLayer._leaflet_id; } } } }); } else { //* else when no basemaps defined optionsMap.noWrap = true; if (schemaType === 'wms') { L.tileLayer .wms(serviceMap, optionsMap) .addTo(map); } else { L.tileLayer(serviceMap, optionsMap).addTo(map); } } }, // Remove the base layers (not the features) removeLayers: function () { map.eachLayer((leafLetlayer) => { if ( Object.values(dictLayersToLeaflet).includes(leafLetlayer._leaflet_id) ) { map.removeLayer(leafLetlayer); } }); dictLayersToLeaflet = {}; }, updateOpacity(layerId, opacity) { const internalLeafletLayerId = dictLayersToLeaflet[layerId]; map.eachLayer((layer) => { if (layer._leaflet_id === internalLeafletLayerId) { layer.setOpacity(opacity); } }); }, updateOrder(layers) { // First remove existing layers undefined layers = layers.filter(function (x) { return x !== undefined; }); // First remove existing layers map.eachLayer((leafLetlayer) => { layers.forEach((layerOptions) => { if (dictLayersToLeaflet[layerOptions.id] === leafLetlayer._leaflet_id) { map.removeLayer(leafLetlayer); } }); }); dictLayersToLeaflet = {}; // Redraw the layers this.addLayers(layers); }, retrieveFeatureColor: function (featureType, properties) { const colorsStyle = featureType.colors_style; if (featureType && colorsStyle && colorsStyle.custom_field_name) { const currentValue = properties[colorsStyle.custom_field_name]; const colorStyle = colorsStyle.colors[currentValue]; return colorStyle ? colorStyle : featureType.color; } return featureType.color; }, addVectorTileLayer: function (url, projectSlug, featureTypes, formFilters) { layerMVT = L.vectorGrid.protobuf(url, { noWrap:true, vectorTileLayerStyles: { default: (properties) => { const featureType = featureTypes.find((x) => x.slug.split('-')[0] === '' + properties.feature_type_id); const color = this.retrieveFeatureColor(featureType, properties); const colorValue = color.value && color.value.length ? color.value : typeof color === 'string' && color.length ? color : '#000000'; const hiddenStyle = ({ radius: 0, fillOpacity: 0.5, weight: 0, fill: false, color: featureType.color, }); const defaultStyle = { radius: 4, fillOpacity: 0.5, weight: 3, fill: true, color: colorValue, }; // Filtre sur le feature type if (formFilters && formFilters.type.selected) { if (featureType.title !== formFilters.type.selected) { return hiddenStyle; } } // Filtre sur le statut if (formFilters && formFilters.status.selected.value) { if (properties.status !== formFilters.status.selected.value) { return hiddenStyle; } } // Filtre sur le titre if (formFilters && formFilters.title) { if (!properties.title.toLowerCase().includes(formFilters.title.toLowerCase())) { return hiddenStyle; } } return defaultStyle; }, }, // subdomains: "0123", // key: 'abcdefghi01234567890', interactive: true, maxNativeZoom: 18, getFeatureId: function (f) { return f.properties.id; } }); layerMVT.on('click', (e) => { // The .on method attaches an event handler const popupContent = this._createContentPopup(e.layer, featureTypes, projectSlug); L.popup() .setContent(popupContent) .setLatLng(e.latlng) .openOn(map); }); layerMVT.addTo(map); window.layerMVT = layerMVT; }, addFeatures: function (features, filter, addToMap = true, featureTypes, projectSlug) { const featureGroup = new L.FeatureGroup(); features.forEach((feature) => { const featureProperties = feature.properties ? feature.properties : feature; const featureType = featureTypes .find((ft) => ft.slug === (featureProperties.feature_type.slug || featureProperties.feature_type)); let filters = []; if (filter) { const typeCheck = filter.featureType && featureProperties.feature_type.slug === filter.featureType; const statusCheck = filter.featureStatus && featureProperties.status.value === filter.featureStatus; const titleCheck = filter.featureTitle && featureProperties.title.includes(filter.featureTitle); filters = [typeCheck, statusCheck, titleCheck]; } if ( !filter || !Object.values(filter).some(val => val) || Object.values(filter).some(val => val) && filters.length && filters.every(val => val !== false) ) { const geomJSON = flip(feature.geometry || feature.geom); const popupContent = this._createContentPopup(feature, featureTypes, projectSlug); // Look for a custom field let customField; let customFieldOption; if ( featureType.customfield_set && Object.keys(featureProperties).some(el => featureType.customfield_set.map(e => e.name).includes(el)) ) { customField = Object.keys(featureProperties) .filter(el => featureType.customfield_set.map(e => e.name).includes(el)); customFieldOption = featureProperties[customField[0]]; } let color = '#000000'; if (feature.overideColor) { color = feature.overideColor; } else { color = this.retrieveFeatureColor(featureType, featureProperties) || featureProperties.color; if (color.value && color.value.length) { color = color.value; } } if (geomJSON.type === 'Point') { if ( customFieldOption && featureType.colors_style && featureType.colors_style.value && featureType.colors_style.value.icons && !!Object.keys(featureType.colors_style.value.icons).length ) { if ( featureType.colors_style.value.icons[customFieldOption] && featureType.colors_style.value.icons[customFieldOption] !== 'circle' ) { const iconHTML = ` <em class="fas fa-${featureType.colors_style.value.icons[customFieldOption]} fa-lg" style="color: ${color}" ></em> `; const customMapIcon = L.divIcon({ html: iconHTML, iconSize: [20, 20], className: 'myDivIcon', }); L.marker(geomJSON.coordinates, { icon: customMapIcon, color: color, zIndexOffset: 100 }) .bindPopup(popupContent) .addTo(featureGroup); } else { L.circleMarker(geomJSON.coordinates, { color: color, radius: 4, fillOpacity: 0.5, weight: 3, }) .bindPopup(popupContent) .addTo(featureGroup); } } else { if (featureType.icon && featureType.icon !== 'circle') { const iconHTML = ` <em class="fas fa-${featureType.icon} fa-lg" style="color: ${color}" ></em> `; const customMapIcon = L.divIcon({ html: iconHTML, iconSize: [20, 20], className: 'myDivIcon', }); L.marker(geomJSON.coordinates, { icon: customMapIcon, color: color, zIndexOffset: 100 }) .bindPopup(popupContent) .addTo(featureGroup); } else { L.circleMarker(geomJSON.coordinates, { color: color, radius: 4, fillOpacity: 0.5, weight: 3, }) .bindPopup(popupContent) .addTo(featureGroup); } } } else if (geomJSON.type === 'LineString') { L.polyline(geomJSON.coordinates, { color: color, weight: 3, }) .bindPopup(popupContent) .addTo(featureGroup); } else if (geomJSON.type === 'Polygon') { L.polygon(geomJSON.coordinates, { color: color, weight: 3, fillOpacity: 0.5, }) .bindPopup(popupContent) .addTo(featureGroup); } } }); if (map && addToMap) { map.addLayer(featureGroup); } return featureGroup; }, addMapEventListener: function (eventName, callback) { map.on(eventName, callback); }, _createContentPopup: function (feature, featureTypes, projectSlug) { const formatDate = (currentDatetime) => { let formattedDate = currentDatetime.getFullYear() + '-' + ('0' + (currentDatetime.getMonth() + 1)).slice(-2) + '-' + ('0' + currentDatetime.getDate()).slice(-2) + ' ' + ('0' + currentDatetime.getHours()).slice(-2) + ':' + ('0' + currentDatetime.getMinutes()).slice(-2); return formattedDate; }; let featureType = feature.properties ? feature.properties.feature_type : feature.feature_type; let featureUrl = feature.feature_url; let status = feature.status; let featureTypeUrl = feature.feature_type_url; let dateMaj = feature.updated_on; if (feature.properties) { status = feature.properties.status; dateMaj = feature.properties.updated_on ? formatDate(new Date(feature.properties.updated_on)) : '<em>indisponible</em>'; featureTypeUrl = feature.properties.feature_type_url; featureUrl = feature.properties.feature_url; } if (featureTypes && feature.properties) { // => VectorTile featureType = feature.properties.feature_type_id ? featureTypes.find((x) => x.slug.split('-')[0] === '' + feature.properties.feature_type_id) : featureTypes.find((fType) => fType.slug === feature.properties.feature_type); //* geojson status = statusList.find((x) => x.value === feature.properties.status.value).name; if (featureType) featureTypeUrl = `/geocontrib/projet/${projectSlug}/type-signalement/${featureType.slug}/`; featureUrl = `${featureTypeUrl}signalement/${feature.properties.feature_id}/`; } else { status = feature.properties ? feature.properties.status.label : feature.status.label; } //* adapt link url for shared-project restricted navigation if (window.location.pathname.includes('projet-partage')) { featureUrl = featureUrl.replace('projet', 'projet-partage'); featureTypeUrl = featureTypeUrl.replace('projet', 'projet-partage'); } let author = ''; const creator = feature.properties ? feature.properties.creator : feature.creator; if (creator) { author = creator.full_name ? `<div> Auteur : ${creator.first_name} ${creator.last_name} </div>` : creator.username ? `<div>Auteur: ${creator.username}</div>` : ''; } let title = feature.properties ? feature.properties.title : feature.title; if (featureUrl) { title = `<a href="${featureUrl}">${title}</a>`; } else { title = `<span>${title|| '<em>indisponible</em>'}</span>`; } if (featureTypeUrl) { `<a href="${featureTypeUrl}"> ${featureType.title || '<em>indisponible</em>'} </a>`; } return ` <h4> <${featureUrl ? `a href="${featureUrl}"` : 'span'}>${title || '<em>indisponible</em>'}</${featureUrl ? 'a' : 'span'}> </h4> <div> Statut : ${status || '<em>indisponible</em>'} </div> <div> Type : <${featureTypeUrl ? `a href="${featureTypeUrl}"` : 'span'}> ${featureType.title || '<em>indisponible</em>'} </${featureTypeUrl ? 'a' : 'span'}> </div> <div> Dernière mise à jour : ${dateMaj || '<em>indisponible</em>'} </div> ${author} `; }, }; export { mapUtil };