Mercurial > gemma
view client/src/store/map.js @ 5650:cb3c4ba0f052 DQR adjustments
Merged default branch
author | Sascha Wilde <wilde@sha-bang.de> |
---|---|
date | Thu, 29 Jun 2023 13:18:54 +0200 |
parents | 482046e5b63b |
children |
line wrap: on
line source
/* This is Free Software under GNU Affero General Public License v >= 3.0 * without warranty, see README.md and license for details. * * SPDX-License-Identifier: AGPL-3.0-or-later * License-Filename: LICENSES/AGPL-3.0.txt * * Copyright (C) 2018, 2019 by via donau * – Österreichische Wasserstraßen-Gesellschaft mbH * Software engineering by Intevation GmbH * * Author(s): * * Bernhard Reiter <bernhard.reiter@intevation.de> * * Markus Kottländer <markus@intevation.de> * * Thomas Junk <thomas.junk@intevation.de> */ import { Circle, Fill, Stroke, Style } from "ol/style"; import { getArea, getLength } from "ol/sphere"; import Draw from "ol/interaction/Draw"; import Feature from "ol/Feature"; import { HTTP } from "@/lib/http"; import Point from "ol/geom/Point"; import { Vector as VectorLayer } from "ol/layer"; import app from "@/main"; import bbox from "@turf/bbox"; import { fromLonLat } from "ol/proj"; import { toLonLat } from "ol/proj"; import { transformExtent } from "ol/proj"; // initial state const init = () => { return { oldLayerVisibility: [], singleLayerVisible: false, openLayersMaps: [], syncedMaps: [], syncedView: null, mapPopup: null, mapPopupEnabled: true, initialLoad: true, extent: { lat: 6155376, lon: 1819178, zoom: 11 }, identifiedFeatures: [], // map features identified by clicking on the map identifiedCoordinates: null, currentMeasurement: null, // distance or area from line-/polygon-/cutTool lineToolEnabled: false, polygonToolEnabled: false, cutToolEnabled: false, isolinesLegendImgDataURL: "", differencesLegendImgDataURL: "", ongoingRefresh: false, ongoingTimeSlide: false, reviewActive: false }; }; export default { init, namespaced: true, state: init(), getters: { openLayersMap: state => id => { return state.openLayersMaps.find( map => map.getTarget() === "map-" + (id || "main") ); }, filteredIdentifiedFeatures: state => { return state.identifiedFeatures.filter(f => f.getId()); } }, mutations: { viewSingleLayer: (state, layerId) => { state.singleLayerVisible = !state.singleLayerVisible; if (state.singleLayerVisible) { state.oldLayerVisibility = state.openLayersMaps.reduce((o, m) => { let current = []; m.getLayers().forEach(l => { current.push(l.getVisible()); l.setVisible(l.get("id") == layerId); }); o.push(current); return o; }, []); } else { state.oldLayerVisibility.forEach((m, i) => { let layers = state.openLayersMaps[i].getLayers().getArray(); m.forEach((visible, index) => { layers[index].setVisible(visible); }); }); } }, reviewActive: (state, active) => { state.reviewActive = active; }, startRefreshLayers: state => { state.ongoingRefresh = true; }, finishRefreshLayers: state => { state.ongoingRefresh = false; }, startTimeSlide: state => { state.ongoingTimeSlide = true; }, finishTimeSlide: state => { state.ongoingTimeSlide = false; }, initialLoad: (state, initialLoad) => { state.initialLoad = initialLoad; }, extent: (state, extent) => { state.extent = extent; }, addOpenLayersMap: (state, map) => { state.openLayersMaps.push(map); }, removeOpenLayersMap: (state, map) => { let index = state.openLayersMaps.findIndex( m => m.getTarget() === map.getTarget() ); if (index !== -1) { state.openLayersMaps.splice(index, 1); } }, syncedMaps: (state, ids) => { state.syncedMaps = ids; }, syncedView: (state, view) => { state.syncedView = view; }, mapPopup: (state, popup) => { state.mapPopup = popup; }, mapPopupEnabled: (state, enabled) => { state.mapPopupEnabled = enabled; }, setIdentifiedFeatures: (state, identifiedFeatures) => { state.identifiedFeatures = identifiedFeatures; }, addIdentifiedFeatures: (state, identifiedFeatures) => { state.identifiedFeatures = state.identifiedFeatures.concat( identifiedFeatures ); }, identifiedCoordinates: (state, coordinates) => { state.identifiedCoordinates = coordinates; }, setCurrentMeasurement: (state, measurement) => { state.currentMeasurement = measurement; }, lineToolEnabled: (state, enabled) => { state.lineToolEnabled = enabled; state.openLayersMaps.forEach(m => { let tool = m .getInteractions() .getArray() .find(i => i.get("id") === "linetool"); if (tool) { tool.setActive(enabled); } }); }, polygonToolEnabled: (state, enabled) => { state.polygonToolEnabled = enabled; state.openLayersMaps.forEach(m => { let tool = m .getInteractions() .getArray() .find(i => i.get("id") === "polygontool"); if (tool) { tool.setActive(enabled); } }); }, cutToolEnabled: (state, enabled) => { state.cutToolEnabled = enabled; state.openLayersMaps.forEach(m => { let tool = m .getInteractions() .getArray() .find(i => i.get("id") === "cuttool"); if (tool) { tool.setActive(enabled); } }); }, isolinesLegendImgDataURL: (state, isolinesLegendImgDataURL) => { state.isolinesLegendImgDataURL = isolinesLegendImgDataURL; }, differencesLegendImgDataURL: (state, differencesLegendImgDataURL) => { state.differencesLegendImgDataURL = differencesLegendImgDataURL; } }, actions: { openLayersMap({ state, commit, dispatch }, map) { const drawVectorSrc = map.getLayer("DRAWTOOL").getSource(); const cutVectorSrc = map.getLayer("CUTTOOL").getSource(); // init line tool const lineTool = new Draw({ source: drawVectorSrc, type: "LineString", maxPoints: 2, stopClick: true }); lineTool.set("id", "linetool"); lineTool.setActive(false); lineTool.on("drawstart", () => { state.openLayersMaps.forEach(m => { m.getLayer("DRAWTOOL") .getSource() .clear(); }); commit("setCurrentMeasurement", null); }); lineTool.on("drawend", event => { commit("setCurrentMeasurement", { quantity: app.$gettext("Length"), unitSymbol: "m", value: Math.round(getLength(event.feature.getGeometry()) * 10) / 10 }); commit("application/showIdentify", true, { root: true }); }); // init polygon tool const polygonTool = new Draw({ source: drawVectorSrc, type: "Polygon", maxPoints: 50, stopClick: true }); polygonTool.set("id", "polygontool"); polygonTool.setActive(false); polygonTool.on("drawstart", () => { state.openLayersMaps.forEach(m => { m.getLayer("DRAWTOOL") .getSource() .clear(); }); commit("setCurrentMeasurement", null); }); polygonTool.on("drawend", event => { const areaSize = getArea(event.feature.getGeometry()); commit("setCurrentMeasurement", { quantity: app.$gettext("Area"), unitSymbol: areaSize > 100000 ? "km²" : "m²", value: areaSize > 100000 ? Math.round(areaSize / 1000) / 1000 // convert into 1 km² == 1000*1000 m² and round to 1000 m² : Math.round(areaSize) }); commit("application/showIdentify", true, { root: true }); }); // init cut tool const cutTool = new Draw({ source: cutVectorSrc, type: "LineString", maxPoints: 2, stopClick: true, style: new Style({ stroke: new Stroke({ color: "#444", width: 2, lineDash: [7, 7] }), image: new Circle({ fill: new Fill({ color: "#333" }), stroke: new Stroke({ color: "#fff", width: 1.5 }), radius: 6 }) }) }); cutTool.set("id", "cuttool"); cutTool.setActive(false); cutTool.on("drawstart", () => { state.openLayersMaps.forEach(m => { m.getLayer("CUTTOOL") .getSource() .clear(); }); }); cutTool.on("drawend", event => { commit("fairwayprofile/selectedCut", null, { root: true }); dispatch("fairwayprofile/cut", event.feature, { root: true }); }); map.addInteraction(lineTool); map.addInteraction(cutTool); map.addInteraction(polygonTool); // If there are multiple maps and you enable one of the draw tools, when // moving the mouse to another map, the cursor for the draw tool stays // visible in the first map, right next to the edge where the cursor left // the map. So here we disable all draw layers except the ones in the map // that the user currently hovering with the mouse. map.getTargetElement().addEventListener("mouseenter", () => { if ( state.lineToolEnabled || state.polygonToolEnabled || state.cutToolEnabled ) { state.openLayersMaps.forEach(m => { let lineTool = m .getInteractions() .getArray() .find(i => i.get("id") === "linetool"); let polygonTool = m .getInteractions() .getArray() .find(i => i.get("id") === "polygontool"); let cutTool = m .getInteractions() .getArray() .find(i => i.get("id") === "cuttool"); if (lineTool) lineTool.setActive(false); if (polygonTool) polygonTool.setActive(false); if (cutTool) cutTool.setActive(false); }); let lineTool = map .getInteractions() .getArray() .find(i => i.get("id") === "linetool"); let polygonTool = map .getInteractions() .getArray() .find(i => i.get("id") === "polygontool"); let cutTool = map .getInteractions() .getArray() .find(i => i.get("id") === "cuttool"); if (lineTool && state.lineToolEnabled) lineTool.setActive(true); if (polygonTool && state.polygonToolEnabled) polygonTool.setActive(true); if (cutTool && state.cutToolEnabled) cutTool.setActive(true); } }); commit("addOpenLayersMap", map); }, initIdentifyTool({ state, rootState, commit, dispatch }, map) { map.on(["singleclick", "dblclick"], event => { commit( "identifiedCoordinates", toLonLat(event.coordinate, map.getView().getProjection()) ); state.mapPopup.setPosition(undefined); commit("setIdentifiedFeatures", []); // checking our WFS layers var features = map.getFeaturesAtPixel(event.pixel, { hitTolerance: 7 }); if (features) { let all = []; let bottlenecks = []; let gauges = []; let stretches = []; let sections = []; for (let feature of features) { // avoid identifying the same feature twice if (all.findIndex(f => f.getId() === feature.getId()) === -1) all.push(feature); let id = feature.getId(); // RegExp.prototype.test() works with number, str and undefined // get selected bottleneck if (/^bottlenecks/.test(id)) bottlenecks.push(feature); // get selected gauge if (/^gauges/.test(id)) gauges.push(feature); // get selected stretch if (/^stretches/.test(id)) stretches.push(feature); // get selected section if (/^sections/.test(id)) sections.push(feature); } commit("setIdentifiedFeatures", all); // Decide whether we open a related dialog immediately or show the // popup with possible options first. // The following cases require a manual decision via the popup because // the targeted feature is not clear. if ( (bottlenecks.length || gauges.length > 1 || stretches.length > 1 || sections.length > 1 || (sections.length && stretches.length) || (gauges.length && sections.length) || (gauges.length && stretches.length)) && state.mapPopupEnabled ) { state.mapPopup.setMap(map); state.mapPopup.setPosition(event.coordinate); } // The following scenarios lead to a distinct action without popup. if ( gauges.length === 1 && !bottlenecks.length && !sections.length && !stretches.length ) { commit("application/showGauges", true, { root: true }); dispatch("gauges/selectedGaugeISRS", gauges[0].get("isrs_code"), { root: true }); } if ( stretches.length === 1 && !sections.length && !bottlenecks.length && !gauges.length ) { if (rootState.imports.selectedStretchId === stretches[0].getId()) { commit("imports/selectedStretchId", null, { root: true }); } else { commit("imports/selectedStretchId", stretches[0].getId(), { root: true }); commit("fairwayavailability/type", "stretches", { root: true }); commit("application/showFairwayDepth", true, { root: true }); dispatch("moveToFeauture", { feature: stretches[0], zoom: 17 }); } } if ( sections.length === 1 && !stretches.length && !bottlenecks.length && !gauges.length ) { if (rootState.imports.selectedSectionId === sections[0].getId()) { commit("imports/selectedSectionId", null, { root: true }); } else { commit("imports/selectedSectionId", sections[0].getId(), { root: true }); commit("fairwayavailability/type", "sections", { root: true }); commit("application/showFairwayDepth", true, { root: true }); dispatch("moveToFeauture", { feature: sections[0], zoom: 17 }); } } } let currentResolution = map.getView().getResolution(); var waterwayAxisSource = map.getLayer("WATERWAYAXIS").getSource(); var waxisUrl = waterwayAxisSource.getGetFeatureInfoUrl( event.coordinate, currentResolution /* resolution */, "EPSG:3857", // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d { INFO_FORMAT: "application/json" } ); if (waxisUrl) { // cannot directly query here because of SOP HTTP.get(waxisUrl, { headers: { "X-Gemma-Auth": localStorage.getItem("token") } }).then(response => { let features = response.data.features.map(f => { let feat = new Feature({ geometry: new Point(f.geometry.coordinates) }); feat.setId(f.id); feat.setProperties(f.properties); return feat; }); commit("addIdentifiedFeatures", features); }); } var waterwayAreaSource = map.getLayer("WATERWAYAREA").getSource(); var wareaUrl = waterwayAreaSource.getGetFeatureInfoUrl( event.coordinate, currentResolution /* resolution */, "EPSG:3857", // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d { INFO_FORMAT: "application/json" } ); if (wareaUrl) { HTTP.get(wareaUrl, { headers: { "X-Gemma-Auth": localStorage.getItem("token") } }).then(response => { let features = response.data.features.map(f => { let feat = new Feature({ geometry: new Point(f.geometry.coordinates) }); feat.setId(f.id); feat.setProperties(f.properties); return feat; }); commit("addIdentifiedFeatures", features); }); } var dmSource = map.getLayer("DISTANCEMARKS").getSource(); var dmUrl = dmSource.getGetFeatureInfoUrl( event.coordinate, currentResolution /* resolution */, "EPSG:3857", // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d { INFO_FORMAT: "application/json" } ); if (dmUrl) { HTTP.get(dmUrl + "&BUFFER=5", { headers: { "X-Gemma-Auth": localStorage.getItem("token") } }).then(response => { let features = response.data.features.map(f => { let feat = new Feature({ geometry: new Point(f.geometry.coordinates) }); feat.setId(f.id); feat.setProperties(f.properties); return feat; }); commit("addIdentifiedFeatures", features); }); } var dmaSource = map.getLayer("DISTANCEMARKSAXIS").getSource(); var dmaUrl = dmaSource.getGetFeatureInfoUrl( event.coordinate, currentResolution /* resolution */, "EPSG:3857", // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d { INFO_FORMAT: "application/json" } ); if (dmaUrl) { HTTP.get(dmaUrl + "&BUFFER=5", { headers: { "X-Gemma-Auth": localStorage.getItem("token") } }).then(response => { let features = response.data.features.map(f => { let feat = new Feature({ geometry: new Point(f.geometry.coordinates) }); feat.setId(f.id); feat.setProperties(f.properties); return feat; }); commit("addIdentifiedFeatures", features); }); } var srSource = map.getLayer("BOTTLENECKISOLINE").getSource(); var srUrl = srSource.getGetFeatureInfoUrl( event.coordinate, currentResolution /* resolution */, "EPSG:3857", // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d { INFO_FORMAT: "application/json" } ); if (srUrl) { HTTP.get(srUrl + "&BUFFER=5", { headers: { "X-Gemma-Auth": localStorage.getItem("token") } }).then(response => { let features = response.data.features.map(f => { let feat = new Feature({ geometry: new Point(f.geometry.coordinates) }); feat.setId(f.id); feat.setProperties(f.properties); return feat; }); commit("addIdentifiedFeatures", features); }); } var fmSource = map.getLayer("FAIRWAYMARKS").getSource(); var fmURL = fmSource.getGetFeatureInfoUrl( event.coordinate, currentResolution, "EPSG:3857", { INFO_FORMAT: "application/json", TIME: rootState.application.currentVisibleTime.toISOString() } ); if (fmSource) { HTTP.get(fmURL, { headers: { "X-Gemma-Auth": localStorage.getItem("token") } }).then(response => { let features = response.data.features.map(f => { let feat = new Feature({ geometry: new Point(f.geometry.coordinates) }); feat.setId(f.id); feat.setProperties(f.properties); return feat; }); commit("addIdentifiedFeatures", features); }); } }); }, refreshLayers({ state }) { state.openLayersMaps.forEach(map => { let layers = map.getLayers().getArray(); for (let i = 0; i < layers.length; i++) { let layer = layers[i]; if (layer instanceof VectorLayer) { layer.getSource().clear(true); } else { layer.getSource().refresh(); } } }); }, refreshTimebasedLayers({ state, rootState }) { const layers = [ "BOTTLENECKS", "FAIRWAYDIMENSIONSLOS1", "FAIRWAYDIMENSIONSLOS2", "FAIRWAYDIMENSIONSLOS3", "WATERWAYAXIS", "FAIRWAYMARKS" ]; state.openLayersMaps.forEach(map => { for (let i = 0; i < layers.length; i++) { let layer = map.getLayer(layers[i]); if (layer instanceof VectorLayer) { layer.getSource().clear(true); } else { // Refresh layers with updated TIME value layer.getSource().updateParams({ TIME: rootState.application.refreshLayersTime.toISOString() }); layer.getSource().refresh(); } } }); }, moveToBoundingBox( { state }, { boundingBox, zoom, preventZoomOut, duration } ) { const extent = transformExtent(boundingBox, "EPSG:4326", "EPSG:3857"); const currentZoom = state.syncedView.getZoom(); zoom = zoom || currentZoom; state.syncedView.fit(extent, { maxZoom: preventZoomOut ? Math.max(zoom, currentZoom) : zoom, duration: duration || 700 }); }, moveToFeauture({ dispatch }, { feature, zoom, preventZoomOut }) { const boundingBox = feature.hasOwnProperty("geometry") ? bbox(feature.geometry) : feature .getGeometry() .clone() .transform("EPSG:3857", "EPSG:4326") .getExtent(); dispatch("moveToBoundingBox", { boundingBox, zoom, preventZoomOut }); }, moveMap({ state }, { coordinates, zoom, preventZoomOut }) { const currentZoom = state.syncedView.getZoom(); zoom = zoom || currentZoom; state.syncedView.animate({ zoom: preventZoomOut ? Math.max(zoom, currentZoom) : zoom, center: fromLonLat(coordinates, state.syncedView.getProjection()), duration: 700 }); } } };