changeset 3006:44493664d40e

client: refactored layers config Layers are not stored in the vuex store anymore but instead they are served from a factory function, so that different maps can haved individual layer objects
author Markus Kottlaender <markus@intevation.de>
date Thu, 11 Apr 2019 11:44:11 +0200
parents 870d2a0e866b
children 792d4476d5d5
files client/src/components/ImportStretches.vue client/src/components/Maplayer.vue client/src/components/Pdftool.vue client/src/components/fairway/Profiles.vue client/src/components/importoverview/BottleneckDetail.vue client/src/components/importoverview/StretchDetails.vue client/src/components/layers/Layers.vue client/src/components/layers/Layerselect.vue client/src/components/layers/layers.js client/src/components/layers/styles.js client/src/components/toolbar/Linetool.vue client/src/components/toolbar/Polygontool.vue client/src/components/toolbar/Toolbar.vue client/src/store/bottlenecks.js client/src/store/fairway.js client/src/store/map.js
diffstat 16 files changed, 821 insertions(+), 735 deletions(-) [+]
line wrap: on
line diff
--- a/client/src/components/ImportStretches.vue	Thu Apr 11 10:11:59 2019 +0200
+++ b/client/src/components/ImportStretches.vue	Thu Apr 11 11:44:11 2019 +0200
@@ -338,7 +338,11 @@
   },
   computed: {
     ...mapState("application", ["searchQuery"]),
-    ...mapState("map", ["layers", "identifiedFeatures", "currentMeasurement"]),
+    ...mapState("map", [
+      "openLayersMap",
+      "identifiedFeatures",
+      "currentMeasurement"
+    ]),
     ...mapGetters("user", ["isSysAdmin"]),
     ...mapState("imports", ["stretches"]),
     defineStretchesLabel() {
@@ -458,7 +462,7 @@
     },
     moveMapToStretch(stretch) {
       this.$store.commit("imports/selectedStretchId", stretch.id);
-      this.layers.STRETCHES.setVisible(true);
+      this.openLayersMap.getLayer("STRETCHES").setVisible(true);
       this.$store.commit("map/moveToExtent", {
         feature: stretch,
         zoom: 17,
@@ -496,7 +500,7 @@
       this.edit = true;
     },
     togglePipette(t) {
-      this.layers.DISTANCEMARKSAXIS.setVisible(true);
+      this.openLayersMap.getLayer("DISTANCEMARKSAXIS").setVisible(true);
       if (t === "start") {
         this.pipetteStart = !this.pipetteStart;
         this.pipetteEnd = false;
--- a/client/src/components/Maplayer.vue	Thu Apr 11 10:11:59 2019 +0200
+++ b/client/src/components/Maplayer.vue	Thu Apr 11 11:44:11 2019 +0200
@@ -41,11 +41,10 @@
 import { HTTP } from "@/lib/http";
 import { mapState } from "vuex";
 import { Map, View } from "ol";
-import { WFS, GeoJSON } from "ol/format";
-import { equalTo } from "ol/format/filter";
 import { Stroke, Style, Fill } from "ol/style";
 import { displayError } from "@/lib/errors";
 import { uuid } from "@/lib/mixins";
+import layers from "@/components/layers/layers";
 import "ol/ol.css";
 
 /* for the sake of debugging */
@@ -61,7 +60,6 @@
     ...mapState("map", [
       "initialLoad",
       "extent",
-      "layers",
       "openLayersMap",
       "lineTool",
       "polygonTool",
@@ -79,60 +77,18 @@
     }
   },
   methods: {
-    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));
-            }
-            vectorSource.addFeatures(features);
-          })
-          .catch(() => {
-            vectorSource.removeLoadedExtent(extent);
-          });
-      };
-    },
     updateBottleneckFilter(bottleneck_id, datestr) {
       const exists = bottleneck_id != "does_not_exist";
 
       if (exists) {
-        this.layers.BOTTLENECKISOLINE.getSource().updateParams({
-          cql_filter: `date_info='${datestr}' AND bottleneck_id='${bottleneck_id}'`
-        });
+        layers
+          .get("BOTTLENECKISOLINE")
+          .getSource()
+          .updateParams({
+            cql_filter: `date_info='${datestr}' AND bottleneck_id='${bottleneck_id}'`
+          });
       }
-      this.layers.BOTTLENECKISOLINE.setVisible(exists);
+      layers.get("BOTTLENECKISOLINE").setVisible(exists);
     }
   },
   watch: {
@@ -162,7 +118,9 @@
       }
     },
     selectedStretchId(id) {
-      this.layers.STRETCHES.getSource()
+      layers
+        .get("STRETCHES")
+        .getSource()
         .getFeatures()
         .forEach(f => {
           f.set("highlighted", false);
@@ -173,8 +131,8 @@
     }
   },
   mounted() {
-    let map = new Map({
-      layers: [...Object.values(this.layers)],
+    const map = new Map({
+      layers: layers.config,
       target: "map-" + this.uuid,
       controls: [],
       view: new View({
@@ -184,6 +142,10 @@
         projection: "EPSG:3857"
       })
     });
+    map.getLayer = id => layers.get(id);
+
+    // store map position on every move
+    // will be obsolete once we abandoned the separated admin context
     map.on("moveend", event => {
       const center = event.map.getView().getCenter();
       this.$store.commit("map/extent", {
@@ -194,6 +156,8 @@
     });
     this.$store.dispatch("map/openLayersMap", map);
 
+    // move to user specific default extent if map loads for the first timeout
+    // checking initialLoad will be obsolete once we abandoned the separated admin context
     if (this.initialLoad) {
       this.$store.commit("map/initialLoad", false);
       var currentUser = this.$store.state.user.user;
@@ -224,137 +188,7 @@
         });
     }
 
-    // TODO make display of layers more dynamic, e.g. from a list
-
-    this.layers.FAIRWAYDIMENSIONSLOS1.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureTypes: ["fairway_dimensions"],
-          filter: equalTo("level_of_service", 1)
-        },
-        this.layers.FAIRWAYDIMENSIONSLOS1.getSource(),
-        true
-      )
-    );
-
-    this.layers.FAIRWAYDIMENSIONSLOS2.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureTypes: ["fairway_dimensions"],
-          filter: equalTo("level_of_service", 2)
-        },
-        this.layers.FAIRWAYDIMENSIONSLOS2.getSource(),
-        true
-      )
-    );
-
-    this.layers.FAIRWAYDIMENSIONSLOS3.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureTypes: ["fairway_dimensions"],
-          filter: equalTo("level_of_service", 3)
-        },
-        this.layers.FAIRWAYDIMENSIONSLOS3.getSource(),
-        true
-      )
-    );
-
-    // load following layers with bboxStrategy (using our request builder)
-    this.layers.WATERWAYAREA.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureTypes: ["waterway_area"],
-          geometryName: "area"
-        },
-        this.layers.WATERWAYAREA.getSource()
-      )
-    );
-
-    this.layers.WATERWAYAXIS.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureTypes: ["waterway_axis"],
-          geometryName: "wtwaxs"
-        },
-        this.layers.WATERWAYAXIS.getSource()
-      )
-    );
-
-    this.layers.WATERWAYPROFILES.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureTypes: ["waterway_profiles"],
-          geometryName: "geom"
-        },
-        this.layers.WATERWAYPROFILES.getSource()
-      )
-    );
-
-    this.layers.DISTANCEMARKS.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureTypes: ["distance_marks_ashore_geoserver"],
-          geometryName: "geom"
-        },
-        this.layers.DISTANCEMARKS.getSource()
-      )
-    );
-
-    this.layers.DISTANCEMARKSAXIS.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureTypes: ["distance_marks_geoserver"],
-          geometryName: "geom"
-        },
-        this.layers.DISTANCEMARKSAXIS.getSource()
-      )
-    );
-
-    this.layers.GAUGES.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureTypes: ["gauges_geoserver"],
-          geometryName: "geom"
-        },
-        this.layers.GAUGES.getSource()
-      )
-    );
-
-    this.layers.STRETCHES.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureTypes: ["stretches_geoserver"],
-          geometryName: "area"
-        },
-        this.layers.STRETCHES.getSource(),
-        f => {
-          if (f.getId() === this.selectedStretchId) {
-            f.set("highlighted", true);
-          }
-          return f;
-        }
-      )
-    );
-
-    this.layers.BOTTLENECKSTATUS.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureTypes: ["bottlenecks_geoserver"],
-          geometryName: "area"
-        },
-        this.layers.BOTTLENECKSTATUS.getSource()
-      )
-    );
-
-    this.layers.BOTTLENECKS.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureTypes: ["bottlenecks_geoserver"],
-          geometryName: "area"
-        },
-        this.layers.BOTTLENECKS.getSource()
-      )
-    );
+    // load configured bottleneck colors
     HTTP.get("/system/style/Bottlenecks/stroke", {
       headers: { "X-Gemma-Auth": localStorage.getItem("token") }
     })
@@ -374,7 +208,7 @@
                 color: btlnFillC
               })
             });
-            this.layers.BOTTLENECKS.setStyle(newStyle);
+            layers.get("BOTTLENECKS").setStyle(newStyle);
           })
           .catch(error => {
             console.log(error);
--- a/client/src/components/Pdftool.vue	Thu Apr 11 10:11:59 2019 +0200
+++ b/client/src/components/Pdftool.vue	Thu Apr 11 11:44:11 2019 +0200
@@ -165,7 +165,7 @@
   computed: {
     ...mapState("application", ["showPdfTool", "logoForPDF"]),
     ...mapState("bottlenecks", ["selectedBottleneck", "selectedSurvey"]),
-    ...mapState("map", ["openLayersMap", "isolinesLegendImgDataURL", "layers"]),
+    ...mapState("map", ["openLayersMap", "isolinesLegendImgDataURL"]),
     ...mapState("user", ["user"]),
     generatePdfLable() {
       return this.$gettext("Generate PDF");
@@ -752,7 +752,7 @@
       if (
         this.selectedBottleneck &&
         this.selectedSurvey &&
-        this.layers.BOTTLENECKISOLINE.getVisible()
+        this.openLayersMap.getLayer("BOTTLENECKISOLINE").getVisible()
       ) {
         // transforming into an HTMLImageElement only to find out
         // the width x height of the legend image
@@ -791,7 +791,7 @@
       if (
         this.selectedBottleneck &&
         this.selectedSurvey &&
-        this.layers.BOTTLENECKISOLINE.getVisible()
+        this.openLayersMap.getLayer("BOTTLENECKISOLINE").getVisible()
       ) {
         let survey = this.selectedSurvey;
 
--- a/client/src/components/fairway/Profiles.vue	Thu Apr 11 10:11:59 2019 +0200
+++ b/client/src/components/fairway/Profiles.vue	Thu Apr 11 11:44:11 2019 +0200
@@ -264,7 +264,7 @@
   },
   computed: {
     ...mapState("application", ["showProfiles"]),
-    ...mapState("map", ["layers", "lineTool", "polygonTool", "cutTool"]),
+    ...mapState("map", ["openLayersMap", "lineTool", "polygonTool", "cutTool"]),
     ...mapState("bottlenecks", [
       "bottlenecksList",
       "surveys",
@@ -348,7 +348,10 @@
         if (!cut) {
           this.$store.commit("fairwayprofile/clearCurrentProfile");
           this.$store.commit("application/showSplitscreen", false);
-          this.layers.CUTTOOL.getSource().clear();
+          this.openLayersMap
+            .getLayer("CUTTOOL")
+            .getSource()
+            .clear();
         }
       }
     },
@@ -409,19 +412,22 @@
         }
       )
         .then(() => {
-          this.layers.DIFFERENCES.getSource().updateParams({
-            cql_filter:
-              "objnam='" +
-              this.selectedBottleneck +
-              "' AND " +
-              "minuend='" +
-              this.selectedSurvey.date_info +
-              "' AND subtrahend='" +
-              this.additionalSurvey.date_info +
-              "'"
-          });
-          this.layers.BOTTLENECKISOLINE.setVisible(false);
-          this.layers.DIFFERENCES.setVisible(true);
+          this.openLayersMap
+            .getLayer("DIFFERENCES")
+            .getSource()
+            .updateParams({
+              cql_filter:
+                "objnam='" +
+                this.selectedBottleneck +
+                "' AND " +
+                "minuend='" +
+                this.selectedSurvey.date_info +
+                "' AND subtrahend='" +
+                this.additionalSurvey.date_info +
+                "'"
+            });
+          this.openLayersMap.getLayer("BOTTLENECKISOLINE").setVisible(false);
+          this.openLayersMap.getLayer("DIFFERENCES").setVisible(true);
         })
         .catch(error => {
           const { status, data } = error.response;
@@ -478,14 +484,20 @@
       coordinates = coordinates.filter(c => Number(c) === c);
       if (coordinates.length === 4) {
         // draw line on map
-        this.layers.CUTTOOL.getSource().clear();
+        this.openLayersMap
+          .getLayer("CUTTOOL")
+          .getSource()
+          .clear();
         const cut = new Feature({
           geometry: new LineString([
             [coordinates[0], coordinates[1]],
             [coordinates[2], coordinates[3]]
           ]).transform("EPSG:4326", "EPSG:3857")
         });
-        this.layers.CUTTOOL.getSource().addFeature(cut);
+        this.openLayersMap
+          .getLayer("CUTTOOL")
+          .getSource()
+          .addFeature(cut);
 
         // draw diagram
         this.$store.dispatch("fairwayprofile/cut", cut);
--- a/client/src/components/importoverview/BottleneckDetail.vue	Thu Apr 11 10:11:59 2019 +0200
+++ b/client/src/components/importoverview/BottleneckDetail.vue	Thu Apr 11 11:44:11 2019 +0200
@@ -101,7 +101,7 @@
   },
   computed: {
     ...mapState("imports", ["showLogs", "details"]),
-    ...mapState("map", ["layers"])
+    ...mapState("map", ["openLayersMap"])
   },
   methods: {
     loadBottlenecks() {
@@ -147,7 +147,7 @@
         });
     },
     moveToBottleneck(index) {
-      this.layers.BOTTLENECKS.setVisible(true);
+      this.openLayersMap.getLayer("BOTTLENECKS").setVisible(true);
       this.moveToExtent(this.bottlenecks[index]);
     },
     moveToExtent(feature) {
--- a/client/src/components/importoverview/StretchDetails.vue	Thu Apr 11 10:11:59 2019 +0200
+++ b/client/src/components/importoverview/StretchDetails.vue	Thu Apr 11 11:44:11 2019 +0200
@@ -31,7 +31,7 @@
   },
   computed: {
     ...mapState("imports", ["showAdditional", "details"]),
-    ...mapState("map", ["layers"])
+    ...mapState("map", ["openLayersMap"])
   },
   methods: {
     moveToExtent(feature) {
@@ -43,7 +43,7 @@
     },
     zoomToStretch() {
       const name = this.details.summary.stretch;
-      this.layers.STRETCHES.setVisible(true);
+      this.openLayersMap.getLayer("STRETCHES").setVisible(true);
       this.$store
         .dispatch("imports/loadStretch", name)
         .then(response => {
--- a/client/src/components/layers/Layers.vue	Thu Apr 11 10:11:59 2019 +0200
+++ b/client/src/components/layers/Layers.vue	Thu Apr 11 11:44:11 2019 +0200
@@ -11,23 +11,23 @@
         :title="layersLabel"
         :closeCallback="close"
       />
-      <div class="box-body small">
-        <Layerselect :layer="layers.OPENSTREETMAP" />
-        <Layerselect :layer="layers.INLANDECDIS" />
-        <Layerselect :layer="layers.WATERWAYAREA" />
-        <Layerselect :layer="layers.STRETCHES" />
-        <Layerselect :layer="layers.FAIRWAYDIMENSIONSLOS3" />
-        <Layerselect :layer="layers.FAIRWAYDIMENSIONSLOS2" />
-        <Layerselect :layer="layers.FAIRWAYDIMENSIONSLOS1" />
-        <Layerselect :layer="layers.WATERWAYAXIS" />
-        <Layerselect :layer="layers.WATERWAYPROFILES" />
-        <Layerselect :layer="layers.BOTTLENECKS" />
-        <Layerselect :layer="layers.BOTTLENECKISOLINE" />
-        <Layerselect :layer="layers.DIFFERENCES" />
-        <Layerselect :layer="layers.BOTTLENECKSTATUS" />
-        <Layerselect :layer="layers.DISTANCEMARKS" />
-        <Layerselect :layer="layers.DISTANCEMARKSAXIS" />
-        <Layerselect :layer="layers.GAUGES" />
+      <div class="box-body small" v-if="openLayersMap">
+        <Layerselect :layer="openLayersMap.getLayer('OPENSTREETMAP')" />
+        <Layerselect :layer="openLayersMap.getLayer('INLANDECDIS')" />
+        <Layerselect :layer="openLayersMap.getLayer('WATERWAYAREA')" />
+        <Layerselect :layer="openLayersMap.getLayer('STRETCHES')" />
+        <Layerselect :layer="openLayersMap.getLayer('FAIRWAYDIMENSIONSLOS3')" />
+        <Layerselect :layer="openLayersMap.getLayer('FAIRWAYDIMENSIONSLOS2')" />
+        <Layerselect :layer="openLayersMap.getLayer('FAIRWAYDIMENSIONSLOS1')" />
+        <Layerselect :layer="openLayersMap.getLayer('WATERWAYAXIS')" />
+        <Layerselect :layer="openLayersMap.getLayer('WATERWAYPROFILES')" />
+        <Layerselect :layer="openLayersMap.getLayer('BOTTLENECKS')" />
+        <Layerselect :layer="openLayersMap.getLayer('BOTTLENECKISOLINE')" />
+        <Layerselect :layer="openLayersMap.getLayer('DIFFERENCES')" />
+        <Layerselect :layer="openLayersMap.getLayer('BOTTLENECKSTATUS')" />
+        <Layerselect :layer="openLayersMap.getLayer('DISTANCEMARKS')" />
+        <Layerselect :layer="openLayersMap.getLayer('DISTANCEMARKSAXIS')" />
+        <Layerselect :layer="openLayersMap.getLayer('GAUGES')" />
       </div>
     </div>
   </div>
@@ -55,7 +55,7 @@
     Layerselect: () => import("./Layerselect")
   },
   computed: {
-    ...mapState("map", ["layers"]),
+    ...mapState("map", ["openLayersMap"]),
     ...mapState("application", ["showLayers"]),
     layersLabel() {
       return this.$gettext("Layers");
--- a/client/src/components/layers/Layerselect.vue	Thu Apr 11 10:11:59 2019 +0200
+++ b/client/src/components/layers/Layerselect.vue	Thu Apr 11 11:44:11 2019 +0200
@@ -15,10 +15,10 @@
         {{ label }}
       </label>
     </div>
-    <div v-if="layer.getVisible() && layer === layers.BOTTLENECKISOLINE">
+    <div v-if="layer.getVisible() && layer.get('id') === 'BOTTLENECKISOLINE'">
       <img class="rounded my-1 d-block" :src="isolinesLegendImgDataURL" />
     </div>
-    <div v-if="layer.getVisible() && layer === layers.DIFFERENCES">
+    <div v-if="layer.getVisible() && layer.get('id') === 'DIFFERENCES'">
       <img class="rounded my-1 d-block" :src="differencesLegendImgDataURL" />
     </div>
   </div>
@@ -89,13 +89,13 @@
     }
   },
   created() {
-    if (this.layer === this.layers.BOTTLENECKISOLINE) {
+    if (this.layer.get("id") === "BOTTLENECKISOLINE") {
       this.loadLegendImage(
         "sounding_results_contour_lines_geoserver",
         "isolinesLegendImgDataURL"
       );
     }
-    if (this.layer === this.layers.DIFFERENCES) {
+    if (this.layer.get("id") === "DIFFERENCES") {
       this.loadLegendImage(
         "sounding_differences",
         "differencesLegendImgDataURL"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/layers/layers.js	Thu Apr 11 11:44:11 2019 +0200
@@ -0,0 +1,494 @@
+import TileWMS from "ol/source/TileWMS";
+import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer";
+import OSM from "ol/source/OSM";
+import { Icon, Stroke, Style } from "ol/style";
+import VectorSource from "ol/source/Vector";
+import Point from "ol/geom/Point";
+import { bbox as bboxStrategy } from "ol/loadingstrategy";
+import { WFS, GeoJSON } from "ol/format";
+import { equalTo } from "ol/format/filter";
+import { HTTP } from "@/lib/http";
+import styles from "./styles";
+
+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));
+        }
+        vectorSource.addFeatures(features);
+      })
+      .catch(() => {
+        vectorSource.removeLoadedExtent(extent);
+      });
+  };
+};
+
+export default (function() {
+  return {
+    get(id) {
+      return this.config.find(l => l.get("id") === id);
+    },
+    config: [
+      new TileLayer({
+        id: "OPENSTREETMAP",
+        label: "Open Streetmap",
+        visible: true,
+        source: new OSM()
+      }),
+      new TileLayer({
+        id: "INLANDECDIS",
+        label: "Inland ECDIS chart Danube",
+        visible: true,
+        source: new TileWMS({
+          preload: 1,
+          url: "https://service.d4d-portal.info/wms/",
+          crossOrigin: "anonymous",
+          params: { LAYERS: "d4d", VERSION: "1.1.1", TILED: true }
+        })
+      }),
+      (function() {
+        const source = new VectorSource({ strategy: bboxStrategy });
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["waterway_area"],
+              geometryName: "area"
+            },
+            source
+          )
+        );
+        return new VectorLayer({
+          id: "WATERWAYAREA",
+          label: "Waterway Area",
+          visible: true,
+          style: new Style({
+            stroke: new Stroke({
+              color: "rgba(0, 102, 0, 1)",
+              width: 2
+            })
+          }),
+          source
+        });
+      })(),
+      (function() {
+        const source = new VectorSource({ strategy: bboxStrategy });
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["stretches_geoserver"],
+              geometryName: "area"
+            },
+            source,
+            f => {
+              if (f.getId() === this.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();
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["fairway_dimensions"],
+              filter: equalTo("level_of_service", 1)
+            },
+            source,
+            true
+          )
+        );
+        return new VectorLayer({
+          id: "FAIRWAYDIMENSIONSLOS1",
+          label: "LOS 1 Fairway Dimensions",
+          visible: false,
+          style: styles.fwd1,
+          source
+        });
+      })(),
+      (function() {
+        const source = new VectorSource();
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["fairway_dimensions"],
+              filter: equalTo("level_of_service", 2)
+            },
+            source,
+            true
+          )
+        );
+        return new VectorLayer({
+          id: "FAIRWAYDIMENSIONSLOS2",
+          label: "LOS 2 Fairway Dimensions",
+          visible: false,
+          style: styles.fwd2,
+          source
+        });
+      })(),
+      (function() {
+        const source = new VectorSource();
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["fairway_dimensions"],
+              filter: equalTo("level_of_service", 3)
+            },
+            source,
+            true
+          )
+        );
+        return new VectorLayer({
+          id: "FAIRWAYDIMENSIONSLOS3",
+          label: "LOS 3 Fairway Dimensions",
+          visible: true,
+          style: styles.fwd3,
+          source
+        });
+      })(),
+      (function() {
+        const source = new VectorSource({ strategy: bboxStrategy });
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["waterway_axis"],
+              geometryName: "wtwaxs"
+            },
+            source
+          )
+        );
+        return new VectorLayer({
+          id: "WATERWAYAXIS",
+          label: "Waterway Axis",
+          visible: true,
+          style: new Style({
+            stroke: new Stroke({
+              color: "rgba(0, 0, 255, .5)",
+              lineDash: [5, 5],
+              width: 2
+            })
+          }),
+          // TODO: Set layer in layertree active/inactive depending on
+          // resolution.
+          maxResolution: 5,
+          minResolution: 0,
+          source
+        });
+      })(),
+      (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() {
+        const source = new VectorSource({ strategy: bboxStrategy });
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["bottlenecks_geoserver"],
+              geometryName: "area"
+            },
+            source
+          )
+        );
+        return new VectorLayer({
+          id: "BOTTLENECKS",
+          label: "Bottlenecks",
+          visible: true,
+          style: styles.bottleneck,
+          source
+        });
+      })(),
+      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:4326",
+          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() {
+        const source = new VectorSource({ strategy: bboxStrategy });
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["bottlenecks_geoserver"],
+              geometryName: "area"
+            },
+            source
+          )
+        );
+        return new VectorLayer({
+          id: "BOTTLENECKSTATUS",
+          label: "Critical Bottlenecks",
+          forLegendStyle: { point: true, resolution: 16 },
+          visible: true,
+          style: styles.bottleneckStatus,
+          source
+        });
+      })(),
+      (function() {
+        const source = new VectorSource({ strategy: bboxStrategy });
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["distance_marks_ashore_geoserver"],
+              geometryName: "geom"
+            },
+            source
+          )
+        );
+        return new VectorLayer({
+          id: "DISTANCEMARKS",
+          label: "Distance marks",
+          forLegendStyle: { point: true, resolution: 8 },
+          visible: false,
+          source
+        });
+      })(),
+      (function() {
+        const source = new VectorSource({ strategy: bboxStrategy });
+        source.setLoader(
+          buildVectorLoader(
+            {
+              featureTypes: ["distance_marks_geoserver"],
+              geometryName: "geom"
+            },
+            source
+          )
+        );
+        return new VectorLayer({
+          id: "DISTANCEMARKSAXIS",
+          label: "Distance marks, Axis",
+          forLegendStyle: { point: true, resolution: 8 },
+          visible: true,
+          style: styles.dma,
+          source
+        });
+      })(),
+      (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
+        });
+      })(),
+      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;
+        }
+      }),
+      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: "#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;
+        }
+      })
+    ]
+  };
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/layers/styles.js	Thu Apr 11 11:44:11 2019 +0200
@@ -0,0 +1,201 @@
+import { Icon, Stroke, Style, Fill, Text, Circle } from "ol/style";
+import Point from "ol/geom/Point";
+import { getCenter } from "ol/extent";
+
+const styles = {
+  blue1: new Style({
+    stroke: new Stroke({
+      color: "rgba(0, 0, 255, 0.8)",
+      lineDash: [2, 4],
+      lineCap: "round",
+      width: 2
+    }),
+    fill: new Fill({
+      color: "rgba(240, 230, 0, 0.2)"
+    })
+  }),
+  blue2: new Style({
+    stroke: new Stroke({
+      color: "rgba(0, 0, 255, 0.9)",
+      lineDash: [3, 6],
+      lineCap: "round",
+      width: 2
+    }),
+    fill: new Fill({
+      color: "rgba(240, 230, 0, 0.1)"
+    })
+  }),
+  blue3: new Style({
+    stroke: new Stroke({
+      color: "rgba(0, 0, 255, 1.0)",
+      width: 2
+    }),
+    fill: new Fill({
+      color: "rgba(255, 255, 255, 0.4)"
+    })
+  }),
+  yellow1: new Style({
+    stroke: new Stroke({
+      color: "rgba(230, 230, 10, .8)",
+      width: 4
+    }),
+    fill: new Fill({
+      color: "rgba(230, 230, 10, .3)"
+    })
+  }),
+  yellow2: new Style({
+    stroke: new Stroke({
+      color: "rgba(250, 200, 0, .8)",
+      width: 2
+    }),
+    fill: new Fill({
+      color: "rgba(250, 200, 10, .3)"
+    })
+  }),
+  yellow3: new Style({
+    stroke: new Stroke({
+      color: "rgba(250, 240, 10, .9)",
+      width: 5
+    }),
+    fill: new Fill({
+      color: "rgba(250, 240, 0, .7)"
+    })
+  }),
+  red1: new Style({
+    stroke: new Stroke({
+      color: "rgba(255, 0, 0, 1)",
+      width: 4
+    })
+  }),
+  circleBlue: new Style({
+    image: new Circle({
+      radius: 5,
+      fill: new Fill({ color: "rgba(255, 0, 0, 0.1)" }),
+      stroke: new Stroke({ color: "blue", width: 1 })
+    })
+  }),
+  textFW1: new Style({
+    text: new Text({
+      font: 'bold 12px "Open Sans", "sans-serif"',
+      placement: "line",
+      fill: new Fill({
+        color: "black"
+      }),
+      text: "LOS: 1"
+      //, zIndex: 10
+    })
+  }),
+  textFW2: new Style({
+    text: new Text({
+      font: 'bold 12px "Open Sans", "sans-serif"',
+      placement: "line",
+      fill: new Fill({
+        color: "black"
+      }),
+      text: "LOS: 2"
+      //, zIndex: 10
+    })
+  }),
+  textFW3: new Style({
+    text: new Text({
+      font: 'bold 12px "Open Sans", "sans-serif"',
+      placement: "line",
+      fill: new Fill({
+        color: "black"
+      }),
+      text: "LOS: 3"
+      //, zIndex: 10
+    })
+  })
+};
+
+export default {
+  stretches(feature) {
+    let style = styles.yellow2;
+    if (feature.get("highlighted")) {
+      style = styles.yellow3;
+    }
+    return style;
+  },
+  fwd1() {
+    return [styles.blue1, styles.textFW1];
+  },
+  fwd2() {
+    return [styles.blue2, styles.textFW2];
+  },
+  fwd3() {
+    return [styles.blue3, styles.textFW3];
+  },
+  bottleneck() {
+    return styles.yellow1;
+  },
+  bottleneckStatus(feature, resolution, isLegend) {
+    let s = [];
+    if ((feature.get("fa_critical") && resolution > 15) || isLegend) {
+      let bnCenter = getCenter(feature.getGeometry().getExtent());
+      s.push(
+        new Style({
+          geometry: new Point(bnCenter),
+          image: new Icon({
+            src: require("@/assets/marker-bottleneck-critical.png"),
+            anchor: [0.5, 0.5],
+            scale: isLegend ? 0.5 : 1
+          })
+        })
+      );
+    }
+    if (feature.get("fa_critical") && !isLegend) {
+      s.push(styles.red1);
+    }
+    return s;
+  },
+  dma(feature, resolution) {
+    if (resolution < 10) {
+      var s = styles.circleBlue;
+      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;
+    }
+    return [];
+  },
+  gauge(feature, resolution, isLegend) {
+    return [
+      new Style({
+        image: new Icon({
+          src: require("@/assets/marker-gauge.png"),
+          anchor: [0.5, isLegend ? 0.5 : 1],
+          scale: isLegend ? 0.5 : 1
+        }),
+        text: new Text({
+          font: '10px "Open Sans", "sans-serif"',
+          offsetY: 8,
+          fill: new Fill({
+            color: "white"
+          }),
+          text: feature.get("objname")
+        })
+      }),
+      new Style({
+        text: new Text({
+          font: '10px "Open Sans", "sans-serif"',
+          offsetY: 7,
+          offsetX: -1,
+          fill: new Fill({
+            color: "black"
+          }),
+          text: feature.get("objname")
+        })
+      })
+    ];
+  }
+};
--- a/client/src/components/toolbar/Linetool.vue	Thu Apr 11 10:11:59 2019 +0200
+++ b/client/src/components/toolbar/Linetool.vue	Thu Apr 11 11:44:11 2019 +0200
@@ -26,7 +26,7 @@
 export default {
   name: "linetool",
   computed: {
-    ...mapState("map", ["layers", "lineTool", "polygonTool", "cutTool"]),
+    ...mapState("map", ["openLayersMap", "lineTool", "polygonTool", "cutTool"]),
     label() {
       return this.$gettext("Measure Distance");
     }
@@ -37,7 +37,10 @@
       this.polygonTool.setActive(false);
       this.cutTool.setActive(false);
       this.$store.commit("map/setCurrentMeasurement", null);
-      this.layers.DRAWTOOL.getSource().clear();
+      this.openLayersMap
+        .getLayer("DRAWTOOL")
+        .getSource()
+        .clear();
     }
   }
 };
--- a/client/src/components/toolbar/Polygontool.vue	Thu Apr 11 10:11:59 2019 +0200
+++ b/client/src/components/toolbar/Polygontool.vue	Thu Apr 11 11:44:11 2019 +0200
@@ -30,7 +30,7 @@
 export default {
   name: "polygontool",
   computed: {
-    ...mapState("map", ["layers", "lineTool", "polygonTool", "cutTool"]),
+    ...mapState("map", ["openLayersMap", "lineTool", "polygonTool", "cutTool"]),
     label() {
       return this.$gettext("Measure Area");
     }
@@ -41,7 +41,10 @@
       this.lineTool.setActive(false);
       this.cutTool.setActive(false);
       this.$store.commit("map/setCurrentMeasurement", null);
-      this.layers.DRAWTOOL.getSource().clear();
+      this.openLayersMap
+        .getLayer("DRAWTOOL")
+        .getSource()
+        .clear();
     }
   }
 };
--- a/client/src/components/toolbar/Toolbar.vue	Thu Apr 11 10:11:59 2019 +0200
+++ b/client/src/components/toolbar/Toolbar.vue	Thu Apr 11 11:44:11 2019 +0200
@@ -125,7 +125,7 @@
     Pdftool: () => import("./Pdftool")
   },
   computed: {
-    ...mapState("map", ["layers", "lineTool", "polygonTool", "cutTool"]),
+    ...mapState("map", ["openLayersMap", "lineTool", "polygonTool", "cutTool"]),
     ...mapState("application", ["expandToolbar"])
   },
   mounted() {
@@ -137,7 +137,10 @@
         this.cutTool.setActive(false);
         this.$store.commit("map/setCurrentMeasurement", null);
         this.$store.dispatch("map/enableIdentifyTool");
-        this.layers.DRAWTOOL.getSource().clear();
+        this.openLayersMap
+          .getLayer("DRAWTOOL")
+          .getSource()
+          .clear();
       }
     });
   }
--- a/client/src/store/bottlenecks.js	Thu Apr 11 10:11:59 2019 +0200
+++ b/client/src/store/bottlenecks.js	Thu Apr 11 11:44:11 2019 +0200
@@ -74,7 +74,10 @@
             commit("application/splitscreenLoading", false, { root: true });
           }, 350);
           rootState.map.cutTool.setActive(false);
-          rootState.map.layers.CUTTOOL.getSource().clear();
+          rootState.map.openLayersMap
+            .getLayer("CUTTOOL")
+            .getSource()
+            .clear();
         }
         if (name) {
           commit("application/showProfiles", true, { root: true });
--- a/client/src/store/fairway.js	Thu Apr 11 10:11:59 2019 +0200
+++ b/client/src/store/fairway.js	Thu Apr 11 11:44:11 2019 +0200
@@ -137,7 +137,10 @@
       dispatch("map/enableIdentifyTool", null, { root: true });
       commit("clearCurrentProfile");
       rootState.map.cutTool.setActive(false);
-      rootState.map.layers.CUTTOOL.getSource().clear();
+      rootState.map.openLayersMap
+        .getLayer("CUTTOOL")
+        .getSource()
+        .clear();
     },
     loadProfile({ commit, state }, survey) {
       if (state.startPoint && state.endPoint) {
@@ -216,7 +219,9 @@
           Promise.all(profileLoaders)
             .then(() => {
               rootState.map.cutTool.setActive(false);
-              const los3 = rootState.map.layers.FAIRWAYDIMENSIONSLOS3;
+              const los3 = rootState.map.openLayersMap.getLayer(
+                "FAIRWAYDIMENSIONSLOS3"
+              );
               los3.getSource().forEachFeatureIntersectingExtent(
                 profileLine
                   .clone()
@@ -236,7 +241,9 @@
                   }
                 }
               );
-              const los2 = rootState.map.layers.FAIRWAYDIMENSIONSLOS2;
+              const los2 = rootState.map.openLayersMap.getLayer(
+                "FAIRWAYDIMENSIONSLOS2"
+              );
               los2.getSource().forEachFeatureIntersectingExtent(
                 profileLine
                   .clone()
@@ -256,7 +263,9 @@
                   }
                 }
               );
-              const los1 = rootState.map.layers.FAIRWAYDIMENSIONSLOS1;
+              const los1 = rootState.map.openLayersMap.getLayer(
+                "FAIRWAYDIMENSIONSLOS1"
+              );
               los1.getSource().forEachFeatureIntersectingExtent(
                 profileLine
                   .clone()
--- a/client/src/store/map.js	Thu Apr 11 10:11:59 2019 +0200
+++ b/client/src/store/map.js	Thu Apr 11 11:44:11 2019 +0200
@@ -14,17 +14,8 @@
  * * Thomas Junk <thomas.junk@intevation.de>
  */
 
-//import { HTTP } from "../lib/http";
-
-import TileWMS from "ol/source/TileWMS";
-import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer";
-import OSM from "ol/source/OSM";
 import Draw from "ol/interaction/Draw";
-import { Icon, Stroke, Style, Fill, Text, Circle } from "ol/style";
-import VectorSource from "ol/source/Vector";
-import Point from "ol/geom/Point";
-import { bbox as bboxStrategy } from "ol/loadingstrategy";
-import { HTTP } from "@/lib/http";
+import { Stroke, Style, Fill, Circle } from "ol/style";
 import { fromLonLat } from "ol/proj";
 import { getLength, getArea } from "ol/sphere";
 import { unByKey } from "ol/Observable";
@@ -42,203 +33,6 @@
   });
 };
 
-const blue1 = new Style({
-  stroke: new Stroke({
-    color: "rgba(0, 0, 255, 0.8)",
-    lineDash: [2, 4],
-    lineCap: "round",
-    width: 2
-  }),
-  fill: new Fill({
-    color: "rgba(240, 230, 0, 0.2)"
-  })
-});
-const blue2 = new Style({
-  stroke: new Stroke({
-    color: "rgba(0, 0, 255, 0.9)",
-    lineDash: [3, 6],
-    lineCap: "round",
-    width: 2
-  }),
-  fill: new Fill({
-    color: "rgba(240, 230, 0, 0.1)"
-  })
-});
-const blue3 = new Style({
-  stroke: new Stroke({
-    color: "rgba(0, 0, 255, 1.0)",
-    width: 2
-  }),
-  fill: new Fill({
-    color: "rgba(255, 255, 255, 0.4)"
-  })
-});
-const yellow1 = new Style({
-  stroke: new Stroke({
-    color: "rgba(230, 230, 10, .8)",
-    width: 4
-  }),
-  fill: new Fill({
-    color: "rgba(230, 230, 10, .3)"
-  })
-});
-const yellow2 = new Style({
-  stroke: new Stroke({
-    color: "rgba(250, 200, 0, .8)",
-    width: 2
-  }),
-  fill: new Fill({
-    color: "rgba(250, 200, 10, .3)"
-  })
-});
-const yellow3 = new Style({
-  stroke: new Stroke({
-    color: "rgba(250, 240, 10, .9)",
-    width: 5
-  }),
-  fill: new Fill({
-    color: "rgba(250, 240, 0, .7)"
-  })
-});
-const red1 = new Style({
-  stroke: new Stroke({
-    color: "rgba(255, 0, 0, 1)",
-    width: 4
-  })
-});
-
-const circleBlue = new Style({
-  image: new Circle({
-    radius: 5,
-    fill: new Fill({ color: "rgba(255, 0, 0, 0.1)" }),
-    stroke: new Stroke({ color: "blue", width: 1 })
-  })
-});
-
-const textFW1 = new Style({
-  text: new Text({
-    font: 'bold 12px "Open Sans", "sans-serif"',
-    placement: "line",
-    fill: new Fill({
-      color: "black"
-    }),
-    text: "LOS: 1"
-    //, zIndex: 10
-  })
-});
-const textFW2 = new Style({
-  text: new Text({
-    font: 'bold 12px "Open Sans", "sans-serif"',
-    placement: "line",
-    fill: new Fill({
-      color: "black"
-    }),
-    text: "LOS: 2"
-    //, zIndex: 10
-  })
-});
-const textFW3 = new Style({
-  text: new Text({
-    font: 'bold 12px "Open Sans", "sans-serif"',
-    placement: "line",
-    fill: new Fill({
-      color: "black"
-    }),
-    text: "LOS: 3"
-    //, zIndex: 10
-  })
-});
-
-const stretchesStyle = feature => {
-  let style = yellow2;
-  if (feature.get("highlighted")) {
-    style = yellow3;
-  }
-  return style;
-};
-const fwd1Style = () => {
-  return [blue1, textFW1];
-};
-const fwd2Style = () => {
-  return [blue2, textFW2];
-};
-const fwd3Style = () => {
-  return [blue3, textFW3];
-};
-const bottleneckStyle = () => {
-  return yellow1;
-};
-const bottleneckStatusStyle = (feature, resolution, isLegend) => {
-  let styles = [];
-  if ((feature.get("fa_critical") && resolution > 15) || isLegend) {
-    let bnCenter = getCenter(feature.getGeometry().getExtent());
-    styles.push(
-      new Style({
-        geometry: new Point(bnCenter),
-        image: new Icon({
-          src: require("@/assets/marker-bottleneck-critical.png"),
-          anchor: [0.5, 0.5],
-          scale: isLegend ? 0.5 : 1
-        })
-      })
-    );
-  }
-  if (feature.get("fa_critical") && !isLegend) {
-    styles.push(red1);
-  }
-  return styles;
-};
-const dmaStyle = (feature, resolution) => {
-  if (resolution < 10) {
-    var s = circleBlue;
-    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 [];
-  }
-};
-const gaugeStyle = (feature, resolution, isLegend) => {
-  return [
-    new Style({
-      image: new Icon({
-        src: require("@/assets/marker-gauge.png"),
-        anchor: [0.5, isLegend ? 0.5 : 1],
-        scale: isLegend ? 0.5 : 1
-      }),
-      text: new Text({
-        font: '10px "Open Sans", "sans-serif"',
-        offsetY: 8,
-        fill: new Fill({
-          color: "white"
-        }),
-        text: feature.get("objname")
-      })
-    }),
-    new Style({
-      text: new Text({
-        font: '10px "Open Sans", "sans-serif"',
-        offsetY: 7,
-        offsetX: -1,
-        fill: new Fill({
-          color: "black"
-        }),
-        text: feature.get("objname")
-      })
-    })
-  ];
-};
-
 // initial state
 const init = () => {
   return {
@@ -256,283 +50,7 @@
     polygonTool: null, // open layers interaction object (Draw)
     cutTool: null, // open layers interaction object (Draw)
     isolinesLegendImgDataURL: "",
-    differencesLegendImgDataURL: "",
-    layers: {
-      OPENSTREETMAP: new TileLayer({
-        label: "Open Streetmap",
-        visible: true,
-        source: new OSM()
-      }),
-      INLANDECDIS: new TileLayer({
-        label: "Inland ECDIS chart Danube",
-        visible: true,
-        source: new TileWMS({
-          preload: 1,
-          url: "https://service.d4d-portal.info/wms/",
-          crossOrigin: "anonymous",
-          params: { LAYERS: "d4d", VERSION: "1.1.1", TILED: true }
-        })
-      }),
-      WATERWAYAREA: new VectorLayer({
-        label: "Waterway Area",
-        visible: true,
-        source: new VectorSource({
-          strategy: bboxStrategy
-        }),
-        style: new Style({
-          stroke: new Stroke({
-            color: "rgba(0, 102, 0, 1)",
-            width: 2
-          })
-        })
-      }),
-      STRETCHES: new VectorLayer({
-        label: "Stretches",
-        visible: false,
-        source: new VectorSource({
-          strategy: bboxStrategy
-        }),
-        style: stretchesStyle
-      }),
-      FAIRWAYDIMENSIONSLOS1: new VectorLayer({
-        label: "LOS 1 Fairway Dimensions",
-        visible: false,
-        source: new VectorSource(),
-        style: fwd1Style
-      }),
-      FAIRWAYDIMENSIONSLOS2: new VectorLayer({
-        label: "LOS 2 Fairway Dimensions",
-        visible: false,
-        source: new VectorSource(),
-        style: fwd2Style
-      }),
-      FAIRWAYDIMENSIONSLOS3: new VectorLayer({
-        label: "LOS 3 Fairway Dimensions",
-        visible: true,
-        source: new VectorSource(),
-        style: fwd3Style
-      }),
-      WATERWAYAXIS: new VectorLayer({
-        label: "Waterway Axis",
-        visible: true,
-        source: new VectorSource({
-          strategy: bboxStrategy
-        }),
-        style: new Style({
-          stroke: new Stroke({
-            color: "rgba(0, 0, 255, .5)",
-            lineDash: [5, 5],
-            width: 2
-          })
-        }),
-        // TODO: Set layer in layertree active/inactive depending on
-        // resolution.
-        maxResolution: 5,
-        minResolution: 0
-      }),
-      WATERWAYPROFILES: new VectorLayer({
-        label: "Waterway Profiles",
-        visible: true,
-        source: new VectorSource({
-          strategy: bboxStrategy
-        }),
-        style: new Style({
-          stroke: new Stroke({
-            color: "rgba(0, 0, 255, .5)",
-            lineDash: [5, 5],
-            width: 2
-          })
-        }),
-        maxResolution: 2.5,
-        minResolution: 0
-      }),
-      BOTTLENECKS: new VectorLayer({
-        label: "Bottlenecks",
-        visible: true,
-        source: new VectorSource({
-          strategy: bboxStrategy
-        }),
-        style: bottleneckStyle
-      }),
-      BOTTLENECKISOLINE: new TileLayer({
-        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);
-        })
-      }),
-      DIFFERENCES: new TileLayer({
-        label: "Bottleneck Differences",
-        visible: false,
-        source: new TileWMS({
-          preload: 0,
-          projection: "EPSG:4326",
-          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);
-        })
-      }),
-      BOTTLENECKSTATUS: new VectorLayer({
-        label: "Critical Bottlenecks",
-        forLegendStyle: { point: true, resolution: 16 },
-        visible: true,
-        source: new VectorSource({
-          strategy: bboxStrategy
-        }),
-        style: bottleneckStatusStyle
-      }),
-      DISTANCEMARKS: new VectorLayer({
-        label: "Distance marks",
-        forLegendStyle: { point: true, resolution: 8 },
-        visible: false,
-        source: new VectorSource({
-          strategy: bboxStrategy
-        })
-      }),
-      DISTANCEMARKSAXIS: new VectorLayer({
-        label: "Distance marks, Axis",
-        forLegendStyle: { point: true, resolution: 8 },
-        visible: true,
-        source: new VectorSource({
-          strategy: bboxStrategy
-        }),
-        style: dmaStyle
-      }),
-      GAUGES: new VectorLayer({
-        label: "Gauges",
-        forLegendStyle: { point: true, resolution: 8 },
-        visible: true,
-        source: new VectorSource({
-          strategy: bboxStrategy
-        }),
-        style: gaugeStyle,
-        maxResolution: 100,
-        minResolution: 0
-      }),
-      DRAWTOOL: new VectorLayer({
-        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;
-        }
-      }),
-      CUTTOOL: new VectorLayer({
-        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: "#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;
-        }
-      })
-    }
+    differencesLegendImgDataURL: ""
   };
 };
 
@@ -602,9 +120,9 @@
     }
   },
   actions: {
-    openLayersMap({ commit, dispatch, state }, map) {
-      const drawVectorSrc = state.layers.DRAWTOOL.getSource();
-      const cutVectorSrc = state.layers.CUTTOOL.getSource();
+    openLayersMap({ commit, dispatch }, map) {
+      const drawVectorSrc = map.getLayer("DRAWTOOL").getSource();
+      const cutVectorSrc = map.getLayer("CUTTOOL").getSource();
 
       // init line tool
       const lineTool = new Draw({
@@ -814,7 +332,9 @@
             */
 
             // trying the GetFeatureInfo way for WMS
-            var wmsSource = state.layers.INLANDECDIS.getSource();
+            var wmsSource = state.openLayersMap
+              .getLayer("INLANDECDIS")
+              .getSource();
             var url = wmsSource.getGetFeatureInfoUrl(
               event.coordinate,
               100 /* resolution */,