view client/src/map/Maplayer.vue @ 842:ee6f127f573c

client: add first prototype for bottleneck iso layer * Add prototype with internal direct url and TOTOISOs markings.
author Bernhard Reiter <bernhard@intevation.de>
date Fri, 28 Sep 2018 11:27:07 +0200
parents d9da1ea14abf
children d2aa972df342
line wrap: on
line source

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

<style lang="scss">
.mapsplit {
  height: 50vh;
}

.mapfull {
  height: 100vh;
}
</style>

<script>
import { HTTP } from "../application/lib/http";
import { mapGetters, mapState } from "vuex";
import "ol/ol.css";
import { Map, View } from "ol";
import Feature from "ol/Feature";
// import { bbox as bboxFilter } from "ol/format/filter.js";
import { WFS, GeoJSON } from "ol/format.js";
// import GeometryType from "ol/geom/GeometryType.js";
import LineString from "ol/geom/LineString.js";
import Draw from "ol/interaction/Draw.js";
import { Vector as VectorLayer } from "ol/layer.js";
import { Vector as VectorSource } from "ol/source.js";
import { getLength } from "ol/sphere.js";

import distance from "@turf/distance";
import {
  lineString as turfLineString,
  polygon as turfPolygon
} from "@turf/helpers";
//import { lineIntersect as turfLineIntersect } from "@turf/line-intersect";
import lineIntersect from "@turf/line-intersect";
import { displayError } from "../application/lib/errors.js";

const DEMODATA = 2.5;

export default {
  name: "maplayer",
  props: ["drawMode", "lat", "long", "zoom", "split"],
  data() {
    return {
      projection: "EPSG:3857",
      interaction: null,
      vectorLayer: null,
      vectorSource: null
    };
  },
  computed: {
    ...mapGetters("mapstore", ["layers", "getLayerByName"]),
    ...mapState("mapstore", ["openLayersMap", "selectedMorph"]),
    mapStyle() {
      return {
        mapfull: !this.split,
        mapsplit: this.split
      };
    },
    layerData() {
      const l = this.layers.map(x => {
        return x.data;
      });
      return [...l, this.vectorLayer];
    }
  },
  methods: {
    createVectorSource() {
      this.vectorSource = new VectorSource({ wrapX: false });
    },
    createVectorLayer() {
      this.vectorLayer = new VectorLayer({
        source: this.vectorSource
      });
    },
    removeCurrentInteraction() {
      this.openLayersMap.removeInteraction(this.interaction);
      this.interaction = null;
    },
    createInteraction() {
      this.vectorSource.clear();
      var draw = new Draw({
        source: this.vectorSource,
        type: this.drawMode,
        maxPoints: 2
      });
      draw.on("drawstart", event => {
        this.vectorSource.clear();
        this.$store.commit("mapstore/setCurrentMeasurement", null);
        event.feature.setId("drawn.1"); // unique id for new feature
      });
      draw.on("drawend", this.drawEnd);
      return draw;
    },
    drawEnd(event) {
      const length = getLength(event.feature.getGeometry());
      this.$store.commit("mapstore/setCurrentMeasurement", length);
      // also place the a rounded length in a property, so identify can show it
      event.feature.set("length", Math.round(length * 10) / 10);

      // if a survey has been selected, request a profile
      // TODO an improvement could be to check if the line intersects
      // with the bottleneck area's polygon before trying the server request
      if (this.selectedMorph) {
        console.log("requesting profile for", this.selectedMorph);
        this.requestProfile(event, this.selectedMorph);
      }
    },
    requestProfile(event, survey) {
      // survey has to have the properties bottleneck_id and date_info

      // prepare to send the first line seqment to the server as GeoJSON
      const inputLineString = event.feature.getGeometry().clone();
      inputLineString.transform("EPSG:3857", "EPSG:4326");
      const [start, end] = inputLineString.getCoordinates();
      this.$store.commit("fairwayprofile/setStartPoint", start);
      this.$store.commit("fairwayprofile/setEndPoint", end);
      const profileLine = new LineString([start, end]);
      const feature = new Feature({
        geometry: profileLine,
        bottleneck: survey.bottleneck_id,
        date: survey.date_info
      });
      const geoJSON = new GeoJSON({ geometryName: "geometry" }).writeFeature(
        feature
      );
      this.$store
        .dispatch("fairwayprofile/loadProfile", geoJSON)
        .then(() => {
          var vectorSource = this.getLayerByName(
            "Fairway Dimensions"
          ).data.getSource();
          vectorSource.forEachFeatureIntersectingExtent(
            // need to use EPSG:3857 which is the proj of vectorSource
            profileLine
              .clone()
              .transform("EPSG:4326", "EPSG:3857")
              .getExtent(),
            feature => {
              // transform back to prepare for usage
              var intersectingPolygon = feature
                .getGeometry()
                .clone()
                .transform("EPSG:3857", "EPSG:4326");
              this.addToFairwayRectangle(
                profileLine,
                intersectingPolygon,
                DEMODATA
              );
            }
          );
          this.$store.commit("application/openSplitScreen");
        })
        .catch(error => {
          const { status, data } = error.response;
          displayError({
            title: "Backend Error",
            message: `${status}: ${data.message || data}`
          });
        });
    },
    addToFairwayRectangle(profileLine, fairwayGeometry, depth) {
      // both geometries have to be in EPSG:4326
      // uses turfjs distance() function
      let fairwayCoordinates = [];
      var line = turfLineString(profileLine.getCoordinates());
      var polygon = turfPolygon(fairwayGeometry.getCoordinates());
      var intersects = lineIntersect(line, polygon);
      var l = intersects.features.length;
      if (l % 2 != 0) {
        console.log("Ignoring fairway because profile only intersects once.");
      } else {
        for (let i = 0; i < l; i += 2) {
          let pStartPoint = profileLine.getCoordinates()[0];
          let fStartPoint = intersects.features[i].geometry.coordinates;
          let fEndPoint = intersects.features[i + 1].geometry.coordinates;
          let opts = { units: "kilometers" };

          fairwayCoordinates.push([
            distance(pStartPoint, fStartPoint, opts) * 1000,
            distance(pStartPoint, fEndPoint, opts) * 1000,
            depth
          ]);
        }
      }
      this.$store.commit(
        "fairwayprofile/setFairwayCoordinates",
        fairwayCoordinates
      );
    },
    activateInteraction() {
      const interaction = this.createInteraction(this.drawMode);
      this.interaction = interaction;
      this.openLayersMap.addInteraction(interaction);
    },
    activateIdentifyMode() {
      this.openLayersMap.on("singleclick", event => {
        // console.log("single click on map:", event);
        this.identify(event.coordinate, event.pixel);
      });
    },
    identify(coordinate, pixel) {
      this.$store.commit("mapstore/setIdentifiedFeatures", []);
      // checking our WFS layers
      var features = this.openLayersMap.getFeaturesAtPixel(pixel);
      this.$store.commit("mapstore/setIdentifiedFeatures", features);

      // 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;
    },
    // TODOISO call if new survey is selected
    updateBottleneckFilter(datestr) {
      console.log(datestr);
      var wmsSrc = this.getLayerByName("Bottleneck isolines").data.getSource();
      // TODOISO check if this works
      wmsSrc.updateParams({cql_filter: "date_info = '" + datestr + "'"});
    }
  },
  watch: {
    drawMode() {
      if (this.interaction) {
        this.removeCurrentInteraction();
      } else {
        this.activateInteraction();
      }
    },
    split() {
      const map = this.openLayersMap;
      this.$nextTick(() => {
        map.updateSize();
      });
    }
  },
  mounted() {
    this.createVectorSource();
    this.createVectorLayer();
    let map = new Map({
      layers: this.layerData,
      target: "map",
      controls: [],
      view: new View({
        center: [this.long, this.lat],
        zoom: this.zoom,
        projection: this.projection
      })
    });
    this.$store.commit("mapstore/setOpenLayersMap", 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 featureRequest2 = 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(featureRequest2),
      {
        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()
      )
    );

    // so none is shown
    // this.updateBottleneckFilter("1999-10-01");
    // test date
    this.updateBottleneckFilter("2018-08-30");
    this.activateIdentifyMode();
  }
};
</script>