view client/src/components/map/Map.vue @ 5715:e29c942d3e8c default tip

Colors of scale adjusted
author Thomas Junk <thomas.junk@intevation.de>
date Thu, 21 Mar 2024 08:19:21 +0100
parents b1a10654bf0f
children
line wrap: on
line source

<template>
  <div
    :id="'map-' + paneId"
    :class="['map', { nocursor: this.hasActiveInteractions }]"
  >
    <Zoom :map="map" />
  </div>
</template>

<style scoped>
.map {
  width: 100%;
  height: 100%;
  background-color: #eee;
  background-image: linear-gradient(
      45deg,
      #e8e8e8 25%,
      transparent 25%,
      transparent 75%,
      #e8e8e8 75%,
      #e8e8e8
    ),
    linear-gradient(
      45deg,
      #e8e8e8 25%,
      transparent 25%,
      transparent 75%,
      #e8e8e8 75%,
      #e8e8e8
    );
  background-size: 20px 20px;
  background-position: 0 0, 10px 10px;
}
.map.nocursor {
  cursor: none;
}
</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, 2019 by via donau
 *   – Österreichische Wasserstraßen-Gesellschaft mbH
 * Software engineering by Intevation GmbH
 *
 * Author(s):
 * * Thomas Junk <thomas.junk@intevation.de>
 * * Bernhard E. Reiter <bernhard.reiter@intevation.de>
 */

/*eslint no-unused-vars: ["error", { "argsIgnorePattern": "_" }]*/

import { HTTP } from "@/lib/http";
import { mapState } from "vuex";
import { Map, View } from "ol";
import { Stroke, Style, Fill } from "ol/style";
import { displayError } from "@/lib/errors";
import { pane } from "@/lib/mixins";
import { layerFactory } from "@/components/layers/layers";
import { ImageWMS as ImageSource } from "ol/source";
import { ScaleLine } from "ol/control.js";
import { styles } from "@/components/layers/styles";
import "ol/ol.css";

export default {
  mixins: [pane],
  components: {
    Zoom: () => import("@/components/map/Zoom")
  },
  data() {
    return {
      map: null
    };
  },
  computed: {
    ...mapState("map", [
      "initialLoad",
      "extent",
      "syncedMaps",
      "syncedView",
      "ongoingRefresh"
    ]),
    ...mapState("bottlenecks", ["selectedSurvey"]),
    ...mapState("fairwayprofile", ["additionalSurvey"]),
    ...mapState("application", [
      "paneSetup",
      "paneRotate",
      "config",
      "ongoingPDFExport"
    ]),
    ...mapState("imports", ["selectedStretchId", "selectedSectionId"]),
    layers() {
      return layerFactory(this.paneId);
    },
    hasActiveInteractions() {
      return (
        this.map &&
        this.map
          .getInteractions()
          .getArray()
          .filter(
            i =>
              ["linetool", "polygontool", "cuttool"].includes(i.get("id")) &&
              i.getActive()
          ).length
      );
    }
  },
  watch: {
    ongoingRefresh() {
      if (this.ongoingRefresh) return;
      this.loadStyles();
    },
    paneSetup() {
      this.$nextTick(() => this.map.updateSize());
    },
    paneRotate() {
      this.$nextTick(() => this.map.updateSize());
    },
    syncedMaps(syncedMaps) {
      if (syncedMaps.includes(this.paneId) || this.paneId === "main") {
        this.map.setView(this.syncedView);
      } else {
        this.map.setView(
          new View({
            enableRotation: false,
            center: [this.extent.lon, this.extent.lat],
            minZoom: 5, // restrict zooming out to ~size of Europe for width 1000px
            zoom: this.extent.zoom,
            projection: "EPSG:3857"
          })
        );
      }
    },
    selectedSurvey(survey) {
      if (this.paneId === "main") {
        if (survey) {
          this.updateBottleneckFilter(survey.bottleneck_id, survey.date_info);
        } else {
          this.updateBottleneckFilter("does_not_exist", "1999-10-01");
        }
      }
    },
    additionalSurvey(survey) {
      if (this.paneId === "compare-survey") {
        if (survey) {
          this.updateBottleneckFilter(survey.bottleneck_id, survey.date_info);
        } else {
          this.updateBottleneckFilter("does_not_exist", "1999-10-01");
        }
      }
    },
    selectedStretchId(id) {
      let stretch = this.layers.get("STRETCHES");
      stretch
        .getSource()
        .getFeatures()
        .forEach(f => {
          f.set("highlighted", false);
          f.setStyle(null);
          if (id === f.getId()) {
            f.set("highlighted", true);
            let highlight = new Style({
              fill: new Fill({
                color: this.colorLuminance(
                  stretch
                    .getStyle()
                    .getFill()
                    .getColor(),
                  0.3
                )
              }),
              stroke: new Stroke({
                color: this.colorLuminance(
                  stretch
                    .getStyle()
                    .getStroke()
                    .getColor(),
                  0.3
                ),
                width: 3
              })
            });
            f.setStyle(highlight);
          }
        });
    },
    selectedSectionId(id) {
      let section = this.layers.get("SECTIONS");
      section
        .getSource()
        .getFeatures()
        .forEach(f => {
          f.set("highlighted", false);
          if (id === f.getId()) {
            f.set("highlighted", true);
            let highlight = new Style({
              fill: new Fill({
                color: this.colorLuminance(
                  section
                    .getStyle()
                    .getFill()
                    .getColor(),
                  0.3
                )
              }),
              stroke: new Stroke({
                color: this.colorLuminance(
                  section
                    .getStyle()
                    .getStroke()
                    .getColor(),
                  0.3
                ),
                width: 4
              })
            });
            f.setStyle(highlight);
          }
        });
    }
  },
  methods: {
    colorLuminance(color, lum) {
      let [r, g, b, a] = color
        .substring(5, color.length - 1)
        .split(",")
        .map(e => Number(e));
      let [r1, g1, b1] = [r, g, b].map(e =>
        Math.round(Math.min(Math.max(0, e + e * lum), 255))
      );
      return `rgba(${r1},${g1},${b1},${a})`;
    },
    updateBottleneckFilter(bottleneck_id, datestr) {
      if (!bottleneck_id) return;
      const exists = bottleneck_id != "does_not_exist";
      if (exists) {
        this.layers
          .get("BOTTLENECKISOLINE")
          .getSource()
          .updateParams({
            cql_filter: `date_info='${datestr}' AND bottleneck_id='${bottleneck_id}'`
          });
      }
      this.layers.get("BOTTLENECKISOLINE").setVisible(exists);
    },
    initMap() {
      if (!this.syncedView) {
        this.$store.commit(
          "map/syncedView",
          new View({
            enableRotation: false,
            center: [this.extent.lon, this.extent.lat],
            minZoom: 5, // restrict zooming out to ~size of Europe for width 1000px
            zoom: this.extent.zoom,
            projection: "EPSG:3857"
          })
        );
      }

      // move to user specific default extent if map loads for the first time
      // checking initialLoad will be obsolete once we abandoned the separated admin context
      if (this.initialLoad) {
        this.$store.commit("map/initialLoad", false);
        var currentUser = this.$store.state.user.user;
        HTTP.get("/users/" + encodeURIComponent(`${currentUser}`), {
          headers: {
            "X-Gemma-Auth": localStorage.getItem("token"),
            "Content-type": "text/xml; charset=UTF-8"
          }
        })
          .then(response => {
            this.mountMap();
            this.$store.dispatch("map/moveToBoundingBox", {
              boundingBox: [
                response.data.extent.x1,
                response.data.extent.y1,
                response.data.extent.x2,
                response.data.extent.y2
              ],
              zoom: 17,
              preventZoomOut: true,
              duration: 0
            });
          })
          .catch(error => {
            this.mountMap();
            let message = "Backend not reachable";
            if (error.response) {
              const { status, data } = error.response;
              message = `${status}: ${data.message || data}`;
            }
            displayError({
              title: this.$gettext("Backend Error"),
              message: message
            });
          });
      } else {
        this.mountMap();
      }
    },
    mountMap() {
      const source = new ImageSource({
        preload: 1,
        url: this.config.ecdis_wms_url,
        crossOrigin: "anonymous",
        params: JSON.parse(this.config.ecdis_wms_params)
      });
      source.on("imageloaderror", _ => {
        if (this.ongoingPDFExport) {
          displayError({
            title: this.$gettext("Loading Error"),
            message: this.$gettext(
              "The ECDIS chart could not be loaded completely, the map might be corrupted. Please retry later"
            ),
            options: {
              timeout: 0,
              showProgressBar: false,
              closeOnClick: true,
              pauseOnHover: true,
              bodyMaxLength: 1024
            }
          });
        }
      });

      this.layers.get("INLANDECDIS").setSource(source);
      this.map = new Map({
        layers: this.layers.config,
        target: "map-" + this.paneId,
        controls: [new ScaleLine()],
        view:
          this.syncedMaps.includes(this.paneId) || this.paneId === "main"
            ? this.syncedView
            : new View({
                center: [this.extent.lon, this.extent.lat],
                minZoom: 5,
                zoom: this.extent.zoom,
                projection: "EPSG:3857"
              })
      });
      this.map.getLayer = id => this.layers.get(id);
      // store map position on every move
      // will be obsolete once we abandoned the separated admin context
      this.map.on("moveend", event => {
        const center = event.map.getView().getCenter();
        this.$store.commit("map/extent", {
          lat: center[1],
          lon: center[0],
          zoom: event.map.getView().getZoom()
        });
      });
      this.$store.dispatch("map/openLayersMap", this.map);
      this.$store.dispatch("map/initIdentifyTool", this.map);
    },
    loadStyles() {
      const getRGBA = color => {
        return (
          "rgba(" +
          parseInt(color.slice(1, 3), 16) +
          ", " +
          parseInt(color.slice(3, 5), 16) +
          ", " +
          parseInt(color.slice(5, 7), 16) +
          ", " +
          (color.length > 7 ? parseInt(color.slice(7, 9), 16) / 255 : "1") +
          ")"
        );
      };
      // load configured bottleneck colors
      HTTP.get("/system/settings", {
        headers: { "X-Gemma-Auth": localStorage.getItem("token") }
      }).then(response => {
        let btlnStrokeC = getRGBA(response.data.bottlenecks_stroke),
          btlnFillC = getRGBA(response.data.bottlenecks_fill),
          strFillC = getRGBA(response.data.stretches_fill),
          strStrokeC = getRGBA(response.data.stretches_stroke),
          secStrokeC = getRGBA(response.data.sections_stroke),
          secFillC = getRGBA(response.data.sections_fill),
          fwd1StrokeC = getRGBA(response.data.fairwaydimensionslos1_stroke),
          fwd1FillC = getRGBA(response.data.fairwaydimensionslos1_fill),
          fwd2StrokeC = getRGBA(response.data.fairwaydimensionslos2_stroke),
          fwd2FillC = getRGBA(response.data.fairwaydimensionslos2_fill),
          fwd3StrokeC = getRGBA(response.data.fairwaydimensionslos3_stroke),
          fwd3FillC = getRGBA(response.data.fairwaydimensionslos3_fill),
          wwpStokeC = getRGBA(response.data.waterwayprofiles_stroke);
        let btlnStyle = new Style({
            stroke: new Stroke({
              color: btlnStrokeC,
              width: 4
            }),
            fill: new Fill({
              color: btlnFillC
            })
          }),
          strStyle = new Style({
            stroke: new Stroke({
              color: strStrokeC,
              width: 2
            }),
            fill: new Fill({
              color: strFillC
            })
          }),
          secStyle = new Style({
            stroke: new Stroke({
              color: secStrokeC,
              width: 5
            }),
            fill: new Fill({
              color: secFillC
            })
          }),
          fwd1Style = new Style({
            stroke: new Stroke({
              lineDash: [2, 4],
              lineCap: "round",
              color: fwd1StrokeC,
              width: 2
            }),
            fill: new Fill({
              color: fwd1FillC
            })
          }),
          fwd2Style = new Style({
            stroke: new Stroke({
              lineDash: [3, 6],
              lineCap: "round",
              color: fwd2StrokeC,
              width: 2
            }),
            fill: new Fill({
              color: fwd2FillC
            })
          }),
          fwd3Style = new Style({
            stroke: new Stroke({
              color: fwd3StrokeC,
              width: 2
            }),
            fill: new Fill({
              color: fwd3FillC
            })
          }),
          wwpStyle = new Style({
            stroke: new Stroke({
              color: wwpStokeC,
              lineDash: [5, 5],
              width: 2
            })
          });
        this.layers.get("WATERWAYPROFILES").setStyle(wwpStyle);
        this.layers
          .get("FAIRWAYDIMENSIONSLOS1")
          .setStyle(() => [fwd1Style, styles.textFW1]);
        this.layers
          .get("FAIRWAYDIMENSIONSLOS2")
          .setStyle(() => [fwd2Style, styles.textFW2]);
        this.layers
          .get("FAIRWAYDIMENSIONSLOS3")
          .setStyle(() => [fwd3Style, styles.textFW3]);
        this.layers.get("SECTIONS").setStyle(secStyle);
        this.layers.get("STRETCHES").setStyle(strStyle);
        this.layers.get("BOTTLENECKS").setStyle(btlnStyle);
        this.$store.commit("gauges/deleteNashSutcliffeCache");
        this.$store.dispatch("map/refreshLayers");
      });
    }
  },
  mounted() {
    // ToDo set path to correct endpoint in order to retrieve an OSM URL
    HTTP.get("/system/config", {
      headers: { "X-Gemma-Auth": localStorage.getItem("token") }
    })
      .then(response => {
        if (response.data["osm-url"]) {
          this.layers
            .get("OPENSTREETMAP")
            .getSource()
            .setUrl(response.data["osm-url"]);
        }
        this.initMap();

        if (this.selectedSurvey && this.paneId === "main") {
          this.updateBottleneckFilter(
            this.selectedSurvey.bottleneck_id,
            this.selectedSurvey.date_info
          );
        }
        if (this.additionalSurvey && this.paneId === "compare-survey") {
          this.updateBottleneckFilter(
            this.additionalSurvey.bottleneck_id,
            this.additionalSurvey.date_info
          );
        }
        this.loadStyles();
      })
      .catch(error => {
        let message = "Backend not reachable";
        if (error.response) {
          const { status, data } = error.response;
          message = `${status}: ${data.message || data}`;
        }
        displayError({
          title: this.$gettext("Backend Error"),
          message: message
        });
      });
  },
  destroyed() {
    this.$store.commit("map/removeOpenLayersMap", this.map);
  }
};
</script>

<style>
.ol-scale-line {
  background: #fff;
  border-radius: 4px;
  bottom: 8px;
  left: 8px;
  padding: 2px;
  position: absolute;
}
.ol-scale-line-inner {
  color: #000;
  border: 1px solid #000;
  border-top: none;
}
</style>