Newer
Older
import TileWMS from 'ol/source/TileWMS';
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';
import router from '@/router';
let dictLayersToLeaflet = {};
const mapService = {
layers: [],
mvtLayer: undefined,
content: {},
overlay: {},
map: undefined,
getMap() {
return this.map;
},
createMap(el, options) {
const {
lat,
lng,
mapDefaultViewCenter,
mapDefaultViewZoom,
zoom,
zoomControl = true,
interactions = { doubleClickZoom: false, mouseWheelZoom: false, dragPan: true },
if (el.innerHTML) {
el.innerHTML = '';
}
controls: controls ? controls : [
new Attribution(),
new ScaleLine({
units: 'metric',
})],
interactions: defaults(interactions),
view: new View({
center: transform([ //* since 0 is considered false, check for number instead of just defined (though boolean will pass through)
Number(lng) ? lng : mapDefaultViewCenter[1],
Number(lat) ? lat : mapDefaultViewCenter[0],
zoom: Number(mapDefaultViewZoom) ? mapDefaultViewZoom : zoom,
maxZoom
};
this.map = new Map(mapOptions);
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;
if (closer) {
closer.onclick = function () {
overlay.setPosition(undefined);
closer.blur();
return false;
this.map.on('click', this.onMapClick.bind(this));
return this.map;
addRouterToPopup({ featureTypeSlug, featureId, index }) {
function goToBrowseFeatureDetail() {
router.push({
name: 'details-signalement-filtre',
params: {
slug_type_signal: featureTypeSlug,
},
query: {
ordering: /* project.feature_browsing_default_sort */'-updated_on',
feature_type: /* project.feature_browsing_default_filter ? featureTypeSlug : */'',
offset: index
}
});
}
function goToFeatureDetail() {
router.push({
name: 'details-signalement',
params: {
slug_type_signal: featureTypeSlug,
slug_signal: featureId,
function goToFeatureTypeDetail() {
router.push({
name: 'details-type-signalement',
params: {
document.getElementById('goToFeatureDetail').onclick = index ? goToBrowseFeatureDetail : goToFeatureDetail;
document.getElementById('goToFeatureTypeDetail').onclick = goToFeatureTypeDetail;
},
const features = this.map.getFeaturesAtPixel(event.pixel, {
layerFilter: (l) => l === this.mvtLayer || this.olLayer
if (features && features.length > 0) {
const featureId = features[0].properties_ ? features[0].properties_.feature_id : features[0].id_;
const isEdited = router.history.current.name === 'editer-signalement' &&
router.history.current.params.slug_signal === featureId; //* avoid opening popup on feature currently edited
if (featureId && !isEdited) {
const popupContent = this._createContentPopup(features[0], this.featureTypes);
this.content.innerHTML = popupContent.html;
this.overlay.setPosition(event.coordinate);
this.addRouterToPopup({
featureTypeSlug: popupContent.feature_type.slug,
featureId: popupContent.featureId,
index: popupContent.index,
});
}
//const queryableLayerSelected = document.getElementById(`queryable-layers-selector-${this.wmsParams.basemapId}`).getElementsByClassName('selected')[0].textContent;
if (this.layers) {
'request',
'service',
'srs',
'version',
'bbox',
'height',
'width',
'layers',
'query_layers',
'info_format', 'x', 'y', 'i', 'j',
];
if (queryLayer) {
const url = this.getFeatureInfoUrl(event, queryLayer);
const urlInfos = url.split('?');
const urlParams = new URLSearchParams(urlInfos[1]);
params[param.toLowerCase()] = urlParams.get(param);
});
params.url = urlInfos[0];
axios.get(
window.proxy_url,
{ params }
const err = typeof data === 'object' ? null : data;
if (data.features || err) this.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 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);
}
}
},
const source = olLayer.getSource();
const viewResolution = this.map.getView().getResolution();
let url;
const 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) {
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) {
this.layers = layers;
if (layers) { //* if admin has defined basemaps for this project
if (options) {
options.noWrap = true;
options.opacity = layer.opacity;
if (layer.schema_type === 'wms') {
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') {
source: new XYZ({
attributions: options.attribution,
url: layer.service.replace('{s}', '{a-c}')
})
});
this.map.addLayer(layerTms);
dictLayersToLeaflet[layer.id] = layerTms;
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
}
});
} 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);
},
retrieveFeatureStyle: function (featureType, properties) {
const { colors_style, customfield_set } = featureType;
let { color, opacity } = featureType;
if (colors_style && colors_style.custom_field_name && customfield_set) {
const fieldType = customfield_set.find((el) => el.name === colors_style.custom_field_name).field_type;
const currentValue = properties[colors_style.custom_field_name];
switch (fieldType) {
case 'list' :
if (currentValue) {
color = colors_style.colors && colors_style.colors[currentValue];
opacity = colors_style.opacities && colors_style.opacities[currentValue];
break;
case 'char': //* if the custom field is supposed to be a string
//* check if its current value is empty or not, to select a color | https://redmine.neogeo.fr/issues/14048
color = colors_style.value.colors && colors_style.value.colors[currentValue ? 'Non vide' : 'Vide'];
opacity = colors_style.value.opacities && colors_style.value.opacities[currentValue ? 'Non vide' : 'Vide'];
break;
case 'boolean':
color = colors_style.value.colors && colors_style.value.colors[currentValue ? 'Coché' : 'Décoché'];
opacity = colors_style.value.opacities && colors_style.value.opacities[currentValue ? 'Coché' : 'Décoché'];
return { color, opacity };
addVectorTileLayer: function (url, projectId, featureTypes, formFilters) {
const format_cfg = {/*featureClass: Feature*/ };
const mvt = new MVT(format_cfg);
const options = {
urls: [],
matrixSet: 'EPSG:3857'
};
options.format = mvt;
return `${url}/?tile=${p0[0]}/${p0[1]}/${p0[2]}&project_id=${projectId}`;
const styleFunction = (feature) => this.getStyle(feature, featureTypes, formFilters);
this.mvtLayer = new VectorTileLayer({
style: styleFunction,
source: layerSource
});
this.featureTypes = featureTypes; // store featureTypes for popups
this.map.addLayer(this.mvtLayer);
window.layerMVT = this.mvtLayer;
},
getStyle: function (feature, featureTypes, formFilters) {
const properties = feature.getProperties();
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);
}
const { color, opacity } = this.retrieveFeatureStyle(featureType, properties);
color.value && color.value.length ?
color.value : typeof color === 'string' && color.length ?
color : '#000000';
rgbaColor[3] = opacity || 0.5;//opacity
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,
},
),
},
);
const hiddenStyle = new Style(); // hide the feature to apply filters
// Filtre sur le feature type
if(formFilters){
if (formFilters.type && formFilters.type.selected) {
if (featureType.title !== formFilters.type.selected) {
return hiddenStyle;
}
// Filtre sur le statut
if (formFilters.status && formFilters.status.selected.value) {
if (properties.status !== formFilters.status.selected.value) {
return hiddenStyle;
}
// Filtre sur le titre
if (formFilters.title) {
if (!properties.title.toLowerCase().includes(formFilters.title.toLowerCase())) {
return hiddenStyle;
}
return defaultStyle;
} else {
console.error('No corresponding featureType found.');
return;
addFeatures: function (features, filter, featureTypes, addToMap = true) {
console.log('addToMap', addToMap);
if (feature.properties) {
feature.properties['index'] = index;
index += 1;
}
retour = new GeoJSON().readFeature(feature, { dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857' }, featureTypes);
drawSource.addFeature(retour);
} catch (err) {
console.error(err);
}
});
const styleFunction = (feature) => this.getStyle(feature, featureTypes, filter);
const olLayer = new VectorLayer({
source: drawSource,
style: styleFunction,
});

Timothee P
committed
this.drawSource = drawSource;
this.featureTypes = featureTypes; // store featureTypes for popups

Timothee P
committed
removeFeatures: function () {
this.drawSource.clear();
},
addMapEventListener: function (eventName, callback) {
this.map.on(eventName, callback);
},
_createContentPopup: function (feature, featureTypes) {
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 creator;
let index; // index is used to retrieve feature by query when browsing features
const properties = feature.getProperties();
({ status, updated_on, creator, index } = properties); // using parenthesis to allow destructuring object without declaration
console.log({ index });
if (featureTypes) {
feature_type = feature.getProperties().feature_type ||
featureTypes.find((x) => x.slug.split('-')[0] === '' + feature.getProperties().feature_type_id);
}
} else { //? TPD: I couldn't find when this code is used, is this still in use ?
if (status) status = status.name;
updated_on = feature.updated_on;
creator = feature.creator;
if (featureTypes) {
feature_type = featureTypes.find((x) => x.slug === feature.feature_type.slug);
}
if(updated_on && !isNaN(new Date(updated_on))) { //* check if it is already formatted
updated_on = formatDate(new Date(updated_on));
}
if (status) {
if (status.label) { //* when the label is already in the feature
status = status.label;
} else if (featureTypes) { //* if not, retrieve the name/label from the list
status = statusChoices.find((x) => x.value === status).name;
let author = '';
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;
const html = `
<h4>
<a id="goToFeatureDetail" class="pointer">${title}</a>
</h4>
<div>
Statut : ${status}
</div>
<div>
Type : ${ feature_type ? '<a id="goToFeatureTypeDetail" class="pointer">' + feature_type.title + '</a>' : 'Type de signalement inconnu' }
Dernière mise à jour : ${updated_on}
</div>
${author}
`;
const featureId =
feature.getId() ?
feature.getId() :
feature.getProperties ?
feature.getProperties().feature_id :
feature.id;
return { html, feature_type, featureId, index };
zoom(zoomlevel) {
this.map.getView().setZoom(zoomlevel);
},
zoomTo(location, zoomlevel, lon, lat) {
if (lon && lat) {
location = [+lon, +lat];
}
this.map.getView().setCenter(transform(location, 'EPSG:4326', 'EPSG:3857'));
const pos = fromLonLat(loc);
const marker = new Overlay({