view client/src/store/map.js @ 5560:f2204f91d286

Join the log lines of imports to the log exports to recover data from them. Used in SR export to extract information that where in the meta json but now are only found in the log.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Wed, 09 Feb 2022 18:34:40 +0100
parents 482046e5b63b
children
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 { Circle, Fill, Stroke, Style } from "ol/style";
import { getArea, getLength } from "ol/sphere";

import Draw from "ol/interaction/Draw";
import Feature from "ol/Feature";
import { HTTP } from "@/lib/http";
import Point from "ol/geom/Point";
import { Vector as VectorLayer } from "ol/layer";
import app from "@/main";
import bbox from "@turf/bbox";
import { fromLonLat } from "ol/proj";
import { toLonLat } from "ol/proj";
import { transformExtent } from "ol/proj";

// initial state
const init = () => {
  return {
    oldLayerVisibility: [],
    singleLayerVisible: false,
    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,
    ongoingTimeSlide: 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: {
    viewSingleLayer: (state, layerId) => {
      state.singleLayerVisible = !state.singleLayerVisible;
      if (state.singleLayerVisible) {
        state.oldLayerVisibility = state.openLayersMaps.reduce((o, m) => {
          let current = [];
          m.getLayers().forEach(l => {
            current.push(l.getVisible());
            l.setVisible(l.get("id") == layerId);
          });
          o.push(current);
          return o;
        }, []);
      } else {
        state.oldLayerVisibility.forEach((m, i) => {
          let layers = state.openLayersMaps[i].getLayers().getArray();
          m.forEach((visible, index) => {
            layers[index].setVisible(visible);
          });
        });
      }
    },
    reviewActive: (state, active) => {
      state.reviewActive = active;
    },
    startRefreshLayers: state => {
      state.ongoingRefresh = true;
    },
    finishRefreshLayers: state => {
      state.ongoingRefresh = false;
    },
    startTimeSlide: state => {
      state.ongoingTimeSlide = true;
    },
    finishTimeSlide: state => {
      state.ongoingTimeSlide = 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);
          });
        }
        var srSource = map.getLayer("BOTTLENECKISOLINE").getSource();
        var srUrl = srSource.getGetFeatureInfoUrl(
          event.coordinate,
          currentResolution /* resolution */,
          "EPSG:3857",
          // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d
          { INFO_FORMAT: "application/json" }
        );

        if (srUrl) {
          HTTP.get(srUrl + "&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 fmSource = map.getLayer("FAIRWAYMARKS").getSource();
        var fmURL = fmSource.getGetFeatureInfoUrl(
          event.coordinate,
          currentResolution,
          "EPSG:3857",
          {
            INFO_FORMAT: "application/json",
            TIME: rootState.application.currentVisibleTime.toISOString()
          }
        );
        if (fmSource) {
          HTTP.get(fmURL, {
            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);
          } else {
            layer.getSource().refresh();
          }
        }
      });
    },
    refreshTimebasedLayers({ state, rootState }) {
      const layers = [
        "BOTTLENECKS",
        "FAIRWAYDIMENSIONSLOS1",
        "FAIRWAYDIMENSIONSLOS2",
        "FAIRWAYDIMENSIONSLOS3",
        "WATERWAYAXIS",
        "FAIRWAYMARKS"
      ];
      state.openLayersMaps.forEach(map => {
        for (let i = 0; i < layers.length; i++) {
          let layer = map.getLayer(layers[i]);
          if (layer instanceof VectorLayer) {
            layer.getSource().clear(true);
          } else {
            // Refresh layers with updated TIME value
            layer.getSource().updateParams({
              TIME: rootState.application.refreshLayersTime.toISOString()
            });
            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
      });
    }
  }
};