import TileWMS from 'ol/source/TileWMS'; import axios from '@/axios-client.js'; import { View, Map } from 'ol'; import { ScaleLine, Zoom, Attribution } from 'ol/control'; import TileLayer from 'ol/layer/Tile'; import { transform, transformExtent } from 'ol/proj.js'; import { defaults } from 'ol/interaction'; import XYZ from 'ol/source/XYZ'; import VectorTileLayer from 'ol/layer/VectorTile'; import VectorTileSource from 'ol/source/VectorTile'; import { MVT, GeoJSON } from 'ol/format'; import { boundingExtent } from 'ol/extent'; import Overlay from 'ol/Overlay'; import { Fill, Stroke, Style, Circle //RegularShape, Circle as CircleStyle, Text,Icon } from 'ol/style'; import { asArray } from 'ol/color'; import VectorSource from 'ol/source/Vector'; import VectorLayer from 'ol/layer/Vector'; import { fromLonLat } from 'ol/proj.js'; import OverlayPositioning from 'ol/OverlayPositioning'; let dictLayersToLeaflet = {}; let statusList = [ { name: 'Brouillon', value: 'draft', }, { name: 'Publié', value: 'published', }, { name: 'Archivé', value: 'archived', }, { name: 'En attente de publication', value: 'pending', }, ]; const mapService = { layers: [], mvtLayer: undefined, content: {}, overlay: {}, map: undefined, getMap() { return this.map; }, destroyMap() { this.map=undefined; }, createMap(el, options) { const { lat, lng, mapDefaultViewCenter, mapDefaultViewZoom, zoom, zoomControl = true, interactions = { doubleClickZoom: false, mouseWheelZoom: false, dragPan: true }, } = options; console.log(options, zoomControl); this.map = new Map({ layers: [], target: el, controls: [ new Attribution(), new ScaleLine({ units: 'metric', })], interactions: defaults(interactions), view: new View({ center: transform([ !lng ? mapDefaultViewCenter[1] : lng, !lat ? mapDefaultViewCenter[0] : lat, ], 'EPSG:4326', 'EPSG:3857'), zoom: !zoom ? mapDefaultViewZoom : zoom }), }); if (zoomControl) { this.map.addControl(new Zoom({ zoomInTipLabel: 'Zoomer', zoomOutTipLabel: 'Dézoomer' })); } this.map.once('rendercomplete', () => { this.map.updateSize(); }); const container = document.getElementById('popup'); this.content = document.getElementById('popup-content'); const closer = document.getElementById('popup-closer'); this.overlay = new Overlay({ element: container, autoPan: true, autoPanAnimation: { duration: 500, }, }); let overlay = this.overlay; closer.onclick = function () { overlay.setPosition(undefined); closer.blur(); return false; }; this.map.addOverlay(this.overlay); this.map.on('click', this.onMapClick.bind(this)); return this.map; }, onMapClick(event) { console.log(event); let self = this; const features = this.map.getFeaturesAtPixel(event.pixel, { layerFilter(l) { return l === self.mvtLayer; }, }); console.log(features); if (features && features.length > 0) { const popupContent = this._createContentPopup(features[0], this.mvtLayer.featureTypes, this.mvtLayer.project_slug); this.content.innerHTML = popupContent; this.overlay.setPosition(event.coordinate); } //const queryableLayerSelected = document.getElementById(`queryable-layers-selector-${this.wmsParams.basemapId}`).getElementsByClassName('selected')[0].textContent; //console.log(queryableLayerSelected); if (this.layers) { console.log(this.layers.find(x => x.query)); let queryLayer = this.layers.find(x => x.query); // pour compatibilité avec le proxy django let proxyparams = [ 'request', 'service', 'srs', 'version', 'bbox', 'height', 'width', 'layers', 'query_layers', 'info_format', 'x', 'y', 'i', 'j', ]; if (queryLayer) { console.log('get feature infos'); let url = this.getFeatureInfoUrl(event, queryLayer); console.log(url); let urlInfos = url.split('?'); const urlParams = new URLSearchParams(urlInfos[1]); let params = {}; Array.from(urlParams.keys()).forEach(param => { if (proxyparams.indexOf(param.toLowerCase()) >= 0) params[param.toLowerCase()] = urlParams.get(param); }); params.url = urlInfos[0]; let self = 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) { self.showGetFeatureInfo(err, event, data, queryLayer); } }) .catch(error => { throw (error); } ); } } }, showGetFeatureInfo: function (err, event, data, layer) { let content; if (err) { content = ` <h4>${layer.options.title}</h4> <p>Données de la couche inaccessibles</p> `; this.content.innerHTML = content; this.overlay.setPosition(event.coordinate); } 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>${layer.options.title}</h4>`; content = contentTitle.concat(contentLines.join('')); this.content.innerHTML = content; this.overlay.setPosition(event.coordinate); } } }, getFeatureInfoUrl(event, layer) { let olLayer = dictLayersToLeaflet[layer.id]; const source = olLayer.getSource(); const viewResolution = this.map.getView().getResolution(); let url; let wmsOptions = { info_format: 'application/json', query_layers: layer.options.layers }; if (source && source.getFeatureInfoUrl) { url = source.getFeatureInfoUrl(event.coordinate, viewResolution, 'EPSG:3857', wmsOptions); } return url; }, fitBounds(bounds) { console.log(bounds); let ext = boundingExtent([[bounds[0][1], bounds[0][0]], [bounds[1][1], bounds[1][0]]]); ext = transformExtent(ext, 'EPSG:4326', 'EPSG:3857'); this.map.getView().fit(ext, { padding: [25, 25, 25, 25] }); }, fitExtent(ext) { //ext = transformExtent(ext, 'EPSG:4326', 'EPSG:3857'); this.map.getView().fit(ext, { padding: [25, 25, 25, 25] }); }, addLayers: function (layers, serviceMap, optionsMap, schemaType) { console.log('addLayers'); this.layers = layers; 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') { console.log(layer); if (layer.queryable) { options.title = layer.title; dictLayersToLeaflet[layer.id] = this.addWMSLayer(layer.service, options); } else { dictLayersToLeaflet[layer.id] = this.addWMSLayer(layer.service, options); } } else if (layer.schema_type === 'tms') { const layerTms = new TileLayer({ source: new XYZ({ attributions: options.attribution, url: layer.service.replace('{s}', '{a-c}') }) }); this.map.addLayer(layerTms); dictLayersToLeaflet[layer.id] = layerTms; } } } }); } else { //* else when no basemaps defined optionsMap.noWrap = true; if (schemaType === 'wms') { this.addWMSLayer(serviceMap, optionsMap); } else { const layer = new TileLayer({ source: new XYZ({ attributions: optionsMap.attribution, url: serviceMap.replace('{s}', '{a-c}') }) }); this.map.addLayer(layer); } } }, addWMSLayer: function (url, options) { options.VERSION = '1.1.1'; // pour compatibilité avec le proxy django const source = new TileWMS({ attributions: options.attribution, url: url, crossOrigin: 'anonymous', params: options }); const layer = new TileLayer({ source: source }); this.map.addLayer(layer); return layer; }, // Remove the base layers (not the features) removeLayers: function () { Object.values(dictLayersToLeaflet).forEach(element => { this.map.removeLayer(element); }); dictLayersToLeaflet = {}; }, updateOpacity(layerId, opacity) { const layer = dictLayersToLeaflet[layerId]; layer.setOpacity(parseFloat(opacity)); }, updateOrder(layers) { // First remove existing layers undefined layers = layers.filter(function (x) { return x !== undefined; }); this.removeLayers(); // 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; } else { return featureType.color; } }, addVectorTileLayer: function (url, project_id, project_slug, featureTypes, form_filters) { console.log(url, project_slug, featureTypes, form_filters); let format_cfg = {/*featureClass: Feature*/ }; let mvt = new MVT(format_cfg); let options = { urls: [], matrixSet: 'EPSG:3857' }; options.format = mvt; let layerSource = new VectorTileSource(options); layerSource.setTileUrlFunction((p0) => { return url+'/?tile=' + p0[0] + '/' + p0[1] + '/' + p0[2] + '&project_id=' + project_id; }); const styleFunction = (feature) => this.getStyle(feature, featureTypes, form_filters); this.mvtLayer = new VectorTileLayer({ style: styleFunction, source: layerSource }); this.mvtLayer.featureTypes = featureTypes; this.mvtLayer.project_slug = project_slug; this.map.addLayer(this.mvtLayer); window.layerMVT = this.mvtLayer; }, getStyle: function (feature, featureTypes, form_filters) { //console.log(form_filters); let properties = feature.getProperties(); let featureType; // GeoJSON if(properties.feature_type){ featureType = featureTypes .find((ft) => ft.slug === (properties.feature_type.slug || properties.feature_type)); } else { //MVT featureType = featureTypes.find((x) => x.slug.split('-')[0] === '' + properties.feature_type_id); } //console.log(featureType); const color = this.retrieveFeatureColor(featureType, properties); const colorValue = color.value && color.value.length ? color.value : typeof color === 'string' && color.length ? color : '#000000'; let rgbaColor = asArray(colorValue); rgbaColor[3] = 0.5;//opacity const hiddenStyle = new Style(); const defaultStyle = new Style( { image: new Circle({ fill: new Fill( { color: rgbaColor, }, ), stroke: new Stroke( { color: colorValue, width: 2, }, ), radius: 5, }), stroke: new Stroke( { color: colorValue, width: 2, }, ), fill: new Fill( { color: rgbaColor, }, ), }, ); // Filtre sur le feature type if(form_filters){ if (form_filters.type && form_filters.type.selected) { if (featureType.title !== form_filters.type.selected) { return hiddenStyle; } } // Filtre sur le statut if (form_filters.status && form_filters.status.selected.value) { if (properties.status !== form_filters.status.selected.value) { return hiddenStyle; } } // Filtre sur le titre if (form_filters.title) { if (!properties.title.toLowerCase().includes(form_filters.title.toLowerCase())) { return hiddenStyle; } } } return defaultStyle; }, addFeatures: function (features, filter, addToMap = true, featureTypes) { console.log(features, filter, addToMap, featureTypes); let drawSource = new VectorSource(); let retour; // TODO verifier utilité de cette boucle et remplacer par readFeatures plutot features.forEach((feature) => { console.log(feature); retour = new GeoJSON().readFeature(feature, { dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857' }); drawSource.addFeature(retour); 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]; } console.log(featureType, filters); }); const styleFunction = (feature) => this.getStyle(feature, featureTypes, filter); const olLayer = new VectorLayer({ source: drawSource, style: styleFunction, }); this.map.addLayer(olLayer); return drawSource; }, addMapEventListener: function (eventName, callback) { this.map.on(eventName, callback); }, _createContentPopup: function (feature, featureTypes, project_slug) { const formatDate = (current_datetime) => { let formatted_date = current_datetime.getFullYear() + '-' + ('0' + (current_datetime.getMonth() + 1)).slice(-2) + '-' + ('0' + current_datetime.getDate()).slice(-2) + ' ' + ('0' + current_datetime.getHours()).slice(-2) + ':' + ('0' + current_datetime.getMinutes()).slice(-2); return formatted_date; }; let feature_type; let status; let date_maj; let feature_type_url; let feature_url; if (feature.getProperties) { status = feature.getProperties().status; date_maj = feature.getProperties().updated_on; feature_type_url = feature.getProperties().feature_type_url; feature_url = feature.getProperties().feature_url; } else { status = feature.status; date_maj = feature.updated_on; feature_type_url = feature.feature_type_url; feature_url = feature.feature_url; } if (featureTypes) { // => VectorTile feature_type = featureTypes.find((x) => x.slug.split('-')[0] === '' + feature.getProperties().feature_type_id); status = statusList.find((x) => x.value === feature.getProperties().status).name; date_maj = formatDate(new Date(feature.getProperties().updated_on)); feature_type_url = '/geocontrib/projet/' + project_slug + '/type-signalement/' + feature_type.slug + '/'; feature_url = feature_type_url + 'signalement/' + feature.getProperties().feature_id + '/'; } else { feature_type = feature.getProperties ? feature.getProperties().feature_type : feature.feature_type; status = feature.getProperties ? feature.getProperties().status.label : feature.status.label; } //* adapt link url for shared-project restricted navigation if (window.location.pathname.includes('projet-partage')) { feature_url = feature_url.replace('projet', 'projet-partage'); feature_type_url = feature_type_url.replace('projet', 'projet-partage'); } let author = ''; const creator = feature.getProperties ? feature.getProperties().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>` : ''; } const title = feature.getProperties ? feature.getProperties().title : feature.title; return ` <h4> <a href="${feature_url}">${title}</a> </h4> <div> Statut : ${status} </div> <div> Type : <a href="${feature_type_url}"> ${feature_type.title} </a> </div> <div> Dernière mise à jour : ${date_maj} </div> ${author} `; }, zoomTo(location, zoomlevel, lon, lat) { if (lon && lat) { location = [+lon, +lat]; } this.map.getView().setCenter(transform(location, 'EPSG:4326', 'EPSG:3857')); this.map.getView().setZoom(zoomlevel); }, addOverlay(loc) { var pos = fromLonLat(loc); console.log(loc); var marker = new Overlay({ position: pos, positioning: OverlayPositioning.CENTER_CENTER, element: document.getElementById('marker'), stopEvent: false }); this.map.addOverlay(marker); } }; export default mapService;