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 (10)
......@@ -10,11 +10,14 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"@mapbox/vector-tile": "^1.3.1",
"@turf/flip": "^6.5.0",
"axios": "^0.21.1",
"core-js": "^3.6.5",
"leaflet": "^1.5.1",
"leaflet-draw": "^1.0.4",
"leaflet.vectorgrid": "^1.3.0",
"pbf": "^3.2.1",
"register-service-worker": "^1.7.1",
"sortablejs": "^1.14.0",
"vue": "^2.6.11",
......
/* eslint-disable no-mixed-spaces-and-tabs */
/* eslint-disable no-unused-vars */
/* eslint-disable no-undef */
import L from "leaflet"
import "leaflet/dist/leaflet.css";
import flip from '@turf/flip'
import axios from "axios"
import "leaflet.vectorgrid";
axios.defaults.headers.common['X-CSRFToken'] = (name => {
var re = new RegExp(name + "=([^;]+)");
......@@ -9,10 +13,29 @@ axios.defaults.headers.common['X-CSRFToken'] = (name => {
return (value != null) ? unescape(value[1]) : null;
})('csrftoken');
import { FillSymbolizer, PointSymbolizer, LineSymbolizer } from "@/assets/js/vector_tile_fix.js";
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) {
......@@ -274,13 +297,91 @@ const mapUtil = {
// Redraw the layers
this.addLayers(layers);
},
// eslint-disable-next-line no-unused-vars
addVectorTileLayer: function (project_slug,featureTypes,form_filters) {
let project_id=project_slug.split('-')[0];
layerMVT = L.vectorGrid.protobuf("/api/features.mvt/?tile={z}/{x}/{y}&project_id="+project_id, { //
vectorTileLayerStyles: {
"default": function(properties, zoom) {
// console.log(properties);
let featureType=featureTypes.find((x)=>x.slug.split('-')[0]===''+properties.feature_type_id);
let color=featureType.color;
if(featureType.colors_style && featureType.colors_style.custom_field_name){
let currentValue=properties[featureType.colors_style.custom_field_name];
let colorValue=featureType.colors_style.colors[currentValue];
if(colorValue) {
color=colorValue;
}
addFeatures: function (features, filter) {
}
console.log(featureType);
let hiddenStyle=({
radius: 0,
fillOpacity: 0.5,
weight: 0,
fill: false,
color: featureType.color,
})
// Filtre sur le feature type
if (form_filters && form_filters.type.selected) {
if(featureType.title !== form_filters.type.selected){
return hiddenStyle;
}
}
// Filtre sur le statut
if (form_filters && form_filters.status.selected.value) {
if(properties.status !== form_filters.status.selected.value){
return hiddenStyle;
}
}
// Filtre sur le titre
if (form_filters && form_filters.title) {
if(!properties.title.toLowerCase().includes(form_filters.title.toLowerCase())){
return hiddenStyle;
}
}
return ({
radius: 4,
fillOpacity: 0.5,
weight: 3,
fill: true,
color: color,
});
},
},
// subdomains: "0123",
// key: 'abcdefghi01234567890',
interactive:true,
maxNativeZoom: 14,
getFeatureId: function(f) {
return f.properties.id;
}
});
let self=this;
layerMVT.on('click', function(e) { // The .on method attaches an event handler
console.log(e);
const popupContent = self._createContentPopup(e.layer,featureTypes,project_slug);
L.popup()
.setContent(popupContent)
.setLatLng(e.latlng)
.openOn(map)
});
layerMVT.addTo(map);
window.layerMVT=layerMVT;
},
addFeatures: function (features, filter,addToMap=true,featureTypes) {
let featureGroup = new L.FeatureGroup();
features.forEach((feature) => {
let featureType=featureTypes.find((x)=>x.slug.split('-')[0]===''+feature.properties.feature_type_id);
if(feature.properties.feature_type != undefined){
featureType=featureTypes.find((x)=>x.slug===''+feature.properties.feature_type);
}
let filters = [];
if (filter) {
const typeCheck = filter.featureType && feature.properties.feature_type.slug === filter.featureType;
const statusCheck = filter.featureStatus && feature.properties.status.value === filter.featureStatus;
......@@ -293,10 +394,13 @@ const mapUtil = {
const geomJSON = flip(feature.geometry);
const popupContent = this._createContentPopup(feature);
let color=feature.properties.color;
if(color==undefined){
color=featureType.color;
}
if (geomJSON.type === 'Point') {
L.circleMarker(geomJSON.coordinates, {
color: feature.properties.color,
color: color,
radius: 4,
fillOpacity: 0.5,
weight: 3,
......@@ -305,14 +409,14 @@ const mapUtil = {
.addTo(featureGroup);
} else if (geomJSON.type === 'LineString') {
L.polyline(geomJSON.coordinates, {
color: feature.properties.color,
color: color,
weight: 3,
})
.bindPopup(popupContent)
.addTo(featureGroup);
} else if (geomJSON.type === 'Polygon') {
L.polygon(geomJSON.coordinates, {
color: feature.properties.color,
color: color,
weight: 3,
fillOpacity: 0.5,
})
......@@ -321,15 +425,39 @@ const mapUtil = {
}
}
});
map.addLayer(featureGroup);
if(addToMap){
map.addLayer(featureGroup);
}
return featureGroup;
},
addMapEventListener: function (eventName, callback) {
map.on(eventName, callback);
},
_createContentPopup: function (feature) {
_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=feature.properties.status;
let date_maj=feature.properties.updated_on;
let feature_type_url=feature.properties.feature_type_url;
let feature_url=feature.properties.feature_url;
if(featureTypes){ // => VectorTile
feature_type=featureTypes.find((x)=>x.slug.split('-')[0]===''+feature.properties.feature_type_id);
status=statusList.find((x)=>x.value==feature.properties.status).name;
date_maj=formatDate(new Date(feature.properties.updated_on));
feature_type_url='/geocontrib/projet/'+project_slug+'/type_signalement/'+feature_type.slug+'/';
feature_url=feature_type_url+'signalement/'+feature.properties.feature_id+'/';
//status=feature.properties.status;
}else{
feature_type=feature.properties.feature_type;
status=feature.properties.status.label;
}
let author = "";
if (feature.properties.creator) {
author = feature.properties.creator.full_name
......@@ -341,16 +469,16 @@ const mapUtil = {
return `
<h4>
<a href="${feature.properties.feature_url}">${feature.properties.title}</a>
<a href="${feature_url}">${feature.properties.title}</a>
</h4>
<div>
Statut : ${feature.properties.status.label}
Statut : ${status}
</div>
<div>
Type : <a href="${feature.properties.feature_type_url}"> ${feature.properties.feature_type.title} </a>
Type : <a href="${feature_type_url}"> ${feature_type.title} </a>
</div>
<div>
Dernière mise à jour : ${feature.properties.updated_on}
Dernière mise à jour : ${date_maj}
</div>
${author}
`;
......
/* eslint-disable no-unused-vars */
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, layerStyle) {
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
......@@ -128,7 +128,7 @@ export default {
Dropdown,
},
props: ["customForm"],
props: ["customForm", "selectedColorStyle"],
data() {
return {
......@@ -251,6 +251,7 @@ export default {
}
return false;
},
fillCustomFormData(customFormData) {
for (let el in customFormData) {
if (el && this.form[el] && customFormData[el]) {
......@@ -280,6 +281,9 @@ export default {
options: this.form.options.value,
};
this.$store.commit("feature_type/UPDATE_CUSTOM_FORM", data);
if (this.customForm.name === this.selectedColorStyle ) {
this.$emit("update", this.form.options.value);
}
},
trimWhiteSpace(string) {
......
......@@ -617,7 +617,7 @@ export default {
const feature = response.data;
if (feature) {
const currentFeature = [feature];
const featureGroup = mapUtil.addFeatures(currentFeature);
const featureGroup = mapUtil.addFeatures(currentFeature,{},true,this.$store.state.feature_type.feature_types);
mapUtil
.getMap()
.fitBounds(featureGroup.getBounds(), { padding: [25, 25] });
......
......@@ -917,7 +917,12 @@ export default {
mapDefaultViewZoom,
});
const currentFeatureId = this.$route.params.slug_signal;
setTimeout(
function () {
mapUtil.addVectorTileLayer(this.$route.params.slug,this.$store.state.feature_type.feature_types);
}.bind(this),
1000
);
const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/?feature_type__slug=${this.$route.params.slug_type_signal}&output=geojson`;
axios
.get(url)
......@@ -928,7 +933,7 @@ export default {
const allFeaturesExceptCurrent = features.filter(
(feat) => feat.id !== currentFeatureId
);
mapUtil.addFeatures(allFeaturesExceptCurrent);
mapUtil.addFeatures(allFeaturesExceptCurrent,{},false,this.$store.state.feature_type.feature_types);
if (this.currentRouteName === "editer-signalement") {
const currentFeature = features.filter(
(feat) => feat.id === currentFeatureId
......
......@@ -329,14 +329,27 @@ export default {
if (this.featureGroup) {
const features = this.filteredFeatures;
this.featureGroup.clearLayers();
this.featureGroup = mapUtil.addFeatures(features, {});
mapUtil
.getMap()
.fitBounds(this.featureGroup.getBounds(), { padding: [25, 25] });
this.featureGroup = mapUtil.addFeatures(features, {},false,this.$store.state.feature_type.feature_types);
mapUtil.getMap().invalidateSize();
mapUtil.getMap()._onResize();// force refresh for vector tiles
if(this.featureGroup.getLayers().length>0){
mapUtil
.getMap()
.fitBounds(this.featureGroup.getBounds(), { padding: [25, 25] });
}
else{
mapUtil
.getMap()
.zoomOut(1);
}
}
},
initMap() {
console.log(this)
this.zoom = this.$route.query.zoom || "";
this.lat = this.$route.query.lat || "";
this.lng = this.$route.query.lng || "";
......@@ -360,26 +373,28 @@ export default {
});
// --------- End sidebar events ----------
const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/?output=geojson`;
this.$store.commit(
"DISPLAY_LOADER",
"Récupération des signalements en cours..."
);
axios
.get(url)
.then((response) => {
if (response.status === 200 && response.data.features.length > 0) {
this.loadFeatures(response.data.features);
}
this.$store.commit("DISCARD_LOADER");
})
.catch((error) => {
this.$store.commit("DISCARD_LOADER");
throw error;
});
const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/?output=geojson`;
this.$store.commit(
"DISPLAY_LOADER",
"Récupération des signalements en cours..."
);
axios
.get(url)
.then((response) => {
if (response.status === 200 && response.data.features.length > 0) {
this.loadFeatures(response.data.features);
}
this.$store.commit("DISCARD_LOADER");
})
.catch((error) => {
this.$store.commit("DISCARD_LOADER");
throw error;
});
setTimeout(
function () {
mapUtil.addVectorTileLayer(this.$route.params.slug,this.$store.state.feature_type.feature_types,this.form);
mapUtil.addGeocoders(this.$store.state.configuration);
}.bind(this),
1000
......@@ -396,7 +411,7 @@ export default {
featureType,
featureStatus,
featureTitle,
});
},false,this.$store.state.feature_type.feature_types);
// Fit the map to bound only if no initial zoom and center are defined
if (
(this.lat === "" || this.lng === "" || this.zoom === "") &&
......
......@@ -98,17 +98,18 @@
</div>
<div class="colors_selection" id="id_colors_selection" hidden>
<div
v-for="(value, key, index) in form.colors_style.value.colors"
v-for="(value, key, index) in this.form.colors_style.value.colors"
:key="'colors_style-' + index"
class="color-input"
>
<label>{{ key }}</label
><input
:name="key"
type="color"
:value="value"
@input="setColorStyles"
/>
<div v-if="key" class="color-input">
<label>{{ key }}</label
><input
:name="key"
type="color"
:value="value"
@input="setColorStyles"
/>
</div>
</div>
</div>
</div>
......@@ -117,10 +118,12 @@
<div id="formsets">
<FeatureTypeCustomForm
v-for="form in customForms"
:key="form.dataKey"
:dataKey="form.dataKey"
:customForm="form"
v-for="customForm in customForms"
:key="customForm.dataKey"
:dataKey="customForm.dataKey"
:customForm="customForm"
:selectedColorStyle="form.colors_style.value.custom_field_name"
v-on:update="updateColorsStyle($event)"
ref="customForms"
/>
</div>
......@@ -268,23 +271,34 @@ export default {
},
selected_colors_style: {
get() {
const value = this.form.colors_style.value.custom_field_name;
const customField = this.customForms.find((el) => el.name === value);
const name = this.form.colors_style.value.custom_field_name;
const customField = this.customForms.find((el) => el.name === name);
return customField && customField.length !== 0
? customField.label
: value;
: name;
},
set(newValue) {
console.log(newValue);
const newColorsStyle = {
colors: newValue.options.reduce((obj, key) => {
obj[key] = "#000000";
return obj;
}, {}),
custom_field_name: newValue.value,
};
this.form.colors_style.value = newColorsStyle;
this.updateStore();
//* update only if different than custom_form
if (newValue.value !== this.form.colors_style.value.custom_field_name) {
//* get back values from original feature_type
if (
newValue.value === this.feature_type.colors_style.custom_field_name
) {
this.form.colors_style.value = this.feature_type.colors_style;
} else {
const newColorsStyle = {
colors: newValue.options.reduce((obj, key) => {
obj[key] = "#000000";
return obj;
}, {}),
};
this.form.colors_style.value = newColorsStyle;
this.updateStore();
}
}
this.form.colors_style.value.custom_field_name = newValue.value;
this.form = { ...this.form }; //! force reactivity to update custom_field_name
},
},
},
......@@ -338,6 +352,30 @@ export default {
this.form.colors_style.value.colors[name] = value;
},
updateColorsStyle(newOptions) {
const optionNames = Object.keys(this.form.colors_style.value.colors);
//* if new value added
if (newOptions.length > optionNames.length) {
for (const key of newOptions) {
if (key && !optionNames.includes(key)) {
//* check if key is not en empty string
this.form.colors_style.value.colors[key] = "#000000"; //* add new entry
}
}
//* if modified or deleted
} else {
let modifiedColorStyle = {};
for (const [index, key] of newOptions.entries()) {
//* if no key then item will disappear (deleted)
if (key) {
const values = Object.values(this.form.colors_style.value.colors);
modifiedColorStyle[key] = values[index]; //* overide key and get previous value from previous colors style
}
}
this.form.colors_style.value.colors = modifiedColorStyle;
}
},
updateStore() {
this.$store.commit("feature_type/UPDATE_FORM", {
color: this.form.color,
......
......@@ -642,46 +642,52 @@ export default {
setTimeout(() => (this.infoMessage = ""), 3000);
});
},
initMap(){
if (this.project && this.permissions.can_view_project) {
this.$store.dispatch("map/INITIATE_MAP");
const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/?output=geojson`;
let self = this;
this.checkForOfflineFeature();
mapUtil.addVectorTileLayer(this.$route.params.slug,this.$store.state.feature_type.feature_types);
axios
.get(url)
.then((response) => {
let features = response.data.features;
self.arraysOffline.forEach(x=>x.geojson.properties.color="red");
features=response.data.features.concat(self.arraysOffline.map(x=>x.geojson));
const featureGroup = mapUtil.addFeatures(features,{},false,this.$store.state.feature_type.feature_types);
if (featureGroup && featureGroup.getLayers().length > 0) {
mapUtil
.getMap()
.fitBounds(featureGroup.getBounds(), { padding: [25, 25] });
this.$store.commit("map/SET_GEOJSON_FEATURES", features);
} else {
this.$store.commit("map/SET_GEOJSON_FEATURES", []);
}
})
.catch((error) => {
throw error;
});
}
},
},
created() {
this.$store.dispatch("GET_PROJECT_INFO", this.slug);
if (this.user) {
projectAPI
.getProjectSubscription({ projectSlug: this.$route.params.slug })
.then((data) => (this.is_suscriber = data.is_suscriber));
}
},
mounted() {
if (this.project && this.permissions.can_view_project) {
this.$store.dispatch("map/INITIATE_MAP");
const url = `${this.$store.state.configuration.VUE_APP_DJANGO_API_BASE}projects/${this.$route.params.slug}/feature/?output=geojson`;
let self = this;
this.checkForOfflineFeature();
axios
.get(url)
.then((response) => {
let features = response.data.features;
self.arraysOffline.forEach(x=>x.geojson.properties.color="red");
features=response.data.features.concat(self.arraysOffline.map(x=>x.geojson));
const featureGroup = mapUtil.addFeatures(features);
if (featureGroup && featureGroup.getLayers().length > 0) {
mapUtil
.getMap()
.fitBounds(featureGroup.getBounds(), { padding: [25, 25] });
this.$store.commit("map/SET_GEOJSON_FEATURES", features);
} else {
this.$store.commit("map/SET_GEOJSON_FEATURES", []);
}
})
.catch((error) => {
throw error;
});
}
let self=this;
this.$store.dispatch("GET_PROJECT_INFO", this.slug).then(setTimeout(self.initMap,1000));
if (this.message) {
this.tempMessage = this.message;
document
......