view client/src/store/map.js @ 1879:9a2fbeaabd52 dev-pdf-generation

merging in from branch default
author Bernhard Reiter <bernhard@intevation.de>
date Tue, 15 Jan 2019 10:07:10 +0100
parents cc8592bf489b 53f49b109f0c
children 5a37ee321651
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 by via donau
 *   – Österreichische Wasserstraßen-Gesellschaft mbH
 * Software engineering by Intevation GmbH
 *
 * Author(s):
 * Markus Kottländer <markus@intevation.de>
 * Thomas Junk <thomas.junk@intevation.de>
 */

//import { HTTP } from "../lib/http";

import TileWMS from "ol/source/TileWMS.js";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer.js";
import OSM from "ol/source/OSM";
import Draw from "ol/interaction/Draw.js";
import { Icon, Stroke, Style, Fill, Text, Circle } from "ol/style.js";
import VectorSource from "ol/source/Vector.js";
import Point from "ol/geom/Point.js";
import { bbox as bboxStrategy } from "ol/loadingstrategy";
import { HTTP } from "../lib/http";
import { fromLonLat } from "ol/proj";
import { getLength, getArea } from "ol/sphere.js";
import { unByKey } from "ol/Observable";
import { getCenter } from "ol/extent";
import app from "../main";

// initial state
const init = () => {
  return {
    openLayersMap: null,
    extent: {
      lat: 6155376,
      lon: 1819178,
      zoom: 11
    },
    identifyTool: null, // 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)
    layers: [
      {
        name: "Open Streetmap",
        data: new TileLayer({
          source: new OSM()
        }),
        isVisible: true,
        showInLegend: true
      },
      {
        name: "Inland ECDIS chart Danube",
        data: new TileLayer({
          source: new TileWMS({
            preload: 1,
            url: "https://service.d4d-portal.info/wms/",
            crossOrigin: "anonymous",
            params: { LAYERS: "d4d", VERSION: "1.1.1", TILED: true }
          })
        }),
        isVisible: true,
        showInLegend: true
      },
      {
        name: "Fairway Dimensions",
        data: new VectorLayer({
          source: new VectorSource(),
          style: function(feature) {
            return [
              new Style({
                stroke: new Stroke({
                  color: "rgba(0, 0, 255, 1.0)",
                  width: 2
                })
              }),
              new Style({
                text: new Text({
                  font: 'bold 12px "Open Sans", "sans-serif"',
                  placement: "line",
                  fill: new Fill({
                    color: "black"
                  }),
                  text: "LOS: " + feature.get("level_of_service").toString()
                  //, zIndex: 10
                })
              })
            ];
          }
        }),
        isVisible: true,
        showInLegend: true
      },
      {
        name: "Waterway Area, named",
        data: new VectorLayer({
          source: new VectorSource({
            strategy: bboxStrategy
          }),
          style: new Style({
            stroke: new Stroke({
              color: "rgba(0, 132, 0, 1)",
              width: 2
            })
          })
        }),
        isVisible: false,
        showInLegend: true
      },
      {
        name: "Waterway Area",
        data: new VectorLayer({
          source: new VectorSource({
            strategy: bboxStrategy
          }),
          style: new Style({
            stroke: new Stroke({
              color: "rgba(0, 102, 0, 1)",
              width: 2
            })
          })
        }),
        isVisible: true,
        showInLegend: true
      },
      {
        name: "Waterway Axis",
        data: new VectorLayer({
          source: new VectorSource({
            strategy: bboxStrategy
          }),
          style: new Style({
            stroke: new Stroke({
              color: "rgba(0, 0, 255, .5)",
              lineDash: [5, 5],
              width: 2
            })
          })
        }),
        isVisible: true,
        showInLegend: true
      },
      {
        name: "Distance marks",
        forLegendStyle: { point: true, resolution: 8 },
        data: new VectorLayer({
          source: new VectorSource({
            strategy: bboxStrategy
          })
        }),
        isVisible: false,
        showInLegend: true
      },
      {
        name: "Bottlenecks",
        data: new VectorLayer({
          source: new VectorSource({
            strategy: bboxStrategy
          }),
          style: new Style({
            stroke: new Stroke({
              color: "rgba(230, 230, 10, .8)",
              width: 4
            }),
            fill: new Fill({
              color: "rgba(230, 230, 10, .3)"
            })
          })
        }),
        isVisible: true,
        showInLegend: true
      },
      {
        name: "Bottleneck isolines",
        data: new TileLayer({
          source: new TileWMS({
            preload: 0,
            projection: "EPSG:3857",
            url: window.location.origin + "/api/internal/wms",
            params: {
              LAYERS: "sounding_results_contour_lines_geoserver",
              VERSION: "1.1.1",
              TILED: true
            },
            tileLoadFunction: function(tile, src) {
              // console.log("calling for", tile, src);
              HTTP.get(src, {
                headers: {
                  "X-Gemma-Auth": localStorage.getItem("token")
                },
                responseType: "blob"
              }).then(response => {
                tile.getImage().src = URL.createObjectURL(response.data);
              });
            } // TODO  tile.setState(TileState.ERROR);
          })
        }),
        isVisible: false,
        showInLegend: true
      },
      {
        name: "Distance marks, Axis",
        forLegendStyle: { point: true, resolution: 8 },
        data: new VectorLayer({
          source: new VectorSource({
            strategy: bboxStrategy
          }),
          style: function(feature, resolution) {
            if (resolution < 10) {
              var s = new Style({
                image: new Circle({
                  radius: 5,
                  fill: new Fill({ color: "rgba(255, 0, 0, 0.1)" }),
                  stroke: new Stroke({ color: "blue", width: 1 })
                })
              });
              if (resolution < 6) {
                s.setText(
                  new Text({
                    offsetY: 12,
                    font: '10px "Open Sans", "sans-serif"',
                    fill: new Fill({
                      color: "black"
                    }),
                    text: (feature.get("hectometre") / 10).toString()
                  })
                );
              }
              return s;
            } else {
              return [];
            }
          }
        }),
        isVisible: true,
        showInLegend: true
      },
      {
        name: "Draw Tool",
        data: new VectorLayer({
          source: new VectorSource({ wrapX: false }),
          style: function(feature) {
            // adapted from OpenLayer's LineString Arrow Example
            var geometry = feature.getGeometry();
            var styles = [
              // linestring
              new Style({
                stroke: new Stroke({
                  color: "#369aca",
                  width: 2
                })
              })
            ];

            if (geometry.getType() === "LineString") {
              geometry.forEachSegment(function(start, end) {
                var dx = end[0] - start[0];
                var dy = end[1] - start[1];
                var rotation = Math.atan2(dy, dx);
                // arrows
                styles.push(
                  new Style({
                    geometry: new Point(end),
                    image: new Icon({
                      // we need to make sure the image is loaded by Vue Loader
                      src: require("../assets/linestring_arrow.png"),
                      // fiddling with the anchor's y value does not help to
                      // position the image more centered on the line ending, as the
                      // default line style seems to be slightly uncentered in the
                      // anti-aliasing, but the image is not placed with subpixel
                      // precision
                      anchor: [0.75, 0.5],
                      rotateWithView: true,
                      rotation: -rotation
                    })
                  })
                );
              });
            }
            return styles;
          }
        }),
        isVisible: true,
        showInLegend: false
      },
      {
        name: "Cut Tool",
        data: new VectorLayer({
          source: new VectorSource({ wrapX: false }),
          style: function(feature) {
            // adapted from OpenLayer's LineString Arrow Example
            var geometry = feature.getGeometry();
            var styles = [
              // linestring
              new Style({
                stroke: new Stroke({
                  color: "#333333",
                  width: 2,
                  lineDash: [7, 7]
                })
              })
            ];

            if (geometry.getType() === "LineString") {
              geometry.forEachSegment(function(start, end) {
                var dx = end[0] - start[0];
                var dy = end[1] - start[1];
                var rotation = Math.atan2(dy, dx);
                // arrows
                styles.push(
                  new Style({
                    geometry: new Point(end),
                    image: new Icon({
                      // we need to make sure the image is loaded by Vue Loader
                      src: require("../assets/linestring_arrow_grey.png"),
                      // fiddling with the anchor's y value does not help to
                      // position the image more centered on the line ending, as the
                      // default line style seems to be slightly uncentered in the
                      // anti-aliasing, but the image is not placed with subpixel
                      // precision
                      anchor: [0.75, 0.5],
                      rotateWithView: true,
                      rotation: -rotation
                    })
                  })
                );
              });
            }
            return styles;
          }
        }),
        isVisible: true,
        showInLegend: false
      }
    ]
  };
};

export default {
  init,
  namespaced: true,
  state: init(),
  getters: {
    layersForLegend: state => {
      return state.layers.filter(layer => layer.showInLegend);
    },
    getLayerByName: state => name => {
      return state.layers.find(layer => layer.name === name);
    },
    getVSourceByName: (state, getters) => name => {
      return getters.getLayerByName(name).data.getSource();
    }
  },
  mutations: {
    extent: (state, extent) => {
      state.extent = extent;
    },
    toggleVisibility: (state, layer) => {
      state.layers[layer].isVisible = !state.layers[layer].isVisible;
      state.layers[layer].data.setVisible(state.layers[layer].isVisible);
    },
    openLayersMap: (state, map) => {
      state.openLayersMap = map;
    },
    identifyTool: (state, events) => {
      state.identifyTool = events;
    },
    setIdentifiedFeatures: (state, identifiedFeatures) => {
      state.identifiedFeatures = 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;
    },
    moveMap: (state, { coordinates, zoom, preventZoomOut }) => {
      let view = state.openLayersMap.getView();
      const currentZoom = view.getZoom();
      view.animate({
        zoom: preventZoomOut ? Math.max(zoom, currentZoom) : zoom,
        center: fromLonLat(coordinates, view.getProjection()),
        duration: 700
      });
    }
  },
  actions: {
    openLayersMap({ commit, dispatch, getters }, map) {
      const drawVectorSrc = getters.getVSourceByName("Draw Tool");
      const cutVectorSrc = getters.getVSourceByName("Cut Tool");

      // 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", () => {
        dispatch("disableIdentifyTool");
        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(() => dispatch("enableIdentifyTool"), 1000)
        );
      });

      map.addInteraction(lineTool);
      map.addInteraction(cutTool);
      map.addInteraction(polygonTool);

      commit("lineTool", lineTool);
      commit("polygonTool", polygonTool);
      commit("cutTool", cutTool);
      commit("openLayersMap", map);
    },
    disableIdentifyTool({ state }) {
      unByKey(state.identifyTool);
      state.identifyTool = null;
    },
    enableIdentifyTool({ state, rootState, commit, dispatch, getters }) {
      if (!state.identifyTool) {
        state.identifyTool = state.openLayersMap.on(
          ["singleclick", "dblclick"],
          event => {
            commit("setIdentifiedFeatures", []);
            // checking our WFS layers
            var features = state.openLayersMap.getFeaturesAtPixel(event.pixel);
            if (features) {
              commit("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)) {
                  if (
                    rootState.bottlenecks.selectedBottleneck !=
                    feature.get("objnam")
                  ) {
                    dispatch(
                      "bottlenecks/setSelectedBottleneck",
                      feature.get("objnam"),
                      { root: true }
                    ).then(() => {
                      this.commit("bottlenecks/setFirstSurveySelected");
                    });
                    commit("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 = getters.getVSourceByName(
              "Inland ECDIS chart Danube"
            );
            var url = wmsSource.getGetFeatureInfoUrl(
              event.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);
            }
          }
        );
      }
    }
  }
};