Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • geocontrib/geocontrib-frontend
  • ext_matthieu/geocontrib-frontend
  • fnecas/geocontrib-frontend
  • MatthieuE/geocontrib-frontend
4 results
Show changes
Commits on Source (35)
Showing
with 3300 additions and 1997 deletions
Source diff could not be displayed: it is too large. Options to address this: view the blob.
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;
const 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 => {
const 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
const 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) {
const 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 = `
<i
class="fas fa-${featureType.colors_style.value.icons[customFieldOption]} fa-lg"
style="color: ${color}"
></i>
`;
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 = `
<i
class="fas fa-${featureType.icon} fa-lg"
style="color: ${color}"
></i>
`;
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) => {
const 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.slug); //* 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>`;
}
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 };
......@@ -18,7 +18,9 @@ export function csvToJson(csv, delimiter) {
const result = [];
const allLines = csv.split('\n');
const headers = allLines[0].split(delimiter);
const headers = allLines[0].split(delimiter).map(el => {
return el.replace('\r', '');
});
const [, ...lines] = allLines;
for (const line of lines) {
......
import L from 'leaflet';
export var Symbolizer = L.Class.extend({
// 🍂method initialize(feature: GeoJSON, pxPerExtent: Number)
// Initializes a new Line Symbolizer given a GeoJSON feature and the
// pixel-to-coordinate-units ratio. Internal use only.
// 🍂method render(renderer, style)
// Renders this symbolizer in the given tiled renderer, with the given
// `L.Path` options. Internal use only.
render: function(renderer, style) {
this._renderer = renderer;
this.options = style;
renderer._initPath(this);
renderer._updateStyle(this);
},
// 🍂method render(renderer, style)
// Updates the `L.Path` options used to style this symbolizer, and re-renders it.
// Internal use only.
updateStyle: function(renderer, style) {
this.options = style;
renderer._updateStyle(this);
},
_getPixelBounds: function() {
var parts = this._parts;
var bounds = L.bounds([]);
for (var i = 0; i < parts.length; i++) {
var part = parts[i];
for (var j = 0; j < part.length; j++) {
bounds.extend(part[j]);
}
}
var w = this._clickTolerance(),
p = new L.Point(w, w);
bounds.min._subtract(p);
bounds.max._add(p);
return bounds;
},
_clickTolerance: L.Path.prototype._clickTolerance,
});
export var PolyBase = {
_makeFeatureParts: function(feat, pxPerExtent) {
var rings = feat.geometry;
var coord;
this._parts = [];
for (var i = 0; i < rings.length; i++) {
var ring = rings[i];
var part = [];
for (var j = 0; j < ring.length; j++) {
coord = ring[j];
// Protobuf vector tiles return {x: , y:}
// Geojson-vt returns [,]
part.push(L.point(coord).scaleBy(pxPerExtent));
}
this._parts.push(part);
}
},
makeInteractive: function() {
this._pxBounds = this._getPixelBounds();
}
};
export var LineSymbolizer = L.Polyline.extend({
includes: [Symbolizer.prototype, PolyBase],
initialize: function(feature, pxPerExtent) {
this.properties = feature.properties;
this._makeFeatureParts(feature, pxPerExtent);
},
render: function(renderer, style) {
style.fill = false;
Symbolizer.prototype.render.call(this, renderer, style);
this._updatePath();
},
updateStyle: function(renderer, style) {
style.fill = false;
Symbolizer.prototype.updateStyle.call(this, renderer, style);
},
});
export var FillSymbolizer = L.Polygon.extend({
includes: [Symbolizer.prototype, PolyBase],
initialize: function(feature, pxPerExtent) {
this.properties = feature.properties;
this._makeFeatureParts(feature, pxPerExtent);
},
render: function(renderer, style) {
Symbolizer.prototype.render.call(this, renderer, style);
this._updatePath();
}
});
export var PointSymbolizer = L.CircleMarker.extend({
includes: Symbolizer.prototype,
statics: {
iconCache: {}
},
initialize: function(feature, pxPerExtent) {
this.properties = feature.properties;
this._makeFeatureParts(feature, pxPerExtent);
},
render: function(renderer, style) {
Symbolizer.prototype.render.call(this, renderer, style);
this._radius = style.radius || L.CircleMarker.prototype.options.radius;
this._updatePath();
},
_makeFeatureParts: function(feat, pxPerExtent) {
var coord = feat.geometry[0];
if (typeof coord[0] === 'object' && 'x' in coord[0]) {
// Protobuf vector tiles return [{x: , y:}]
this._point = L.point(coord[0]).scaleBy(pxPerExtent);
this._empty = L.Util.falseFn;
} else {
// Geojson-vt returns [,]
this._point = L.point(coord).scaleBy(pxPerExtent);
this._empty = L.Util.falseFn;
}
},
makeInteractive: function() {
this._updateBounds();
},
updateStyle: function(renderer, style) {
this._radius = style.radius || this._radius;
this._updateBounds();
return Symbolizer.prototype.updateStyle.call(this, renderer, style);
},
_updateBounds: function() {
var icon = this.options.icon;
if (icon) {
var size = L.point(icon.options.iconSize),
anchor = icon.options.iconAnchor ||
size && size.divideBy(2, true),
p = this._point.subtract(anchor);
this._pxBounds = new L.Bounds(p, p.add(icon.options.iconSize));
} else {
L.CircleMarker.prototype._updateBounds.call(this);
}
},
_updatePath: function() {
if (this.options.icon) {
this._renderer._updateIcon(this);
} else {
L.CircleMarker.prototype._updatePath.call(this);
}
},
_getImage: function () {
if (this.options.icon) {
var url = this.options.icon.options.iconUrl,
img = PointSymbolizer.iconCache[url];
if (!img) {
var icon = this.options.icon;
img = PointSymbolizer.iconCache[url] = icon.createIcon();
}
return img;
} else {
return null;
}
},
_containsPoint: function(p) {
var icon = this.options.icon;
if (icon) {
return this._pxBounds.contains(p);
} else {
return L.CircleMarker.prototype._containsPoint.call(this, p);
}
}
});
L.VectorGrid.prototype._createLayer=function(feat, pxPerExtent) {
var layer;
switch (feat.type) {
case 1:
layer = new PointSymbolizer(feat, pxPerExtent);
// [YB 2019-10-23: prevent leaflet from treating these canvas points as real markers]
layer.getLatLng = null;
break;
case 2:
layer = new LineSymbolizer(feat, pxPerExtent);
break;
case 3:
layer = new FillSymbolizer(feat, pxPerExtent);
break;
}
if (this.options.interactive) {
layer.addEventParent(this);
}
return layer;
};
\ No newline at end of file
......@@ -352,4 +352,56 @@ body {
.multiselect__option--selected.multiselect__option--highlight {
background: #f3f3f3 !important;
color: #35495e !important;
}
\ No newline at end of file
}
/* OPENLAYERS */
.ol-zoom{
right: 5px !important;
left:unset !important;
}
.ol-popup {
position: absolute;
background-color: white;
padding: 15px;
border-radius: 10px;
border: 1px solid #cccccc;
bottom: 12px;
left: -106px;
width: 212px;
line-height: 1.4;
-webkit-box-shadow: 0px 0px 15px -1px #000000;
box-shadow: 0px 0px 15px -1px #000000;
font: 12px/1.5 Helvetica Neue,Arial,Helvetica,sans-serif;
}
.ol-popup:after, .ol-popup:before {
top: 100%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
}
.ol-popup:after {
border-top-color: white;
border-width: 10px;
left: 50%;
margin-left: -10px;
}
.ol-popup:before {
border-top-color: #cccccc;
border-width: 11px;
left: 50%;
margin-left: -11px;
}
.ol-popup-closer {
text-decoration: none;
position: absolute;
top: 2px;
right: 8px;
}
.ol-popup-closer:after {
content: "✖";
}
.ol-scale-line {
left: 2em;
background: hsla(0,0%,100%,.5);
padding: 0;
}
.ol-scale-line-inner {
color: #333;
font-size: 0.9em;
text-align: left;
padding-left: 0.5em;
border: 2px solid #777;
border-top: none;
}
.ol-control {
border: 2px solid rgba(0,0,0,.2);
background-clip: padding-box;
padding: 0;
}
.ol-control button {
background-color: #fff;
color: #000;
height: 30px;
width: 30px;
font: 700 18px Lucida Console,Monaco,monospace;
margin: 0;
}
.ol-control button:hover {
cursor: pointer;
background-color: #ebebeb;
}
.ol-control button:focus {
background-color: #ebebeb;
}
.editionToolbar {
border: 2px solid rgba(0,0,0,.2);
background-clip: padding-box;
padding: 0;
right: 5px;
}
.leaflet-bar {
box-shadow: none !important;
}
.leaflet-bar a {
background-color: #fff;
color: #000;
height: 30px !important;
width: 30px !important;
font: 700 16px Lucida Console,Monaco,monospace;
margin: 0;
}
.leaflet-bar a:hover {
cursor: pointer;
background-color: #ebebeb;
}
.leaflet-bar a > i {
margin: 0;
}
/* For geocoder search bar */
.multiselect {
font: 500 12px Lucida Console,Monaco,monospace;
}
.multiselect__content-wrapper {
border: 2px solid rgba(0,0,0,.2) !important;
background-clip: padding-box !important;
padding: 0 !important;
border-top: none !important;
left: -2px;
width: calc(100% + 4px) !important;
}
\ No newline at end of file
......@@ -13,8 +13,6 @@
border: 1px solid grey;
top: 0;
position: absolute;
/* Under this value, the map hide the sidebar */
z-index: 400;
}
.sidebar-layers {
......
......@@ -5,6 +5,14 @@
aria-describedby="Table des données du signalement"
>
<tbody>
<tr v-if="feature_type">
<td>
<strong> Type de signalement </strong>
</td>
<td>
<FeatureTypeLink :feature-type="feature_type" />
</td>
</tr>
<tr
v-for="(field, index) in currentFeature.feature_data"
:key="'field' + index"
......@@ -97,12 +105,17 @@
<script>
import { mapState } from 'vuex';
import { mapState, mapGetters } from 'vuex';
import FeatureTypeLink from '@/components/FeatureType/FeatureTypeLink';
export default {
name: 'FeatureTable',
components: {
FeatureTypeLink
},
filters: {
formatDate(value) {
let date = new Date(value);
......@@ -118,6 +131,10 @@ export default {
'statusChoices'
]),
...mapGetters('feature-type', [
'feature_type',
]),
statusIcon() {
switch (this.currentFeature.status) {
case 'archived':
......
<template>
<router-link
:to="{
name: 'details-type-signalement',
params: { feature_type_slug: featureType.slug },
}"
class="feature-type-title"
>
<img
v-if="featureType.geom_type === 'point'"
class="list-image-type"
src="@/assets/img/marker.png"
alt="Géométrie point"
>
<img
v-if="featureType.geom_type === 'linestring'"
class="list-image-type"
src="@/assets/img/line.png"
alt="Géométrie ligne"
>
<img
v-if="featureType.geom_type === 'polygon'"
class="list-image-type"
src="@/assets/img/polygon.png"
alt="Géométrie polygone"
>
{{ featureType.title }}
</router-link>
</template>
<script>
export default {
name: 'FeatureTypeLink',
props: {
featureType : {
type: Object,
default: () => {},
}
},
};
</script>
<style scoped>
.feature-type-title {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 1.5em;
}
.list-image-type {
margin-right: 5px;
height: 25px;
vertical-align: bottom;
}
</style>
<template>
<div class="editionToolbar">
<div v-if="showDrawTool">
<div class="leaflet-bar">
<a
class="leaflet-draw-draw-polygon"
title="Dessiner un polygone"
>
<img
v-if="editionService.geom_type === 'linestring'"
class="list-image-type"
src="@/assets/img/line.png"
>
<img
v-if="editionService.geom_type === 'point'"
class="list-image-type"
src="@/assets/img/marker.png"
>
<img
v-if="editionService.geom_type === 'polygon'"
class="list-image-type"
src="@/assets/img/polygon.png"
>
</a>
</div>
</div>
<div v-if="!showDrawTool">
<div class="leaflet-bar">
<a @click="update">
<i class="edit outline icon" />
<span class="sr-only">Modifier l'objet</span></a>
<a @click="deleteObj">
<i class="trash alternate outline icon" />
<span class="sr-only">Supprimer l'objet</span></a>
</div>
</div>
</div>
</template>
<script>
import editionService from '@/services/edition-service';
export default {
name: 'EditingToolbar',
data() {
return {
editionService: editionService,
};
},
computed: {
showDrawTool() {
return this.editionService && this.editionService.editing_feature === undefined;
}
},
mounted() {
},
methods: {
update(){
console.log(this.editionService.editing_feature);
editionService.activeUpdateFeature();
},
deleteObj(){
editionService.activeDeleteFeature();
},
}
};
</script>
<style scoped>
.editionToolbar{
position: absolute;
top: 80px;
right: 5px;
}
.leaflet-bar {
border: 2px solid rgba(0,0,0,.2);
background-clip: padding-box;
padding: 0;
border-radius: 2px;
}
.leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
border-bottom: none;
}
.leaflet-bar a, .leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a {
background-color: #fff;
width: 30px;
height: 30px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a > i {
margin: 0;
vertical-align: middle;
}
.list-image-type {
height: 20px;
vertical-align: middle;
margin: 5px 0 5px 0;
}
.leaflet-bar a:hover {
cursor: pointer;
background-color: #ebebeb;
}
.leaflet-bar a:focus {
background-color: #ebebeb;
}
</style>
<template>
<div
:class="{ expanded: isExpanded }"
class="geocoder-container"
>
<button
class="button-geocoder"
@click="isExpanded = !isExpanded"
>
<i class="search icon" />
</button>
<Multiselect
v-if="isExpanded"
v-model="selection"
class="expanded-geocoder"
:options="addresses"
:options-limit="5"
:allow-empty="true"
track-by="label"
label="label"
:show-labels="false"
:reset-after="true"
select-label=""
selected-label=""
deselect-label=""
:searchable="true"
:placeholder="placeholder"
:show-no-results="true"
:loading="loading"
:clear-on-select="false"
:preserve-search="true"
@search-change="search"
@select="select"
@close="close"
>
<template
slot="option"
slot-scope="props"
>
<div class="option__desc">
<span class="option__title">{{ props.option.label }}</span>
</div>
</template>
<template slot="clear">
<div
v-if="selection"
class="multiselect__clear"
@click.prevent.stop="selection = null"
>
<i class="close icon" />
</div>
</template>
<span slot="noResult">
Aucun résultat.
</span>
<span slot="noOptions">
Saisissez les premiers caractères ...
</span>
</Multiselect>
<div style="display: none;">
<div
id="marker"
title="Marker"
/>
</div>
</div>
</template>
<script>
import Multiselect from 'vue-multiselect';
import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import axios from 'axios';
import mapService from '@/services/map-service';
const apiAdressAxios = axios.create({
baseURL: 'https://api-adresse.data.gouv.fr',
withCredentials: false,
});
export default {
name: 'Geocoder',
components: {
Multiselect
},
data() {
return {
loading: false,
selection: null,
text: null,
selectedAddress: null,
addresses: [],
resultats: [],
placeholder: 'Rechercher une adresse ...',
isExpanded: false
};
},
mounted() {
this.addressTextChange = new Subject();
this.addressTextChange.pipe(debounceTime(200)).subscribe((res) => this.getAddresses(res));
},
methods: {
getAddresses(query){
// let self = this;
const limit = 5;
apiAdressAxios.get(`https://api-adresse.data.gouv.fr/search/?q=${query}&limit=${limit}`)
.then((retour) => {
this.resultats = retour.data.features;
this.addresses = retour.data.features.map(x=>x.properties);
});
},
selectAddresse(event) {
this.selectedAddress = event;
if (this.selectedAddress !== null && this.selectedAddress.geometry) {
let zoomlevel = 14;
const { type } = this.selectedAddress.properties;
if (type === 'housenumber') {
zoomlevel = 19;
} else if (type === 'street') {
zoomlevel = 16;
} else if (type === 'locality') {
zoomlevel = 16;
} else if (type === 'municipality') {
zoomlevel = 14;
}
// On fait le zoom
mapService.zoomTo(this.selectedAddress.geometry.coordinates, zoomlevel);
// On ajoute un point pour localiser la ville
mapService.addOverlay(this.selectedAddress.geometry.coordinates);
}
},
search(text) {
this.text = text;
this.addressTextChange.next(this.text);
},
select(e) {
this.selectAddresse(this.resultats.find(x=>x.properties.label === e.label));
this.$emit('select', e);
},
close() {
this.$emit('close', this.selection);
}
}
};
</script>
<style scoped lang="less">
.geocoder-container {
position: absolute;
right: 0.5em;
top: calc(1em + 60px);
pointer-events: auto;
z-index: 50000;
border: 2px solid rgba(0,0,0,.2);
background-clip: padding-box;
padding: 0;
border-radius: 2px;
display: flex;
.button-geocoder {
border: none;
padding: 0;
margin: 0;
text-align: center;
background-color: #fff;
color: rgb(39, 39, 39);
width: 28px;
height: 28px;
font: 700 18px Lucida Console,Monaco,monospace;
margin: 0;
border-radius: 2px;
line-height: 1.15;
i {
margin: 0;
font-size: 0.9em;
}
}
.button-geocoder:hover {
cursor: pointer;
background-color: #ebebeb;
}
.expanded-geocoder {
max-width: 400px;
}
}
.expanded {
.button-geocoder {
height: 40px;
color: rgb(99, 99, 99);
}
}
#marker {
width: 20px;
height: 20px;
border: 1px solid rgb(136, 66, 0);
border-radius: 10px;
background-color: rgb(201, 114, 15);
opacity: 0.7;
}
</style>
......@@ -120,7 +120,7 @@
import { mapState } from 'vuex';
import Sortable from 'sortablejs';
import Dropdown from '@/components/Dropdown.vue';
import { mapUtil } from '@/assets/js/map-util.js';
import mapService from '@/services/map-service';
export default {
name: 'SidebarLayers',
......@@ -182,7 +182,7 @@ export default {
this.activeBasemap = this.baseMaps[0];
this.addLayers(this.baseMaps[0]);
} else {
mapUtil.addLayers(
mapService.addLayers(
null,
this.$store.state.configuration.DEFAULT_BASE_MAP_SERVICE,
this.$store.state.configuration.DEFAULT_BASE_MAP_OPTIONS,
......@@ -218,7 +218,7 @@ export default {
},
updateOpacity(event, layer) {
mapUtil.updateOpacity(layer.id, event.target.value);
mapService.updateOpacity(layer.id, event.target.value);
layer.opacity = event.target.value;
},
......@@ -228,6 +228,8 @@ export default {
onQueryLayerChange(layer) {
this.selectedQueryLayer = layer.name;
this.baseMaps[0].layers.find((l) => l.title === layer.name).query = true;
layer.query = true;
},
isQueryable(baseMap) {
......@@ -367,17 +369,17 @@ export default {
layer = Object.assign(layer, layerOptions);
layer.options.basemapId = baseMap.id;
});
mapUtil.removeLayers(mapUtil.getMap());
mapService.removeLayers();
// Reverse is done because the first layer in order has to be added in the map in last.
// Slice is done because reverse() changes the original array, so we make a copy first
mapUtil.addLayers(baseMap.layers.slice().reverse(), null, null, null,);
mapService.addLayers(baseMap.layers.slice().reverse(), null, null, null,);
},
},
};
</script>
<style>
@import "../assets/styles/sidebar-layers.css";
@import "../../assets/styles/sidebar-layers.css";
.queryable-layers-dropdown {
margin-bottom: 1em;
}
......
......@@ -26,34 +26,7 @@
class="item"
>
<div class="feature-type-container">
<router-link
:to="{
name: 'details-type-signalement',
params: { feature_type_slug: type.slug },
}"
class="feature-type-title"
>
<img
v-if="type.geom_type === 'point'"
class="list-image-type"
src="@/assets/img/marker.png"
alt="Géométrie point"
>
<img
v-if="type.geom_type === 'linestring'"
class="list-image-type"
src="@/assets/img/line.png"
alt="Géométrie ligne"
>
<img
v-if="type.geom_type === 'polygon'"
class="list-image-type"
src="@/assets/img/polygon.png"
alt="Géométrie polygone"
>
{{ type.title }}
</router-link>
<FeatureTypeLink :feature-type="type" />
<div class="middle aligned content">
<router-link
v-if="
......@@ -383,7 +356,7 @@
<!-- MODALE FILESIZE -->
<div
:class="isFileSizeModalOpen ? 'active' : ''"
class="ui dimmer"
class="ui dimmer inverted"
>
<div
:class="isFileSizeModalOpen ? 'active' : ''"
......@@ -417,11 +390,16 @@
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
import { fileConvertSizeToMo, csvToJson } from '@/assets/js/utils';
import FeatureTypeLink from '@/components/FeatureType/FeatureTypeLink';
export default {
name: 'ProjectFeatureTypes',
components: {
FeatureTypeLink
},
props: {
loading: {
type: Boolean,
......@@ -429,9 +407,7 @@ export default {
},
project: {
type: Object,
default: () => {
return {};
}
default: () => {},
}
},
......@@ -631,6 +607,9 @@ export default {
fr.result
.split('\n')[0]
.split(delimiter)
.map(el => {
return el.replace('\r', '');
})
.filter(el => {
return el === 'lat' || el === 'lon';
});
......@@ -645,7 +624,6 @@ export default {
return !isNaN(el) && el.indexOf('.') != -1;
})
.filter(Boolean);
if (sampleLine.length > 1 && headersLine.length === 2) {
this.csvError = null;
this.csvImport = csvToJson(fr.result, delimiter);
......@@ -693,11 +671,6 @@ export default {
<style lang="less" scoped>
.list-image-type {
margin-right: 5px;
height: 25px;
vertical-align: bottom;
}
.feature-type-container {
display: flex;
justify-content: space-between;
......@@ -708,13 +681,6 @@ export default {
width: 50%;
}
.feature-type-title {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
line-height: 1.5em;
}
.nouveau-type-signalement {
margin-top: 1em;
}
......
......@@ -5,11 +5,10 @@ import App from './App.vue';
import './registerServiceWorker';
import router from '@/router';
import store from '@/store';
import 'leaflet/dist/leaflet.css';
import 'leaflet-draw/dist/leaflet.draw.css';
import '@/assets/resources/leaflet-control-geocoder-1.13.0/Control.Geocoder.css';
import '@fortawesome/fontawesome-free/css/all.css';
import '@fortawesome/fontawesome-free/js/all.js';
import 'ol/ol.css';
import '@/assets/styles/openlayers-custom.css';
import { library } from '@fortawesome/fontawesome-svg-core';
import { fas } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
......
import { Draw } from 'ol/interaction';
import GeometryType from 'ol/geom/GeometryType';
import Modify from 'ol/interaction/Modify';
import Select from 'ol/interaction/Select';
import MultiPoint from 'ol/geom/MultiPoint';
import Point from 'ol/geom/Point';
import {
Fill, Stroke, Style, Circle, Text //RegularShape, Circle as CircleStyle, Text,Icon
} from 'ol/style';
import VectorSource from 'ol/source/Vector';
import VectorLayer from 'ol/layer/Vector';
import mapService from '@/services/map-service';
import { buffer } from 'ol/extent';
const editionService = {
editing_feature: {},
geom_type: 'linestring',
drawStyle: new Style({
fill: new Fill({
color: 'rgba(255, 255, 255, 0.2)'
}),
stroke: new Stroke({
color: 'rgba(0, 0, 255, 0.5)',
lineDash: [],
width: 2
}),
image: new Circle({
radius: 7,
stroke: new Stroke({
color: 'rgba(255, 0, 0, 0.5)',
lineDash: [],
width: 2
}),
fill: new Fill({
color: 'rgba(255, 255, 255, 0.5)'
})
}),
text: new Text({
font: '12px Calibri,sans-serif',
fill: new Fill({ color: '#000' }),
stroke: new Stroke({
color: '#fff',
width: 1
}),
text: ''
}),
zIndex: 50
}),
setEditingFeature(feature) {
this.editing_feature = feature;
},
startEditFeature(feature){
this.editing_feature=feature;
this.draw.setActive(false);
this.drawSource.addFeature(feature);
this.drawnItems.setZIndex(50);
mapService.fitExtent(buffer(this.drawSource.getExtent(),200));
},
addEditionControls(geomType) {
this.geom_type=geomType;
this.drawSource = new VectorSource();
this.drawnItems = new VectorLayer({
source: this.drawSource,
style: this.drawStyle,
zIndex: 4000
});
window.olMap = mapService.getMap();
mapService.getMap().addLayer(this.drawnItems);
if (this.draw) {
mapService.getMap().removeInteraction(this.draw);
}
let gType = GeometryType.POINT;
if (geomType.toUpperCase().indexOf('POLYGON') >= 0) {
gType = GeometryType.POLYGON;
}
else if (geomType.toUpperCase().indexOf('LINE') >= 0) {
gType = GeometryType.LINE_STRING;
}
this.draw = new Draw({
source: this.drawSource,
type: gType,
style: this.drawStyle,
//geometryName: layer.getGeomAttributeName()
});
mapService.getMap().addInteraction(this.draw);
this.setEditingFeature(undefined);
this.draw.on('drawend', (evt) => {
var feature = evt.feature;
console.log(feature);
this.setEditingFeature(feature);
this.draw.setActive(false);
});
this.selectForUpdate = new Select({
style: [
this.drawStyle,
new Style({
image: new Circle({
radius: 5,
fill: new Fill({
color: 'orange',
}),
}),
geometry: function (feature) {
// return the coordinates of the first ring of the polygon
const coordinates = feature.getGeometry().getCoordinates()[0];
if (feature.getGeometry() instanceof Point){
return feature.getGeometry();
}
return new MultiPoint(coordinates);
},
})
]
});
this.modify = new Modify({
style: this.drawStyle,
features: this.selectForUpdate.getFeatures()
});
// je garde ce bout de code pour l'implementation a venir du snapping
//snapping
// var snap = new Snap({
// source: this.drawSource
//});
// This workaround allows to avoid the ol freeze
// referenced bug : https://github.com/openlayers/openlayers/issues/6310
// May be corrected in a future version
this.modify.handleUpEvent_old = this.modify.handleUpEvent;
this.modify.handleUpEvent = function (evt) {
try {
this.handleUpEvent_old(evt);
} catch (ex) {
console.log(ex);
}
};
mapService.getMap().addInteraction(this.selectForUpdate);
mapService.getMap().addInteraction(this.modify);
// je garde ce bout de code pour l'implementation a venir du snapping
//map.addInteraction(snap);
},
resetAllTools() {
if (this.draw) {
this.draw.setActive(false);
}
if (this.selectForDeletion) {
this.removeSelectInteraction(this.selectForDeletion);
}
if (this.selectForUpdate) {
this.removeSelectInteraction(this.selectForUpdate);
}
if (this.modify) {
this.modify.setActive(false);
}
},
removeSelectInteraction(interaction) {
interaction.getFeatures().clear();
interaction.setActive(false);
},
activeUpdateFeature() {
this.resetAllTools();
this.modify.setActive(true);
this.selectForUpdate.setActive(true);
this.selectForUpdate.getFeatures().push(this.editing_feature);
},
activeDeleteFeature() {
this.resetAllTools();
if (!this.selectForDeletion) {
var style = new Style({
fill: new Fill({
color: 'rgba(255, 0, 0, 0.2)'
}),
stroke: new Stroke({
color: 'rgba(255, 0, 0, 0.5)',
width: 2
}),
image: new Circle({
radius: 7,
fill: new Fill({
color: 'rgba(255, 0, 0, 0.5)'
})
}),
text: new Text({
font: '12px Calibri,sans-serif',
fill: new Fill({ color: 'rgba(255, 0, 0, 0.5)' }),
}),
zIndex: 50
});
this.selectForDeletion = new Select({ style: style });
mapService.getMap().addInteraction(this.selectForDeletion);
// Lorsque de nouvelles features sont sélectionnées
let selected_features = this.selectForDeletion.getFeatures();
this.listenerKey = selected_features.on('add', (evt) => {
var feature = evt.element;
if (feature) {
setTimeout(() => {
if (confirm('Etes-vous sur de vouloir supprimer cet objet ?')) {
// supprimer l'edition de la sélection
this.selectForDeletion.getFeatures().clear();
// supprimer l'edition de la carte
this.drawSource.removeFeature(feature);
this.editing_feature = undefined;
this.draw.setActive(true);
this.selectForDeletion.setActive(false);
}
}, 300);
}
});
} else {
this.selectForDeletion.setActive(true);
}
},
};
export default editionService;
\ No newline at end of file
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;
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) {
let self = this;
const features = this.map.getFeaturesAtPixel(event.pixel, {
layerFilter(l) {
return l === self.mvtLayer;
},
});
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;
if (this.layers) {
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) {
let url = this.getFeatureInfoUrl(event, queryLayer);
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) {
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
layers.forEach((layer) => {
if (layer) {
let options = layer.options;
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') {
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) {
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) {
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);
}
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(addToMap);
let drawSource = new VectorSource();
let retour;
// TODO verifier utilité de cette boucle et remplacer par readFeatures plutot
features.forEach((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);
var marker = new Overlay({
position: pos,
positioning: OverlayPositioning.CENTER_CENTER,
element: document.getElementById('marker'),
stopEvent: false
});
this.map.addOverlay(marker);
}
};
export default mapService;
\ No newline at end of file
......@@ -207,7 +207,7 @@ const feature = {
router.push({
name: 'details-signalement',
params: {
slug_type_signal: rootState.feature_type.current_feature_type_slug,
slug_type_signal: rootState['feature-type'].current_feature_type_slug,
slug_signal: featureId,
message: routeName === 'editer-signalement' ? 'Le signalement a été mis à jour' : 'Le signalement a été crée'
},
......@@ -236,17 +236,17 @@ const feature = {
description: state.form.description.value,
status: state.form.status.value,
project: rootState.projects.project.slug,
feature_type: rootState.feature_type.current_feature_type_slug,
feature_type: rootState['feature-type'].current_feature_type_slug,
...extraFormObject
}
};
}
const geojson = createGeojson();
let url = `${rootState.configuration.VUE_APP_DJANGO_API_BASE}features/`;
if (routeName === 'editer-signalement') {
url += `${state.form.feature_id}/?
feature_type__slug=${rootState.feature_type.current_feature_type_slug}
feature_type__slug=${rootState['feature-type'].current_feature_type_slug}
&project__slug=${rootState.projects.project.slug}`;
}
......@@ -287,7 +287,7 @@ const feature = {
router.push({
name: 'offline-signalement',
params: {
slug_type_signal: rootState.feature_type.current_feature_type_slug
slug_type_signal: rootState['feature-type'].current_feature_type_slug
},
});
}
......@@ -395,7 +395,7 @@ const feature = {
let url = `${rootState.configuration.VUE_APP_DJANGO_API_BASE}features/${feature_id}/?` +
`project__slug=${rootState.projects.project.slug}`;
if (!noFeatureType) {
url +=`&feature_type__slug=${rootState.feature_type.current_feature_type_slug}`;
url +=`&feature_type__slug=${rootState['feature-type'].current_feature_type_slug}`;
}
return axios
.delete(url)
......
import axios from '@/axios-client.js';
import { mapUtil } from '@/assets/js/map-util.js';
import mapService from '@/services/map-service';
const map = {
......@@ -107,24 +107,17 @@ const map = {
INITIATE_MAP({ commit }, el) { //todo: since this function is not anymore called in different components, it would better to move it in project_details.vue
const mapDefaultViewCenter = [46, 2]; // defaultMapView.center;
const mapDefaultViewZoom = 5; // defaultMapView.zoom;
mapUtil.createMap(el, {
mapService.createMap(el, {
mapDefaultViewCenter,
mapDefaultViewZoom,
});
commit('SET_MAP', mapUtil.getMap());
mapUtil.addLayers(
commit('SET_MAP', mapService.getMap());
mapService.addLayers(
null,
this.state.configuration.DEFAULT_BASE_MAP_SERVICE,
this.state.configuration.DEFAULT_BASE_MAP_OPTIONS,
this.state.configuration.DEFAULT_BASE_MAP_SCHEMA_TYPE,
);
// Remove multiple interactions with the map
//mapUtil.getMap().dragging.disable();
mapUtil.getMap().doubleClickZoom.disable();
mapUtil.getMap().scrollWheelZoom.disable();
},
async SAVE_BASEMAPS({ state, rootState, dispatch }, newBasemapIds) {
......
......@@ -23,6 +23,19 @@
id="map"
ref="map"
/>
<div
id="popup"
class="ol-popup"
>
<a
id="popup-closer"
href="#"
class="ol-popup-closer"
/>
<div
id="popup-content"
/>
</div>
</div>
</div>
<div class="row">
......@@ -80,7 +93,7 @@
<script>
import { mapState, mapActions, mapMutations } from 'vuex';
import { mapUtil } from '@/assets/js/map-util.js';
import mapService from '@/services/map-service';
import axios from '@/axios-client.js';
import featureAPI from '@/services/feature-api';
......@@ -89,6 +102,7 @@ import FeatureHeader from '@/components/Feature/Detail/FeatureHeader';
import FeatureTable from '@/components/Feature/Detail/FeatureTable';
import FeatureAttachements from '@/components/Feature/Detail/FeatureAttachements';
import FeatureComments from '@/components/Feature/Detail/FeatureComments';
import { buffer } from 'ol/extent';
export default {
name: 'FeatureDetail',
......@@ -235,13 +249,14 @@ export default {
this.$store.state.configuration.DEFAULT_MAP_VIEW.center;
var mapDefaultViewZoom =
this.$store.state.configuration.DEFAULT_MAP_VIEW.zoom;
this.map = mapUtil.createMap(this.$refs.map, {
this.map = mapService.createMap(this.$refs.map, {
mapDefaultViewCenter,
mapDefaultViewZoom,
interactions : { doubleClickZoom :false,mouseWheelZoom:false,dragPan:false }
});
// Update link to feature list with map zoom and center
mapUtil.addMapEventListener('moveend', function () {
mapService.addMapEventListener('moveend', function () {
// update link to feature list with map zoom and center
/*var $featureListLink = $("#feature-list-link")
var baseUrl = $featureListLink.attr("href").split("?")[0]
......@@ -267,16 +282,13 @@ export default {
});
layersToLoad.reverse();
}
mapUtil.addLayers(
mapService.addLayers(
layersToLoad,
this.$store.state.configuration.DEFAULT_BASE_MAP_SERVICE,
this.$store.state.configuration.DEFAULT_BASE_MAP_OPTIONS,
this.$store.state.configuration.DEFAULT_BASE_MAP_SCHEMA_TYPE,
);
mapUtil.getMap().dragging.disable();
mapUtil.getMap().doubleClickZoom.disable();
mapUtil.getMap().scrollWheelZoom.disable();
this.addFeatureToMap();
},
......@@ -288,15 +300,13 @@ export default {
.get(url)
.then((response) => {
if (response.data.features.length > 0) {
const featureGroup = mapUtil.addFeatures(
const featureGroup = mapService.addFeatures(
response.data.features,
{},
true,
this.feature_types
);
mapUtil
.getMap()
.fitBounds(featureGroup.getBounds(), { padding: [25, 25] });
mapService.fitExtent(buffer(featureGroup.getExtent(),200));
}
})
.catch((error) => {
......