Mercurial > gemma
view client/src/map/Maplayer.vue @ 1115:1b160eda22cf store-refactoring
moved drawMode to map store
author | Markus Kottlaender <markus@intevation.de> |
---|---|
date | Mon, 05 Nov 2018 14:05:01 +0100 |
parents | 595654ad3f66 |
children | 035dc35e1dfc |
line wrap: on
line source
<template> <div id="map" :class="mapStyle"></div> </template> <style lang="scss"> .mapsplit { height: 50vh; } .mapfull { height: 100vh; } @media print { .mapfull { width: 2000px; height: 2828px; } .mapsplit { width: 2000px; height: 2828px; } } </style> <script> /* * 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 by via donau * – Österreichische Wasserstraßen-Gesellschaft mbH * Software engineering by Intevation GmbH * * Author(s): * * Thomas Junk <thomas.junk@intevation.de> * * Bernhard E. Reiter <bernhard.reiter@intevation.de> */ import { HTTP } from "../application/lib/http"; import { mapGetters, mapState } from "vuex"; import "ol/ol.css"; import { Map, View } from "ol"; import { WFS, GeoJSON } from "ol/format.js"; import LineString from "ol/geom/LineString.js"; import Point from "ol/geom/Point.js"; import Draw from "ol/interaction/Draw.js"; import { Vector as VectorLayer } from "ol/layer.js"; import { Vector as VectorSource } from "ol/source.js"; import { getLength, getArea } from "ol/sphere.js"; import { Icon, Stroke, Style, Fill } from "ol/style.js"; import { displayError } from "../application/lib/errors.js"; import { calculateFairwayCoordinates } from "../application/lib/geo.js"; const DEMODATA = 2.5; /* for the sake of debugging */ /* eslint-disable no-console */ export default { name: "maplayer", props: ["lat", "long", "zoom", "split"], data() { return { projection: "EPSG:3857", interaction: null, vectorLayer: null, vectorSource: null }; }, computed: { ...mapGetters("map", ["layers", "getLayerByName"]), ...mapState("map", ["openLayersMap", "drawMode"]), ...mapState("bottlenecks", ["selectedSurvey"]), mapStyle() { return { mapfull: !this.split, mapsplit: this.split }; } }, methods: { drawStyleFunction(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("../application/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; }, removeCurrentInteraction() { this.$store.commit("map/setCurrentMeasurement", null); this.vectorSource.clear(); this.openLayersMap.removeInteraction(this.interaction); this.interaction = null; }, createInteraction(drawMode) { this.vectorSource.clear(); var draw = new Draw({ source: this.vectorSource, type: drawMode, maxPoints: drawMode === "LineString" ? 2 : 50 }); draw.on("drawstart", () => { this.vectorSource.clear(); this.$store.commit("map/setCurrentMeasurement", null); // we are not setting an id here, to avoid the regular identify to // pick it up // event.feature.setId("drawn.1"); // unique id for new feature }); draw.on("drawend", this.drawEnd); return draw; }, drawEnd(event) { if (this.drawMode === "Polygon") { const areaSize = getArea(event.feature.getGeometry()); // also place the a rounded areaSize in a property, // so identify will show it if (areaSize > 100000) { this.$store.commit("map/setCurrentMeasurement", { quantity: "Area", unitSymbol: "km²", // convert into 1 km² == 1000*1000 m² and round to 1000 m² value: Math.round(areaSize / 1000) / 1000 }); } else { this.$store.commit("map/setCurrentMeasurement", { quantity: "Area", unitSymbol: "m²", value: Math.round(areaSize) }); } } if (this.drawMode === "LineString") { const length = getLength(event.feature.getGeometry()); this.$store.commit("map/setCurrentMeasurement", { quantity: "Length", unitSymbol: "m", value: Math.round(length * 10) / 10 }); } // if a survey has been selected, request a profile // TODO an improvement could be to check if the line intersects // with the bottleneck area's polygon before trying the server request if (this.selectedSurvey) { this.$store.commit("fairwayprofile/clearCurrentProfile"); console.log("requesting profile for", this.selectedSurvey); const inputLineString = event.feature.getGeometry().clone(); inputLineString.transform("EPSG:3857", "EPSG:4326"); const [start, end] = inputLineString .getCoordinates() .map(coords => coords.map(coord => parseFloat(coord.toFixed(8)))); this.$store.commit("fairwayprofile/setStartPoint", start); this.$store.commit("fairwayprofile/setEndPoint", end); const profileLine = new LineString([start, end]); this.$store .dispatch("fairwayprofile/loadProfile", this.selectedSurvey) .then(() => { var vectorSource = this.getLayerByName( "Fairway Dimensions" ).data.getSource(); this.calculateIntersection(vectorSource, profileLine); }) .then(() => { this.$store.commit("application/openSplitScreen"); }) .catch(error => { const { status, data } = error.response; displayError({ title: "Backend Error", message: `${status}: ${data.message || data}` }); }); } }, calculateIntersection(vectorSource, profileLine) { const transformedLine = profileLine .clone() .transform("EPSG:4326", "EPSG:3857") .getExtent(); const featureCallback = feature => { // transform back to prepare for usage var intersectingPolygon = feature .getGeometry() .clone() .transform("EPSG:3857", "EPSG:4326"); const fairwayCoordinates = calculateFairwayCoordinates( profileLine, intersectingPolygon, DEMODATA ); this.$store.commit( "fairwayprofile/setFairwayCoordinates", fairwayCoordinates ); }; vectorSource.forEachFeatureIntersectingExtent( // need to use EPSG:3857 which is the proj of vectorSource transformedLine, featureCallback ); }, activateInteraction() { const interaction = this.createInteraction(this.drawMode); this.interaction = interaction; this.openLayersMap.addInteraction(interaction); }, identify(coordinate, pixel) { this.$store.commit("map/setIdentifiedFeatures", []); // checking our WFS layers var features = this.openLayersMap.getFeaturesAtPixel(pixel); if (features) { this.$store.commit("map/setIdentifiedFeatures", features); // get selected bottleneck from identified features for (let feature of features) { let id = feature.getId(); // RegExp.prototype.test() works with number, str and undefined if (/^bottlenecks\./.test(id)) { this.$store.dispatch("bottlenecks/setSelectedBottleneck", feature.get("objnam")); } } } // 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 = this.getLayerByName( "Inland ECDIS chart Danube" ).data.getSource(); var url = wmsSource.getGetFeatureInfoUrl( 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); } }, buildVectorLoader(featureRequestOptions, endpoint, vectorSource) { // build a function to be used for VectorSource.setLoader() // make use of WFS().writeGetFeature to build the request // and use our HTTP library to actually do it // NOTE: a) the geometryName has to be given in featureRequestOptions, // because we want to load depending on the bbox // b) the VectorSource has to have the option strategy: bbox featureRequestOptions["outputFormat"] = "application/json"; var loader = function(extent, resolution, projection) { featureRequestOptions["bbox"] = extent; featureRequestOptions["srsName"] = projection.getCode(); var featureRequest = new WFS().writeGetFeature(featureRequestOptions); // DEBUG console.log(featureRequest); HTTP.post( endpoint, new XMLSerializer().serializeToString(featureRequest), { headers: { "X-Gemma-Auth": localStorage.getItem("token"), "Content-type": "text/xml; charset=UTF-8" } } ) .then(response => { var features = new GeoJSON().readFeatures( JSON.stringify(response.data) ); vectorSource.addFeatures(features); // console.log( // "loaded", // features.length, // featureRequestOptions.featureTypes, // "features" // ); // DEBUG console.log("loaded ", features, "for", vectorSource); // eslint-disable-next-line }) .catch(() => { vectorSource.removeLoadedExtent(extent); }); }; return loader; }, updateBottleneckFilter(bottleneck_id, datestr) { console.log("updating filter with", bottleneck_id, datestr); var layer = this.getLayerByName("Bottleneck isolines"); var wmsSrc = layer.data.getSource(); if (bottleneck_id != "does_not_exist") { wmsSrc.updateParams({ cql_filter: "date_info='" + datestr + "' AND bottleneck_id='" + bottleneck_id + "'" }); layer.isVisible = true; layer.data.setVisible(true); } else { layer.isVisible = false; layer.data.setVisible(false); } }, onBeforePrint(/* evt */) { // console.log("onBeforePrint(", evt ,")"); // // the following code shows how to get the current map canvas // and change it, however this does not work well enough, as // another mechanism seems to update the size again before the rendering // for printing is done: // console.log(this.openLayersMap.getViewport()); // var canvas = this.openLayersMap.getViewport().getElementsByTagName("canvas")[0]; // console.log(canvas); // canvas.width=1000; // canvas.height=1414; // // An experiment which also did not work: // this.openLayersMap.setSize([1000, 1414]); // estimate portait DIN A4 // // according to documentation // http://openlayers.org/en/latest/apidoc/module-ol_PluggableMap-PluggableMap.html#updateSize // "Force a recalculation of the map viewport size. This should be called when third-party code changes the size of the map viewport." // but did not help // this.openLayersMap.updateSize(); }, onAfterPrint(/* evt */) { // could be used to undo changes that have been done for printing // though https://www.tjvantoll.com/2012/06/15/detecting-print-requests-with-javascript/ // reported that this was not feasable (back then). // console.log("onAfterPrint(", evt, ")"); } }, watch: { drawMode(newValue) { if (this.interaction) { this.removeCurrentInteraction(); } if (newValue) { this.activateInteraction(); } }, split() { const map = this.openLayersMap; this.$nextTick(() => { map.updateSize(); }); }, selectedSurvey(newSelectedSurvey) { if (newSelectedSurvey) { this.updateBottleneckFilter( newSelectedSurvey.bottleneck_id, newSelectedSurvey.date_info ); } else { this.updateBottleneckFilter("does_not_exist", "1999-10-01"); } } }, mounted() { this.vectorSource = new VectorSource({ wrapX: false }); this.vectorLayer = new VectorLayer({ source: this.vectorSource, style: this.drawStyleFunction }); let map = new Map({ layers: [...this.layers.map(x => x.data), this.vectorLayer], target: "map", controls: [], view: new View({ center: [this.long, this.lat], zoom: this.zoom, projection: this.projection }) }); this.$store.commit("map/setOpenLayersMap", map); // TODO make display of layers more dynamic, e.g. from a list // loading the full WFS layer, by not setting the loader function // and without bboxStrategy var featureRequest = new WFS().writeGetFeature({ srsName: "EPSG:3857", featureNS: "gemma", featurePrefix: "gemma", featureTypes: ["fairway_dimensions"], outputFormat: "application/json" }); // NOTE: loading the full fairway_dimensions makes sure // that all are available for the intersection with the profile HTTP.post( "/internal/wfs", new XMLSerializer().serializeToString(featureRequest), { headers: { "X-Gemma-Auth": localStorage.getItem("token"), "Content-type": "text/xml; charset=UTF-8" } } ).then(response => { var features = new GeoJSON().readFeatures(JSON.stringify(response.data)); var vectorSrc = this.getLayerByName( "Fairway Dimensions" ).data.getSource(); vectorSrc.addFeatures(features); // would scale to the extend of all resulting features // this.openLayersMap.getView().fit(vectorSrc.getExtent()); }); // load following layers with bboxStrategy (using our request builder) var layer = null; layer = this.getLayerByName("Waterway Area"); layer.data.getSource().setLoader( this.buildVectorLoader( { featurePrefix: "ws-wamos", featureTypes: ["ienc_wtware"], geometryName: "geom" }, "/external/d4d", layer.data.getSource() ) ); layer = this.getLayerByName("Waterway Axis"); layer.data.getSource().setLoader( this.buildVectorLoader( { featurePrefix: "ws-wamos", featureTypes: ["ienc_wtwaxs"], geometryName: "geom" }, "/external/d4d", layer.data.getSource() ) ); layer = this.getLayerByName("Distance marks"); layer.data.getSource().setLoader( this.buildVectorLoader( { featurePrefix: "ws-wamos", featureTypes: ["ienc_dismar"], geometryName: "geom" //, /* restrict loading approximately to extend of danube in Austria */ // filter: bboxFilter("geom", [13.3, 48.0, 17.1, 48.6], "EPSG:4326") }, "/external/d4d", layer.data.getSource() ) ); layer.data.setVisible(layer.isVisible); layer = this.getLayerByName("Distance marks, Axis"); layer.data.getSource().setLoader( this.buildVectorLoader( { featureNS: "gemma", featurePrefix: "gemma", featureTypes: ["distance_marks_geoserver"], geometryName: "geom" }, "/internal/wfs", layer.data.getSource() ) ); layer = this.getLayerByName("Waterway Area, named"); layer.data.getSource().setLoader( this.buildVectorLoader( { featureNS: "gemma", featurePrefix: "gemma", featureTypes: ["hydro_seaare"], geometryName: "geom" }, "/external/d4d", layer.data.getSource() ) ); layer.data.setVisible(layer.isVisible); layer = this.getLayerByName("Bottlenecks"); layer.data.getSource().setLoader( this.buildVectorLoader( { featureNS: "gemma", featurePrefix: "gemma", featureTypes: ["bottlenecks"], geometryName: "area" }, "/internal/wfs", layer.data.getSource() ) ); HTTP.get("/system/style/Bottlenecks/stroke", { headers: { "X-Gemma-Auth": localStorage.getItem("token") } }) .then(response => { this.btlnStrokeC = response.data.code; HTTP.get("/system/style/Bottlenecks/fill", { headers: { "X-Gemma-Auth": localStorage.getItem("token") } }) .then(response => { this.btlnFillC = response.data.code; var newstyle = new Style({ stroke: new Stroke({ color: this.btlnStrokeC, width: 4 }), fill: new Fill({ color: this.btlnFillC }) }); layer.data.setStyle(newstyle); }) .catch(error => { console.log(error); }); }) .catch(error => { console.log(error); }); window.addEventListener("beforeprint", this.onBeforePrint); window.addEventListener("afterprint", this.onAfterPrint); // so none is shown this.updateBottleneckFilter("does_not_exist", "1999-10-01"); this.openLayersMap.on(["singleclick", "dblclick"], event => { this.identify(event.coordinate, event.pixel); }); } }; </script>