Mercurial > gemma
view client/src/store/map.js @ 2787:2ce5ee6d65b5
client:translations: merge translations from weblate
author | Fadi Abbud <fadi.abbud@intevation.de> |
---|---|
date | Mon, 25 Mar 2019 10:34:33 +0100 |
parents | b79f5c5404c2 |
children | b6886706b325 |
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 { HTTP } from "../lib/http"; import TileWMS from "ol/source/TileWMS.js"; import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer.js"; import OSM from "ol/source/OSM"; import Draw from "ol/interaction/Draw.js"; import { Icon, Stroke, Style, Fill, Text, Circle } from "ol/style.js"; import VectorSource from "ol/source/Vector.js"; import Point from "ol/geom/Point.js"; import { bbox as bboxStrategy } from "ol/loadingstrategy"; import { HTTP } from "../lib/http"; import { fromLonLat } from "ol/proj"; import { getLength, getArea } from "ol/sphere.js"; import { unByKey } from "ol/Observable"; import { getCenter } from "ol/extent"; import { transformExtent } from "ol/proj.js"; import bbox from "@turf/bbox"; import app from "../main"; const LAYERS = { OPENSTREETMAP: "Open Streetmap", INLANDECDIS: "Inland ECDIS chart Danube", WATERWAYAREA: "Waterway Area", STRETCHES: "Stretches", FAIRWAYDIMENSIONSLOS1: "LOS 1 Fairway Dimensions", FAIRWAYDIMENSIONSLOS2: "LOS 2 Fairway Dimensions", FAIRWAYDIMENSIONSLOS3: "LOS 3 Fairway Dimensions", WATERWAYAXIS: "Waterway Axis", WATERWAYPROFILES: "Waterway Profiles", BOTTLENECKS: "Bottlenecks", BOTTLENECKSTATUS: "Critical Bottlenecks", BOTTLENECKISOLINE: "Bottleneck isolines", DISTANCEMARKS: "Distance marks", DISTANCEMARKSAXIS: "Distance marks, Axis", GAUGES: "Gauges", DRAWTOOL: "Draw Tool", CUTTOOL: "Cut Tool" }; const moveMap = ({ view, extent, zoom, preventZoomOut }) => { const currentZoom = view.getZoom(); zoom = zoom || currentZoom; view.fit(extent, { maxZoom: preventZoomOut ? Math.max(zoom, currentZoom) : zoom, duration: 700 }); }; // initial state const init = () => { return { openLayersMap: null, initialLoad: true, extent: { lat: 6155376, lon: 1819178, zoom: 11 }, identifyTool: null, // event binding (singleclick, dblclick) identifiedFeatures: [], // map features identified by clicking on the map currentMeasurement: null, // distance or area from line-/polygon-/cutTool lineTool: null, // open layers interaction object (Draw) polygonTool: null, // open layers interaction object (Draw) cutTool: null, // open layers interaction object (Draw) isolinesLegendImgDataURL: "", layers: [ { name: LAYERS.OPENSTREETMAP, data: new TileLayer({ source: new OSM() }), isVisible: true, showInLegend: true }, { name: LAYERS.INLANDECDIS, data: new TileLayer({ source: new TileWMS({ preload: 1, url: "https://service.d4d-portal.info/wms/", crossOrigin: "anonymous", params: { LAYERS: "d4d", VERSION: "1.1.1", TILED: true } }) }), isVisible: true, showInLegend: true }, { name: LAYERS.WATERWAYAREA, data: new VectorLayer({ source: new VectorSource({ strategy: bboxStrategy }), style: new Style({ stroke: new Stroke({ color: "rgba(0, 102, 0, 1)", width: 2 }) }) }), isVisible: true, showInLegend: true }, { name: LAYERS.STRETCHES, data: new VectorLayer({ source: new VectorSource({ strategy: bboxStrategy }), style: new Style({ stroke: new Stroke({ color: "rgba(250, 200, 0, .8)", width: 2 }), fill: new Fill({ color: "rgba(250, 200, 10, .3)" }) }) }), isVisible: false, showInLegend: true }, { name: LAYERS.FAIRWAYDIMENSIONSLOS3, data: new VectorLayer({ source: new VectorSource(), style: function() { return [ new Style({ stroke: new Stroke({ color: "rgba(0, 0, 255, 1.0)", width: 2 }), fill: new Fill({ color: "rgba(255, 255, 255, 0.4)" }) }), new Style({ text: new Text({ font: 'bold 12px "Open Sans", "sans-serif"', placement: "line", fill: new Fill({ color: "black" }), text: "LOS: 3" //, zIndex: 10 }) }) ]; } }), isVisible: true, showInLegend: true }, { name: LAYERS.FAIRWAYDIMENSIONSLOS2, data: new VectorLayer({ source: new VectorSource(), style: function() { return [ new Style({ stroke: new Stroke({ color: "rgba(0, 0, 255, 0.9)", lineDash: [3, 6], lineCap: "round", width: 2 }), fill: new Fill({ color: "rgba(240, 230, 0, 0.1)" }) }), new Style({ text: new Text({ font: 'bold 12px "Open Sans", "sans-serif"', placement: "line", fill: new Fill({ color: "black" }), text: "LOS: 2" //, zIndex: 10 }) }) ]; } }), isVisible: false, showInLegend: true }, { name: LAYERS.FAIRWAYDIMENSIONSLOS1, data: new VectorLayer({ source: new VectorSource(), style: function() { return [ new Style({ stroke: new Stroke({ color: "rgba(0, 0, 255, 0.8)", lineDash: [2, 4], lineCap: "round", width: 2 }), fill: new Fill({ color: "rgba(240, 230, 0, 0.2)" }) }), new Style({ text: new Text({ font: 'bold 12px "Open Sans", "sans-serif"', placement: "line", fill: new Fill({ color: "black" }), text: "LOS: 1" //, zIndex: 10 }) }) ]; } }), isVisible: false, showInLegend: true }, { name: LAYERS.WATERWAYAXIS, data: new VectorLayer({ source: new VectorSource({ strategy: bboxStrategy }), style: new Style({ stroke: new Stroke({ color: "rgba(0, 0, 255, .5)", lineDash: [5, 5], width: 2 }) }), // TODO: Set layer in layertree active/inactive depending on // resolution. maxResolution: 5, minResolution: 0 }), isVisible: true, showInLegend: true }, { name: LAYERS.WATERWAYPROFILES, data: new VectorLayer({ source: new VectorSource({ strategy: bboxStrategy }), style: new Style({ stroke: new Stroke({ color: "rgba(0, 0, 255, .5)", lineDash: [5, 5], width: 2 }) }), maxResolution: 2.5, minResolution: 0 }), isVisible: true, showInLegend: true }, { name: LAYERS.BOTTLENECKS, data: new VectorLayer({ source: new VectorSource({ strategy: bboxStrategy }), style: function() { return new Style({ stroke: new Stroke({ color: "rgba(230, 230, 10, .8)", width: 4 }), fill: new Fill({ color: "rgba(230, 230, 10, .3)" }) }); } }), isVisible: true, showInLegend: true }, { name: LAYERS.BOTTLENECKISOLINE, data: new TileLayer({ source: new TileWMS({ preload: 0, projection: "EPSG:3857", url: window.location.origin + "/api/internal/wms", params: { LAYERS: "sounding_results_contour_lines_geoserver", VERSION: "1.1.1", TILED: true }, tileLoadFunction: function(tile, src) { // console.log("calling for", tile, src); HTTP.get(src, { headers: { "X-Gemma-Auth": localStorage.getItem("token") }, responseType: "blob" }).then(response => { tile.getImage().src = URL.createObjectURL(response.data); }); } // TODO tile.setState(TileState.ERROR); }) }), isVisible: false, showInLegend: true }, { name: LAYERS.BOTTLENECKSTATUS, forLegendStyle: { point: true, resolution: 16 }, data: new VectorLayer({ source: new VectorSource({ strategy: bboxStrategy }), style: function(feature, resolution, isLegend) { let styles = []; if ((feature.get("fa_critical") && resolution > 15) || isLegend) { let bnCenter = getCenter(feature.getGeometry().getExtent()); styles.push( new Style({ geometry: new Point(bnCenter), image: new Icon({ src: require("../assets/marker-bottleneck-critical.png"), anchor: [0.5, 0.5], scale: isLegend ? 0.5 : 1 }) }) ); } if (feature.get("fa_critical") && !isLegend) { styles.push( new Style({ stroke: new Stroke({ color: "rgba(255, 0, 0, 1)", width: 4 }) }) ); } return styles; } }), isVisible: true, showInLegend: true }, { name: LAYERS.DISTANCEMARKS, forLegendStyle: { point: true, resolution: 8 }, data: new VectorLayer({ source: new VectorSource({ strategy: bboxStrategy }) }), isVisible: false, showInLegend: true }, { name: LAYERS.DISTANCEMARKSAXIS, forLegendStyle: { point: true, resolution: 8 }, data: new VectorLayer({ source: new VectorSource({ strategy: bboxStrategy }), style: function(feature, resolution) { if (resolution < 10) { var s = new Style({ image: new Circle({ radius: 5, fill: new Fill({ color: "rgba(255, 0, 0, 0.1)" }), stroke: new Stroke({ color: "blue", width: 1 }) }) }); if (resolution < 6) { s.setText( new Text({ offsetY: 12, font: '10px "Open Sans", "sans-serif"', fill: new Fill({ color: "black" }), text: (feature.get("hectometre") / 10).toString() }) ); } return s; } else { return []; } } }), isVisible: true, showInLegend: true }, { name: LAYERS.GAUGES, forLegendStyle: { point: true, resolution: 8 }, data: new VectorLayer({ source: new VectorSource({ strategy: bboxStrategy }), style: function(feature, resolution, isLegend) { return [ new Style({ image: new Icon({ src: require("../assets/marker-gauge.png"), anchor: [0.5, isLegend ? 0.5 : 1], scale: isLegend ? 0.5 : 1 }), text: new Text({ font: '10px "Open Sans", "sans-serif"', offsetY: 8, fill: new Fill({ color: "white" }), text: feature.get("objname") }) }), new Style({ text: new Text({ font: '10px "Open Sans", "sans-serif"', offsetY: 7, offsetX: -1, fill: new Fill({ color: "black" }), text: feature.get("objname") }) }) ]; }, maxResolution: 100, minResolution: 0 }), isVisible: true, showInLegend: true }, { name: LAYERS.DRAWTOOL, data: new VectorLayer({ source: new VectorSource({ wrapX: false }), style: function(feature) { // adapted from OpenLayer's LineString Arrow Example var geometry = feature.getGeometry(); var styles = [ // linestring new Style({ stroke: new Stroke({ color: "#369aca", width: 2 }) }) ]; if (geometry.getType() === "LineString") { geometry.forEachSegment(function(start, end) { var dx = end[0] - start[0]; var dy = end[1] - start[1]; var rotation = Math.atan2(dy, dx); // arrows styles.push( new Style({ geometry: new Point(end), image: new Icon({ // we need to make sure the image is loaded by Vue Loader src: require("../assets/linestring_arrow.png"), // fiddling with the anchor's y value does not help to // position the image more centered on the line ending, as the // default line style seems to be slightly uncentered in the // anti-aliasing, but the image is not placed with subpixel // precision anchor: [0.75, 0.5], rotateWithView: true, rotation: -rotation }) }) ); }); } return styles; } }), isVisible: true, showInLegend: false }, { name: LAYERS.CUTTOOL, data: new VectorLayer({ source: new VectorSource({ wrapX: false }), style: function(feature) { // adapted from OpenLayer's LineString Arrow Example var geometry = feature.getGeometry(); var styles = [ // linestring new Style({ stroke: new Stroke({ color: "#333333", width: 2, lineDash: [7, 7] }) }) ]; if (geometry.getType() === "LineString") { geometry.forEachSegment(function(start, end) { var dx = end[0] - start[0]; var dy = end[1] - start[1]; var rotation = Math.atan2(dy, dx); // arrows styles.push( new Style({ geometry: new Point(end), image: new Icon({ // we need to make sure the image is loaded by Vue Loader src: require("../assets/linestring_arrow_grey.png"), // fiddling with the anchor's y value does not help to // position the image more centered on the line ending, as the // default line style seems to be slightly uncentered in the // anti-aliasing, but the image is not placed with subpixel // precision anchor: [0.75, 0.5], rotateWithView: true, rotation: -rotation }) }) ); }); } return styles; } }), isVisible: true, showInLegend: false } ] }; }; export default { init, namespaced: true, state: init(), getters: { layersForLegend: state => { return state.layers.filter(layer => layer.showInLegend); }, getLayerByName: state => name => { return state.layers.find(layer => layer.name === name); }, getVSourceByName: (state, getters) => name => { return getters.getLayerByName(name).data.getSource(); }, filteredIdentifiedFeatures: state => { return state.identifiedFeatures.filter(f => f.getId()); } }, mutations: { initialLoad: (state, initialLoad) => { state.initialLoad = initialLoad; }, extent: (state, extent) => { state.extent = extent; }, setLayerVisible: (state, name) => { const layer = state.layers.findIndex(l => l.name === name); state.layers[layer].isVisible = true; state.layers[layer].data.setVisible(true); }, setLayerInvisible: (state, name) => { const layer = state.layers.findIndex(l => l.name === name); state.layers[layer].isVisible = false; state.layers[layer].data.setVisible(false); }, toggleVisibilityByName: (state, name) => { const layer = state.layers.findIndex(l => l.name === name); state.layers[layer].isVisible = !state.layers[layer].isVisible; state.layers[layer].data.setVisible(state.layers[layer].isVisible); }, toggleVisibility: (state, layer) => { state.layers[layer].isVisible = !state.layers[layer].isVisible; state.layers[layer].data.setVisible(state.layers[layer].isVisible); }, openLayersMap: (state, map) => { state.openLayersMap = map; }, identifyTool: (state, events) => { state.identifyTool = events; }, setIdentifiedFeatures: (state, identifiedFeatures) => { state.identifiedFeatures = identifiedFeatures; }, setCurrentMeasurement: (state, measurement) => { state.currentMeasurement = measurement; }, lineTool: (state, lineTool) => { state.lineTool = lineTool; }, polygonTool: (state, polygonTool) => { state.polygonTool = polygonTool; }, cutTool: (state, cutTool) => { state.cutTool = cutTool; }, moveToBoundingBox: (state, { boundingBox, zoom, preventZoomOut }) => { const extent = transformExtent(boundingBox, "EPSG:4326", "EPSG:3857"); let view = state.openLayersMap.getView(); moveMap({ view, extent, zoom, preventZoomOut }); }, moveToExtent: (state, { feature, zoom, preventZoomOut }) => { const boundingBox = bbox(feature.geometry); const extent = transformExtent(boundingBox, "EPSG:4326", "EPSG:3857"); let view = state.openLayersMap.getView(); moveMap({ view, extent, zoom, preventZoomOut }); }, moveMap: (state, { coordinates, zoom, preventZoomOut }) => { let view = state.openLayersMap.getView(); const currentZoom = view.getZoom(); zoom = zoom || currentZoom; view.animate({ zoom: preventZoomOut ? Math.max(zoom, currentZoom) : zoom, center: fromLonLat(coordinates, view.getProjection()), duration: 700 }); }, isolinesLegendImgDataURL: (state, isolinesLegendImgDataURL) => { state.isolinesLegendImgDataURL = isolinesLegendImgDataURL; } }, actions: { openLayersMap({ commit, dispatch, getters }, map) { const drawVectorSrc = getters.getVSourceByName("Draw Tool"); const cutVectorSrc = getters.getVSourceByName("Cut Tool"); // init line tool const lineTool = new Draw({ source: drawVectorSrc, type: "LineString", maxPoints: 2 }); lineTool.setActive(false); lineTool.on("drawstart", () => { drawVectorSrc.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 }); polygonTool.setActive(false); polygonTool.on("drawstart", () => { drawVectorSrc.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, 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.setActive(false); cutTool.on("drawstart", () => { dispatch("disableIdentifyTool"); cutVectorSrc.clear(); }); cutTool.on("drawend", event => { commit("fairwayprofile/selectedCut", null, { root: true }); dispatch("fairwayprofile/cut", event.feature, { root: true }).then(() => // This setTimeout is an ugly workaround. If we would enable the // identifyTool here immediately then the click event from ending the // cut will trigger it. We don't want that. setTimeout(() => dispatch("enableIdentifyTool"), 1000) ); }); map.addInteraction(lineTool); map.addInteraction(cutTool); map.addInteraction(polygonTool); commit("lineTool", lineTool); commit("polygonTool", polygonTool); commit("cutTool", cutTool); commit("openLayersMap", map); }, disableIdentifyTool({ state }) { unByKey(state.identifyTool); state.identifyTool = null; }, enableIdentifyTool({ state, rootState, commit, dispatch, getters }) { if (!state.identifyTool) { state.identifyTool = state.openLayersMap.on( ["singleclick", "dblclick"], event => { commit("setIdentifiedFeatures", []); // checking our WFS layers var features = state.openLayersMap.getFeaturesAtPixel(event.pixel, { hitTolerance: 7 }); if (features) { let identifiedFeatures = []; for (let feature of features) { let id = feature.getId(); // avoid identifying the same feature twice if ( identifiedFeatures.findIndex( f => f.getId() === feature.getId() ) === -1 ) { identifiedFeatures.push(feature); } // get selected bottleneck // RegExp.prototype.test() works with number, str and undefined if (/^bottlenecks/.test(id)) { if ( rootState.bottlenecks.selectedBottleneck != feature.get("objnam") ) { dispatch( "bottlenecks/setSelectedBottleneck", feature.get("objnam"), { root: true } ).then(() => { this.commit("bottlenecks/setFirstSurveySelected"); }); commit("moveMap", { coordinates: getCenter( feature .getGeometry() .clone() .transform("EPSG:3857", "EPSG:4326") .getExtent() ), zoom: 17, preventZoomOut: true }); } } // get selected gauge if (/^gauges/.test(id)) { if ( rootState.gauges.selectedGaugeISRS !== feature.get("isrs_code") ) { dispatch( "gauges/selectedGaugeISRS", feature.get("isrs_code"), { root: true } ); commit("moveMap", { coordinates: getCenter( feature .getGeometry() .clone() .transform("EPSG:3857", "EPSG:4326") .getExtent() ), zoom: null, preventZoomOut: true }); } } } commit("setIdentifiedFeatures", identifiedFeatures); } // 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)); } } } */ // trying the GetFeatureInfo way for WMS var wmsSource = getters.getVSourceByName( "Inland ECDIS chart Danube" ); var url = wmsSource.getGetFeatureInfoUrl( event.coordinate, 100 /* resolution */, "EPSG:3857", // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d { INFO_FORMAT: "text/plain" } ); if (url) { // cannot directly query here because of SOP console.log("GetFeatureInfo url:", url); } } ); } } } }; export { LAYERS };