view client/src/store/map.js @ 3062:87e0422cffa7

client: draw/cut tools work now with multiple maps
author Markus Kottlaender <markus@intevation.de>
date Tue, 16 Apr 2019 12:49:29 +0200
parents 1ef2f4179d30
children 333aff79197d
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 { getCenter } from "ol/extent";
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";

// initial state
const init = () => {
  return {
    openLayersMaps: [],
    initialLoad: true,
    extent: {
      lat: 6155376,
      lon: 1819178,
      zoom: 11
    },
    identifyToolEnabled: true, // event binding (singleclick, dblclick)
    identifiedFeatures: [], // map features identified by clicking on the map
    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 => {
      return state.openLayersMaps.length ? state.openLayersMaps[0] : null;
    },
    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);
      }
    },
    identifyToolEnabled: (state, enabled) => {
      state.identifyToolEnabled = enabled;
    },
    setIdentifiedFeatures: (state, identifiedFeatures) => {
      state.identifiedFeatures = identifiedFeatures;
    },
    addIdentifiedFeatures: (state, identifiedFeatures) => {
      state.identifiedFeatures = state.identifiedFeatures.concat(
        identifiedFeatures
      );
    },
    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
      });
      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
      });
      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,
        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", () => {
        commit("identifyToolEnabled", false);
        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 }).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(() => commit("identifyToolEnabled", true), 1000)
        );
      });

      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 => {
        if (!state.identifyToolEnabled) return;
        commit("setIdentifiedFeatures", []);
        // checking our WFS layers
        var features = map.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");
                });
                dispatch("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
                });
                dispatch("moveMap", {
                  coordinates: getCenter(
                    feature
                      .getGeometry()
                      .clone()
                      .transform("EPSG:3857", "EPSG:4326")
                      .getExtent()
                  ),
                  zoom: null,
                  preventZoomOut: true
                });
              }
            }

            // get selected stretch
            if (/^stretches/.test(id)) {
              if (rootState.imports.selectedStretchId === feature.getId()) {
                commit("imports/selectedStretchId", null, { root: true });
              } else {
                commit("imports/selectedStretchId", feature.getId(), {
                  root: true
                });
                dispatch("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));
              }
            }
          }
          */

        var waterwayAxisSource = map.getLayer("WATERWAYAXIS").getSource();
        var waxisUrl = waterwayAxisSource.getGetFeatureInfoUrl(
          event.coordinate,
          100 /* 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,
          100 /* resolution */,
          "EPSG:3857",
          // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d
          { INFO_FORMAT: "application/json" }
        );

        if (wareaUrl) {
          // cannot directly query here because of SOP
          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 dmaSource = map.getLayer("DISTANCEMARKSAXIS").getSource();
        var dmaUrl = dmaSource.getGetFeatureInfoUrl(
          event.coordinate,
          100 /* resolution */,
          "EPSG:3857",
          // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d
          { INFO_FORMAT: "application/json" }
        );

        if (dmaUrl) {
          HTTP.get(dmaUrl, {
            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,
          100 /* 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);
        }
      });
    },
    moveToBoundingBox({ getters }, { boundingBox, zoom, preventZoomOut }) {
      const extent = transformExtent(boundingBox, "EPSG:4326", "EPSG:3857");
      let view = getters.openLayersMap.getView();
      const currentZoom = view.getZoom();
      zoom = zoom || currentZoom;
      view.fit(extent, {
        maxZoom: preventZoomOut ? Math.max(zoom, currentZoom) : zoom,
        duration: 700
      });
    },
    moveToFeauture({ dispatch }, { feature, zoom, preventZoomOut }) {
      const boundingBox = bbox(feature.geometry);
      dispatch("moveToBoundingBox", { boundingBox, zoom, preventZoomOut });
    },
    moveMap({ getters }, { coordinates, zoom, preventZoomOut }) {
      let view = getters.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
      });
    }
  }
};