view client/src/store/map.js @ 4488:bff6c5c1db4f

client: pdf-gen: improve adding bottleneck info to pdf * Check if the bottleneck is in the current view to add its info to the exported pdf and the pdf filename, this avoid wrong filename and wrong info in pdf in case view has been changed to another location. * Set the bottleneck to print after moving to it in map.
author Fadi Abbud <fadi.abbud@intevation.de>
date Fri, 27 Sep 2019 11:15:02 +0200
parents dc9c150243df
children ad271887fd8d
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: "",
    ongoingRefresh: false,
    reviewActive: false
  };
};

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: {
    reviewActive: (state, active) => {
      state.reviewActive = active;
    },
    startRefreshLayers: state => {
      state.ongoingRefresh = true;
    },
    finishRefreshLayers: state => {
      state.ongoingRefresh = false;
    },
    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 });
            }
          }
        }

        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);
          });
        }
      });
    },
    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.getSource().clear(true);
          }
          layer.getSource().refresh();
        }
      });
    },
    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
      });
    }
  }
};