view client/src/store/map.js @ 3053:61e4feb04a35

client: avoid code duplication
author Markus Kottlaender <markus@intevation.de>
date Tue, 16 Apr 2019 08:42:09 +0200
parents 01210542e028
children 1ef2f4179d30
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";

const moveMap = ({ view, extent, zoom, preventZoomOut }) => {
  const currentZoom = view.getZoom();
  zoom = zoom || currentZoom;
  view.fit(extent, {
    maxZoom: preventZoomOut ? Math.max(zoom, currentZoom) : zoom,
    duration: 700
  });
};

// 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
    lineTool: null, // open layers interaction object (Draw)
    polygonTool: null, // open layers interaction object (Draw)
    cutTool: null, // open layers interaction object (Draw)
    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;
    },
    lineTool: (state, lineTool) => {
      state.lineTool = lineTool;
    },
    polygonTool: (state, polygonTool) => {
      state.polygonTool = polygonTool;
    },
    cutTool: (state, cutTool) => {
      state.cutTool = cutTool;
    },
    isolinesLegendImgDataURL: (state, isolinesLegendImgDataURL) => {
      state.isolinesLegendImgDataURL = isolinesLegendImgDataURL;
    },
    differencesLegendImgDataURL: (state, differencesLegendImgDataURL) => {
      state.differencesLegendImgDataURL = differencesLegendImgDataURL;
    }
  },
  actions: {
    openLayersMap({ 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.setActive(false);
      lineTool.on("drawstart", () => {
        drawVectorSrc.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.setActive(false);
      polygonTool.on("drawstart", () => {
        drawVectorSrc.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.setActive(false);
      cutTool.on("drawstart", () => {
        commit("identifyToolEnabled", false);
        cutVectorSrc.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);

      commit("lineTool", lineTool);
      commit("polygonTool", polygonTool);
      commit("cutTool", cutTool);
      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();
      moveMap({ view, extent, zoom, preventZoomOut });
    },
    moveToExtent({ 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
      });
    }
  }
};