view client/src/drawtool/Drawtool.vue @ 1140:2e06bc53b002

separating line/polygon/cut tools in UI Measurements can now be made while a bottleneck and sounding data is selected. The open layers interaction object(s) are now in the vuex store to disable them from other components (Morphtool.vue). Line and Polygon are now to separate buttons.
author Markus Kottlaender <markus@intevation.de>
date Mon, 12 Nov 2018 14:45:07 +0100
parents
children 5f98d0c9d738
line wrap: on
line source

<template>
    <div class="d-flex flex-column">
        <div @click="toggleLineMode" class="ui-element d-flex shadow drawtool">
            <i :class="['fa fa-pencil', {inverted: drawMode === 'LineString'}]"></i>
        </div>
        <div @click="togglePolygonMode" class="ui-element d-flex shadow drawtool">
            <i :class="['fa fa-edit', {inverted: drawMode === 'Polygon'}]"></i>
        </div>
        <div @click="toggleCutMode" class="ui-element d-flex shadow drawtool" v-if="selectedSurvey">
            <i :class="['fa fa-area-chart', {inverted: cutMode}]"></i>
        </div>
    </div>
</template>

<style lang="sass" scoped>
.drawtool
  background-color: white
  padding: $small-offset
  border-radius: $border-radius
  margin-left: $offset
  height: $icon-width
  width: $icon-height
  margin-bottom: $offset
  margin-right: $offset
  z-index: 2

.inverted
  color: #0077ff
</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>
 * Markus Kottländer <markus.kottlaender@intevation.de>
 */
import { mapState, mapGetters } from "vuex";
import { getLength, getArea } from "ol/sphere.js";
import LineString from "ol/geom/LineString.js";
import Draw from "ol/interaction/Draw.js";
import { displayError } from "../application/lib/errors.js";
import { calculateFairwayCoordinates } from "../application/lib/geo.js";

const DEMODATA = 2.5;

export default {
  name: "drawtool",
  computed: {
    ...mapGetters("map", ["getLayerByName"]),
    ...mapState("map", ["drawMode", "drawTool", "cutMode", "cutTool", "openLayersMap"]),
    ...mapState("bottlenecks", ["selectedSurvey"])
  },
  methods: {
    toggleLineMode() {
      this.disableDrawTool();
      this.disableCutTool();
      this.$store.commit("map/drawMode", this.drawMode !== "LineString" ? "LineString" : null);
      this.$store.commit("map/cutMode", null);
      if (this.drawMode) this.enableDrawTool();
    },
    togglePolygonMode() {
      this.disableDrawTool();
      this.disableCutTool();
      this.$store.commit("map/drawMode", this.drawMode !== "Polygon" ? "Polygon" : null);
      this.$store.commit("map/cutMode", null);
      if (this.drawMode) this.enableDrawTool();
    },
    toggleCutMode() {
      this.disableCutTool();
      this.disableDrawTool();
      this.$store.commit('map/cutMode', !this.cutMode);
      this.$store.commit("map/drawMode", null);
      if (this.cutMode) this.enableCutTool();
    },
    enableDrawTool() {
      const drawVectorSrc = this.getLayerByName("Draw Tool").data.getSource();
      drawVectorSrc.clear();
      const drawTool = new Draw({
        source: drawVectorSrc,
        type: this.drawMode,
        maxPoints: this.drawMode === "LineString" ? 2 : 50
      });
      drawTool.on("drawstart", () => {
        drawVectorSrc.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
      });
      drawTool.on("drawend", this.drawEnd);
      this.$store.commit("map/drawTool", drawTool);
      this.openLayersMap.addInteraction(drawTool);
    },
    disableDrawTool() {
      this.$store.commit("map/setCurrentMeasurement", null);
      this.getLayerByName("Draw Tool").data.getSource().clear();
      this.openLayersMap.removeInteraction(this.drawTool);
      this.$store.commit("map/drawTool", null);
    },
    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
        this.$store.commit("map/setCurrentMeasurement", {
          quantity: "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)
        });
      }
      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
        });
      }
    },
    enableCutTool() {
      const cutVectorSrc = this.getLayerByName("Cut Tool").data.getSource();
      cutVectorSrc.clear();
      const cutTool = new Draw({
        source: cutVectorSrc,
        type: "LineString",
        maxPoints: 2
      });
      cutTool.on("drawstart", () => {
        cutVectorSrc.clear();
        // 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
      });
      cutTool.on("drawend", this.cutEnd);
      this.$store.commit("map/cutTool", cutTool);
      this.openLayersMap.addInteraction(cutTool);
    },
    disableCutTool() {
      this.$store.commit("map/setCurrentMeasurement", null);
      this.getLayerByName("Cut Tool").data.getSource().clear();
      this.openLayersMap.removeInteraction(this.cutTool);
      this.$store.commit("map/cutTool", null);
    },
    cutEnd(event) {
      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/showSplitscreen", true);
          })
          .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
      );
    },
  }
};
</script>