view client/src/map/Maplayer.vue @ 793:073394629ec6

client: add measurement and improve identify * Only display features that have an Id in the identify box, because during the drawmode, after the first click, there is an additional feature that has no Id. Probably this is the point. * Add setting an Id to the feature that is drawn, which makes it show up in the identify box. * Add code to add the length of the drawn line to the store and a rounded value to the feature property. This creates a simple measurement tool, if the feature box is shown.
author Bernhard Reiter <bernhard@intevation.de>
date Thu, 27 Sep 2018 10:02:39 +0200
parents 1b82ae5e637e
children 644172f201e8
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";

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"]),
    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;
    },
    findFeature(profileLine) {
      return new Feature({
        geometry: profileLine,
        // FIXME: hardcoded bottleneck and survey date
        bottleneck: "AT_Bottleneck_44",
        date: "2017-11-20"
      });
    },
    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);

      // 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 = this.findFeature(profileLine);
      const geoJSON = new GeoJSON({ geometryName: "geometry" }).writeFeature(
        feature
      );
      this.$store
        .dispatch("fairwayprofile/loadProfile", geoJSON)
        .then()
        .catch(error => {
          const { status, data } = error.response;
          displayError({
            title: "Backend Error",
            message: `${status}: ${data.message || data}`
          });
        });

      //   // FIXME: assuming that we have the fairway dimensions loaded
      //   var vectorSource = this.getLayerByName(
      //     "Fairway Dimensions"
      //   ).data.getSource();
      //   console.log(vectorSource);

      //   var diagStack = [];
      //   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");
      //       console.log("intersecting:", intersectingPolygon);
      //       this.addToFairwayRectangle(
      //         profileLine,
      //         intersectingPolygon,
      //         2.5, // FIXME use real fairway depth value for this feature
      //         diagStack
      //       );
      //     }
      //  );
    },
    addToFairwayRectangle(profileLine, fairwayGeometry, depth, diagStack) {
      // both geometries have to be in EPSG:4326
      // uses turfjs distance() function
      // pushes pairs of diagram points into diagStack
      //   console.log(
      //     "addToFairwayRectangle(",
      //     profileLine,
      //     fairwayGeometry,
      //     depth,
      //     diagStack,
      //     ")"
      //   );
      var line = turfLineString(profileLine.getCoordinates());
      var polygon = turfPolygon(fairwayGeometry.getCoordinates());
      var intersects = lineIntersect(line, polygon);
      console.log(intersects.features);

      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" };

          diagStack.push([
            distance(pStartPoint, fStartPoint, opts) * 1000,
            distance(pStartPoint, fEndPoint, opts) * 1000,
            depth
          ]);
        }
      }
      //console.log("Resulting diagStack:", diagStack);
    },
    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;
    }
  },
  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()
      )
    );

    this.activateIdentifyMode();
  }
};
</script>