changeset 2973:de6e5b9eae84 unified_import

merge with default
author Thomas Junk <thomas.junk@intevation.de>
date Mon, 08 Apr 2019 15:50:38 +0200
parents 6f351e00e579 (current diff) 33a29ac2c19a (diff)
children e161e1ffb6b5
files
diffstat 17 files changed, 574 insertions(+), 677 deletions(-) [+]
line wrap: on
line diff
--- a/client/src/components/ImportStretches.vue	Mon Apr 08 15:50:23 2019 +0200
+++ b/client/src/components/ImportStretches.vue	Mon Apr 08 15:50:38 2019 +0200
@@ -300,7 +300,6 @@
  */
 import { mapState, mapGetters } from "vuex";
 import { displayError, displayInfo } from "@/lib/errors";
-import { LAYERS } from "@/store/map";
 import { HTTP } from "@/lib/http";
 import { sortTable } from "@/lib/mixins";
 
@@ -339,8 +338,7 @@
   },
   computed: {
     ...mapState("application", ["searchQuery"]),
-    ...mapState("map", ["identifiedFeatures", "currentMeasurement"]),
-    ...mapGetters("map", ["getVSourceByName", "getLayerByName"]),
+    ...mapState("map", ["layers", "identifiedFeatures", "currentMeasurement"]),
     ...mapGetters("user", ["isSysAdmin"]),
     ...mapState("imports", ["stretches"]),
     defineStretchesLabel() {
@@ -460,7 +458,7 @@
     },
     moveMapToStretch(stretch) {
       this.$store.commit("imports/selectedStretchId", stretch.id);
-      this.$store.commit("map/setLayerVisible", LAYERS.STRETCHES);
+      this.layers.STRETCHES.setVisible(true);
       this.$store.commit("map/moveToExtent", {
         feature: stretch,
         zoom: 17,
@@ -498,7 +496,7 @@
       this.edit = true;
     },
     togglePipette(t) {
-      this.$store.commit("map/setLayerVisible", LAYERS.DISTANCEMARKSAXIS);
+      this.layers.DISTANCEMARKSAXIS.setVisible(true);
       if (t === "start") {
         this.pipetteStart = !this.pipetteStart;
         this.pipetteEnd = false;
--- a/client/src/components/Maplayer.vue	Mon Apr 08 15:50:23 2019 +0200
+++ b/client/src/components/Maplayer.vue	Mon Apr 08 15:50:38 2019 +0200
@@ -35,14 +35,13 @@
  * * Bernhard E. Reiter <bernhard.reiter@intevation.de>
  */
 import { HTTP } from "@/lib/http";
-import { mapGetters, mapState } from "vuex";
+import { mapState } from "vuex";
 import "ol/ol.css";
 import { Map, View } from "ol";
 import { WFS, GeoJSON } from "ol/format.js";
 import { equalTo } from "ol/format/filter.js";
 import { Stroke, Style, Fill } from "ol/style.js";
 import { displayError } from "@/lib/errors.js";
-import { LAYERS } from "@/store/map.js";
 
 /* for the sake of debugging */
 /* eslint-disable no-console */
@@ -55,7 +54,6 @@
     };
   },
   computed: {
-    ...mapGetters("map", ["getLayerByName", "getVSourceByName"]),
     ...mapState("map", [
       "initialLoad",
       "extent",
@@ -129,21 +127,14 @@
       return loader;
     },
     updateBottleneckFilter(bottleneck_id, datestr) {
-      const layer = this.getLayerByName(LAYERS.BOTTLENECKISOLINE);
-      const wmsSrc = layer.data.getSource();
       const exists = bottleneck_id != "does_not_exist";
 
       if (exists) {
-        wmsSrc.updateParams({
-          cql_filter:
-            "date_info='" +
-            datestr +
-            "' AND bottleneck_id='" +
-            bottleneck_id +
-            "'"
+        this.layers.BOTTLENECKISOLINE.getSource().updateParams({
+          cql_filter: `date_info='${datestr}' AND bottleneck_id='${bottleneck_id}'`
         });
       }
-      layer.data.setVisible(exists);
+      this.layers.BOTTLENECKISOLINE.setVisible(exists);
     }
   },
   watch: {
@@ -173,7 +164,7 @@
       }
     },
     selectedStretchId(id) {
-      this.getVSourceByName(LAYERS.STRETCHES)
+      this.layers.STRETCHES.getSource()
         .getFeatures()
         .forEach(f => {
           f.set("highlighted", false);
@@ -184,9 +175,8 @@
     }
   },
   mounted() {
-    const Layers = Object.keys(this.layers).map(x => this.layers[x].data);
     let map = new Map({
-      layers: [...Layers],
+      layers: [...Object.values(this.layers)],
       target: "map",
       controls: [],
       view: new View({
@@ -240,14 +230,13 @@
 
     // load different fairway dimension layers (level of service)
     [
-      LAYERS.FAIRWAYDIMENSIONSLOS1,
-      LAYERS.FAIRWAYDIMENSIONSLOS2,
-      LAYERS.FAIRWAYDIMENSIONSLOS3
+      "FAIRWAYDIMENSIONSLOS1",
+      "FAIRWAYDIMENSIONSLOS2",
+      "FAIRWAYDIMENSIONSLOS3"
     ].forEach((los, i) => {
       // loading the full WFS layer without bboxStrategy
-      var source = this.getVSourceByName(los);
-      /*eslint-disable no-unused-vars */
-      var loader = function(extent, resolution, projection) {
+      var source = this.layers[los].getSource();
+      var loader = function() {
         var featureRequest = new WFS().writeGetFeature({
           srsName: "EPSG:3857",
           featureNS: "gemma",
@@ -278,15 +267,11 @@
         });
       };
 
-      layer = this.getLayerByName(los);
-      layer.data.getSource().setLoader(loader);
+      this.layers[los].getSource().setLoader(loader);
     });
 
     // load following layers with bboxStrategy (using our request builder)
-    var layer = null;
-
-    layer = this.getLayerByName(LAYERS.WATERWAYAREA);
-    layer.data.getSource().setLoader(
+    this.layers.WATERWAYAREA.getSource().setLoader(
       this.buildVectorLoader(
         {
           featureNS: "gemma",
@@ -295,12 +280,11 @@
           geometryName: "area"
         },
         "/internal/wfs",
-        layer.data.getSource()
+        this.layers.WATERWAYAREA.getSource()
       )
     );
 
-    layer = this.getLayerByName(LAYERS.WATERWAYAXIS);
-    layer.data.getSource().setLoader(
+    this.layers.WATERWAYAXIS.getSource().setLoader(
       this.buildVectorLoader(
         {
           featureNS: "gemma",
@@ -309,12 +293,11 @@
           geometryName: "wtwaxs"
         },
         "/internal/wfs",
-        layer.data.getSource()
+        this.layers.WATERWAYAXIS.getSource()
       )
     );
 
-    layer = this.getLayerByName(LAYERS.WATERWAYPROFILES);
-    layer.data.getSource().setLoader(
+    this.layers.WATERWAYPROFILES.getSource().setLoader(
       this.buildVectorLoader(
         {
           featureNS: "gemma",
@@ -323,12 +306,11 @@
           geometryName: "geom"
         },
         "/internal/wfs",
-        layer.data.getSource()
+        this.layers.WATERWAYPROFILES.getSource()
       )
     );
 
-    layer = this.getLayerByName(LAYERS.DISTANCEMARKS);
-    layer.data.getSource().setLoader(
+    this.layers.DISTANCEMARKS.getSource().setLoader(
       this.buildVectorLoader(
         {
           featureNS: "gemma",
@@ -337,12 +319,11 @@
           geometryName: "geom"
         },
         "/internal/wfs",
-        layer.data.getSource()
+        this.layers.DISTANCEMARKS.getSource()
       )
     );
 
-    layer = this.getLayerByName(LAYERS.DISTANCEMARKSAXIS);
-    layer.data.getSource().setLoader(
+    this.layers.DISTANCEMARKSAXIS.getSource().setLoader(
       this.buildVectorLoader(
         {
           featureNS: "gemma",
@@ -351,12 +332,11 @@
           geometryName: "geom"
         },
         "/internal/wfs",
-        layer.data.getSource()
+        this.layers.DISTANCEMARKSAXIS.getSource()
       )
     );
 
-    layer = this.getLayerByName(LAYERS.GAUGES);
-    layer.data.getSource().setLoader(
+    this.layers.GAUGES.getSource().setLoader(
       this.buildVectorLoader(
         {
           featureNS: "gemma",
@@ -365,12 +345,11 @@
           geometryName: "geom"
         },
         "/internal/wfs",
-        layer.data.getSource()
+        this.layers.GAUGES.getSource()
       )
     );
 
-    layer = this.getLayerByName(LAYERS.STRETCHES);
-    layer.data.getSource().setLoader(
+    this.layers.STRETCHES.getSource().setLoader(
       this.buildVectorLoader(
         {
           featureNS: "gemma",
@@ -379,7 +358,7 @@
           geometryName: "area"
         },
         "/internal/wfs",
-        layer.data.getSource(),
+        this.layers.STRETCHES.getSource(),
         f => {
           if (f.getId() === this.selectedStretchId) {
             f.set("highlighted", true);
@@ -389,8 +368,7 @@
       )
     );
 
-    layer = this.getLayerByName(LAYERS.BOTTLENECKSTATUS);
-    layer.data.getSource().setLoader(
+    this.layers.BOTTLENECKSTATUS.getSource().setLoader(
       this.buildVectorLoader(
         {
           featureNS: "gemma",
@@ -399,12 +377,11 @@
           geometryName: "area"
         },
         "/internal/wfs",
-        layer.data.getSource()
+        this.layers.BOTTLENECKSTATUS.getSource()
       )
     );
 
-    layer = this.getLayerByName(LAYERS.BOTTLENECKS);
-    layer.data.getSource().setLoader(
+    this.layers.BOTTLENECKS.getSource().setLoader(
       this.buildVectorLoader(
         {
           featureNS: "gemma",
@@ -413,7 +390,7 @@
           geometryName: "area"
         },
         "/internal/wfs",
-        layer.data.getSource()
+        this.layers.BOTTLENECKS.getSource()
       )
     );
     HTTP.get("/system/style/Bottlenecks/stroke", {
@@ -435,7 +412,7 @@
                 color: btlnFillC
               })
             });
-            layer.data.setStyle(newStyle);
+            this.layers.BOTTLENECKS.setStyle(newStyle);
           })
           .catch(error => {
             console.log(error);
--- a/client/src/components/Pdftool.vue	Mon Apr 08 15:50:23 2019 +0200
+++ b/client/src/components/Pdftool.vue	Mon Apr 08 15:50:38 2019 +0200
@@ -93,14 +93,13 @@
  * * Bernhard E. Reiter <bernhard@intevation.de>
  * * Fadi Abbud <fadi.abbud@intevation.de>
  */
-import { mapGetters, mapState } from "vuex";
+import { mapState } from "vuex";
 import jsPDF from "jspdf";
 import "@/lib/font-linbiolinum.js";
 import { getPointResolution } from "ol/proj.js";
 import locale2 from "locale2";
 import { HTTP } from "@/lib/http";
 import { displayError } from "@/lib/errors.js";
-import { LAYERS } from "@/store/map.js";
 
 var paperSizes = {
   // in millimeter, landscape [width, height]
@@ -166,8 +165,7 @@
   computed: {
     ...mapState("application", ["showPdfTool", "logoForPDF"]),
     ...mapState("bottlenecks", ["selectedBottleneck", "selectedSurvey"]),
-    ...mapState("map", ["openLayersMap", "isolinesLegendImgDataURL"]),
-    ...mapGetters("map", ["getLayerByName"]),
+    ...mapState("map", ["openLayersMap", "isolinesLegendImgDataURL", "layers"]),
     ...mapState("user", ["user"]),
     generatePdfLable() {
       return this.$gettext("Generate PDF");
@@ -754,7 +752,7 @@
       if (
         this.selectedBottleneck &&
         this.selectedSurvey &&
-        this.getLayerByName(LAYERS.BOTTLENECKISOLINE).data.getVisible()
+        this.layers.BOTTLENECKISOLINE.getVisible()
       ) {
         // transforming into an HTMLImageElement only to find out
         // the width x height of the legend image
@@ -793,7 +791,7 @@
       if (
         this.selectedBottleneck &&
         this.selectedSurvey &&
-        this.getLayerByName(LAYERS.BOTTLENECKISOLINE).data.getVisible()
+        this.BOTTLENECKISOLINE.getVisible()
       ) {
         let survey = this.selectedSurvey;
 
--- a/client/src/components/fairway/Profiles.vue	Mon Apr 08 15:50:23 2019 +0200
+++ b/client/src/components/fairway/Profiles.vue	Mon Apr 08 15:50:38 2019 +0200
@@ -247,11 +247,10 @@
  * Author(s):
  * Markus Kottländer <markus.kottlaender@intevation.de>
  */
-import { mapState, mapGetters } from "vuex";
+import { mapState } from "vuex";
 import Feature from "ol/Feature";
 import LineString from "ol/geom/LineString";
 import { displayError, displayInfo } from "@/lib/errors.js";
-import { LAYERS } from "@/store/map.js";
 import { HTTP } from "@/lib/http";
 
 export default {
@@ -264,9 +263,8 @@
     };
   },
   computed: {
-    ...mapGetters("map", ["getVSourceByName", "getLayerByName"]),
     ...mapState("application", ["showProfiles"]),
-    ...mapState("map", ["lineTool", "polygonTool", "cutTool"]),
+    ...mapState("map", ["layers", "lineTool", "polygonTool", "cutTool"]),
     ...mapState("bottlenecks", [
       "bottlenecksList",
       "surveys",
@@ -350,7 +348,7 @@
         if (!cut) {
           this.$store.commit("fairwayprofile/clearCurrentProfile");
           this.$store.commit("application/showSplitscreen", false);
-          this.getVSourceByName(LAYERS.CUTTOOL).clear();
+          this.layers.CUTTOOL.getSource().clear();
         }
       }
     },
@@ -411,9 +409,7 @@
         }
       )
         .then(() => {
-          const layer = this.getLayerByName(LAYERS.DIFFERENCES);
-          const wmsSrc = layer.data.getSource();
-          wmsSrc.updateParams({
+          this.layers.DIFFERENCES.getSource().updateParams({
             cql_filter:
               "objnam='" +
               this.selectedBottleneck +
@@ -424,8 +420,8 @@
               this.additionalSurvey.date_info +
               "'"
           });
-          this.$store.commit("map/setLayerInvisible", LAYERS.BOTTLENECKISOLINE);
-          this.$store.commit("map/setLayerVisible", LAYERS.DIFFERENCES);
+          this.layers.BOTTLENECKISOLINE.setVisible(false);
+          this.layers.DIFFERENCES.setVisible(true);
         })
         .catch(error => {
           const { status, data } = error.response;
@@ -482,14 +478,14 @@
       coordinates = coordinates.filter(c => Number(c) === c);
       if (coordinates.length === 4) {
         // draw line on map
-        this.getVSourceByName(LAYERS.CUTTOOL).clear();
+        this.layers.CUTTOOL.getSource().clear();
         const cut = new Feature({
           geometry: new LineString([
             [coordinates[0], coordinates[1]],
             [coordinates[2], coordinates[3]]
           ]).transform("EPSG:4326", "EPSG:3857")
         });
-        this.getVSourceByName(LAYERS.CUTTOOL).addFeature(cut);
+        this.layers.CUTTOOL.getSource().addFeature(cut);
 
         // draw diagram
         this.$store.dispatch("fairwayprofile/cut", cut);
--- a/client/src/components/importoverview/BottleneckDetail.vue	Mon Apr 08 15:50:23 2019 +0200
+++ b/client/src/components/importoverview/BottleneckDetail.vue	Mon Apr 08 15:50:38 2019 +0200
@@ -83,7 +83,6 @@
  * Thomas Junk <thomas.junk@intevation.de>
  */
 
-import { LAYERS } from "@/store/map.js";
 import { HTTP } from "@/lib/http";
 import { WFS } from "ol/format.js";
 import { or as orFilter, equalTo as equalToFilter } from "ol/format/filter.js";
@@ -101,7 +100,8 @@
     this.loadBottlenecks();
   },
   computed: {
-    ...mapState("imports", ["showLogs", "details"])
+    ...mapState("imports", ["showLogs", "details"]),
+    ...mapState("map", ["layers"])
   },
   methods: {
     loadBottlenecks() {
@@ -147,7 +147,7 @@
         });
     },
     moveToBottleneck(index) {
-      this.$store.commit("map/setLayerVisible", LAYERS.BOTTLENECKS);
+      this.layers.BOTTLENECKS.setVisible(true);
       this.moveToExtent(this.bottlenecks[index]);
     },
     moveToExtent(feature) {
--- a/client/src/components/importoverview/StretchDetails.vue	Mon Apr 08 15:50:23 2019 +0200
+++ b/client/src/components/importoverview/StretchDetails.vue	Mon Apr 08 15:50:38 2019 +0200
@@ -21,7 +21,6 @@
  * Thomas Junk <thomas.junk@intevation.de>
  */
 import { displayError } from "@/lib/errors.js";
-import { LAYERS } from "@/store/map.js";
 import { mapState } from "vuex";
 
 export default {
@@ -31,7 +30,8 @@
     this.$store.commit("imports/hideAdditionalInfo");
   },
   computed: {
-    ...mapState("imports", ["showAdditional", "details"])
+    ...mapState("imports", ["showAdditional", "details"]),
+    ...mapState("map", ["layers"])
   },
   methods: {
     moveToExtent(feature) {
@@ -43,7 +43,7 @@
     },
     zoomToStretch() {
       const name = this.details.summary.stretch;
-      this.$store.commit("map/setLayerVisible", LAYERS.STRETCHES);
+      this.layers.STRETCHES.setVisible(true);
       this.$store
         .dispatch("imports/loadStretch", name)
         .then(response => {
--- a/client/src/components/layers/Layers.vue	Mon Apr 08 15:50:23 2019 +0200
+++ b/client/src/components/layers/Layers.vue	Mon Apr 08 15:50:38 2019 +0200
@@ -12,12 +12,22 @@
         :closeCallback="close"
       />
       <div class="box-body small">
-        <Layerselect
-          v-for="(layer, name) in layersForLegend"
-          :layer="layer"
-          :name="name"
-          :key="name"
-        ></Layerselect>
+        <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>
     </div>
   </div>
@@ -39,10 +49,8 @@
  * Markus Kottländer <markus.kottlaender@intevation.de>
  */
 import { mapState } from "vuex";
-import { LAYERS } from "@/store/map.js";
 
 export default {
-  name: "layers",
   components: {
     Layerselect: () => import("./Layerselect")
   },
@@ -51,35 +59,12 @@
     ...mapState("application", ["showLayers"]),
     layersLabel() {
       return this.$gettext("Layers");
-    },
-    layersForLegend() {
-      let orderedLayers = {};
-      this.$options.LAYOUT.forEach(el => (orderedLayers[el] = this.layers[el]));
-      return orderedLayers;
     }
   },
   methods: {
     close() {
       this.$store.commit("application/showLayers", false);
     }
-  },
-  LAYOUT: [
-    LAYERS.OPENSTREETMAP,
-    LAYERS.INLANDECDIS,
-    LAYERS.WATERWAYAREA,
-    LAYERS.STRETCHES,
-    LAYERS.FAIRWAYDIMENSIONSLOS3,
-    LAYERS.FAIRWAYDIMENSIONSLOS2,
-    LAYERS.FAIRWAYDIMENSIONSLOS1,
-    LAYERS.WATERWAYAXIS,
-    LAYERS.WATERWAYPROFILES,
-    LAYERS.BOTTLENECKS,
-    LAYERS.BOTTLENECKISOLINE,
-    LAYERS.DIFFERENCES,
-    LAYERS.BOTTLENECKSTATUS,
-    LAYERS.DISTANCEMARKS,
-    LAYERS.DISTANCEMARKSAXIS,
-    LAYERS.GAUGES
-  ]
+  }
 };
 </script>
--- a/client/src/components/layers/Layerselect.vue	Mon Apr 08 15:50:23 2019 +0200
+++ b/client/src/components/layers/Layerselect.vue	Mon Apr 08 15:50:38 2019 +0200
@@ -3,23 +3,22 @@
     <div class="form-check d-flex flex-start">
       <input
         class="form-check-input"
+        type="checkbox"
         @change="visibilityToggled"
-        :id="name"
-        type="checkbox"
-        :checked="layer.data.getVisible()"
+        :checked="layer.getVisible()"
       />
-      <LegendElement :name="name"></LegendElement>
+      <LegendElement :layer="layer"></LegendElement>
       <label
         class="pointer layername form-check-label ml-2"
         @click="visibilityToggled"
       >
-        {{ name }}
+        {{ label }}
       </label>
     </div>
-    <div v-if="layer.data.getVisible() && isBottleneckIsolineLayer">
+    <div v-if="layer.getVisible() && isBottleneckIsolineLayer">
       <img class="rounded my-1 d-block" :src="isolinesLegendImgDataURL" />
     </div>
-    <div v-if="layer.data.getVisible() && isBottleneckDifferences">
+    <div v-if="layer.getVisible() && isBottleneckDifferences">
       <img class="rounded my-1 d-block" :src="differencesLegendImgDataURL" />
     </div>
   </div>
@@ -42,29 +41,31 @@
  */
 import { HTTP } from "@/lib/http";
 import { mapState } from "vuex";
-import { LAYERS } from "@/store/map.js";
 
 export default {
-  props: ["layer", "name"],
-  name: "layerselect",
+  props: ["layer"],
   components: {
     LegendElement: () => import("./LegendElement.vue")
   },
   computed: {
     ...mapState("map", [
+      "layers",
       "isolinesLegendImgDataURL",
       "differencesLegendImgDataURL"
     ]),
     isBottleneckIsolineLayer() {
-      return this.name == LAYERS.BOTTLENECKISOLINE;
+      return this.layer == this.layers.BOTTLENECKISOLINE;
     },
     isBottleneckDifferences() {
-      return this.name == LAYERS.DIFFERENCES;
+      return this.layer == this.layers.DIFFERENCES;
+    },
+    label() {
+      return this.$gettext(this.layer.get("label"));
     }
   },
   methods: {
     visibilityToggled() {
-      this.$store.commit("map/toggleVisibilityByName", this.name);
+      this.layer.setVisible(!this.layer.getVisible());
     }
   },
   created() {
--- a/client/src/components/layers/LegendElement.vue	Mon Apr 08 15:50:23 2019 +0200
+++ b/client/src/components/layers/LegendElement.vue	Mon Apr 08 15:50:38 2019 +0200
@@ -16,7 +16,7 @@
  * Author(s):
  * Thomas Junk <thomas.junk@intevation.de>
  */
-import { mapGetters } from "vuex";
+import { mapState } from "vuex";
 
 import { Map, View } from "ol";
 import Feature from "ol/Feature";
@@ -26,22 +26,26 @@
 import Point from "ol/geom/Point";
 
 export default {
-  name: "legendelement",
-  props: ["name"],
+  props: ["layer"],
   data: function() {
     return {
-      myMap: null,
-      mapLayer: null
+      myMap: null
     };
   },
   computed: {
-    ...mapGetters("map", ["getLayerByName"]),
+    ...mapState("map", ["layers"]),
     id() {
-      return "legendelement-" + this.name;
+      return (
+        "legendelement-" +
+        this.layer
+          .get("label")
+          .toLowerCase()
+          .replace(/\s/g, "")
+      );
     },
     mstyle() {
-      if (this.mapLayer && this.mapLayer.data.getStyle) {
-        return this.mapLayer.data.getStyle();
+      if (this.layer && this.layer.getStyle) {
+        return this.layer.getStyle();
       }
     }
   },
@@ -57,8 +61,7 @@
     }
   },
   mounted() {
-    this.mapLayer = this.getLayerByName(this.name);
-    if (this.mapLayer.data.getType() == "VECTOR") {
+    if (this.layer.getType() == "VECTOR") {
       this.initMap();
     } else {
       // TODO other tiles
@@ -81,7 +84,7 @@
       });
     },
     createVectorLayer() {
-      let mapStyle = this.mapLayer.data.getStyle();
+      let mapStyle = this.layer.getStyle();
 
       let feature = new Feature({
         geometry: new LineString([[-1, 0.5], [0, 0], [0.7, 0], [1.3, -0.7]])
@@ -89,20 +92,21 @@
 
       // special case if we need to call the style function with a special
       // parameter or to detect a point layer
-      if (this.mapLayer["forLegendStyle"]) {
-        if (this.mapLayer.forLegendStyle.point) {
+      let legendStyle = this.layer.get("forLegendStyle");
+      if (legendStyle) {
+        if (legendStyle) {
           feature.setGeometry(new Point([0, 0]));
         }
-        mapStyle = this.mapLayer.data.getStyleFunction()(
+        mapStyle = this.layer.getStyleFunction()(
           feature,
-          this.mapLayer.forLegendStyle.resolution,
+          legendStyle.resolution,
           true
         );
       }
 
       // we could add extra properties here, if they are needed for
       // the styling function in the future. An idea is to extend the
-      // this.mapLayer["forLegendStyle"] for it.
+      // this.layer["forLegendStyle"] for it.
       // FIXME, this is a special case for the Fairway Dimensions style
       feature.set("level_of_service", "");
       return new VectorLayer({
--- a/client/src/components/toolbar/Linetool.vue	Mon Apr 08 15:50:23 2019 +0200
+++ b/client/src/components/toolbar/Linetool.vue	Mon Apr 08 15:50:38 2019 +0200
@@ -21,14 +21,12 @@
  * Author(s):
  * Markus Kottländer <markus.kottlaender@intevation.de>
  */
-import { mapState, mapGetters } from "vuex";
-import { LAYERS } from "@/store/map.js";
+import { mapState } from "vuex";
 
 export default {
   name: "linetool",
   computed: {
-    ...mapGetters("map", ["getVSourceByName"]),
-    ...mapState("map", ["lineTool", "polygonTool", "cutTool"]),
+    ...mapState("map", ["layers", "lineTool", "polygonTool", "cutTool"]),
     label() {
       return this.$gettext("Measure Distance");
     }
@@ -39,7 +37,7 @@
       this.polygonTool.setActive(false);
       this.cutTool.setActive(false);
       this.$store.commit("map/setCurrentMeasurement", null);
-      this.getVSourceByName(LAYERS.DRAWTOOL).clear();
+      this.layers.DRAWTOOL.getSource().clear();
     }
   }
 };
--- a/client/src/components/toolbar/Polygontool.vue	Mon Apr 08 15:50:23 2019 +0200
+++ b/client/src/components/toolbar/Polygontool.vue	Mon Apr 08 15:50:38 2019 +0200
@@ -25,14 +25,12 @@
  * Author(s):
  * Markus Kottländer <markus.kottlaender@intevation.de>
  */
-import { mapState, mapGetters } from "vuex";
-import { LAYERS } from "@/store/map.js";
+import { mapState } from "vuex";
 
 export default {
   name: "polygontool",
   computed: {
-    ...mapGetters("map", ["getVSourceByName"]),
-    ...mapState("map", ["lineTool", "polygonTool", "cutTool"]),
+    ...mapState("map", ["layers", "lineTool", "polygonTool", "cutTool"]),
     label() {
       return this.$gettext("Measure Area");
     }
@@ -43,7 +41,7 @@
       this.lineTool.setActive(false);
       this.cutTool.setActive(false);
       this.$store.commit("map/setCurrentMeasurement", null);
-      this.getVSourceByName(LAYERS.DRAWTOOL).clear();
+      this.layers.DRAWTOOL.getSource().clear();
     }
   }
 };
--- a/client/src/components/toolbar/Toolbar.vue	Mon Apr 08 15:50:23 2019 +0200
+++ b/client/src/components/toolbar/Toolbar.vue	Mon Apr 08 15:50:38 2019 +0200
@@ -111,8 +111,7 @@
  * Author(s):
  * Markus Kottländer <markus.kottlaender@intevation.de>
  */
-import { mapState, mapGetters } from "vuex";
-import { LAYERS } from "@/store/map.js";
+import { mapState } from "vuex";
 
 export default {
   name: "toolbar",
@@ -126,8 +125,7 @@
     Pdftool: () => import("./Pdftool.vue")
   },
   computed: {
-    ...mapGetters("map", ["getVSourceByName"]),
-    ...mapState("map", ["lineTool", "polygonTool", "cutTool"]),
+    ...mapState("map", ["layers", "lineTool", "polygonTool", "cutTool"]),
     ...mapState("application", ["expandToolbar"])
   },
   mounted() {
@@ -139,7 +137,7 @@
         this.cutTool.setActive(false);
         this.$store.commit("map/setCurrentMeasurement", null);
         this.$store.dispatch("map/enableIdentifyTool");
-        this.getVSourceByName(LAYERS.DRAWTOOL).clear();
+        this.layers.DRAWTOOL.getSource().clear();
       }
     });
   }
--- a/client/src/store/bottlenecks.js	Mon Apr 08 15:50:23 2019 +0200
+++ b/client/src/store/bottlenecks.js	Mon Apr 08 15:50:38 2019 +0200
@@ -15,7 +15,6 @@
 import { HTTP } from "@/lib/http";
 import { WFS } from "ol/format.js";
 import { displayError } from "@/lib/errors.js";
-import { LAYERS } from "@/store/map.js";
 
 // initial state
 const init = () => {
@@ -61,7 +60,7 @@
     }
   },
   actions: {
-    setSelectedBottleneck({ state, commit, rootState, rootGetters }, name) {
+    setSelectedBottleneck({ state, commit, rootState }, name) {
       return new Promise((resolve, reject) => {
         if (name !== state.selectedBottleneck) {
           commit("selectedSurvey", null);
@@ -75,7 +74,7 @@
             commit("application/splitscreenLoading", false, { root: true });
           }, 350);
           rootState.map.cutTool.setActive(false);
-          rootGetters["map/getVSourceByName"](LAYERS.CUTTOOL).clear();
+          rootState.map.layers.CUTTOOL.getSource().clear();
         }
         if (name) {
           commit("application/showProfiles", true, { root: true });
--- a/client/src/store/fairway.js	Mon Apr 08 15:50:23 2019 +0200
+++ b/client/src/store/fairway.js	Mon Apr 08 15:50:38 2019 +0200
@@ -20,7 +20,6 @@
 import { getLength } from "ol/sphere.js";
 import { displayError } from "@/lib/errors.js";
 import { featureToFairwayCoordinates } from "@/lib/geo.js";
-import { LAYERS } from "@/store/map.js";
 
 // initial state
 const init = () => {
@@ -133,12 +132,12 @@
     }
   },
   actions: {
-    clearSelection({ commit, dispatch, rootGetters, rootState }) {
+    clearSelection({ commit, dispatch, rootState }) {
       dispatch("bottlenecks/setSelectedBottleneck", null, { root: true });
       dispatch("map/enableIdentifyTool", null, { root: true });
       commit("clearCurrentProfile");
       rootState.map.cutTool.setActive(false);
-      rootGetters["map/getVSourceByName"](LAYERS.CUTTOOL).clear();
+      rootState.map.layers.CUTTOOL.getSource().clear();
     },
     loadProfile({ commit, state }, survey) {
       if (state.startPoint && state.endPoint) {
@@ -176,7 +175,7 @@
         });
       }
     },
-    cut({ commit, dispatch, rootState, rootGetters }, cut) {
+    cut({ commit, dispatch, rootState }, cut) {
       return new Promise(resolve => {
         const length = getLength(cut.getGeometry());
         commit(
@@ -217,10 +216,8 @@
           Promise.all(profileLoaders)
             .then(() => {
               rootState.map.cutTool.setActive(false);
-              const los3 = rootGetters["map/getLayerByName"](
-                LAYERS.FAIRWAYDIMENSIONSLOS3
-              );
-              los3.data.getSource().forEachFeatureIntersectingExtent(
+              const los3 = rootState.map.layers.FAIRWAYDIMENSIONSLOS3;
+              los3.getSource().forEachFeatureIntersectingExtent(
                 profileLine
                   .clone()
                   .transform("EPSG:4326", "EPSG:3857")
@@ -232,17 +229,15 @@
                   );
                   let fairwayData = {
                     coordinates: fairwayCoordinates,
-                    style: los3.data.getStyle()
+                    style: los3.getStyle()
                   };
                   if (fairwayCoordinates.length > 0) {
                     commit("addFairwayData", fairwayData);
                   }
                 }
               );
-              const los2 = rootGetters["map/getLayerByName"](
-                LAYERS.FAIRWAYDIMENSIONSLOS2
-              );
-              los2.data.getSource().forEachFeatureIntersectingExtent(
+              const los2 = rootState.map.layers.FAIRWAYDIMENSIONSLOS2;
+              los2.getSource().forEachFeatureIntersectingExtent(
                 profileLine
                   .clone()
                   .transform("EPSG:4326", "EPSG:3857")
@@ -254,17 +249,15 @@
                   );
                   let fairwayData = {
                     coordinates: fairwayCoordinates,
-                    style: los2.data.getStyle()
+                    style: los2.getStyle()
                   };
                   if (fairwayCoordinates.length > 0) {
                     commit("addFairwayData", fairwayData);
                   }
                 }
               );
-              const los1 = rootGetters["map/getLayerByName"](
-                LAYERS.FAIRWAYDIMENSIONSLOS1
-              );
-              los1.data.getSource().forEachFeatureIntersectingExtent(
+              const los1 = rootState.map.layers.FAIRWAYDIMENSIONSLOS1;
+              los1.getSource().forEachFeatureIntersectingExtent(
                 profileLine
                   .clone()
                   .transform("EPSG:4326", "EPSG:3857")
@@ -276,7 +269,7 @@
                   );
                   let fairwayData = {
                     coordinates: fairwayCoordinates,
-                    style: los1.data.getStyle()
+                    style: los1.getStyle()
                   };
                   if (fairwayCoordinates.length > 0) {
                     commit("addFairwayData", fairwayData);
--- a/client/src/store/map.js	Mon Apr 08 15:50:23 2019 +0200
+++ b/client/src/store/map.js	Mon Apr 08 15:50:38 2019 +0200
@@ -33,27 +33,6 @@
 import bbox from "@turf/bbox";
 import app from "@/main";
 
-const LAYERS = {
-  OPENSTREETMAP: "Open Streetmap",
-  INLANDECDIS: "Inland ECDIS chart Danube",
-  WATERWAYAREA: "Waterway Area",
-  STRETCHES: "Stretches",
-  FAIRWAYDIMENSIONSLOS1: "LOS 1 Fairway Dimensions",
-  FAIRWAYDIMENSIONSLOS2: "LOS 2 Fairway Dimensions",
-  FAIRWAYDIMENSIONSLOS3: "LOS 3 Fairway Dimensions",
-  WATERWAYAXIS: "Waterway Axis",
-  WATERWAYPROFILES: "Waterway Profiles",
-  BOTTLENECKS: "Bottlenecks",
-  BOTTLENECKSTATUS: "Critical Bottlenecks",
-  BOTTLENECKISOLINE: "Bottleneck isolines",
-  DISTANCEMARKS: "Distance marks",
-  DISTANCEMARKSAXIS: "Distance marks, Axis",
-  GAUGES: "Gauges",
-  DRAWTOOL: "Draw Tool",
-  CUTTOOL: "Cut Tool",
-  DIFFERENCES: "Bottleneck Differences"
-};
-
 const moveMap = ({ view, extent, zoom, preventZoomOut }) => {
   const currentZoom = view.getZoom();
   zoom = zoom || currentZoom;
@@ -82,484 +61,466 @@
     isolinesLegendImgDataURL: "",
     differencesLegendImgDataURL: "",
     layers: {
-      [LAYERS.OPENSTREETMAP]: {
-        data: new TileLayer({
-          visible: true,
-          source: new OSM()
+      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 }
         })
-      },
-      [LAYERS.INLANDECDIS]: {
-        data: new TileLayer({
-          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
           })
         })
-      },
-      [LAYERS.WATERWAYAREA]: {
-        data: new VectorLayer({
-          visible: true,
-          source: new VectorSource({
-            strategy: bboxStrategy
-          }),
-          style: new Style({
+      }),
+      STRETCHES: new VectorLayer({
+        label: "Stretches",
+        visible: false,
+        source: new VectorSource({
+          strategy: bboxStrategy
+        }),
+        style: feature => {
+          let style = new Style({
             stroke: new Stroke({
-              color: "rgba(0, 102, 0, 1)",
+              color: "rgba(250, 200, 0, .8)",
               width: 2
+            }),
+            fill: new Fill({
+              color: "rgba(250, 200, 10, .3)"
             })
-          })
-        })
-      },
-      [LAYERS.STRETCHES]: {
-        data: new VectorLayer({
-          visible: false,
-          source: new VectorSource({
-            strategy: bboxStrategy
-          }),
-          style: feature => {
-            let style = new Style({
+          });
+          if (feature.get("highlighted")) {
+            style = new Style({
               stroke: new Stroke({
-                color: "rgba(250, 200, 0, .8)",
-                width: 2
+                color: "rgba(250, 240, 10, .9)",
+                width: 5
               }),
               fill: new Fill({
-                color: "rgba(250, 200, 10, .3)"
-              })
-            });
-            if (feature.get("highlighted")) {
-              style = new Style({
-                stroke: new Stroke({
-                  color: "rgba(250, 240, 10, .9)",
-                  width: 5
-                }),
-                fill: new Fill({
-                  color: "rgba(250, 240, 0, .7)"
-                })
-              });
-            }
-
-            return style;
-          }
-        })
-      },
-      [LAYERS.FAIRWAYDIMENSIONSLOS3]: {
-        data: new VectorLayer({
-          visible: true,
-          source: new VectorSource(),
-          style: function() {
-            return [
-              new Style({
-                stroke: new Stroke({
-                  color: "rgba(0, 0, 255, 1.0)",
-                  width: 2
-                }),
-                fill: new Fill({
-                  color: "rgba(255, 255, 255, 0.4)"
-                })
-              }),
-              new Style({
-                text: new Text({
-                  font: 'bold 12px "Open Sans", "sans-serif"',
-                  placement: "line",
-                  fill: new Fill({
-                    color: "black"
-                  }),
-                  text: "LOS: 3"
-                  //, zIndex: 10
-                })
-              })
-            ];
-          }
-        })
-      },
-      [LAYERS.FAIRWAYDIMENSIONSLOS2]: {
-        data: new VectorLayer({
-          visible: false,
-          source: new VectorSource(),
-          style: function() {
-            return [
-              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)"
-                })
-              }),
-              new Style({
-                text: new Text({
-                  font: 'bold 12px "Open Sans", "sans-serif"',
-                  placement: "line",
-                  fill: new Fill({
-                    color: "black"
-                  }),
-                  text: "LOS: 2"
-                  //, zIndex: 10
-                })
-              })
-            ];
-          }
-        })
-      },
-      [LAYERS.FAIRWAYDIMENSIONSLOS1]: {
-        data: new VectorLayer({
-          visible: false,
-          source: new VectorSource(),
-          style: function() {
-            return [
-              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)"
-                })
-              }),
-              new Style({
-                text: new Text({
-                  font: 'bold 12px "Open Sans", "sans-serif"',
-                  placement: "line",
-                  fill: new Fill({
-                    color: "black"
-                  }),
-                  text: "LOS: 1"
-                  //, zIndex: 10
-                })
-              })
-            ];
-          }
-        })
-      },
-      [LAYERS.WATERWAYAXIS]: {
-        data: new VectorLayer({
-          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
-        })
-      },
-      [LAYERS.WATERWAYPROFILES]: {
-        data: new VectorLayer({
-          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
-        })
-      },
-      [LAYERS.BOTTLENECKS]: {
-        data: new VectorLayer({
-          visible: true,
-          source: new VectorSource({
-            strategy: bboxStrategy
-          }),
-          style: function() {
-            return new Style({
-              stroke: new Stroke({
-                color: "rgba(230, 230, 10, .8)",
-                width: 4
-              }),
-              fill: new Fill({
-                color: "rgba(230, 230, 10, .3)"
+                color: "rgba(250, 240, 0, .7)"
               })
             });
           }
-        })
-      },
-      [LAYERS.BOTTLENECKISOLINE]: {
-        data: new TileLayer({
-          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);
+
+          return style;
+        }
+      }),
+      FAIRWAYDIMENSIONSLOS1: new VectorLayer({
+        label: "LOS 1 Fairway Dimensions",
+        visible: false,
+        source: new VectorSource(),
+        style: function() {
+          return [
+            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)"
+              })
+            }),
+            new Style({
+              text: new Text({
+                font: 'bold 12px "Open Sans", "sans-serif"',
+                placement: "line",
+                fill: new Fill({
+                  color: "black"
+                }),
+                text: "LOS: 1"
+                //, zIndex: 10
+              })
+            })
+          ];
+        }
+      }),
+      FAIRWAYDIMENSIONSLOS2: new VectorLayer({
+        label: "LOS 2 Fairway Dimensions",
+        visible: false,
+        source: new VectorSource(),
+        style: function() {
+          return [
+            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)"
+              })
+            }),
+            new Style({
+              text: new Text({
+                font: 'bold 12px "Open Sans", "sans-serif"',
+                placement: "line",
+                fill: new Fill({
+                  color: "black"
+                }),
+                text: "LOS: 2"
+                //, zIndex: 10
+              })
+            })
+          ];
+        }
+      }),
+      FAIRWAYDIMENSIONSLOS3: new VectorLayer({
+        label: "LOS 3 Fairway Dimensions",
+        visible: true,
+        source: new VectorSource(),
+        style: function() {
+          return [
+            new Style({
+              stroke: new Stroke({
+                color: "rgba(0, 0, 255, 1.0)",
+                width: 2
+              }),
+              fill: new Fill({
+                color: "rgba(255, 255, 255, 0.4)"
+              })
+            }),
+            new Style({
+              text: new Text({
+                font: 'bold 12px "Open Sans", "sans-serif"',
+                placement: "line",
+                fill: new Fill({
+                  color: "black"
+                }),
+                text: "LOS: 3"
+                //, zIndex: 10
+              })
+            })
+          ];
+        }
+      }),
+      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: function() {
+          return new Style({
+            stroke: new Stroke({
+              color: "rgba(230, 230, 10, .8)",
+              width: 4
+            }),
+            fill: new Fill({
+              color: "rgba(230, 230, 10, .3)"
+            })
+          });
+        }
+      }),
+      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);
         })
-      },
-      [LAYERS.DIFFERENCES]: {
-        data: new TileLayer({
-          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);
-          })
+      }),
+      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);
         })
-      },
-      [LAYERS.BOTTLENECKSTATUS]: {
+      }),
+      BOTTLENECKSTATUS: new VectorLayer({
+        label: "Critical Bottlenecks",
         forLegendStyle: { point: true, resolution: 16 },
-        data: new VectorLayer({
-          visible: true,
-          source: new VectorSource({
-            strategy: bboxStrategy
-          }),
-          style: function(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
-                  })
+        visible: true,
+        source: new VectorSource({
+          strategy: bboxStrategy
+        }),
+        style: function(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(
+              new Style({
+                stroke: new Stroke({
+                  color: "rgba(255, 0, 0, 1)",
+                  width: 4
                 })
-              );
-            }
-            if (feature.get("fa_critical") && !isLegend) {
-              styles.push(
-                new Style({
-                  stroke: new Stroke({
-                    color: "rgba(255, 0, 0, 1)",
-                    width: 4
-                  })
+              })
+            );
+          }
+          return styles;
+        }
+      }),
+      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: 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 styles;
+            return s;
+          } else {
+            return [];
           }
-        })
-      },
-      [LAYERS.DISTANCEMARKS]: {
-        forLegendStyle: { point: true, resolution: 8 },
-        data: new VectorLayer({
-          visible: false,
-          source: new VectorSource({
-            strategy: bboxStrategy
-          })
-        })
-      },
-      [LAYERS.DISTANCEMARKSAXIS]: {
+        }
+      }),
+      GAUGES: new VectorLayer({
+        label: "Gauges",
         forLegendStyle: { point: true, resolution: 8 },
-        data: new VectorLayer({
-          visible: true,
-          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 [];
-            }
-          }
-        })
-      },
-      [LAYERS.GAUGES]: {
-        forLegendStyle: { point: true, resolution: 8 },
-        data: new VectorLayer({
-          visible: true,
-          source: new VectorSource({
-            strategy: bboxStrategy
-          }),
-          style: function(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
+        visible: true,
+        source: new VectorSource({
+          strategy: bboxStrategy
+        }),
+        style: function(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: 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")
-                })
+                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")
+              })
+            })
+          ];
+        },
+        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
               })
-            ];
-          },
-          maxResolution: 100,
-          minResolution: 0
-        })
-      },
-      [LAYERS.DRAWTOOL]: {
-        data: new VectorLayer({
-          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
-                    })
+          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;
+                })
+              );
+            });
           }
-        })
-      },
-      [LAYERS.CUTTOOL]: {
-        data: new VectorLayer({
-          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]
-                })
+          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
-                    })
+          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;
+                })
+              );
+            });
           }
-        })
-      }
+          return styles;
+        }
+      })
     }
   };
 };
@@ -569,12 +530,6 @@
   namespaced: true,
   state: init(),
   getters: {
-    getLayerByName: state => name => {
-      return state.layers[name];
-    },
-    getVSourceByName: (state, getters) => name => {
-      return getters.getLayerByName(name).data.getSource();
-    },
     filteredIdentifiedFeatures: state => {
       return state.identifiedFeatures.filter(f => f.getId());
     }
@@ -586,15 +541,6 @@
     extent: (state, extent) => {
       state.extent = extent;
     },
-    setLayerVisible: (state, name) => {
-      state.layers[name].data.setVisible(true);
-    },
-    setLayerInvisible: (state, name) => {
-      state.layers[name].data.setVisible(false);
-    },
-    toggleVisibilityByName: (state, name) => {
-      state.layers[name].data.setVisible(!state.layers[name].data.getVisible());
-    },
     openLayersMap: (state, map) => {
       state.openLayersMap = map;
     },
@@ -645,9 +591,9 @@
     }
   },
   actions: {
-    openLayersMap({ commit, dispatch, getters }, map) {
-      const drawVectorSrc = getters.getVSourceByName("Draw Tool");
-      const cutVectorSrc = getters.getVSourceByName("Cut Tool");
+    openLayersMap({ commit, dispatch, state }, map) {
+      const drawVectorSrc = state.layers.DRAWTOOL.getSource();
+      const cutVectorSrc = state.layers.CUTTOOL.getSource();
 
       // init line tool
       const lineTool = new Draw({
@@ -739,7 +685,7 @@
       unByKey(state.identifyTool);
       state.identifyTool = null;
     },
-    enableIdentifyTool({ state, rootState, commit, dispatch, getters }) {
+    enableIdentifyTool({ state, rootState, commit, dispatch }) {
       if (!state.identifyTool) {
         state.identifyTool = state.openLayersMap.on(
           ["singleclick", "dblclick"],
@@ -857,9 +803,7 @@
             */
 
             // trying the GetFeatureInfo way for WMS
-            var wmsSource = getters.getVSourceByName(
-              "Inland ECDIS chart Danube"
-            );
+            var wmsSource = state.layers.INLANDECDIS.getSource();
             var url = wmsSource.getGetFeatureInfoUrl(
               event.coordinate,
               100 /* resolution */,
@@ -878,5 +822,3 @@
     }
   }
 };
-
-export { LAYERS };
--- a/pkg/scheduler/scheduler.go	Mon Apr 08 15:50:23 2019 +0200
+++ b/pkg/scheduler/scheduler.go	Mon Apr 08 15:50:38 2019 +0200
@@ -78,6 +78,8 @@
 
 	cr := cron.New()
 
+	var numJobs int
+
 	for {
 		var ba BoundAction
 		ok, err := next(&ba)
@@ -97,8 +99,11 @@
 			cfgID:     ba.CfgID,
 		}
 		cr.Schedule(schedule, job)
+		numJobs++
 	}
 
+	log.Printf("info: booting %d scheduler jobs from database.\n", numJobs)
+
 	s.mu.Lock()
 	defer s.mu.Unlock()
 
--- a/schema/isrs_functions.sql	Mon Apr 08 15:50:23 2019 +0200
+++ b/schema/isrs_functions.sql	Mon Apr 08 15:50:38 2019 +0200
@@ -65,13 +65,18 @@
                 FROM waterway.waterway_axis, utm_zone),
         -- In order to guarantee the following ST_Covers to work,
         -- snap distance mark coordinates to axis
-        points AS (
+        points0 AS (
             SELECT ST_ClosestPoint(
                     wtwaxs,
                     ST_Transform(geom, z)) AS geom
                 FROM ST_Dump(ISRSrange_points(stretch)), utm_zone, (
                     SELECT ST_Collect(wtwaxs) AS wtwaxs
                         FROM axis) AS ax),
+        -- Ensure two distinct points on axis have been found
+        points AS (
+            SELECT geom
+                FROM points0
+                WHERE 2 = (SELECT count(DISTINCT geom) FROM points0)),
         axis_snapped AS (
             -- Iteratively connect non-contiguous axis chunks
             -- to find the contiguous axis on which given distance marks lie
@@ -119,8 +124,8 @@
         -- end of the resulting linestring, that significantly differ from
         -- the direction of the input linestring due to finite precision
         -- of the calculation. The generated small segment of the
-        -- resulting line leads to unexpected results of the buffer with
-        -- endcap=flat in the CTE below.
+        -- resulting line would lead e.g. to unexpected results in an area
+        -- generated by ISRSrange_area().
         SELECT ST_SimplifyPreserveTopology(ST_LineSubstring(
                     axis_segment.line, min(fractions.f), max(fractions.f)),
                 0.0001) AS line