view client/src/components/map/Maplayer.vue @ 1372:553aadd97087

new cross profile workflow (WIP) Needs fixing of some bugs and not so nice looks.
author Markus Kottlaender <markus@intevation.de>
date Tue, 27 Nov 2018 12:59:26 +0100
parents ca33ad696594
children b350b0b5cb6c
line wrap: on
line source

<template>
  <div id="map" :class="mapStyle"></div>
</template>

<style lang="sass" scoped>
.nocursor
  cursor: none

.mapsplit
  height: 50vh

.mapfull
  height: 100vh

@media print
  .mapfull
    width: 2000px
    height: 2828px

  .mapsplit
    width: 2000px
    height: 2828px
</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 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>
 */
import { HTTP } from "../../lib/http";
import { mapGetters, mapState } from "vuex";
import "ol/ol.css";
import { Map, View } from "ol";
import { WFS, GeoJSON } from "ol/format.js";
import { Stroke, Style, Fill } from "ol/style.js";
import { getCenter } from "ol/extent";

/* for the sake of debugging */
/* eslint-disable no-console */
export default {
  name: "maplayer",
  props: ["split"],
  data() {
    return {
      projection: "EPSG:3857"
    };
  },
  computed: {
    ...mapGetters("map", ["getLayerByName"]),
    ...mapState("map", [
      "extent",
      "layers",
      "openLayersMap",
      "lineTool",
      "polygonTool",
      "cutTool"
    ]),
    ...mapState("bottlenecks", ["selectedSurvey"]),
    mapStyle() {
      return {
        mapfull: !this.split,
        mapsplit: this.split,
        nocursor: this.hasActiveInteractions
      };
    },
    hasActiveInteractions() {
      return (
        (this.lineTool && this.lineTool.getActive()) ||
        (this.polygonTool && this.polygonTool.getActive()) ||
        (this.cutTool && this.cutTool.getActive())
      );
    }
  },
  methods: {
    identify(coordinate, pixel) {
      if (!this.hasActiveInteractions) {
        this.$store.commit("map/setIdentifiedFeatures", []);
        // checking our WFS layers
        var features = this.openLayersMap.getFeaturesAtPixel(pixel);
        if (features) {
          this.$store.commit("map/setIdentifiedFeatures", features);

          // get selected bottleneck from identified features
          for (let feature of features) {
            let id = feature.getId();
            // RegExp.prototype.test() works with number, str and undefined
            if (/^bottlenecks\./.test(id)) {
              this.$store.dispatch(
                "bottlenecks/setSelectedBottleneck",
                feature.get("objnam")
              );
              this.$store.commit("map/moveMap", {
                coordinates: getCenter(
                  feature
                    .getGeometry()
                    .clone()
                    .transform("EPSG:3857", "EPSG:4326")
                    .getExtent()
                ),
                zoom: 17,
                preventZoomOut: true
              });
            }
          }
        }

        // 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));
            }
          }
        }
        */

        // trying the GetFeatureInfo way for WMS
        var wmsSource = this.getLayerByName(
          "Inland ECDIS chart Danube"
        ).data.getSource();
        var url = wmsSource.getGetFeatureInfoUrl(
          coordinate,
          100 /* resolution */,
          "EPSG:3857",
          // { INFO_FORMAT: "application/vnd.ogc.gml" } // not allowed by d4d
          { INFO_FORMAT: "text/plain" }
        );

        if (url) {
          // cannot directly query here because of SOP
          console.log("GetFeatureInfo url:", url);
        }
      }
    },
    buildVectorLoader(featureRequestOptions, endpoint, vectorSource) {
      // build a function to be used for VectorSource.setLoader()
      // make use of WFS().writeGetFeature to build the request
      // and use our HTTP library to actually do it
      // NOTE: a) the geometryName has to be given in featureRequestOptions,
      //          because we want to load depending on the bbox
      //  b) the VectorSource has to have the option strategy: bbox
      featureRequestOptions["outputFormat"] = "application/json";
      var loader = function(extent, resolution, projection) {
        featureRequestOptions["bbox"] = extent;
        featureRequestOptions["srsName"] = projection.getCode();
        var featureRequest = new WFS().writeGetFeature(featureRequestOptions);
        // DEBUG console.log(featureRequest);
        HTTP.post(
          endpoint,
          new XMLSerializer().serializeToString(featureRequest),
          {
            headers: {
              "X-Gemma-Auth": localStorage.getItem("token"),
              "Content-type": "text/xml; charset=UTF-8"
            }
          }
        )
          .then(response => {
            var features = new GeoJSON().readFeatures(
              JSON.stringify(response.data)
            );
            vectorSource.addFeatures(features);
            // console.log(
            //   "loaded",
            //   features.length,
            //   featureRequestOptions.featureTypes,
            //   "features"
            // );
            // DEBUG console.log("loaded ", features, "for", vectorSource);
            // eslint-disable-next-line
          })
          .catch(() => {
            vectorSource.removeLoadedExtent(extent);
          });
      };
      return loader;
    },
    updateBottleneckFilter(bottleneck_id, datestr) {
      console.log("updating filter with", bottleneck_id, datestr);
      var layer = this.getLayerByName("Bottleneck isolines");
      var wmsSrc = layer.data.getSource();

      if (bottleneck_id != "does_not_exist") {
        wmsSrc.updateParams({
          cql_filter:
            "date_info='" +
            datestr +
            "' AND bottleneck_id='" +
            bottleneck_id +
            "'"
        });
        layer.isVisible = true;
        layer.data.setVisible(true);
      } else {
        layer.isVisible = false;
        layer.data.setVisible(false);
      }
    },
    onBeforePrint(/* evt */) {
      // console.log("onBeforePrint(", evt ,")");
      //
      // the following code shows how to get the current map canvas
      // and change it, however this does not work well enough, as
      // another mechanism seems to update the size again before the rendering
      // for printing is done:
      // console.log(this.openLayersMap.getViewport());
      // var canvas = this.openLayersMap.getViewport().getElementsByTagName("canvas")[0];
      // console.log(canvas);
      // canvas.width=1000;
      // canvas.height=1414;
      //
      // An experiment which also did not work:
      // this.openLayersMap.setSize([1000, 1414]); // estimate portait DIN A4
      //
      // according to documentation
      // http://openlayers.org/en/latest/apidoc/module-ol_PluggableMap-PluggableMap.html#updateSize
      // "Force a recalculation of the map viewport size. This should be called when third-party code changes the size of the map viewport."
      // but did not help
      // this.openLayersMap.updateSize();
    },
    onAfterPrint(/* evt */) {
      // could be used to undo changes that have been done for printing
      // though https://www.tjvantoll.com/2012/06/15/detecting-print-requests-with-javascript/
      // reported that this was not feasable (back then).
      // console.log("onAfterPrint(", evt, ")");
    }
  },
  watch: {
    split() {
      const map = this.openLayersMap;
      this.$nextTick(() => {
        map && map.updateSize();
      });
    },
    selectedSurvey(newSelectedSurvey) {
      if (newSelectedSurvey) {
        this.updateBottleneckFilter(
          newSelectedSurvey.bottleneck_id,
          newSelectedSurvey.date_info
        );
      } else {
        this.updateBottleneckFilter("does_not_exist", "1999-10-01");
      }
    }
  },
  mounted() {
    let map = new Map({
      layers: [...this.layers.map(x => x.data)],
      target: "map",
      controls: [],
      view: new View({
        center: [this.extent.lon, this.extent.lat],
        zoom: this.extent.zoom,
        projection: this.projection
      })
    });
    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", map);

    // TODO make display of layers more dynamic, e.g. from a list

    // loading the full WFS layer, by not setting the loader function
    // and without bboxStrategy
    var featureRequest = new WFS().writeGetFeature({
      srsName: "EPSG:3857",
      featureNS: "gemma",
      featurePrefix: "gemma",
      featureTypes: ["fairway_dimensions"],
      outputFormat: "application/json"
    });

    // NOTE: loading the full fairway_dimensions makes sure
    //       that all are available for the intersection with the profile
    HTTP.post(
      "/internal/wfs",
      new XMLSerializer().serializeToString(featureRequest),
      {
        headers: {
          "X-Gemma-Auth": localStorage.getItem("token"),
          "Content-type": "text/xml; charset=UTF-8"
        }
      }
    ).then(response => {
      var features = new GeoJSON().readFeatures(JSON.stringify(response.data));
      var vectorSrc = this.getLayerByName(
        "Fairway Dimensions"
      ).data.getSource();
      vectorSrc.addFeatures(features);
      // would scale to the extend of all resulting features
      // this.openLayersMap.getView().fit(vectorSrc.getExtent());
    });

    // load following layers with bboxStrategy (using our request builder)
    var layer = null;

    layer = this.getLayerByName("Waterway Area");
    layer.data.getSource().setLoader(
      this.buildVectorLoader(
        {
          featurePrefix: "ws-wamos",
          featureTypes: ["ienc_wtware"],
          geometryName: "geom"
        },
        "/external/d4d",
        layer.data.getSource()
      )
    );

    layer = this.getLayerByName("Waterway Axis");
    layer.data.getSource().setLoader(
      this.buildVectorLoader(
        {
          featurePrefix: "ws-wamos",
          featureTypes: ["ienc_wtwaxs"],
          geometryName: "geom"
        },
        "/external/d4d",
        layer.data.getSource()
      )
    );

    layer = this.getLayerByName("Distance marks");
    layer.data.getSource().setLoader(
      this.buildVectorLoader(
        {
          featurePrefix: "ws-wamos",
          featureTypes: ["ienc_dismar"],
          geometryName: "geom" //,
          /* restrict loading approximately to extend of danube in Austria */
          // filter: bboxFilter("geom", [13.3, 48.0, 17.1, 48.6], "EPSG:4326")
        },
        "/external/d4d",
        layer.data.getSource()
      )
    );
    layer.data.setVisible(layer.isVisible);

    layer = this.getLayerByName("Distance marks, Axis");
    layer.data.getSource().setLoader(
      this.buildVectorLoader(
        {
          featureNS: "gemma",
          featurePrefix: "gemma",
          featureTypes: ["distance_marks_geoserver"],
          geometryName: "geom"
        },
        "/internal/wfs",
        layer.data.getSource()
      )
    );

    layer = this.getLayerByName("Waterway Area, named");
    layer.data.getSource().setLoader(
      this.buildVectorLoader(
        {
          featureNS: "gemma",
          featurePrefix: "gemma",
          featureTypes: ["hydro_seaare"],
          geometryName: "geom"
        },
        "/external/d4d",
        layer.data.getSource()
      )
    );
    layer.data.setVisible(layer.isVisible);

    layer = this.getLayerByName("Bottlenecks");
    layer.data.getSource().setLoader(
      this.buildVectorLoader(
        {
          featureNS: "gemma",
          featurePrefix: "gemma",
          featureTypes: ["bottlenecks"],
          geometryName: "area"
        },
        "/internal/wfs",
        layer.data.getSource()
      )
    );
    HTTP.get("/system/style/Bottlenecks/stroke", {
      headers: { "X-Gemma-Auth": localStorage.getItem("token") }
    })
      .then(response => {
        this.btlnStrokeC = response.data.code;
        HTTP.get("/system/style/Bottlenecks/fill", {
          headers: { "X-Gemma-Auth": localStorage.getItem("token") }
        })
          .then(response => {
            this.btlnFillC = response.data.code;
            var newstyle = new Style({
              stroke: new Stroke({
                color: this.btlnStrokeC,
                width: 4
              }),
              fill: new Fill({
                color: this.btlnFillC
              })
            });
            layer.data.setStyle(newstyle);
          })
          .catch(error => {
            console.log(error);
          });
      })
      .catch(error => {
        console.log(error);
      });

    window.addEventListener("beforeprint", this.onBeforePrint);
    window.addEventListener("afterprint", this.onAfterPrint);

    // so none is shown
    this.updateBottleneckFilter("does_not_exist", "1999-10-01");
    this.openLayersMap.on(["singleclick", "dblclick"], event => {
      this.identify(event.coordinate, event.pixel);
    });
    this.$store.dispatch("bottlenecks/loadBottlenecks");
  }
};
</script>