view client/src/components/map/layers.js @ 4336:45307cf4931a

Review of fairway dimensions In order to review fairway dimensions, the ids from the summary are used to retrieve the according features from the geoserver. Then instances of the type Feature are generated with the information given from the geoserver. These are then added to the VectorSource layer and should be displayed. Currently the display doesn't work.
author Thomas Junk <thomas.junk@intevation.de>
date Thu, 05 Sep 2019 15:52:43 +0200
parents 18a34d9b289c
children e9d2573329da
line wrap: on
line source

import TileWMS from "ol/source/TileWMS";
import {
  Tile as TileLayer,
  Vector as VectorLayer,
  Image as ImageLayer
} from "ol/layer";
import { Icon, Stroke, Style } from "ol/style";
import VectorSource from "ol/source/Vector";
import { ImageWMS as ImageSource } from "ol/source";
import Point from "ol/geom/Point";
import { bbox as bboxStrategy } from "ol/loadingstrategy";
import { WFS, GeoJSON } from "ol/format";
import OSM from "ol/source/OSM";
import { equalTo } from "ol/format/filter";
import { HTTP } from "@/lib/http";
import { styleFactory } from "./styles";
import store from "@/store/index";

const buildVectorLoader = (
  featureRequestOptions,
  vectorSource,
  bboxStrategyDisabled,
  featurePostProcessor
) => {
  // 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: the geometryName has to be given in featureRequestOptions if
  // bboxStrategy (default) is used
  featureRequestOptions.featureNS = "gemma";
  featureRequestOptions.featurePrefix = "gemma";
  featureRequestOptions.outputFormat = "application/json";
  return (extent, resolution, projection) => {
    if (!bboxStrategyDisabled) {
      featureRequestOptions.bbox = extent;
    }
    featureRequestOptions.srsName = projection.getCode();
    HTTP.post(
      "/internal/wfs",
      new XMLSerializer().serializeToString(
        new WFS().writeGetFeature(featureRequestOptions)
      ),
      {
        headers: {
          "X-Gemma-Auth": localStorage.getItem("token"),
          "Content-type": "text/xml; charset=UTF-8"
        }
      }
    )
      .then(response => {
        const features = new GeoJSON().readFeatures(
          JSON.stringify(response.data)
        );
        if (featurePostProcessor) {
          features.map(f => featurePostProcessor(f, store, features));
        }
        vectorSource.addFeatures(features);
      })
      .catch(() => {
        vectorSource.removeLoadedExtent(extent);
      });
  };
};

// SHARED LAYERS:
// DRAW- and CUTLAYER are shared across maps. E.g. you want to see the cross cut
// arrow on both maps when comparing surveys. So we don't need to initialize a
// new VectorLayer object for each map. Instead we use these two constants so
// that all maps use the same object.
const DRAWLAYER = new VectorLayer({
  id: "DRAWTOOL",
  label: "Draw Tool",
  visible: true,
  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;
  }
});

const CUTLAYER = new VectorLayer({
  id: "CUTTOOL",
  label: "Cut Tool",
  visible: true,
  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: "#FFFFFF",
          width: 5,
          lineDash: [7, 7]
        })
      }),
      new Style({
        stroke: new Stroke({
          color: "#333333",
          width: 3,
          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;
  }
});

let layerConfigs = {};

export const unsetLayerConfigs = function() {
  layerConfigs = {};
};

export const layerFactory = function(mapId) {
  const styles = styleFactory(mapId);
  // Shared feature source for layers:
  // BOTTLENECKS, BOTTLENECKSTATUS and BOTTLENECKFAIRWAYAVAILABILITY
  // Reduces bottlenecks_geoserver requests and number of stored feature objects.
  const FDREVIEWLAYER = new VectorLayer({
    id: "FDREVIEWLAYER",
    label: "Review",
    visible: true,
    source: new VectorSource(),
    style: styles.sections
  });
  const bottlenecksSource = new VectorSource({ strategy: bboxStrategy });
  bottlenecksSource.setLoader(
    buildVectorLoader(
      {
        featureTypes: ["bottlenecks_geoserver"],
        geometryName: "area"
      },
      bottlenecksSource,
      false,
      async (f, store) => {
        if (f.get("fa_critical")) {
          // look for fairway availability data in store. If present and
          // not older than 15 min use it or fetch new data and store it.
          let data = store.getters["fairwayavailability/fwLNWLOverviewData"](f);
          if (
            data &&
            new Date().getTime() - data.createdAt.getTime() < 900000
          ) {
            f.set("fa_data", data.data);
          } else {
            let date = new Date();
            data = await store.dispatch(
              "fairwayavailability/loadAvailableFairwayDepthLNWLForMap",
              {
                feature: f,
                from: date.toISOString().split("T")[0],
                to: date.toISOString().split("T")[0],
                frequency: "monthly",
                LOS: 3
              }
            );
            if (data) {
              store.commit("fairwayavailability/addFwLNWLOverviewData", {
                feature: f,
                data,
                createdAt: new Date()
              });
              f.set("fa_data", data);
            }
          }
        }
        return f;
      }
    )
  );

  // either use existing config or create new one
  // important is only each map has its individual layer config
  // but we don't want to create new layer objects each time a store value
  // that is used here changes.
  const config = layerConfigs.hasOwnProperty(mapId)
    ? layerConfigs[mapId]
    : [
        new TileLayer({
          id: "OPENSTREETMAP",
          label: "Open Streetmap",
          visible: true,
          source: new OSM()
        }),
        new ImageLayer({
          id: "INLANDECDIS",
          label: "Inland ECDIS chart Danube",
          visible: true,
          source: null
        }),
        new ImageLayer({
          id: "WATERWAYAREA",
          label: "Waterway Area",
          maxResolution: 100,
          minResolution: 0,
          source: new ImageSource({
            url: window.location.origin + "/api/internal/wms",
            params: { LAYERS: "waterway_area", VERSION: "1.1.1", TILED: true },
            imageLoadFunction: function(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);
          })
        }),
        (function() {
          const source = new VectorSource({ strategy: bboxStrategy });
          source.setLoader(
            buildVectorLoader(
              {
                featureTypes: ["stretches_geoserver"],
                geometryName: "area"
              },
              source,
              true,
              (f, store) => {
                if (f.getId() === store.state.imports.selectedStretchId) {
                  f.set("highlighted", true);
                }
                return f;
              }
            )
          );
          return new VectorLayer({
            id: "STRETCHES",
            label: "Stretches",
            visible: false,
            style: styles.stretches,
            source
          });
        })(),
        (function() {
          const source = new VectorSource({ strategy: bboxStrategy });
          source.setLoader(
            buildVectorLoader(
              {
                featureTypes: ["sections_geoserver"],
                geometryName: "area"
              },
              source,
              true,
              (f, store) => {
                if (f.getId() === store.state.imports.selectedSectionId) {
                  f.set("highlighted", true);
                }
                return f;
              }
            )
          );
          return new VectorLayer({
            id: "SECTIONS",
            label: "Sections",
            visible: false,
            style: styles.sections,
            source
          });
        })(),
        (function() {
          const source = new VectorSource({ strategy: bboxStrategy });
          source.setLoader(
            buildVectorLoader(
              {
                geometryName: "area",
                featureTypes: ["fairway_dimensions"],
                filter: equalTo("level_of_service", 1)
              },
              source,
              false
            )
          );
          return new VectorLayer({
            id: "FAIRWAYDIMENSIONSLOS1",
            label: "LOS 1 Fairway Dimensions",
            visible: false,
            style: styles.fwd1,
            maxResolution: 80,
            minResolution: 0,
            source
          });
        })(),
        (function() {
          const source = new VectorSource({ strategy: bboxStrategy });
          source.setLoader(
            buildVectorLoader(
              {
                geometryName: "area",
                featureTypes: ["fairway_dimensions"],
                filter: equalTo("level_of_service", 2)
              },
              source,
              false
            )
          );
          return new VectorLayer({
            id: "FAIRWAYDIMENSIONSLOS2",
            label: "LOS 2 Fairway Dimensions",
            visible: false,
            style: styles.fwd2,
            maxResolution: 80,
            minResolution: 0,
            source
          });
        })(),
        (function() {
          const source = new VectorSource({ strategy: bboxStrategy });
          source.setLoader(
            buildVectorLoader(
              {
                geometryName: "area",
                featureTypes: ["fairway_dimensions"],
                filter: equalTo("level_of_service", 3)
              },
              source,
              false
            )
          );
          return new VectorLayer({
            id: "FAIRWAYDIMENSIONSLOS3",
            label: "LOS 3 Fairway Dimensions",
            visible: true,
            style: styles.fwd3,
            maxResolution: 80,
            minResolution: 0,
            source
          });
        })(),
        new ImageLayer({
          id: "WATERWAYAXIS",
          label: "Waterway Axis",
          source: new ImageSource({
            url: window.location.origin + "/api/internal/wms",
            params: { LAYERS: "waterway_axis", VERSION: "1.1.1", TILED: true },
            imageLoadFunction: function(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);
          })
        }),
        (function() {
          const source = new VectorSource({ strategy: bboxStrategy });
          source.setLoader(
            buildVectorLoader(
              {
                featureTypes: ["waterway_profiles"],
                geometryName: "geom"
              },
              source
            )
          );
          return new VectorLayer({
            id: "WATERWAYPROFILES",
            label: "Waterway Profiles",
            visible: true,
            style: new Style({
              stroke: new Stroke({
                color: "rgba(0, 0, 255, .5)",
                lineDash: [5, 5],
                width: 2
              })
            }),
            maxResolution: 2.5,
            minResolution: 0,
            source
          });
        })(),
        (function() {
          return new VectorLayer({
            id: "BOTTLENECKS",
            label: "Bottlenecks",
            visible: true,
            style: styles.bottleneck,
            source: bottlenecksSource
          });
        })(),
        new TileLayer({
          id: "BOTTLENECKISOLINE",
          label: "Bottleneck isolines",
          visible: false,
          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);
          })
        }),
        new TileLayer({
          id: "DIFFERENCES",
          label: "Bottleneck Differences",
          visible: false,
          source: new TileWMS({
            preload: 0,
            projection: "EPSG:3857",
            url: window.location.origin + "/api/internal/wms",
            params: {
              LAYERS: "sounding_differences",
              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);
          })
        }),
        (function() {
          return new VectorLayer({
            id: "BOTTLENECKSTATUS",
            label: "Critical Bottlenecks",
            forLegendStyle: { point: true, resolution: 16 },
            visible: true,
            zIndex: 1,
            style: styles.bottleneckStatus,
            source: bottlenecksSource
          });
        })(),
        (function() {
          return new VectorLayer({
            id: "BOTTLENECKFAIRWAYAVAILABILITY",
            label: "Bottleneck Fairway Availability",
            forLegendStyle: { point: true, resolution: 16 },
            visible: false,
            zIndex: 1,
            style: styles.bottleneckFairwayAvailability,
            source: bottlenecksSource
          });
        })(),
        (function() {
          const source = new VectorSource({ strategy: bboxStrategy });
          source.setLoader(
            buildVectorLoader(
              {
                featureTypes: [
                  "bottlenecks_geoserver",
                  "gauges_geoserver",
                  "stretches_geoserver",
                  "sections_geoserver"
                ]
              },
              source,
              true,
              // since we don't use bbox strategy, features will contain all features and we can use it
              // to find reference gauges for bottlenecks, yeah!
              async (f, store, features) => {
                // attach reference gauge to bottleneck
                if (f.getId().indexOf("bottlenecks") > -1) {
                  f.set(
                    "gauge_obj",
                    features.find(feat => {
                      return (
                        feat.getId().indexOf("gauges") > -1 &&
                        feat.get("objname") === f.get("gauge_objname")
                      );
                    })
                  );
                }

                // attach nsc data to gauge
                if (f.getId().indexOf("gauges") > -1) {
                  store
                    .dispatch(
                      "gauges/getNashSutcliffeForISRS",
                      f.get("isrs_code")
                    )
                    .then(response => {
                      f.set("nsc_data", response);
                    });
                }
              }
            )
          );
          return new VectorLayer({
            id: "DATAAVAILABILITY",
            label: "Data Availability/Accuracy",
            forLegendStyle: { point: true, resolution: 16 },
            visible: false,
            zIndex: 1,
            style: styles.dataAvailability,
            source
          });
        })(),
        new ImageLayer({
          id: "DISTANCEMARKS",
          label: "Distance Marks",
          maxResolution: 10,
          minResolution: 0,
          source: new ImageSource({
            url: window.location.origin + "/api/internal/wms",
            params: {
              LAYERS: "distance_marks_ashore_geoserver",
              VERSION: "1.1.1",
              TILED: true
            },
            imageLoadFunction: function(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);
          })
        }),
        new ImageLayer({
          id: "DISTANCEMARKSAXIS",
          label: "Distance Marks, Axis",
          source: new ImageSource({
            url: window.location.origin + "/api/internal/wms",
            params: {
              LAYERS: "distance_marks_geoserver",
              VERSION: "1.1.1",
              TILED: true
            },
            imageLoadFunction: function(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);
          })
        }),
        (function() {
          const source = new VectorSource({ strategy: bboxStrategy });
          source.setLoader(
            buildVectorLoader(
              {
                featureTypes: ["gauges_geoserver"],
                geometryName: "geom"
              },
              source
            )
          );
          return new VectorLayer({
            id: "GAUGES",
            label: "Gauges",
            forLegendStyle: { point: true, resolution: 8 },
            visible: true,
            style: styles.gauge,
            maxResolution: 100,
            minResolution: 0,
            source
          });
        })(),
        DRAWLAYER,
        CUTLAYER,
        FDREVIEWLAYER
      ];

  layerConfigs[mapId] = config;

  return {
    get(id) {
      return config.find(l => l.get("id") === id);
    },
    config
  };
};