Mercurial > gemma
view client/src/store/map.js @ 3678:8f58851927c0
client: make layer factory only return new layer config for individual maps
instead of each time it is invoked. The purpose of the factory was to support multiple maps with individual layers.
But returning a new config each time it is invoked leads to bugs that rely on the layer's state. Now this factory
reuses the same objects it created before, per map.
author | Markus Kottlaender <markus@intevation.de> |
---|---|
date | Mon, 17 Jun 2019 17:31:35 +0200 |
parents | c0f5f62343c9 |
children | 45eab8e9b580 |
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 Draw from "ol/interaction/Draw"; import { Stroke, Style, Fill, Circle } from "ol/style"; import { fromLonLat } from "ol/proj"; import { getLength, getArea } from "ol/sphere"; import { transformExtent } from "ol/proj"; import bbox from "@turf/bbox"; import app from "@/main"; import { HTTP } from "@/lib/http"; import Feature from "ol/Feature"; import Point from "ol/geom/Point"; import { Vector as VectorLayer } from "ol/layer"; import { toLonLat } from "ol/proj"; // initial state const init = () => { return { 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: "" }; }; 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: { 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 }); } } } // DEBUG output and example how to remove the GeometryName /* for (let feature of features) { console.log("Identified:", feature.getId()); for (let key of feature.getKeys()) { if (key != feature.getGeometryName()) { console.log(key, feature.get(key)); } } } */ 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); }); } // trying the GetFeatureInfo way for WMS var iecdisSource = map.getLayer("INLANDECDIS").getSource(); var iecdisUrl = iecdisSource.getGetFeatureInfoUrl( event.coordinate, currentResolution /* resolution */, "EPSG:3857", // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d { INFO_FORMAT: "text/plain" } ); if (iecdisUrl) { // cannot directly query here because of SOP console.log("GetFeatureInfo url:", iecdisUrl); } }); }, 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.get("source").loader_.name != "VOID" ) { layer.getSource().clear(true); layer.getSource().refresh({ force: true }); } } }); }, 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 }); } } };