changeset 1687:54df04e9e974

Merged.
author Sascha L. Teichmann <teichmann@intevation.de>
date Sat, 29 Dec 2018 16:07:40 +0100
parents 451c7d3fe6be (current diff) 8f5a5c86f2a9 (diff)
children 774174d09d30
files client/src/components/admin/Importqueue.vue client/src/components/admin/Logs.vue client/src/components/admin/Systemconfiguration.vue client/src/components/admin/importschedule/Importschedule.vue client/src/components/admin/importschedule/Importscheduledetail.vue client/src/components/admin/usermanagement/Passwordfield.vue client/src/components/admin/usermanagement/Userdetail.vue client/src/components/admin/usermanagement/Usermanagement.vue client/src/components/map/Identify.vue client/src/components/map/Main.vue client/src/components/map/Maplayer.vue client/src/components/map/Pdftool.vue client/src/components/map/Search.vue client/src/components/map/Zoom.vue client/src/components/map/contextbox/Bottlenecks.vue client/src/components/map/contextbox/Contextbox.vue client/src/components/map/contextbox/ImportSoundingresults.vue client/src/components/map/contextbox/Staging.vue client/src/components/map/fairway/Fairwayprofile.vue client/src/components/map/fairway/Infobar.vue client/src/components/map/fairway/Profiles.vue client/src/components/map/layers/Layers.vue client/src/components/map/layers/Layerselect.vue client/src/components/map/layers/LegendElement.vue client/src/components/map/toolbar/Identify.vue client/src/components/map/toolbar/Layers.vue client/src/components/map/toolbar/Linetool.vue client/src/components/map/toolbar/Pdftool.vue client/src/components/map/toolbar/Polygontool.vue client/src/components/map/toolbar/Profiles.vue client/src/components/map/toolbar/Toolbar.vue cmd/bottlenecks/main.go
diffstat 139 files changed, 14083 insertions(+), 6379 deletions(-) [+]
line wrap: on
line diff
--- a/3rdpartylibs.sh	Sat Dec 29 16:06:54 2018 +0100
+++ b/3rdpartylibs.sh	Sat Dec 29 16:07:40 2018 +0100
@@ -32,6 +32,9 @@
 go get -u -v github.com/jonas-p/go-shp
 # MIT
 
+go get -u -v github.com/robfig/cron
+# MIT
+
 ## list of additional licenses that get fetched and installed as dependencies
 # github.com/fsnotify/fsnotify/ BSD-3-Clause
 # github.com/hashicorp/hcl/ MPL-2.0
--- a/client/package.json	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/package.json	Sat Dec 29 16:07:40 2018 +0100
@@ -1,6 +1,6 @@
 {
   "name": "gemmajs",
-  "version": "1.99.0-dev",
+  "version": "2.0.0-dev",
   "license": "AGPL-3.0-or-later",
   "repository": {
     "type": "hg",
@@ -11,6 +11,7 @@
     "run:both": "concurrently \"../cmd/gemma/gemma\" \"vue-cli-service serve\"",
     "serve": "VUE_APP_HGREV=$(hg log -r . --template \"{data|shortdate}-{node|short}\") vue-cli-service serve",
     "build": "VUE_APP_HGREV=$(hg log -r . --template \"{data|shortdate}-{node|short}\") vue-cli-service build",
+    "analyze": "ANALYZE=true vue-cli-service serve",
     "lint": "vue-cli-service lint",
     "test:unit": "vue-cli-service test:unit",
     "test:e2e": "vue-cli-service test:e2e"
@@ -21,6 +22,7 @@
     "@fortawesome/free-regular-svg-icons": "^5.5.0",
     "@fortawesome/free-solid-svg-icons": "^5.5.0",
     "@fortawesome/vue-fontawesome": "^0.1.2",
+    "@turf/center": "^6.0.1",
     "@turf/distance": "^6.0.1",
     "@turf/helpers": "^6.1.4",
     "@turf/line-intersect": "^6.0.2",
@@ -34,12 +36,14 @@
     "locale2": "^2.2.0",
     "ol": "^5.3.0",
     "path": "^0.12.7",
+    "prettier": "^1.15.3",
     "purgecss-webpack-plugin": "^1.4.0",
     "v-tooltip": "^2.0.0-rc.33",
     "vue": "^2.5.16",
     "vue-clipboard2": "^0.2.1",
     "vue-color": "^2.6.0",
     "vue-highlightjs": "^1.3.3",
+    "vue-js-toggle-button": "^1.3.0",
     "vue-router": "^3.0.2",
     "vue-snotify": "^3.2.1",
     "vuex": "^3.0.1",
@@ -59,7 +63,7 @@
     "copy-webpack-plugin": "^4.6.0",
     "easygettext": "^2.7.0",
     "node-sass": "^4.10.0",
-    "pretty-quick": "^1.6.0",
+    "pretty-quick": "^1.8.0",
     "sass-loader": "^7.0.1",
     "vue-gettext": "^2.1.1",
     "vue-template-compiler": "^2.5.17",
--- a/client/src/assets/application.scss	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/assets/application.scss	Sat Dec 29 16:07:40 2018 +0100
@@ -57,6 +57,11 @@
   transform: translate(-50%, -50%);
 }
 
+.header {
+  font-weight: bold;
+  font-size: 0.9em;
+}
+
 .ui-element {
   pointer-events: auto;
 }
--- a/client/src/components/App.vue	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/components/App.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -87,16 +87,16 @@
     }
   },
   components: {
-    Profiles: () => import("./map/fairway/Profiles"),
-    Infobar: () => import("./map/fairway/Infobar"),
-    Pdftool: () => import("./map/Pdftool"),
-    Zoom: () => import("./map/Zoom"),
-    Identify: () => import("./map/Identify"),
-    Layers: () => import("./map/layers/Layers"),
+    Profiles: () => import("./fairway/Profiles"),
+    Infobar: () => import("./fairway/Infobar"),
+    Pdftool: () => import("./Pdftool"),
+    Zoom: () => import("./Zoom"),
+    Identify: () => import("./Identify"),
+    Layers: () => import("./layers/Layers"),
     Sidebar: () => import("./Sidebar"),
-    Search: () => import("./map/Search"),
-    Contextbox: () => import("./map/contextbox/Contextbox"),
-    Toolbar: () => import("./map/toolbar/Toolbar")
+    Search: () => import("./Search"),
+    Contextbox: () => import("./Contextbox"),
+    Toolbar: () => import("./toolbar/Toolbar")
   }
 };
 </script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/Bottlenecks.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,323 @@
+<template>
+  <div>
+    <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
+      <font-awesome-icon icon="ship" class="mr-2"></font-awesome-icon>
+      <translate>Bottlenecks</translate>
+    </h6>
+    <div class="row p-2 text-left small">
+      <div class="col-5">
+        <a href="#" @click="sortBy('name')" class="sort-link">
+          <translate>Name</translate>
+        </a>
+        <font-awesome-icon
+          :icon="sortIcon"
+          class="ml-1"
+          v-if="sortColumn === 'name'"
+        ></font-awesome-icon>
+      </div>
+      <div class="col-2">
+        <a href="#" @click="sortBy('latestMeasurement')" class="sort-link">
+          <translate>Latest</translate> <br />
+          <translate>Measurement</translate>
+        </a>
+        <font-awesome-icon
+          :icon="sortIcon"
+          class="ml-1"
+          v-if="sortColumn === 'latestMeasurement'"
+        ></font-awesome-icon>
+      </div>
+      <div class="col-3">
+        <a href="#" @click="sortBy('chainage')" class="sort-link">
+          <translate>Chainage</translate>
+        </a>
+        <font-awesome-icon
+          :icon="sortIcon"
+          class="ml-1"
+          v-if="sortColumn === 'chainage'"
+        ></font-awesome-icon>
+      </div>
+      <div class="col-2"></div>
+    </div>
+    <div
+      class="bottleneck-list small text-left"
+      :style="'max-height: ' + (showSplitscreen ? 18 : 35) + 'rem'"
+      v-if="filteredAndSortedBottlenecks().length"
+    >
+      <div
+        v-for="bottleneck in filteredAndSortedBottlenecks()"
+        :key="bottleneck.properties.name"
+        class="border-top row bottleneck-row mx-0"
+      >
+        <div class="col-5 py-2 text-left">
+          <a href="#" @click="selectBottleneck(bottleneck)">{{
+            bottleneck.properties.name
+          }}</a>
+        </div>
+        <div class="col-2 py-2">
+          {{ formatSurveyDate(bottleneck.properties.current) }}
+        </div>
+        <div class="col-3 py-2">
+          {{
+            displayCurrentChainage(
+              bottleneck.properties.from,
+              bottleneck.properties.to
+            )
+          }}
+        </div>
+        <div class="col-2 pr-0 text-right d-flex flex-column">
+          <a
+            class="text-info mt-auto mb-auto mr-2"
+            @click="loadSurveys(bottleneck.properties.name)"
+            v-if="bottleneck.properties.current"
+          >
+            <font-awesome-icon
+              icon="spinner"
+              fixed-width
+              spin
+              v-if="loading === bottleneck.properties.name"
+            ></font-awesome-icon>
+            <font-awesome-icon
+              icon="angle-down"
+              fixed-width
+              v-if="
+                loading !== bottleneck.properties.name &&
+                  openBottleneck !== bottleneck.properties.name
+              "
+            ></font-awesome-icon>
+            <font-awesome-icon
+              icon="angle-up"
+              fixed-width
+              v-if="
+                loading !== bottleneck.properties.name &&
+                  openBottleneck === bottleneck.properties.name
+              "
+            ></font-awesome-icon>
+          </a>
+        </div>
+        <div
+          :class="[
+            'col-12 p-0',
+            'surveys',
+            { open: openBottleneck === bottleneck.properties.name }
+          ]"
+        >
+          <a
+            href="#"
+            class="d-block px-3 py-2"
+            v-for="(survey, index) in openBottleneckSurveys"
+            :key="index"
+            @click="selectSurvey(survey, bottleneck)"
+            >{{ formatSurveyDate(survey.date_info) }}</a
+          >
+        </div>
+      </div>
+    </div>
+    <div v-else class="small text-center py-3 border-top">
+      <translate>No results.</translate>
+    </div>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState } from "vuex";
+import { HTTP } from "@/lib/http";
+import { displayError } from "@/lib/errors.js";
+import { formatSurveyDate } from "@/lib/date.js";
+
+export default {
+  name: "bottlenecks",
+  data() {
+    return {
+      sortColumn: "name",
+      sortDirection: "ASC",
+      openBottleneck: null,
+      openBottleneckSurveys: null,
+      loading: null
+    };
+  },
+  computed: {
+    ...mapState("application", [
+      "searchQuery",
+      "showSearchbarLastState",
+      "showSplitscreen"
+    ]),
+    ...mapState("bottlenecks", ["bottlenecks"]),
+    sortIcon() {
+      return this.sortDirection === "ASC"
+        ? "sort-amount-down"
+        : "sort-amount-up";
+    }
+  },
+  methods: {
+    formatSurveyDate(date) {
+      return formatSurveyDate(date);
+    },
+    filteredAndSortedBottlenecks() {
+      return this.bottlenecks
+        .filter(bn => {
+          return bn.properties.name
+            .toLowerCase()
+            .includes(this.searchQuery.toLowerCase());
+        })
+        .sort((bnA, bnB) => {
+          switch (this.sortColumn) {
+            case "name":
+              if (
+                bnA.properties.name.toLowerCase() <
+                bnB.properties.name.toLowerCase()
+              )
+                return this.sortDirection === "ASC" ? -1 : 1;
+              if (
+                bnA.properties.name.toLowerCase() >
+                bnB.properties.name.toLowerCase()
+              )
+                return this.sortDirection === "ASC" ? 1 : -1;
+              return 0;
+
+            case "latestMeasurement": {
+              if (
+                (bnA.properties.current || "") < (bnB.properties.current || "")
+              )
+                return this.sortDirection === "ASC" ? -1 : 1;
+              if (
+                (bnA.properties.current || "") > (bnB.properties.current || "")
+              )
+                return this.sortDirection === "ASC" ? 1 : -1;
+              return 0;
+            }
+
+            case "chainage":
+              if (bnA.properties.from < bnB.properties.from)
+                return this.sortDirection === "ASC" ? -1 : 1;
+              if (bnA.properties.from > bnB.properties.from)
+                return this.sortDirection === "ASC" ? 1 : -1;
+              return 0;
+
+            default:
+              return 0;
+          }
+        });
+    },
+    selectSurvey(survey, bottleneck) {
+      this.$store
+        .dispatch(
+          "bottlenecks/setSelectedBottleneck",
+          bottleneck.properties.name
+        )
+        .then(() => {
+          this.$store.commit("bottlenecks/selectedSurvey", survey);
+        })
+        .then(() => {
+          this.$store.commit("map/moveMap", {
+            coordinates: bottleneck.geometry.coordinates,
+            zoom: 17,
+            preventZoomOut: true
+          });
+        });
+    },
+    selectBottleneck(bottleneck) {
+      this.$store
+        .dispatch(
+          "bottlenecks/setSelectedBottleneck",
+          bottleneck.properties.name
+        )
+        .then(() => {
+          this.$store.commit("bottlenecks/setFirstSurveySelected");
+        })
+        .then(() => {
+          this.$store.commit("map/moveMap", {
+            coordinates: bottleneck.geometry.coordinates,
+            zoom: 17,
+            preventZoomOut: true
+          });
+        });
+    },
+    sortBy(column) {
+      this.sortColumn = column;
+      this.sortDirection = this.sortDirection === "ASC" ? "DESC" : "ASC";
+    },
+    loadSurveys(name) {
+      this.openBottleneckSurveys = null;
+      if (name === this.openBottleneck) {
+        this.openBottleneck = null;
+      } else {
+        this.openBottleneck = name;
+        this.loading = name;
+
+        HTTP.get("/surveys/" + name, {
+          headers: {
+            "X-Gemma-Auth": localStorage.getItem("token"),
+            "Content-type": "text/xml; charset=UTF-8"
+          }
+        })
+          .then(response => {
+            this.openBottleneckSurveys = response.data.surveys.sort((a, b) => {
+              return a.date_info < b.date_info ? 1 : -1;
+            });
+          })
+          .catch(error => {
+            const { status, data } = error.response;
+            displayError({
+              title: this.$gettext("Backend Error"),
+              message: `${status}: ${data.message || data}`
+            });
+          })
+          .finally(() => (this.loading = null));
+      }
+    },
+    displayCurrentChainage(from, to) {
+      return from / 10 + " - " + to / 10;
+    }
+  },
+  mounted() {
+    this.$store.dispatch("bottlenecks/loadBottlenecks");
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.bottleneck-list {
+  overflow-y: auto;
+}
+
+.bottleneck-list .bottleneck-row a {
+  text-decoration: none;
+}
+
+.bottleneck-list .bottleneck-row:hover {
+  background: #fbfbfb;
+}
+
+.surveys {
+  max-height: 0;
+  min-height: 0;
+  overflow: hidden;
+}
+
+.surveys a:hover {
+  background: #f3f3f3;
+}
+
+.surveys.open {
+  max-height: 250px;
+  overflow: auto;
+}
+
+.sort-link {
+  color: #444;
+  font-weight: bold;
+}
+</style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/Contextbox.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,94 @@
+<template>
+  <div :class="style">
+    <div @click="close" class="ui-element close-contextbox text-muted">
+      <font-awesome-icon icon="times"></font-awesome-icon>
+    </div>
+    <Bottlenecks v-if="contextBoxContent === 'bottlenecks'"></Bottlenecks>
+    <Staging v-if="contextBoxContent === 'staging'"></Staging>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState } from "vuex";
+
+export default {
+  name: "contextbox",
+  components: {
+    Bottlenecks: () => import("./Bottlenecks"),
+    Staging: () => import("./staging/Staging.vue")
+  },
+  computed: {
+    ...mapState("application", [
+      "showSearchbarLastState",
+      "contextBoxContent",
+      "showContextBox"
+    ]),
+    style() {
+      return [
+        "ui-element shadow-xs contextbox",
+        {
+          contextboxcollapsed: !this.showContextBox,
+          contextboxextended: this.showContextBox,
+          "rounded-bottom": this.contextBoxContent !== "imports",
+          rounded: this.contextBoxContent === "imports"
+        }
+      ];
+    }
+  },
+  methods: {
+    close() {
+      this.$store.commit("application/showContextBox", false);
+      this.$store.commit(
+        "application/showSearchbar",
+        this.showSearchbarLastState
+      );
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.contextbox {
+  position: relative;
+  background-color: #ffffff;
+  opacity: $slight-transparent;
+  transition: max-width 0.3s, max-height 0.3s;
+  overflow: hidden;
+  background: #fff;
+}
+.contextbox > div:last-child {
+  width: 600px;
+}
+
+.contextboxcollapsed {
+  max-width: 0;
+  max-height: 0;
+}
+
+.contextboxextended {
+  max-width: 700px;
+  max-height: 640px;
+}
+
+.close-contextbox {
+  position: absolute;
+  z-index: 2;
+  right: 0;
+  top: 7px;
+  height: $icon-width;
+  width: $icon-height;
+}
+</style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/Identify.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,124 @@
+<template>
+  <div
+    :class="[
+      'box ui-element rounded bg-white text-nowrap',
+      { expanded: showIdentify }
+    ]"
+  >
+    <div style="width: 20rem">
+      <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
+        <font-awesome-icon icon="info" class="mr-2"></font-awesome-icon>
+        <translate>Identified</translate>
+        <font-awesome-icon
+          icon="times"
+          class="ml-auto text-muted"
+          @click="$store.commit('application/showIdentify', false)"
+        ></font-awesome-icon>
+      </h6>
+      <div class="d-flex flex-column features p-3 flex-grow-1 text-left">
+        <div v-if="currentMeasurement">
+          <b>
+            {{ currentMeasurement.quantity }} ({{
+              currentMeasurement.unitSymbol
+            }}):
+          </b>
+          <br />
+          <small>{{ currentMeasurement.value }}</small>
+        </div>
+        <div v-for="(feature, i) of identifiedFeatures" :key="feature.getId()">
+          <div v-if="feature.getId()" :class="{ 'mt-2': i }">
+            <strong>
+              {{
+                feature.getId().replace(/[.][^.]*$/, "")
+                /* cut away everything from the last . to the end */
+              }}:
+            </strong>
+            <small
+              v-for="(value, key) in prepareProperties(feature)"
+              :key="key"
+            >
+              <div v-if="value">{{ key }}:{{ value }}</div>
+            </small>
+          </div>
+        </div>
+        <div
+          v-if="!currentMeasurement && !identifiedFeatures.length"
+          class="text-muted small text-center my-auto"
+        >
+          <translate>No features identified.</translate>
+        </div>
+      </div>
+      <div class="versioninfo border-top p-3 text-left">
+        <span v-translate="{ license: 'AGPL-3.0-or-later' }">
+          This app uses <i>gemma</i>, which is Free Software under <br />
+          %{ license } without warranty, see docs for details.
+        </span>
+        <br />
+        <a href="https://hg.intevation.de/gemma/file/tip">
+          <translate>source-code</translate>
+        </a>
+        {{ versionStr }} <br />© via donau. &#x24D4; Intevation. <br />
+        <span v-translate="{ name: 'OpenSteetMap' }"
+          >Some data ©
+          <a href="https://www.openstreetmap.org/copyright">%{ name }</a>
+          contributors.
+        </span>
+        <p v-translate="{ geoLicense: 'CC-BY-4.0' }">
+          Uses
+          <a href="https://download.geonames.org/export/dump/readme.txt"
+            >GeoNames</a
+          >
+          under %{ geoLicense }.
+        </p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.features {
+  max-height: 19rem;
+  overflow-y: auto;
+}
+
+.versioninfo {
+  font-size: 60%;
+  white-space: normal;
+}
+</style>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ * Bernhard E. Reiter <bernhard.reiter@intevation.de>
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState, mapGetters } from "vuex";
+
+export default {
+  name: "identify",
+  computed: {
+    ...mapGetters("application", ["versionStr"]),
+    ...mapState("application", ["showIdentify"]),
+    ...mapState("map", ["identifiedFeatures", "currentMeasurement"])
+  },
+  methods: {
+    prepareProperties(feature) {
+      // return dict object with propertyname:plainvalue prepared for display
+      var properties = feature.getProperties();
+      delete properties[feature.getGeometryName()];
+      return properties;
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/ImportSoundingresults.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,394 @@
+<template>
+  <div class="main d-flex flex-column">
+    <div class="d-flex flex-row">
+      <Spacer></Spacer>
+      <div class="card shadow-xs mt-3 mr-3 w-100 importsoundingresultscard">
+        <h6
+          class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
+        >
+          <font-awesome-icon icon="upload" class="mr-2"></font-awesome-icon>
+          <translate class="headline">Import Soundingresults</translate>
+        </h6>
+        <div v-if="editState" class="ml-auto mr-auto mt-4 w-95">
+          <div class="d-flex flex-column">
+            <div class="d-flex flex-row">
+              <div class="mt-1 text-left w-50 ml-2 mr-4">
+                <small class="text-muted">
+                  <translate>Bottleneck</translate>
+                </small>
+                <select v-model="bottleneck" class="custom-select">
+                  <option
+                    v-for="bottleneck in availableBottlenecks"
+                    :key="bottleneck"
+                    >{{ bottleneck }}</option
+                  >
+                </select>
+                <span class="text-danger">
+                  <small v-if="!bottleneck">
+                    <translate>Please select a bottleneck</translate>
+                  </small>
+                </span>
+              </div>
+              <div class="d-flex flex-column mt-1 text-left w-50 mr-2">
+                <small class="text-muted">
+                  <translate>Projection</translate>&nbsp;(EPSG)
+                </small>
+                <input
+                  class="form-control"
+                  v-model="projection"
+                  value="4326"
+                  placeholder="e.g. 4326"
+                  type="number"
+                />
+                <span class="text-left text-danger">
+                  <small v-if="!projection">
+                    <translate>Please enter a projection</translate>
+                  </small>
+                </span>
+              </div>
+            </div>
+            <div class="d-flex flex-row">
+              <div class="mt-1 text-left w-50 ml-2 mr-4">
+                <small class="text-muted">
+                  <translate>Depthreference</translate>
+                </small>
+                <select
+                  v-model="depthReference"
+                  class="custom-select"
+                  id="depthreference"
+                >
+                  <option
+                    v-for="option in this.$options.depthReferenceOptions"
+                    :key="option"
+                    >{{ option }}</option
+                  >
+                </select>
+                <span class="text-left text-danger">
+                  <small v-if="!depthReference">
+                    <translate>Please enter a reference</translate>
+                  </small>
+                </span>
+              </div>
+              <div class="mt-1 text-left w-50 mr-2">
+                <small class="text-muted"> <translate>Date</translate> </small>
+                <input
+                  id="importdate"
+                  type="date"
+                  class="form-control"
+                  placeholder="Date of import"
+                  aria-label="bottleneck"
+                  aria-describedby="bottlenecklabel"
+                  v-model="importDate"
+                />
+                <span class="text-left text-danger">
+                  <small v-if="!importDate">
+                    <translate>Please enter a date</translate>
+                  </small>
+                </span>
+              </div>
+            </div>
+          </div>
+          <div class="ml-2 mt-2 text-left">
+            <small v-for="(message, index) in messages" :key="index">
+              {{ message }}
+            </small>
+          </div>
+        </div>
+        <div class="w-95 ml-auto mr-auto mt-4 mb-4">
+          <div v-if="uploadState" class="d-flex flex-row input-group mb-4">
+            <div class="custom-file">
+              <input
+                accept=".zip"
+                type="file"
+                @change="fileSelected"
+                class="custom-file-input"
+                id="uploadFile"
+              />
+              <label class="custom-file-label" for="uploadFile">
+                {{ uploadLabel }}
+              </label>
+            </div>
+          </div>
+          <div class="buttons text-right">
+            <a
+              v-if="editState"
+              download="meta.json"
+              :href="dataLink"
+              class="btn btn-outline-info pull-left mt-4"
+            >
+              <translate>Download Meta.json</translate>
+            </a>
+            <button
+              v-if="editState"
+              @click="deleteTempData"
+              class="btn btn-danger mt-4"
+              type="button"
+            >
+              <translate>Cancel Upload</translate>
+            </button>
+            <button
+              :disabled="disableUploadButton"
+              @click="submit"
+              class="btn btn-info mt-4"
+              type="button"
+            >
+              {{ uploadState ? Upload : Confirm }}
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { HTTP } from "@/lib/http";
+import { displayError, displayInfo } from "@/lib/errors.js";
+import { mapState } from "vuex";
+import Spacer from "./Spacer";
+
+const IMPORTSTATE = { UPLOAD: "UPLOAD", EDIT: "EDIT" };
+
+export default {
+  name: "imports",
+  components: {
+    Spacer
+  },
+  data() {
+    return {
+      importState: IMPORTSTATE.UPLOAD,
+      depthReference: "",
+      bottleneck: "",
+      projection: "",
+      importDate: "",
+      uploadLabel: this.$gettext("choose .zip- file"),
+      uploadFile: null,
+      disableUpload: false,
+      token: null,
+      messages: []
+    };
+  },
+  methods: {
+    initialState() {
+      this.importState = IMPORTSTATE.UPLOAD;
+      this.depthReference = "";
+      this.bottleneck = "";
+      this.projection = "";
+      this.importDate = "";
+      this.uploadLabel = this.$gettext("choose .zip- file");
+      this.uploadFile = null;
+      this.disableUpload = false;
+      this.token = null;
+      this.messages = [];
+    },
+    fileSelected(e) {
+      const files = e.target.files || e.dataTransfer.files;
+      if (!files) return;
+      this.uploadLabel = files[0].name;
+      this.uploadFile = files[0];
+    },
+    deleteTempData() {
+      HTTP.delete("/imports/soundingresult-upload/" + this.token, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token")
+        }
+      })
+        .then(() => {
+          this.initialState();
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    },
+    submit() {
+      if (!this.uploadFile || this.disableUpload) return;
+      if (this.importState === IMPORTSTATE.UPLOAD) {
+        this.upload();
+      } else {
+        this.confirm();
+      }
+    },
+    upload() {
+      let formData = new FormData();
+      formData.append("soundingresult", this.uploadFile);
+      HTTP.post("/imports/soundingresult-upload", formData, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-Type": "multipart/form-data"
+        }
+      })
+        .then(response => {
+          if (response.data.meta) {
+            const { bottleneck, date, epsg } = response.data.meta;
+            const depthReference = response.data.meta["depth-reference"];
+            this.bottleneck = bottleneck;
+            this.depthReference = depthReference;
+            this.importDate = new Date(date).toISOString().split("T")[0];
+            this.projection = epsg;
+          }
+          this.importState = IMPORTSTATE.EDIT;
+          this.token = response.data.token;
+          this.messages = response.data.messages;
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          const messages = data.messages ? data.messages.join(", ") : "";
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${messages}`
+          });
+        });
+    },
+    confirm() {
+      let formData = new FormData();
+      formData.append("token", this.token);
+      if (this.bottleneck) formData.append("bottleneck", this.bottleneck);
+      if (this.importDate)
+        formData.append("date", this.importDate.split("T")[0]);
+      if (this.depthReference)
+        formData.append("depth-reference", this.depthReference);
+      if (this.projection) formData.append("", this.projection);
+
+      HTTP.post("/imports/soundingresult", formData, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-Type": "multipart/form-data"
+        }
+      })
+        .then(() => {
+          displayInfo({
+            title: this.$gettext("Import"),
+            message: this.$gettext("Starting import for ") + this.bottleneck
+          });
+          this.initialState();
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    }
+  },
+  mounted() {
+    this.$store.dispatch("bottlenecks/loadBottlenecks");
+  },
+  watch: {
+    showContextBox() {
+      if (!this.showContextBox && this.token) this.deleteTempData();
+    }
+  },
+  computed: {
+    ...mapState("application", ["showContextBox"]),
+    ...mapState("bottlenecks", ["bottlenecks"]),
+    disableUploadButton() {
+      if (this.importState === IMPORTSTATE.UPLOAD) return this.disableUpload;
+      if (
+        !this.bottleneck ||
+        !this.importDate ||
+        !this.depthReference ||
+        !this.projection
+      )
+        return true;
+      return this.disableUpload;
+    },
+    availableBottlenecks() {
+      return this.bottlenecks.map(x => x.properties.name);
+    },
+    editState() {
+      return this.importState === IMPORTSTATE.EDIT;
+    },
+    uploadState() {
+      return this.importState === IMPORTSTATE.UPLOAD;
+    },
+    Upload() {
+      return this.$gettext("Upload");
+    },
+    Confirm() {
+      return this.$gettext("Confirm");
+    },
+    dataLink() {
+      return (
+        "data:text/json;charset=utf-8," +
+        encodeURIComponent(
+          JSON.stringify({
+            depthReference: this.depthReference,
+            bottleneck: this.bottleneck,
+            date: this.importDate
+          })
+        )
+      );
+    }
+  },
+  depthReferenceOptions: [
+    "",
+    // "NAP",
+    // "KP",
+    // "FZP",
+    // "ADR",
+    // "TAW",
+    // "PUL",
+    // "NGM",
+    // "ETRS",
+    // "POT",
+    // "LDC",
+    // "HDC",
+    // "ZPG",
+    // "GLW",
+    // "HSW",
+    // "LNW",
+    // "HNW",
+    // "IGN",
+    // "WGS",
+    "RN" //,
+    // "HBO"
+  ]
+};
+</script>
+
+<style lang="scss" scoped>
+.importsoundingresultscard {
+  height: 100%;
+}
+
+.projectionLabel {
+  margin-left: $small-offset;
+}
+
+.depthreferencelabel {
+  margin-left: $small-offset;
+}
+
+.offset-r {
+  margin-right: $small-offset;
+}
+
+.buttons button {
+  margin-left: $offset !important;
+}
+
+.label-text {
+  width: 5rem;
+  text-align: left;
+  line-height: 2.25rem;
+}
+</style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/ImportStretches.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,85 @@
+<template>
+  <div class="d-flex flex-row">
+    <Spacer></Spacer>
+    <div class="card sysconfig mt-3 shadow-xs w-100 h-100 mr-3">
+      <h6
+        class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
+      >
+        <font-awesome-icon
+          icon="cloud-upload-alt"
+          class="mr-2"
+        ></font-awesome-icon>
+        <translate class="headline">Import streches</translate>
+      </h6>
+      <div class="card-body stretches-card">
+        <div class="w-95 ml-auto mr-auto mt-4 mb-4">
+          <div class="d-flex flex-row input-group mb-4">
+            <div class="flex-column w-100">
+              <div class="flex-row text-left">
+                <small class="text-muted"> <translate>URL</translate> </small>
+              </div>
+              <div><input class="form-control" type="url" /></div>
+            </div>
+          </div>
+          <div class="buttons text-right">
+            <button
+              :disabled="disableUploadButton"
+              @click="submit"
+              class="btn btn-info mt-4"
+              type="button"
+            >
+              <font-awesome-icon
+                class="fa-fw mr-2"
+                fixed-width
+                icon="play"
+              ></font-awesome-icon>
+              <translate>Trigger import</translate>
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+
+import { displayInfo } from "@/lib/errors.js";
+
+export default {
+  name: "importstretches",
+  data() {
+    return {
+      disableUploadButton: false,
+      uploadLabel: "",
+      uploadFile: null
+    };
+  },
+  methods: {
+    submit() {
+      displayInfo({
+        title: this.$gettext("Import stretches"),
+        message: this.$gettext("under construction")
+      });
+    }
+  },
+  components: {
+    Spacer: () => import("./Spacer")
+  }
+};
+</script>
+
+<style lang="scss" scoped></style>
--- a/client/src/components/Login.vue	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/components/Login.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -3,7 +3,7 @@
     <div class="m-5">
       <!-- logo section -->
       <div class="d-flex flex-row justify-content-center mb-3">
-        <div class="logo mr-3"><img src="../assets/logo.png" /></div>
+        <div class="logo mr-3"><img src="@/assets/logo.png" /></div>
         <div class="title">
           <h1>{{ appTitle }}</h1>
         </div>
@@ -127,8 +127,8 @@
  * Markus Kottländer <markus@intevation.de >
  */
 import { mapState } from "vuex";
-import { HTTP } from "../lib/http.js";
-import { displayError } from "../lib/errors.js";
+import { HTTP } from "@/lib/http.js";
+import { displayError } from "@/lib/errors.js";
 
 const UNAUTHORIZED = 401;
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/Logs.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,179 @@
+<template>
+  <div class="main d-flex flex-column">
+    <div class="d-flex flex-row">
+      <Spacer></Spacer>
+      <div class="card logs shadow-xs mt-3 mr-3">
+        <h6
+          class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
+        >
+          <font-awesome-icon class="mr-2 fa-fw" icon="book"></font-awesome-icon>
+          <translate class="headline">Logs</translate>
+        </h6>
+        <div class="logoutput text-left bg-white">
+          <pre id="code" v-highlightjs="logs">
+          <code class="bash hljs hljs-string"></code>
+          </pre>
+        </div>
+        <div class="logmenu">
+          <div class="d-flex align-self-center">
+            <ul class="nav nav-pills">
+              <li class="nav-item">
+                <a
+                  :class="accesslogStyle"
+                  @click="fetch('system/log/apache2/access.log', 'accesslog')"
+                  href="#"
+                >
+                  <translate>Accesslog</translate>
+                </a>
+              </li>
+              <li class="nav-item">
+                <a
+                  :class="errorlogStyle"
+                  @click="fetch('system/log/apache2/error.log', 'errorlog')"
+                  href="#"
+                >
+                  <translate>Errorlog</translate>
+                </a>
+              </li>
+            </ul>
+          </div>
+          <div class="statuscontainer d-flex flex-row mb-3">
+            <div class="statusline align-self-center">
+              <h3><translate>Last refresh:</translate> {{ refreshed }}</h3>
+            </div>
+            <div class="refresh">
+              <button
+                @click="fetch(currentFile, currentLog)"
+                class="btn btn-dark"
+              >
+                <translate>Refresh</translate>
+              </button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.statuscontainer {
+  width: 87%;
+  position: relative;
+}
+
+.logmenu {
+  position: relative;
+  margin-left: $offset;
+  margin-top: $offset;
+}
+
+.logs {
+  height: 85vh;
+}
+
+#code {
+  overflow: auto;
+}
+
+.refresh {
+  position: absolute;
+  right: $offset;
+  bottom: 0;
+}
+
+.logoutput {
+  margin-left: $offset;
+  margin-right: $offset;
+  margin-top: $offset;
+  height: 90%;
+  overflow: auto;
+  transition: $transition-fast;
+}
+
+.statusline {
+  position: absolute;
+  right: 0;
+  margin-right: 9rem;
+  bottom: -0.5rem;
+}
+
+.statuscontainer {
+  width: 100%;
+}
+</style>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+import { mapState } from "vuex";
+import { HTTP } from "@/lib/http.js";
+import "../../node_modules/highlight.js/styles/paraiso-dark.css";
+import Vue from "vue";
+import VueHighlightJS from "vue-highlightjs";
+Vue.use(VueHighlightJS);
+
+const ACCESSLOG = "accesslog";
+const ERRORLOG = "errorlog";
+
+export default {
+  name: "logs",
+  components: {
+    Spacer: () => import("./Spacer")
+  },
+  mounted() {
+    this.fetch("system/log/apache2/access.log", ACCESSLOG);
+  },
+  data() {
+    return {
+      logs: null,
+      currentLog: null,
+      currentFile: null,
+      refreshed: null
+    };
+  },
+  methods: {
+    fetch(file, type) {
+      HTTP.get(file, {
+        headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+      })
+        .then(response => {
+          this.logs = response.data.content;
+          this.currentLog = type;
+          this.refreshed = new Date().toLocaleString();
+          this.currentFile = file;
+        })
+        .catch();
+    },
+    disallow(e) {
+      e.target.blur();
+    }
+  },
+  computed: {
+    ...mapState("application", ["showSidebar"]),
+    accesslogStyle() {
+      return {
+        active: this.currentLog == ACCESSLOG,
+        "nav-link": true
+      };
+    },
+    errorlogStyle() {
+      return {
+        active: this.currentLog == ERRORLOG,
+        "nav-link": true
+      };
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/Main.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,30 @@
+<template>
+  <div class="main d-flex flex-column">
+    <Maplayer></Maplayer>
+    <FairwayProfile></FairwayProfile>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+
+export default {
+  name: "mainview",
+  components: {
+    Maplayer: () => import("./Maplayer"),
+    FairwayProfile: () => import("./fairway/Fairwayprofile")
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/Maplayer.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,383 @@
+<template>
+  <div id="map" :class="mapStyle"></div>
+</template>
+
+<style lang="scss" scoped>
+.nocursor {
+  cursor: none;
+}
+
+.mapsplit {
+  height: 50vh;
+}
+
+.mapfull {
+  height: 100vh;
+}
+
+// the following css part is for browser-printing based pdf generation
+@page {
+  size: A4 landscape !important;
+  margin: 4mm !important;
+  // according to https://www.w3.org/TR/css-page-3/#page-size-prop
+  // we shall now have 210 - 2*4 = 202 mm width and 297 - 2*4 = 289 mm height
+}
+
+@media print {
+  .mapfull {
+    width: 2000px;
+    height: 2828px;
+  }
+  .mapsplit {
+    width: 2000px;
+    height: 2828px;
+  }
+}
+</style>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * * Thomas Junk <thomas.junk@intevation.de>
+ * * Bernhard E. Reiter <bernhard.reiter@intevation.de>
+ */
+import { HTTP } from "@/lib/http";
+import { mapGetters, mapState } from "vuex";
+import "ol/ol.css";
+import { Map, View } from "ol";
+import { WFS, GeoJSON } from "ol/format.js";
+import { Stroke, Style, Fill } from "ol/style.js";
+
+/* for the sake of debugging */
+/* eslint-disable no-console */
+export default {
+  name: "maplayer",
+  data() {
+    return {
+      projection: "EPSG:3857"
+    };
+  },
+  computed: {
+    ...mapGetters("map", ["getLayerByName", "getVSourceByName"]),
+    ...mapState("map", [
+      "extent",
+      "layers",
+      "openLayersMap",
+      "lineTool",
+      "polygonTool",
+      "cutTool"
+    ]),
+    ...mapState("bottlenecks", ["selectedSurvey"]),
+    ...mapState("application", ["showSplitscreen"]),
+    mapStyle() {
+      return {
+        mapfull: !this.showSplitscreen,
+        mapsplit: this.showSplitscreen,
+        nocursor: this.hasActiveInteractions
+      };
+    },
+    hasActiveInteractions() {
+      return (
+        (this.lineTool && this.lineTool.getActive()) ||
+        (this.polygonTool && this.polygonTool.getActive()) ||
+        (this.cutTool && this.cutTool.getActive())
+      );
+    }
+  },
+  methods: {
+    buildVectorLoader(featureRequestOptions, endpoint, vectorSource) {
+      // build a function to be used for VectorSource.setLoader()
+      // make use of WFS().writeGetFeature to build the request
+      // and use our HTTP library to actually do it
+      // NOTE: a) the geometryName has to be given in featureRequestOptions,
+      //          because we want to load depending on the bbox
+      //  b) the VectorSource has to have the option strategy: bbox
+      featureRequestOptions["outputFormat"] = "application/json";
+      var loader = function(extent, resolution, projection) {
+        featureRequestOptions["bbox"] = extent;
+        featureRequestOptions["srsName"] = projection.getCode();
+        var featureRequest = new WFS().writeGetFeature(featureRequestOptions);
+        // DEBUG console.log(featureRequest);
+        HTTP.post(
+          endpoint,
+          new XMLSerializer().serializeToString(featureRequest),
+          {
+            headers: {
+              "X-Gemma-Auth": localStorage.getItem("token"),
+              "Content-type": "text/xml; charset=UTF-8"
+            }
+          }
+        )
+          .then(response => {
+            var features = new GeoJSON().readFeatures(
+              JSON.stringify(response.data)
+            );
+            vectorSource.addFeatures(features);
+            // console.log(
+            //   "loaded",
+            //   features.length,
+            //   featureRequestOptions.featureTypes,
+            //   "features"
+            // );
+            // DEBUG console.log("loaded ", features, "for", vectorSource);
+            // eslint-disable-next-line
+          })
+          .catch(() => {
+            vectorSource.removeLoadedExtent(extent);
+          });
+      };
+      return loader;
+    },
+    updateBottleneckFilter(bottleneck_id, datestr) {
+      console.log("updating filter with", bottleneck_id, datestr);
+      const layer = this.getLayerByName("Bottleneck isolines");
+      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 +
+            "'"
+        });
+      }
+      layer.isVisible = exists;
+      layer.data.setVisible(exists);
+    },
+    onBeforePrint(/* evt */) {
+      // console.log("onBeforePrint(", evt ,")");
+      //
+      // the following code shows how to get the current map canvas
+      // and change it, however this does not work well enough, as
+      // another mechanism seems to update the size again before the rendering
+      // for printing is done:
+      // console.log(this.openLayersMap.getViewport());
+      // var canvas = this.openLayersMap.getViewport().getElementsByTagName("canvas")[0];
+      // console.log(canvas);
+      // canvas.width=1000;
+      // canvas.height=1414;
+      //
+      // An experiment which also did not work:
+      // this.openLayersMap.setSize([1000, 1414]); // estimate portait DIN A4
+      //
+      // according to documentation
+      // http://openlayers.org/en/latest/apidoc/module-ol_PluggableMap-PluggableMap.html#updateSize
+      // "Force a recalculation of the map viewport size. This should be called when third-party code changes the size of the map viewport."
+      // but did not help
+      // this.openLayersMap.updateSize();
+    },
+    onAfterPrint(/* evt */) {
+      // could be used to undo changes that have been done for printing
+      // though https://www.tjvantoll.com/2012/06/15/detecting-print-requests-with-javascript/
+      // reported that this was not feasable (back then).
+      // console.log("onAfterPrint(", evt, ")");
+    }
+  },
+  watch: {
+    showSplitscreen() {
+      const map = this.openLayersMap;
+      this.$nextTick(() => {
+        map && map.updateSize();
+      });
+    },
+    selectedSurvey(newSelectedSurvey) {
+      if (newSelectedSurvey) {
+        this.updateBottleneckFilter(
+          newSelectedSurvey.bottleneck_id,
+          newSelectedSurvey.date_info
+        );
+      } else {
+        this.updateBottleneckFilter("does_not_exist", "1999-10-01");
+      }
+    }
+  },
+  mounted() {
+    let map = new Map({
+      layers: [...this.layers.map(x => x.data)],
+      target: "map",
+      controls: [],
+      view: new View({
+        center: [this.extent.lon, this.extent.lat],
+        zoom: this.extent.zoom,
+        projection: this.projection
+      })
+    });
+    map.on("moveend", event => {
+      const center = event.map.getView().getCenter();
+      this.$store.commit("map/extent", {
+        lat: center[1],
+        lon: center[0],
+        zoom: event.map.getView().getZoom()
+      });
+    });
+    this.$store.dispatch("map/openLayersMap", map);
+
+    // TODO make display of layers more dynamic, e.g. from a list
+
+    // loading the full WFS layer, by not setting the loader function
+    // and without bboxStrategy
+    var featureRequest = new WFS().writeGetFeature({
+      srsName: "EPSG:3857",
+      featureNS: "gemma",
+      featurePrefix: "gemma",
+      featureTypes: ["fairway_dimensions"],
+      outputFormat: "application/json"
+    });
+
+    // NOTE: loading the full fairway_dimensions makes sure
+    //       that all are available for the intersection with the profile
+    HTTP.post(
+      "/internal/wfs",
+      new XMLSerializer().serializeToString(featureRequest),
+      {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-type": "text/xml; charset=UTF-8"
+        }
+      }
+    ).then(response => {
+      this.getVSourceByName("Fairway Dimensions").addFeatures(
+        new GeoJSON().readFeatures(JSON.stringify(response.data))
+      );
+      // would scale to the extend of all resulting features
+      // this.openLayersMap.getView().fit(vectorSrc.getExtent());
+    });
+
+    // load following layers with bboxStrategy (using our request builder)
+    var layer = null;
+
+    layer = this.getLayerByName("Waterway Area");
+    layer.data.getSource().setLoader(
+      this.buildVectorLoader(
+        {
+          featurePrefix: "ws-wamos",
+          featureTypes: ["ienc_wtware"],
+          geometryName: "geom"
+        },
+        "/external/d4d",
+        layer.data.getSource()
+      )
+    );
+
+    layer = this.getLayerByName("Waterway Axis");
+    layer.data.getSource().setLoader(
+      this.buildVectorLoader(
+        {
+          featurePrefix: "ws-wamos",
+          featureTypes: ["ienc_wtwaxs"],
+          geometryName: "geom"
+        },
+        "/external/d4d",
+        layer.data.getSource()
+      )
+    );
+
+    layer = this.getLayerByName("Distance marks");
+    layer.data.getSource().setLoader(
+      this.buildVectorLoader(
+        {
+          featurePrefix: "ws-wamos",
+          featureTypes: ["ienc_dismar"],
+          geometryName: "geom" //,
+          /* restrict loading approximately to extend of danube in Austria */
+          // filter: bboxFilter("geom", [13.3, 48.0, 17.1, 48.6], "EPSG:4326")
+        },
+        "/external/d4d",
+        layer.data.getSource()
+      )
+    );
+    layer.data.setVisible(layer.isVisible);
+
+    layer = this.getLayerByName("Distance marks, Axis");
+    layer.data.getSource().setLoader(
+      this.buildVectorLoader(
+        {
+          featureNS: "gemma",
+          featurePrefix: "gemma",
+          featureTypes: ["distance_marks_geoserver"],
+          geometryName: "geom"
+        },
+        "/internal/wfs",
+        layer.data.getSource()
+      )
+    );
+
+    layer = this.getLayerByName("Waterway Area, named");
+    layer.data.getSource().setLoader(
+      this.buildVectorLoader(
+        {
+          featureNS: "gemma",
+          featurePrefix: "gemma",
+          featureTypes: ["hydro_seaare"],
+          geometryName: "geom"
+        },
+        "/external/d4d",
+        layer.data.getSource()
+      )
+    );
+    layer.data.setVisible(layer.isVisible);
+
+    layer = this.getLayerByName("Bottlenecks");
+    layer.data.getSource().setLoader(
+      this.buildVectorLoader(
+        {
+          featureNS: "gemma",
+          featurePrefix: "gemma",
+          featureTypes: ["bottlenecks"],
+          geometryName: "area"
+        },
+        "/internal/wfs",
+        layer.data.getSource()
+      )
+    );
+    HTTP.get("/system/style/Bottlenecks/stroke", {
+      headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+    })
+      .then(response => {
+        this.btlnStrokeC = response.data.code;
+        HTTP.get("/system/style/Bottlenecks/fill", {
+          headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+        })
+          .then(response => {
+            this.btlnFillC = response.data.code;
+            var newstyle = new Style({
+              stroke: new Stroke({
+                color: this.btlnStrokeC,
+                width: 4
+              }),
+              fill: new Fill({
+                color: this.btlnFillC
+              })
+            });
+            layer.data.setStyle(newstyle);
+          })
+          .catch(error => {
+            console.log(error);
+          });
+      })
+      .catch(error => {
+        console.log(error);
+      });
+
+    window.addEventListener("beforeprint", this.onBeforePrint);
+    window.addEventListener("afterprint", this.onAfterPrint);
+
+    // so none is shown
+    this.updateBottleneckFilter("does_not_exist", "1999-10-01");
+    this.$store.dispatch("map/enableIdentifyTool");
+    this.$store.dispatch("bottlenecks/loadBottlenecks");
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/Pdftool.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,130 @@
+<template>
+  <div
+    :class="[
+      'box ui-element rounded bg-white text-nowrap',
+      { expanded: showPdfTool }
+    ]"
+  >
+    <div style="width: 20rem">
+      <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
+        <font-awesome-icon icon="file-pdf" class="mr-2"></font-awesome-icon
+        ><translate>Generate PDF</translate>
+        <font-awesome-icon
+          icon="times"
+          class="ml-auto text-muted"
+          @click="$store.commit('application/showPdfTool', false)"
+        ></font-awesome-icon>
+      </h6>
+      <div class="p-3">
+        <b><translate>Chose format:</translate></b>
+        <select v-model="form.format" class="form-control d-block w-100">
+          <option value="landscape"><translate>landscape</translate></option>
+          <option value="portrait"><translate>portrait</translate></option>
+        </select>
+        <select v-model="form.paperSize" class="form-control d-block w-100">
+          <option value="a3"><translate>ISO A3</translate></option>
+          <option value="a4"><translate>ISO A4</translate></option>
+        </select>
+        <small class="d-block my-2">
+          <input
+            type="radio"
+            id="pdfexport-downloadtype-download"
+            value="download"
+            v-model="form.downloadType"
+            selected
+          />
+          <label for="pdfexport-downloadtype-download" class="ml-1 mr-2"
+            ><translate>Download</translate></label
+          >
+          <input
+            type="radio"
+            id="pdfexport-downloadtype-open"
+            value="open"
+            v-model="form.downloadType"
+          />
+          <label for="pdfexport-downloadtype-open" class="ml-1"
+            ><translate>Open in new window</translate></label
+          >
+        </small>
+        <button
+          @click="download"
+          type="button"
+          class="btn btn-sm btn-info d-block w-100"
+        >
+          <translate>Generate PDF</translate>
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * * Markus Kottländer <markus.kottlaender@intevation.de>
+ * * Bernhard E. Reiter <bernhard@intevation.de>
+ */
+import { mapState } from "vuex";
+
+var paperSizes = {
+  // in millimeter, landscape [width, height]
+  a3: [420, 297],
+  a4: [297, 210]
+};
+
+export default {
+  name: "pdftool",
+  data() {
+    return {
+      form: {
+        format: "landscape",
+        paperSize: "a4",
+        downloadType: "download"
+      }
+    };
+  },
+  computed: {
+    ...mapState("application", ["showPdfTool"]),
+    ...mapState("bottlenecks", ["selectedSurvey"])
+  },
+  methods: {
+    isLandscape() {
+      return this.form.format !== "portrait";
+    },
+    download() {
+      /* eslint-disable no-unused-vars */
+      const width = this.isLandscape()
+        ? paperSizes[this.form.paperSize][0]
+        : paperSizes[this.form.paperSize][1];
+      const height = this.isLandscape()
+        ? paperSizes[this.form.paperSize][1]
+        : paperSizes[this.form.paperSize][0];
+
+      // TODO: replace this src with an API reponse after actually generating PDFs
+      let src = !this.isLandscape()
+        ? "/img/PrintTemplate-Var2-Landscape.pdf"
+        : "/img/PrintTemplate-Var2-Portrait.pdf";
+
+      let a = document.createElement("a");
+      a.href = src;
+
+      if (this.form.downloadType === "download")
+        a.download = src.substr(src.lastIndexOf("/") + 1);
+      else a.target = "_blank";
+
+      document.body.appendChild(a);
+      a.click();
+      document.body.removeChild(a);
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/Search.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,290 @@
+<template>
+  <div :class="searchbarContainerStyle">
+    <div class="input-group-prepend m-0 d-print-none">
+      <span @click="toggleSearchbar" :class="searchButtonStyle" for="search">
+        <font-awesome-icon icon="search"></font-awesome-icon>
+      </span>
+    </div>
+    <div
+      :class="[
+        'searchgroup',
+        {
+          'searchgroup-collapsed': !showSearchbar,
+          big:
+            showContextBox &&
+            ['bottlenecks', 'staging'].indexOf(contextBoxContent) !== -1
+        }
+      ]"
+    >
+      <input
+        @keyup.enter="takeFirstSearchresult"
+        id="search"
+        v-model="searchQuery"
+        type="text"
+        :class="searchInputStyle"
+      />
+    </div>
+    <div
+      v-if="showSearchbar && searchResults !== null && !showContextBox"
+      class="searchresults border-top ui-element bg-white rounded-bottom d-print-none position-absolute"
+    >
+      <div
+        v-for="entry of searchResults"
+        :key="entry.name"
+        class="border-top text-left"
+      >
+        <a
+          href="#"
+          @click.prevent="moveToSearchResult(entry)"
+          class="p-2 d-block text-nowrap"
+        >
+          <font-awesome-icon
+            icon="ship"
+            v-if="entry.type === 'bottleneck'"
+            class="mr-1"
+            fixed-width
+          />
+          <font-awesome-icon
+            icon="water"
+            v-if="entry.type === 'rhm'"
+            class="mr-1"
+            fixed-width
+          />
+          <font-awesome-icon
+            icon="city"
+            v-if="entry.type === 'city'"
+            class="mr-1"
+            fixed-width
+          />
+          {{ entry.name }}
+        </a>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.searchcontainer {
+  opacity: 0.96;
+}
+
+.searchcontainer .searchbar {
+  border-top-left-radius: 0 !important;
+  border-bottom-left-radius: 0 !important;
+}
+
+.searchgroup {
+  margin-left: -3px;
+  transition: width 0.3s;
+  width: 300px;
+  overflow: hidden;
+}
+
+.searchgroup.big {
+  width: 571px;
+}
+
+.searchgroup-collapsed {
+  width: 0;
+}
+
+.searchbar {
+  height: 2rem !important;
+  box-shadow: none !important;
+}
+
+.searchbar.rounded-top-right {
+  border-radius: 0 !important;
+  border-top-right-radius: 0.25rem !important;
+}
+
+.searchlabel.rounded-top-left {
+  border-radius: 0 !important;
+  border-top-left-radius: 0.25rem !important;
+}
+
+.input-group-text {
+  height: 2rem;
+  width: 2rem;
+}
+
+.input-group-prepend svg path {
+  fill: #666;
+}
+
+.searchresults {
+  box-shadow: 0 0.1rem 0.5rem rgba(0, 0, 0, 0.2);
+  top: 2rem;
+  left: 0;
+  right: 0;
+  max-height: 24rem;
+  overflow: auto;
+}
+
+.searchresults > div:first-child {
+  border-top: 0 !important;
+}
+
+.searchresults a {
+  text-decoration: none;
+}
+
+.searchresults a:hover {
+  background: #f8f8f8;
+}
+</style>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import debounce from "lodash.debounce";
+import { mapState } from "vuex";
+
+import { displayError } from "@/lib/errors.js";
+import { HTTP } from "@/lib/http";
+
+const setFocus = () => document.querySelector("#search").focus();
+
+export default {
+  name: "search",
+  data() {
+    return {
+      searchQueryIsDirty: false,
+      searchResults: null,
+      isSearching: false
+    };
+  },
+  computed: {
+    ...mapState("application", [
+      "showSearchbar",
+      "showContextBox",
+      "contextBoxContent"
+    ]),
+    searchQuery: {
+      get() {
+        return this.$store.state.application.searchQuery;
+      },
+      set(value) {
+        this.$store.commit("application/searchQuery", value);
+      }
+    },
+    searchIndicator: function() {
+      if (this.isSearching) {
+        return "⟳";
+      } else if (this.searchQueryIsDirty) {
+        return "";
+      } else {
+        return "✓";
+      }
+    },
+    searchbarContainerStyle() {
+      return [
+        "input-group searchcontainer shadow-xs",
+        {
+          "d-flex": this.contextBoxContent !== "imports",
+          "d-none": this.contextBoxContent === "imports" && this.showContextBox
+        }
+      ];
+    },
+    searchInputStyle() {
+      return [
+        "form-control ui-element search searchbar d-print-none border-0",
+        { "rounded-top-right": this.showContextBox || this.searchResults }
+      ];
+    },
+    searchButtonStyle() {
+      return [
+        "ui-element input-group-text p-0 d-flex border-0 justify-content-center searchlabel bg-white d-print-none",
+        {
+          rounded: !this.showSearchbar,
+          "rounded-left": this.showSearchbar,
+          "rounded-top-left":
+            this.showSearchbar && (this.showContextBox || this.searchResults)
+        }
+      ];
+    }
+  },
+  watch: {
+    searchQuery: function() {
+      this.searchQueryIsDirty = true;
+      this.triggerSearch();
+    }
+  },
+  methods: {
+    takeFirstSearchresult() {
+      if (!this.searchResults || this.searchResults.length != 1) return;
+      this.moveToSearchResult(this.searchResults[0]);
+    },
+    triggerSearch: debounce(function() {
+      this.doSearch();
+    }, 500),
+    doSearch() {
+      this.isCalculating = true;
+      this.searchResults = null;
+
+      if (this.searchQuery == "") {
+        return;
+      }
+
+      HTTP.post(
+        "/search",
+        { string: this.searchQuery },
+        {
+          headers: {
+            "X-Gemma-Auth": localStorage.getItem("token"),
+            "Content-type": "text/xml; charset=UTF-8"
+          }
+        }
+      )
+        .then(response => {
+          this.searchResults = response.data;
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+
+      this.isCalculating = false;
+      this.searchQueryIsDirty = false;
+    },
+    moveToSearchResult(resultEntry) {
+      if (resultEntry.geom.type == "Point") {
+        let zoom = 11;
+        if (resultEntry.type === "bottleneck") zoom = 17;
+        if (resultEntry.type === "rhm") zoom = 15;
+        if (resultEntry.type === "city") zoom = 13;
+
+        this.$store.commit("map/moveMap", {
+          coordinates: resultEntry.geom.coordinates,
+          zoom,
+          preventZoomOut: true
+        });
+      }
+      // this.searchQuery = ""; // clear search query again
+      this.toggleSearchbar();
+    },
+    toggleSearchbar() {
+      if (!this.showContextBox) {
+        if (!this.showSearchbar) {
+          setTimeout(setFocus, 300);
+        }
+        this.$store.commit("application/showSearchbar", !this.showSearchbar);
+      }
+    }
+  }
+};
+</script>
--- a/client/src/components/Sidebar.vue	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/components/Sidebar.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -4,11 +4,12 @@
       @click="$store.commit('application/showSidebar', !showSidebar)"
       class="menubutton ui-element d-print-none p-2 bg-white rounded position-absolute d-flex justify-content-center"
     >
-      <font-awesome-icon icon="bars"></font-awesome-icon>
+      <font-awesome-icon class="fa-fw" icon="bars"></font-awesome-icon>
     </div>
     <div class="menu text-nowrap text-left">
       <router-link to="/">
         <font-awesome-icon
+          class="fa-fw mr-2"
           fixed-width
           icon="map-marked-alt"
         ></font-awesome-icon>
@@ -19,61 +20,100 @@
         @click="toggleContextBox('bottlenecks')"
         href="#"
       >
-        <font-awesome-icon fixed-width icon="ship"></font-awesome-icon>
+        <font-awesome-icon
+          class="fa-fw  mr-2"
+          fixed-width
+          icon="ship"
+        ></font-awesome-icon>
         <span class="fix-trans-space" v-translate>Bottlenecks</span>
       </a>
       <div v-if="isWaterwayAdmin">
         <a
-          :class="['secondary', { active: isActive('imports') }]"
-          @click="toggleContextBox('imports')"
-          href="#"
-        >
-          <font-awesome-icon fixed-width icon="upload"></font-awesome-icon>
-          <span class="fix-trans-space" v-translate
-            >Import soundingresults</span
-          >
-        </a>
-        <a
           :class="['secondary', { active: isActive('staging') }]"
           @click="toggleContextBox('staging')"
           href="#"
         >
           <font-awesome-icon
+            class="fa-fw mr-2"
             fixed-width
             icon="clipboard-check"
           ></font-awesome-icon>
           <span class="fix-trans-space" v-translate>Staging area</span>
         </a>
+        <small class="text-muted pl-3"> <translate>Import</translate> </small>
+        <hr class="m-0" />
+        <router-link to="/importsoundingresults">
+          <font-awesome-icon
+            class="fa-fw mr-2"
+            fixed-width
+            icon="upload"
+          ></font-awesome-icon>
+          <span class="fix-trans-space" v-translate
+            >Import soundingresults</span
+          >
+        </router-link>
+        <router-link to="/importstretches" v-if="this.$options.IMPORTSTRETCHES">
+          <font-awesome-icon
+            class="fa-fw mr-2"
+            fixed-width
+            icon="cloud-upload-alt"
+          ></font-awesome-icon>
+          <span class="fix-trans-space" v-translate>Import stretches</span>
+        </router-link>
+        <router-link to="importschedule" v-if="this.$options.IMPORTSCHEDULE">
+          <font-awesome-icon
+            class="fa-fw mr-2"
+            fixed-width
+            icon="clock"
+          ></font-awesome-icon>
+          <translate class="fix-trans-space">Imports</translate>
+        </router-link>
         <small class="text-muted pl-3">
           <translate>Systemadministration</translate>
         </small>
         <hr class="m-0" />
         <router-link to="usermanagement">
-          <font-awesome-icon fixed-width icon="users-cog"></font-awesome-icon>
+          <font-awesome-icon
+            class="fa-fw mr-2"
+            fixed-width
+            icon="users-cog"
+          ></font-awesome-icon>
           <span class="fix-trans-space" v-translate>Users</span>
         </router-link>
       </div>
       <div v-if="isSysAdmin">
         <router-link to="systemconfiguration">
-          <font-awesome-icon fixed-width icon="wrench"></font-awesome-icon>
+          <font-awesome-icon
+            class="fa-fw mr-2"
+            fixed-width
+            icon="wrench"
+          ></font-awesome-icon>
           <span class="fix-trans-space" v-translate>Configuration</span>
         </router-link>
         <router-link to="logs">
-          <font-awesome-icon fixed-width icon="book"></font-awesome-icon>
+          <font-awesome-icon
+            class="fa-fw mr-2"
+            fixed-width
+            icon="book"
+          ></font-awesome-icon>
           <span class="fix-trans-space" v-translate>Logs</span>
         </router-link>
         <router-link to="importqueue">
-          <font-awesome-icon fixed-width icon="tasks"></font-awesome-icon>
+          <font-awesome-icon
+            class="fa-fw mr-2"
+            fixed-width
+            icon="tasks"
+          ></font-awesome-icon>
           <span class="fix-trans-space" v-translate>Importqueue</span>
         </router-link>
-        <router-link to="importschedule" v-if="this.$options.IMPORTSCHEDULE">
-          <font-awesome-icon fixed-width icon="clock"></font-awesome-icon>
-          <translate class="fix-trans-space">Importschedule</translate>
-        </router-link>
       </div>
       <hr class="m-0" />
       <a @click="logoff" href="#">
-        <font-awesome-icon fixed-width icon="power-off"></font-awesome-icon>
+        <font-awesome-icon
+          class="fa-fw mr-2"
+          fixed-width
+          icon="power-off"
+        ></font-awesome-icon>
         <span class="fix-trans-space" v-translate>Logout</span> {{ user }}
       </a>
     </div>
@@ -96,11 +136,22 @@
  * Markus Kottländer <markus.kottlaender@intevation.de>
  */
 import { mapGetters, mapState } from "vuex";
-import app from "../main";
+import app from "@/main";
 
 export default {
   name: "sidebar",
   props: ["routeName"],
+  watch: {
+    $route() {
+      const { review } = this.$route.query;
+      if (review) {
+        this.toggleContextBox("staging");
+        this.$store.commit("imports/setImportToReview", review);
+      } else {
+        this.$store.commit("imports/setImportToReview", -99);
+      }
+    }
+  },
   computed: {
     ...mapGetters("user", ["isSysAdmin", "isWaterwayAdmin"]),
     ...mapState("user", ["user"]),
@@ -121,6 +172,7 @@
     }
   },
   IMPORTSCHEDULE: process.env.VUE_APP_FEATURE_IMPORTSCHEDULE,
+  IMPORTSTRETCHES: process.env.VUE_APP_FEATURE_IMPORTSTRETCHES,
   methods: {
     logoff() {
       app.$snotify.clear();
@@ -129,7 +181,7 @@
       this.$router.push("/login");
     },
     toggleContextBox(context) {
-      this.$router.push("/");
+      if (this.$route.path !== "/") this.$router.push("/");
       this.$store.commit("application/showContextBox", true);
       this.$store.commit("application/contextBoxContent", context);
       this.$store.commit("application/showSearchbar", true);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/Spacer.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,50 @@
+<template>
+  <div :class="room"></div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+import { mapState } from "vuex";
+export default {
+  name: "spacer",
+  computed: {
+    ...mapState("application", ["showSidebar"]),
+    room() {
+      return [
+        "spacer ml-3",
+        {
+          "spacer-expanded": this.showSidebar,
+          "spacer-collapsed": !this.showSidebar
+        }
+      ];
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.spacer {
+  height: 90vh;
+}
+
+.spacer-collapsed {
+  min-width: $icon-width + $offset;
+  transition: $transition-fast;
+}
+
+.spacer-expanded {
+  min-width: $sidebar-width + $offset;
+}
+</style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/Systemconfiguration.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,171 @@
+<template>
+  <div class="d-flex flex-row">
+    <Spacer></Spacer>
+    <div class="card sysconfig mt-3 shadow-xs">
+      <h6
+        class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
+      >
+        <font-awesome-icon icon="wrench" class="mr-2"></font-awesome-icon>
+        <translate class="headline">Systemconfiguration</translate>
+      </h6>
+      <div class="card-body config">
+        <section class="configsection">
+          <h4 class="card-title">
+            <translate>Bottleneck Areas stroke-color</translate>
+          </h4>
+          <compact-picker v-model="strokeColor" />
+        </section>
+        <section>
+          <h4 class="card-title">
+            <translate>Bottleneck Areas fill-color</translate>
+          </h4>
+          <chrome-picker v-model="fillColor" />
+        </section>
+        <div class="sendbutton">
+          <a @click.prevent="submit" class="btn btn-info text-white">
+            <translate>Send</translate>
+          </a>
+        </div>
+      </div>
+      <!-- card-body -->
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.config {
+  text-align: left;
+}
+
+.configsection {
+  margin-bottom: $large-offset;
+}
+
+.sendbutton {
+  position: absolute;
+  right: $offset;
+  bottom: $offset;
+}
+
+.inputs {
+  margin-left: auto;
+  margin-right: auto;
+}
+
+.sysconfig {
+  margin-right: $offset;
+  width: 100%;
+  height: 100%;
+}
+</style>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ * Bernhard Reiter <bernhard@intevation.de>
+ */
+import { Chrome } from "vue-color";
+import { Compact } from "vue-color";
+
+import { HTTP } from "@/lib/http";
+import { displayError } from "@/lib/errors.js";
+import { mapState } from "vuex";
+
+export default {
+  name: "systemconfiguration",
+  data() {
+    return {
+      sent: false,
+      strokeColor: { r: 0, g: 0, b: 0, a: 1.0 },
+      fillColor: { r: 0, g: 0, b: 0, a: 1.0 },
+      currentConfig: null
+    };
+  },
+  components: {
+    "chrome-picker": Chrome,
+    "compact-picker": Compact,
+    Spacer: () => import("./Spacer")
+  },
+  computed: {
+    ...mapState("application", ["showSidebar"])
+  },
+  methods: {
+    submit() {
+      HTTP.put("/system/style/Bottlenecks/stroke", this.strokeColor.rgba, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-type": "application/json"
+        }
+      })
+        .then()
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+
+      HTTP.put("/system/style/Bottlenecks/fill", this.fillColor.rgba, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-type": "application/json"
+        }
+      })
+        .then()
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    }
+  },
+  mounted() {
+    HTTP.get("/system/style/Bottlenecks/stroke", {
+      headers: {
+        "X-Gemma-Auth": localStorage.getItem("token"),
+        "Content-type": "application/json"
+      }
+    })
+      .then(response => {
+        this.strokeColor = response.data.colour;
+      })
+      .catch(error => {
+        const { status, data } = error.response;
+        displayError({
+          title: this.$gettext("Backend Error"),
+          message: `${status}: ${data.message || data}`
+        });
+      });
+
+    HTTP.get("/system/style/Bottlenecks/fill", {
+      headers: {
+        "X-Gemma-Auth": localStorage.getItem("token"),
+        "Content-type": "application/json"
+      }
+    })
+      .then(response => {
+        this.fillColor = response.data.colour;
+      })
+      .catch(error => {
+        const { status, data } = error.response;
+        displayError({
+          title: this.$gettext("Backend Error"),
+          message: `${status}: ${data.message || data}`
+        });
+      });
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/Zoom.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,76 @@
+<template>
+  <div
+    class="d-flex buttoncontainer shadow-xs mb-3 position-absolute"
+    :style="showSplitscreen ? 'margin-bottom: 51vh !important' : ''"
+  >
+    <button
+      class="zoomButton border-0 bg-white rounded-left ui-element"
+      @click="zoomOut"
+    >
+      <font-awesome-icon icon="minus"></font-awesome-icon>
+    </button>
+    <button
+      class="zoomButton border-0 bg-white rounded-right ui-element border-right"
+      @click="zoomIn"
+    >
+      <font-awesome-icon icon="plus"></font-awesome-icon>
+    </button>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.buttoncontainer {
+  bottom: 0;
+  left: 50%;
+  margin-left: -$icon-width;
+}
+
+.zoomButton {
+  min-height: $icon-width;
+  min-width: $icon-width;
+  z-index: 1;
+  outline: none;
+  color: #666;
+}
+</style>
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus@intevation.de>
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+import { mapState } from "vuex";
+
+export default {
+  name: "zoom",
+  computed: {
+    ...mapState("map", ["openLayersMap"]),
+    ...mapState("application", ["showSplitscreen"]),
+    zoomLevel: {
+      get() {
+        return this.openLayersMap.getView().getZoom();
+      },
+      set(value) {
+        this.openLayersMap.getView().animate({ zoom: value, duration: 300 });
+      }
+    }
+  },
+  methods: {
+    zoomIn() {
+      this.zoomLevel = this.zoomLevel + 1;
+    },
+    zoomOut() {
+      this.zoomLevel = this.zoomLevel - 1;
+    }
+  }
+};
+</script>
--- a/client/src/components/admin/Importqueue.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,283 +0,0 @@
-<template>
-  <div class="d-flex flex-row">
-    <div :class="spacerStyle"></div>
-    <div class="mt-3">
-      <div class="card importqueuecard shadow-xs">
-        <h6
-          class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
-        >
-          <font-awesome-icon icon="tasks" class="mr-2"></font-awesome-icon>
-          <translate class="headline">Importqueue</translate>
-        </h6>
-        <div class="card-body importcardbody">
-          <div class="card-body importcardbody">
-            <div class="searchandfilter d-flex flex-row">
-              <div class="searchgroup input-group">
-                <div class="input-group-prepend">
-                  <span class="input-group-text" id="search">
-                    <font-awesome-icon icon="search"></font-awesome-icon>
-                  </span>
-                </div>
-                <input
-                  v-model="searchQuery"
-                  type="text"
-                  class="form-control"
-                  placeholder
-                  aria-label="Search"
-                  aria-describedby="search"
-                />
-              </div>
-              <div class="filters">
-                <button
-                  @click="setFilter('successful')"
-                  :class="successfulStyle"
-                >
-                  <translate>Successful</translate>
-                </button>
-                <button @click="setFilter('failed')" :class="failedStyle">
-                  <translate>Failed</translate>
-                </button>
-                <button @click="setFilter('pending')" :class="pendingStyle">
-                  <translate>Pending</translate>
-                </button>
-                <button @click="setFilter('rejected')" :class="rejectedStyle">
-                  <translate>Rejected</translate>
-                </button>
-                <button @click="setFilter('accepted')" :class="acceptedStyle">
-                  <translate>Accepted</translate>
-                </button>
-              </div>
-            </div>
-            <table class="table">
-              <thead>
-                <tr>
-                  <th><translate>Enqueued</translate></th>
-                  <th><translate>Kind</translate></th>
-                  <th><translate>User</translate></th>
-                  <th><translate>Signer</translate></th>
-                  <th><translate>State</translate></th>
-                </tr>
-              </thead>
-              <tbody>
-                <tr v-for="job in filteredImports" :key="job.id">
-                  <td>{{ job.enqueued }}</td>
-                  <td>{{ job.kind }}</td>
-                  <td>{{ job.user }}</td>
-                  <td>{{ job.signer }}</td>
-                  <td>{{ job.state }}</td>
-                </tr>
-              </tbody>
-            </table>
-            <div>
-              <button @click="refresh" class="btn btn-info refresh">
-                <translate>Refresh</translate>
-              </button>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus@intevation.de>
- */
-import { displayError } from "../../lib/errors.js";
-import { mapState } from "vuex";
-
-export default {
-  name: "importqueue",
-  data() {
-    return {
-      searchQuery: "",
-      successful: false,
-      failed: false,
-      pending: false,
-      rejected: false,
-      accepted: false
-    };
-  },
-  mounted() {
-    this.loadQueue();
-  },
-  methods: {
-    setFilter(name) {
-      this[name] = !this[name];
-      const allSet =
-        this.successful &&
-        this.failed &&
-        this.pending &&
-        this.accepted &&
-        this.rejected;
-      if (allSet) {
-        this.successful = false;
-        this.failed = false;
-        this.pending = false;
-        this.accepted = false;
-        this.rejected = false;
-      }
-    },
-    loadQueue() {
-      this.$store.dispatch("imports/getImports").catch(error => {
-        const { status, data } = error.response;
-        displayError({
-          title: this.$gettext("Backend Error"),
-          message: `${status}: ${data.message || data}`
-        });
-      });
-    },
-    refresh() {
-      this.loadQueue();
-    }
-  },
-  computed: {
-    ...mapState("imports", ["imports"]),
-    ...mapState("application", ["showSidebar"]),
-    filteredImports() {
-      const filtered = this.imports
-        .filter(element => {
-          if (!this.searchQuery) return true;
-          return [(element.kind, element.user, element.enqueued)].some(x => {
-            return x.toLowerCase().includes(this.searchQuery.toLowerCase());
-          });
-        })
-        .filter(y => {
-          if (
-            !this.successful &&
-            !this.failed &&
-            !this.pending &&
-            !this.accepted &&
-            !this.rejected
-          )
-            return true;
-          let filterCriteria = [];
-          if (this.successful) filterCriteria.push("successful");
-          if (this.failed) filterCriteria.push("failed");
-          if (this.pending) filterCriteria.push("pending");
-          if (this.accepted) filterCriteria.push("accepted");
-          if (this.rejected) filterCriteria.push("rejected");
-          const result = filterCriteria.map(selectedState => {
-            return y.state === selectedState;
-          });
-          return result.some(x => x);
-        });
-      return filtered;
-    },
-    spacerStyle() {
-      return [
-        "spacer ml-3",
-        {
-          "spacer-expanded": this.showSidebar,
-          "spacer-collapsed": !this.showSidebar
-        }
-      ];
-    },
-    successfulStyle() {
-      return {
-        btn: true,
-        "btn-light": !this.successful,
-        "btn-dark": this.successful
-      };
-    },
-    pendingStyle() {
-      return {
-        btn: true,
-        "btn-light": !this.pending,
-        "btn-dark": this.pending
-      };
-    },
-    failedStyle() {
-      return {
-        btn: true,
-        "btn-light": !this.failed,
-        "btn-dark": this.failed
-      };
-    },
-    rejectedStyle() {
-      return {
-        btn: true,
-        "btn-light": !this.rejected,
-        "btn-dark": this.rejected
-      };
-    },
-    acceptedStyle() {
-      return {
-        btn: true,
-        "btn-light": !this.accepted,
-        "btn-dark": this.accepted
-      };
-    }
-  }
-};
-</script>
-
-<style lang="scss" scoped>
-.refresh {
-  position: absolute;
-  right: $offset;
-  bottom: $offset;
-}
-
-.spacer {
-  height: 100vh;
-}
-
-.spacer-collapsed {
-  min-width: $icon-width + $offset;
-  transition: $transition-fast;
-}
-
-.spacer-expanded {
-  min-width: $sidebar-width + $offset;
-}
-
-.importqueuecard {
-  width: 75vw;
-  min-height: 20rem;
-}
-
-.card-body {
-  width: 100%;
-  margin-left: auto;
-  margin-right: auto;
-}
-
-.searchandfilter {
-  position: relative;
-  margin-bottom: $xx-large-offset;
-}
-
-.filters {
-  position: absolute;
-  right: 0;
-}
-
-.filters button {
-  margin-right: $small-offset;
-}
-
-.table td,
-.table th {
-  border-top: 0 !important;
-  text-align: left;
-  padding: $small-offset !important;
-}
-
-.searchgroup {
-  position: absolute;
-  left: 0;
-  width: 45%;
-}
-</style>
--- a/client/src/components/admin/Logs.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,198 +0,0 @@
-<template>
-  <div class="main d-flex flex-column">
-    <div class="d-flex flex-row">
-      <div :class="spacer"></div>
-      <div class="card logs shadow-xs mt-3 mr-3">
-        <h6
-          class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
-        >
-          <font-awesome-icon class="mr-2" icon="book"></font-awesome-icon>
-          <translate class="headline">Logs</translate>
-        </h6>
-        <div class="logoutput text-left bg-white">
-          <pre id="code" v-highlightjs="logs">
-          <code class="bash hljs hljs-string"></code>
-          </pre>
-        </div>
-        <div class="logmenu">
-          <div class="d-flex align-self-center">
-            <ul class="nav nav-pills">
-              <li class="nav-item">
-                <a
-                  :class="accesslogStyle"
-                  @click="fetch('system/log/apache2/access.log', 'accesslog')"
-                  href="#"
-                >
-                  <translate>Accesslog</translate>
-                </a>
-              </li>
-              <li class="nav-item">
-                <a
-                  :class="errorlogStyle"
-                  @click="fetch('system/log/apache2/error.log', 'errorlog')"
-                  href="#"
-                >
-                  <translate>Errorlog</translate>
-                </a>
-              </li>
-            </ul>
-          </div>
-          <div class="statuscontainer d-flex flex-row mb-3">
-            <div class="statusline align-self-center">
-              <h3><translate>Last refresh:</translate> {{ refreshed }}</h3>
-            </div>
-            <div class="refresh">
-              <button
-                @click="fetch(currentFile, currentLog)"
-                class="btn btn-dark"
-              >
-                <translate>Refresh</translate>
-              </button>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-.statuscontainer {
-  width: 87%;
-  position: relative;
-}
-
-.logmenu {
-  position: relative;
-  margin-left: $offset;
-  margin-top: $offset;
-}
-
-.logs {
-  height: 85vh;
-}
-
-#code {
-  overflow: auto;
-}
-
-.refresh {
-  position: absolute;
-  right: $offset;
-  bottom: 0;
-}
-
-.logoutput {
-  margin-left: $offset;
-  margin-right: $offset;
-  margin-top: $offset;
-  height: 90%;
-  overflow: auto;
-  transition: $transition-fast;
-}
-
-.spacer {
-  height: 90vh;
-}
-
-.spacer-collapsed {
-  min-width: $icon-width + $offset;
-  transition: $transition-fast;
-}
-
-.spacer-expanded {
-  min-width: $sidebar-width + $offset;
-}
-
-.statusline {
-  position: absolute;
-  right: 0;
-  margin-right: 9rem;
-  bottom: -0.5rem;
-}
-
-.statuscontainer {
-  width: 100%;
-}
-</style>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- */
-import { mapState } from "vuex";
-import { HTTP } from "../../lib/http.js";
-import "../../../node_modules/highlight.js/styles/paraiso-dark.css";
-import Vue from "vue";
-import VueHighlightJS from "vue-highlightjs";
-Vue.use(VueHighlightJS);
-
-const ACCESSLOG = "accesslog";
-const ERRORLOG = "errorlog";
-
-export default {
-  name: "logs",
-  mounted() {
-    this.fetch("system/log/apache2/access.log", ACCESSLOG);
-  },
-  data() {
-    return {
-      logs: null,
-      currentLog: null,
-      currentFile: null,
-      refreshed: null
-    };
-  },
-  methods: {
-    fetch(file, type) {
-      HTTP.get(file, {
-        headers: { "X-Gemma-Auth": localStorage.getItem("token") }
-      })
-        .then(response => {
-          this.logs = response.data.content;
-          this.currentLog = type;
-          this.refreshed = new Date().toLocaleString();
-          this.currentFile = file;
-        })
-        .catch();
-    },
-    disallow(e) {
-      e.target.blur();
-    }
-  },
-  computed: {
-    ...mapState("application", ["showSidebar"]),
-    accesslogStyle() {
-      return {
-        active: this.currentLog == ACCESSLOG,
-        "nav-link": true
-      };
-    },
-    errorlogStyle() {
-      return {
-        active: this.currentLog == ERRORLOG,
-        "nav-link": true
-      };
-    },
-    spacer() {
-      return [
-        "spacer ml-3",
-        {
-          "spacer-expanded": this.showSidebar,
-          "spacer-collapsed": !this.showSidebar
-        }
-      ];
-    }
-  }
-};
-</script>
--- a/client/src/components/admin/Systemconfiguration.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,190 +0,0 @@
-<template>
-  <div class="d-flex flex-row">
-    <div :class="spacerStyle"></div>
-    <div class="card sysconfig mt-3 shadow-xs">
-      <h6
-        class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
-      >
-        <font-awesome-icon icon="wrench" class="mr-2"></font-awesome-icon>
-        <translate class="headline">Systemconfiguration</translate>
-      </h6>
-      <div class="card-body config">
-        <section class="configsection">
-          <h4 class="card-title">
-            <translate>Bottleneck Areas stroke-color</translate>
-          </h4>
-          <compact-picker v-model="strokeColor" />
-        </section>
-        <section>
-          <h4 class="card-title">
-            <translate>Bottleneck Areas fill-color</translate>
-          </h4>
-          <chrome-picker v-model="fillColor" />
-        </section>
-        <div class="sendbutton">
-          <a @click.prevent="submit" class="btn btn-info text-white">
-            <translate>Send</translate>
-          </a>
-        </div>
-      </div>
-      <!-- card-body -->
-    </div>
-  </div>
-</template>
-
-<style scoped lang="scss">
-.config {
-  text-align: left;
-}
-
-.configsection {
-  margin-bottom: $large-offset;
-}
-
-.sendbutton {
-  position: absolute;
-  right: $offset;
-  bottom: $offset;
-}
-
-.inputs {
-  margin-left: auto;
-  margin-right: auto;
-}
-
-.sysconfig {
-  width: 30vw;
-  height: 100%;
-}
-
-.spacer {
-  height: 100vh;
-}
-
-.spacer-collapsed {
-  min-width: $icon-width + $offset;
-  transition: $transition-fast;
-}
-
-.spacer-expanded {
-  min-width: $sidebar-width + $offset;
-}
-</style>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- * Bernhard Reiter <bernhard@intevation.de>
- */
-import { Chrome } from "vue-color";
-import { Compact } from "vue-color";
-
-import { HTTP } from "../../lib/http";
-import { displayError } from "../../lib/errors.js";
-import { mapState } from "vuex";
-export default {
-  name: "systemconfiguration",
-  data() {
-    return {
-      sent: false,
-      strokeColor: { r: 0, g: 0, b: 0, a: 1.0 },
-      fillColor: { r: 0, g: 0, b: 0, a: 1.0 },
-      currentConfig: null
-    };
-  },
-  components: {
-    "chrome-picker": Chrome,
-    "compact-picker": Compact
-  },
-  computed: {
-    ...mapState("application", ["showSidebar"]),
-    spacerStyle() {
-      return [
-        "spacer ml-3",
-        {
-          "spacer-expanded": this.showSidebar,
-          "spacer-collapsed": !this.showSidebar
-        }
-      ];
-    }
-  },
-  methods: {
-    submit() {
-      HTTP.put("/system/style/Bottlenecks/stroke", this.strokeColor.rgba, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-type": "application/json"
-        }
-      })
-        .then()
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: this.$gettext("Backend Error"),
-            message: `${status}: ${data.message || data}`
-          });
-        });
-
-      HTTP.put("/system/style/Bottlenecks/fill", this.fillColor.rgba, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-type": "application/json"
-        }
-      })
-        .then()
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: this.$gettext("Backend Error"),
-            message: `${status}: ${data.message || data}`
-          });
-        });
-    }
-  },
-  mounted() {
-    HTTP.get("/system/style/Bottlenecks/stroke", {
-      headers: {
-        "X-Gemma-Auth": localStorage.getItem("token"),
-        "Content-type": "application/json"
-      }
-    })
-      .then(response => {
-        this.strokeColor = response.data.colour;
-      })
-      .catch(error => {
-        const { status, data } = error.response;
-        displayError({
-          title: this.$gettext("Backend Error"),
-          message: `${status}: ${data.message || data}`
-        });
-      });
-
-    HTTP.get("/system/style/Bottlenecks/fill", {
-      headers: {
-        "X-Gemma-Auth": localStorage.getItem("token"),
-        "Content-type": "application/json"
-      }
-    })
-      .then(response => {
-        this.fillColor = response.data.colour;
-      })
-      .catch(error => {
-        const { status, data } = error.response;
-        displayError({
-          title: this.$gettext("Backend Error"),
-          message: `${status}: ${data.message || data}`
-        });
-      });
-  }
-};
-</script>
--- a/client/src/components/admin/importschedule/Importschedule.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,166 +0,0 @@
-<template>
-  <div class="d-flex flex-row">
-    <div :class="spacerStyle"></div>
-    <div class="mt-3">
-      <div class="card schedulecard shadow-xs">
-        <h6
-          class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
-        >
-          <font-awesome-icon icon="clock" class="mr-2"></font-awesome-icon>
-          <translate class="headline">Importschedule</translate>
-        </h6>
-        <div class="card-body schedulecardbody">
-          <div class="card-body schedulecardbody">
-            <div class="searchandfilter d-flex flex-row">
-              <div class="searchgroup input-group">
-                <div class="input-group-prepend">
-                  <span class="input-group-text" id="search">
-                    <font-awesome-icon icon="search"></font-awesome-icon>
-                  </span>
-                </div>
-                <input
-                  v-model="searchQuery"
-                  type="text"
-                  class="form-control"
-                  placeholder
-                  aria-label="Search"
-                  aria-describedby="search"
-                />
-              </div>
-            </div>
-            <table v-if="schedules.length" class="table">
-              <thead>
-                <tr>
-                  <th><translate>Import</translate></th>
-                  <th><translate>Type</translate></th>
-                  <th><translate>Author</translate></th>
-                  <th><translate>Schedule</translate></th>
-                  <th><translate>Email</translate></th>
-                  <th>&nbsp;</th>
-                  <th>&nbsp;</th>
-                </tr>
-              </thead>
-              <tbody>
-                <tr v-for="(schedule, index) in schedules" :key="index">
-                  <td></td>
-                  <td></td>
-                  <td></td>
-                  <td></td>
-                  <td></td>
-                  <td>
-                    <font-awesome-icon
-                      icon="pencil-alt"
-                      fixed-width
-                    ></font-awesome-icon>
-                  </td>
-                  <td>
-                    <font-awesome-icon
-                      @click="deleteSchedule"
-                      icon="trash"
-                      fixed-width
-                    ></font-awesome-icon>
-                  </td>
-                </tr>
-              </tbody>
-            </table>
-            <div v-else class="mt-4 small text-center py-3">
-              <translate>No schedules</translate>
-            </div>
-            <button
-              @click="newImport"
-              class="btn btn-info position-absolute newbutton"
-            >
-              <translate>New Import</translate>
-            </button>
-          </div>
-        </div>
-      </div>
-    </div>
-    <Importscheduledetail></Importscheduledetail>
-  </div>
-</template>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- */
-
-import { mapState } from "vuex";
-import Importscheduledetail from "./Importscheduledetail";
-//import { SCHEDULES } from "../../store/imports.js";
-
-export default {
-  name: "importschedule",
-  components: {
-    Importscheduledetail
-  },
-  data() {
-    return {
-      searchQuery: ""
-    };
-  },
-  methods: {
-    newImport() {
-      this.$store.commit("imports/setImportScheduleDetailVisible");
-    },
-    deleteSchedule(index) {
-      this.$store.commit("imports/deleteSchedule", index);
-    }
-  },
-  computed: {
-    ...mapState("application", ["showSidebar"]),
-    ...mapState("imports", ["schedules"]),
-    spacerStyle() {
-      return [
-        "spacer ml-3",
-        {
-          "spacer-expanded": this.showSidebar,
-          "spacer-collapsed": !this.showSidebar
-        }
-      ];
-    }
-  }
-};
-</script>
-
-<style lang="scss" scoped>
-.spacer {
-  height: 100vh;
-}
-
-.spacer-collapsed {
-  min-width: $icon-width + $offset;
-  transition: $transition-fast;
-}
-
-.spacer-expanded {
-  min-width: $sidebar-width + $offset;
-}
-
-.schedulecard {
-  width: 40vw;
-  min-height: 20rem;
-}
-
-.schedulecard-body {
-  width: 100%;
-  margin-left: auto;
-  margin-right: auto;
-}
-
-.newbutton {
-  position: absolute;
-  bottom: $offset;
-  right: $offset;
-}
-</style>
--- a/client/src/components/admin/importschedule/Importscheduledetail.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-<template>
-  <div class="importscheduledetails" v-if="importScheduleDetailVisible">
-    <div class="card shadow-xs">
-      <h6
-        class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
-      >
-        <translate>New import</translate>
-        <span @click="closeDetailview" class="closebutton">
-          <font-awesome-icon icon="times"></font-awesome-icon>
-        </span>
-      </h6>
-      <div class="card-body">
-        <h1>lalala</h1>
-        <form @submit.prevent="save" class="ml-3"></form>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-import { mapState } from "vuex";
-
-export default {
-  name: "importscheduledetail",
-  computed: {
-    ...mapState("imports", ["importScheduleDetailVisible"])
-  },
-  methods: {
-    closeDetailview() {
-      this.$store.commit("imports/clearImportScheduleDetail");
-      this.$store.commit("imports/setImportScheduleDetailInvisible");
-    }
-  }
-};
-</script>
-
-<style lang="scss" scoped>
-.importscheduledetails {
-  margin-top: $offset;
-  margin-left: $offset;
-}
-</style>
--- a/client/src/components/admin/usermanagement/Passwordfield.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-<template>
-  <div>
-    <label for="password">{{ this.label }}</label>
-    <div class="d-flex d-row">
-      <input
-        :type="isPasswordVisible"
-        @change="fieldChanged"
-        class="form-control"
-        :placeholder="placeholder"
-        :required="required"
-      />
-      <span class="input-group-text" @click="showPassword">
-        <font-awesome-icon
-          :icon="readablePassword ? 'eye-slash' : 'eye'"
-        ></font-awesome-icon>
-      </span>
-    </div>
-    <div v-show="passworderrors" class="text-danger">
-      <small>
-        <font-awesome-icon icon="exclamation-triangle"></font-awesome-icon>
-        {{ this.passworderrors }}
-      </small>
-    </div>
-  </div>
-</template>
-
-<style>
-/* FIXME does not work here, unclear why, so added to Login.vue
-input[type="password"]::-ms-reveal {
-  display: none;
-} */
-</style>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- */
-export default {
-  name: "passwordfield",
-  props: ["model", "placeholder", "label", "passworderrors", "required"],
-  data() {
-    return {
-      password: "",
-      readablePassword: false
-    };
-  },
-  methods: {
-    showPassword() {
-      this.readablePassword = !this.readablePassword;
-    },
-    fieldChanged(e) {
-      this.$emit("fieldchange", e.target.value);
-    }
-  },
-  computed: {
-    isPasswordVisible() {
-      return this.readablePassword ? "text" : "password";
-    }
-  }
-};
-</script>
--- a/client/src/components/admin/usermanagement/Userdetail.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,384 +0,0 @@
-<template>
-  <div class="userdetails h-100 mt-3 mr-auto shadow fadeIn animated">
-    <div class="card">
-      <h6
-        class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
-      >
-        {{ this.cardHeader }}
-        <span @click="closeDetailview" class="closebutton">
-          <font-awesome-icon icon="times"></font-awesome-icon>
-        </span>
-      </h6>
-      <div class="card-body">
-        <form @submit.prevent="save" class="ml-3">
-          <div class="formfields">
-            <div v-if="currentUser.isNew" class="form-group row">
-              <label for="user"> <translate>Username</translate> </label>
-              <input
-                type="user"
-                :placeholder="userNamePlaceholder"
-                class="form-control form-control-sm"
-                id="user"
-                aria-describedby="userHelp"
-                v-model="currentUser.user"
-              />
-              <div v-show="errors.user" class="text-danger">
-                <small>
-                  <font-awesome-icon
-                    icon="exclamation-triangle"
-                  ></font-awesome-icon>
-                  {{ errors.user }}
-                </small>
-              </div>
-            </div>
-            <div class="form-group row">
-              <label for="country"> <translate>Country</translate> </label>
-              <select
-                class="form-control form-control-sm"
-                v-on:change="validateCountry"
-                v-model="currentUser.country"
-              >
-                <option disabled value>
-                  <translate>Please select one</translate>
-                </option>
-                <option
-                  v-for="country in countries"
-                  v-bind:value="country"
-                  v-bind:key="country"
-                  >{{ country }}</option
-                >
-              </select>
-              <div v-show="errors.country" class="text-danger">
-                <small>
-                  <font-awesome-icon
-                    icon="exclamation-triangle"
-                  ></font-awesome-icon>
-                  {{ errors.country }}
-                </small>
-              </div>
-            </div>
-            <div class="form-group row">
-              <label for="email"> <translate>Email address</translate> </label>
-              <input
-                type="email"
-                v-on:change="validateEmailaddress"
-                class="form-control form-control-sm"
-                id="email"
-                aria-describedby="emailHelp"
-                v-model="currentUser.email"
-              />
-              <div v-show="errors.email" class="text-danger">
-                <small>
-                  <font-awesome-icon
-                    icon="exclamation-triangle"
-                  ></font-awesome-icon>
-                  {{ errors.email }}
-                </small>
-              </div>
-            </div>
-            <div class="form-group row">
-              <label for="role"> <translate>Role</translate> </label>
-              <select
-                class="form-control form-control-sm"
-                v-on:change="validateRole"
-                v-model="currentUser.role"
-              >
-                <option disabled value>
-                  <translate>Please select one</translate>
-                </option>
-                <option value="sys_admin">
-                  <translate>Sysadmin</translate>
-                </option>
-                <option value="waterway_admin">
-                  <translate>Waterway Admin</translate>
-                </option>
-                <option value="waterway_user">
-                  <translate>Waterway User</translate>
-                </option>
-              </select>
-              <div v-show="errors.role" class="text-danger">
-                <small>
-                  <font-awesome-icon
-                    icon="exclamation-triangle"
-                  ></font-awesome-icon>
-                  {{ errors.role }}
-                </small>
-              </div>
-            </div>
-            <div class="form-group row">
-              <PasswordField
-                @fieldchange="passwordChanged"
-                :placeholder="passwordPlaceholder"
-                :label="passwordLabel"
-                :passworderrors="errors.password"
-              ></PasswordField>
-            </div>
-            <div class="form-group row">
-              <PasswordField
-                @fieldchange="passwordReChanged"
-                :placeholder="passwordRePlaceholder"
-                :label="passwordReLabel"
-                :passworderrors="errors.passwordre"
-              ></PasswordField>
-            </div>
-          </div>
-          <div>
-            <button
-              type="submit"
-              :disabled="submitted"
-              class="shadow-sm btn btn-info pull-right"
-            >
-              <translate>Submit</translate>
-            </button>
-          </div>
-          <div
-            v-if="currentUser.role != 'waterway_user'"
-            class="form-group row d-flex flex-row justify-content-start mailbutton"
-          >
-            <a @click="sendTestMail" class="btn btn-light">
-              <font-awesome-icon icon="paper-plane"></font-awesome-icon>
-              <translate>Send testmail</translate>
-            </a>
-            <div v-if="mailsent"><translate>Mail was sent</translate></div>
-          </div>
-        </form>
-      </div>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-.mailbutton {
-  width: 12vw;
-}
-
-.formfields {
-  width: 10vw;
-}
-
-.userdetails {
-  min-width: 40vw;
-}
-
-form {
-  font-size: $smaller;
-}
-</style>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- */
-import { HTTP } from "../../../lib/http";
-import { displayError } from "../../../lib/errors.js";
-import { mapState } from "vuex";
-import PasswordField from "./Passwordfield";
-
-const emptyErrormessages = () => {
-  return {
-    email: "",
-    country: "",
-    role: "",
-    password: "",
-    passwordre: ""
-  };
-};
-
-const isEmailValid = email => {
-  /**
-   *
-   * For convenience purposes the same regex used as in the go code
-   * cf. types.go
-   *
-   */
-  // eslint-disable-next-line
-  return /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(
-    email
-  );
-};
-
-const violatedPasswordRules = password => {
-  return (
-    // rules according to issue 70
-    password.length < 7 ||
-    /\W/.test(password) == false ||
-    /\d/.test(password) == false
-  );
-};
-
-export default {
-  name: "userdetail",
-  components: {
-    PasswordField
-  },
-  data() {
-    return {
-      mailsent: false,
-      passwordLabel: this.$gettext("Password"),
-      passwordReLabel: this.$gettext("Repeat Password"),
-      passwordPlaceholder: this.$gettext("password"),
-      passwordRePlaceholder: this.$gettext("password again"),
-      password: "",
-      passwordre: "",
-      currentUser: {},
-      path: null,
-      submitted: false,
-      errors: {
-        email: "",
-        country: "",
-        role: "",
-        password: "",
-        passwordre: ""
-      }
-    };
-  },
-  mounted() {
-    this.currentUser = { ...this.user };
-    this.path = this.user.name;
-  },
-  watch: {
-    user() {
-      this.currentUser = { ...this.user };
-      this.path = this.user.name;
-      this.clearPassword();
-      this.clearErrors();
-    }
-  },
-  computed: {
-    cardHeader() {
-      if (this.currentUser.isNew) return "N.N";
-      return this.currentUser.user;
-    },
-    userNamePlaceholder() {
-      if (this.currentUser.isNew) return "N.N";
-      return "";
-    },
-    ...mapState("application", ["countries"]),
-    user() {
-      return this.$store.getters["usermanagement/currentUser"];
-    },
-    isFormValid() {
-      return (
-        isEmailValid(this.currentUser.email) &&
-        this.currentUser.country &&
-        this.password === this.passwordre &&
-        (this.password === "" || !violatedPasswordRules(this.password))
-      );
-    }
-  },
-  methods: {
-    sendTestMail() {
-      if (this.mailsent) return;
-      HTTP.get("/testmail/" + this.currentUser.user, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-type": "text/xml; charset=UTF-8"
-        }
-      })
-        .then(() => {
-          this.mailsent = true;
-        })
-        .catch(error => {
-          this.loginFailed = true;
-          this.submitted = false;
-          const { status, data } = error.response;
-          displayError({
-            title: this.$gettext("Backend Error"),
-            message: `${status}: ${data.message || data}`
-          });
-        });
-    },
-    passwordChanged(value) {
-      this.password = value;
-      this.validatePassword();
-    },
-    passwordReChanged(value) {
-      this.passwordre = value;
-      this.validatePassword();
-    },
-    clearErrors() {
-      this.errors = emptyErrormessages();
-    },
-    clearPassword() {
-      this.password = "";
-      this.passwordre = "";
-    },
-    closeDetailview() {
-      this.$store.commit("usermanagement/clearCurrentUser");
-      this.$store.commit("usermanagement/setUserDetailsInvisible");
-    },
-    validateCountry() {
-      this.errors.country = this.currentUser.country
-        ? ""
-        : this.$gettext("Please choose a country");
-    },
-    validateRole() {
-      this.errors.role = this.currentUser.role
-        ? ""
-        : this.$gettext("Please choose a role");
-    },
-    validatePassword() {
-      this.errors.passwordre =
-        this.password === this.passwordre
-          ? ""
-          : this.$gettext("Passwords do not match!");
-      this.errors.password =
-        this.password === "" || !violatedPasswordRules(this.password)
-          ? ""
-          : this.$gettext(
-              "Password should at least be 8 char long including 1 digit and 1 special char like $"
-            );
-    },
-    validateEmailaddress() {
-      this.errors.email = isEmailValid(this.currentUser.email)
-        ? ""
-        : this.$gettext("invalid email");
-    },
-    validate() {
-      this.validateCountry();
-      this.validateRole();
-      this.validatePassword();
-      this.validateEmailaddress();
-    },
-    save() {
-      this.validate();
-      if (!this.isFormValid) return;
-      if (this.password) this.currentUser.password = this.password;
-      this.submitted = true;
-      this.$store
-        .dispatch("usermanagement/saveCurrentUser", {
-          path: this.user.user,
-          user: this.currentUser
-        })
-        .then(() => {
-          this.submitted = false;
-          this.$store.dispatch("usermanagement/loadUsers").catch(error => {
-            const { status, data } = error.response;
-            displayError({
-              title: this.$gettext("Backend Error"),
-              message: `${status}: ${data.message || data}`
-            });
-          });
-        })
-        .catch(error => {
-          this.submitted = false;
-          const { status, data } = error.response;
-          displayError({
-            title: this.$gettext("Error while saving user"),
-            message: `${status}: ${data.message || data}`
-          });
-        });
-    }
-  }
-};
-</script>
--- a/client/src/components/admin/usermanagement/Usermanagement.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,345 +0,0 @@
-<template>
-  <div class="main d-flex flex-row">
-    <div :class="spacerStyle"></div>
-    <div class="d-flex content flex-column">
-      <div class="d-flex flex-row">
-        <div :class="userlistStyle">
-          <div class="card">
-            <h6
-              class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
-            >
-              <font-awesome-icon
-                icon="users-cog"
-                class="mr-2"
-              ></font-awesome-icon>
-              <translate class="headline">Users</translate>
-            </h6>
-            <div class="card-body">
-              <table id="datatable" :class="tableStyle">
-                <thead>
-                  <tr>
-                    <th scope="col" @click="sortBy('user')">
-                      <span
-                        >Username&nbsp;
-                        <font-awesome-icon
-                          v-if="sortCriterion == 'user'"
-                          icon="angle-down"
-                        ></font-awesome-icon>
-                      </span>
-                    </th>
-                    <th scope="col" @click="sortBy('country')">
-                      <span
-                        >Country&nbsp;
-                        <font-awesome-icon
-                          v-if="sortCriterion == 'country'"
-                          icon="angle-down"
-                        ></font-awesome-icon>
-                      </span>
-                    </th>
-                    <th scope="col" @click="sortBy('email')">
-                      <span
-                        >Email&nbsp;
-                        <font-awesome-icon
-                          v-if="sortCriterion == 'email'"
-                          icon="angle-down"
-                        ></font-awesome-icon>
-                      </span>
-                    </th>
-                    <th scope="col" @click="sortBy('role')">
-                      <span
-                        >Role&nbsp;
-                        <font-awesome-icon
-                          v-if="sortCriterion == 'role'"
-                          icon="angle-down"
-                        ></font-awesome-icon>
-                      </span>
-                    </th>
-                    <th scope="col"></th>
-                  </tr>
-                </thead>
-                <tbody>
-                  <tr
-                    v-for="user in users"
-                    :key="user.user"
-                    @click="selectUser(user.user)"
-                  >
-                    <td>{{ user.user }}</td>
-                    <td>{{ user.country }}</td>
-                    <td>{{ user.email }}</td>
-                    <td>
-                      <font-awesome-icon
-                        :icon="roleIcon(user.role)"
-                        @click="deleteUser(user.user)"
-                      ></font-awesome-icon>
-                    </td>
-                    <td>
-                      <font-awesome-icon
-                        icon="trash"
-                        @click="deleteUser(user.user)"
-                      ></font-awesome-icon>
-                    </td>
-                  </tr>
-                </tbody>
-              </table>
-            </div>
-            <div class="d-flex mx-auto align-items-center">
-              <button
-                @click="prevPage"
-                v-if="this.currentPage !== 1"
-                class="mr-2 btn btn-sm btn-light align-self-center"
-              >
-                <font-awesome-icon icon="angle-left"></font-awesome-icon>
-              </button>
-              {{ this.currentPage }} / {{ this.pages }}
-              <button
-                @click="nextPage"
-                v-if="this.currentPage !== this.pages"
-                class="ml-2 btn btn-sm btn-light align-self-center"
-              >
-                <font-awesome-icon icon="angle-right"></font-awesome-icon>
-              </button>
-            </div>
-            <div class="mr-3 pb-3">
-              <button
-                @click="addUser"
-                class="btn btn-info pull-right shadow-sm"
-              >
-                <translate>Add User</translate>
-              </button>
-            </div>
-          </div>
-        </div>
-        <Userdetail v-if="isUserDetailsVisible"></Userdetail>
-      </div>
-    </div>
-  </div>
-</template>
-
-<style scoped lang="scss">
-@import "../../../assets/tooltip.scss";
-
-.spacer {
-  height: 100vh;
-  margin-left: $offset;
-}
-
-.spacer-collapsed {
-  min-width: $icon-width + $offset;
-  transition: $transition-fast;
-}
-
-.spacer-expanded {
-  min-width: $sidebar-width + $offset;
-}
-
-.main {
-  height: 100vh;
-}
-
-.icon {
-  font-size: large;
-}
-
-.userlist {
-  min-width: 520px;
-  height: 100%;
-}
-
-.userlistsmall {
-  width: 30vw;
-}
-
-.userlistextended {
-  width: 70vw;
-}
-
-.table {
-  width: 90% !important;
-  margin: auto;
-}
-
-.table th {
-  cursor: pointer;
-}
-
-.table th,
-td {
-  font-size: $smaller;
-  border-top: 0px !important;
-  text-align: left;
-  padding: $small-offset !important;
-}
-
-.table td {
-  font-size: $smaller;
-  cursor: pointer;
-}
-
-tr span {
-  display: flex;
-}
-</style>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- */
-import Userdetail from "./Userdetail";
-import store from "../../../store";
-import { mapGetters, mapState } from "vuex";
-import { displayError } from "../../../lib/errors.js";
-
-export default {
-  name: "userview",
-  data() {
-    return {
-      sortCriterion: "user",
-      pageSize: 10,
-      currentPage: 1
-    };
-  },
-  components: {
-    Userdetail
-  },
-  computed: {
-    ...mapGetters("usermanagement", ["isUserDetailsVisible"]),
-    ...mapState("application", ["showSidebar"]),
-    spacerStyle() {
-      return [
-        "spacer",
-        {
-          "spacer-expanded": this.showSidebar,
-          "spacer-collapsed": !this.showSidebar
-        }
-      ];
-    },
-    users() {
-      let users = [...this.$store.getters["usermanagement/users"]];
-      users.sort((a, b) => {
-        if (
-          a[this.sortCriterion].toLowerCase() <
-          b[this.sortCriterion].toLowerCase()
-        )
-          return -1;
-        if (
-          a[this.sortCriterion].toLowerCase() >
-          b[this.sortCriterion].toLowerCase()
-        )
-          return 1;
-        return 0;
-      });
-      const start = (this.currentPage - 1) * this.pageSize;
-      return users.slice(start, start + this.pageSize);
-    },
-    pages() {
-      let users = [...this.$store.getters["usermanagement/users"]];
-      return Math.ceil(users.length / this.pageSize);
-    },
-    tableStyle() {
-      return {
-        table: true,
-        "table-hover": true,
-        "table-sm": this.isUserDetailsVisible,
-        fadeIn: true,
-        animated: true
-      };
-    },
-    userlistStyle() {
-      return [
-        "userlist mt-3 mr-3 shadow-xs",
-        {
-          userlistsmall: this.isUserDetailsVisible,
-          userlistextended: !this.isUserDetailsVisible
-        }
-      ];
-    }
-  },
-  methods: {
-    tween() {},
-    nextPage() {
-      if (this.currentPage < this.pages) {
-        document.querySelector("#datatable").classList.add("fadeOut");
-        setTimeout(() => {
-          document.querySelector("#datatable").classList.remove("fadeOut");
-          this.currentPage += 1;
-        }, 10);
-      }
-      return;
-    },
-    prevPage() {
-      if (this.currentPage > 0) {
-        document.querySelector("#datatable").classList.add("fadeOut");
-        setTimeout(() => {
-          document.querySelector("#datatable").classList.remove("fadeOut");
-          this.currentPage -= 1;
-        }, 10);
-      }
-      return;
-    },
-    sortBy(criterion) {
-      this.sortCriterion = criterion;
-    },
-    deleteUser(name) {
-      this.$store
-        .dispatch("usermanagement/deleteUser", { name: name })
-        .then(() => {
-          this.submitted = false;
-          this.$store.dispatch("usermanagement/loadUsers").catch(error => {
-            const { status, data } = error.response;
-            displayError({
-              title: this.$gettext("Backend Error"),
-              message: `${status}: ${data.message || data}`
-            });
-          });
-        })
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: this.$gettext("Backend Error"),
-            message: `${status}: ${data.message || data}`
-          });
-        });
-    },
-    addUser() {
-      this.$store.commit("usermanagement/clearCurrentUser");
-      this.$store.commit("usermanagement/setUserDetailsVisible");
-    },
-    selectUser(name) {
-      const user = this.$store.getters["usermanagement/getUserByName"](name);
-      this.$store.commit("usermanagement/setCurrentUser", user);
-    },
-    roleIcon(role) {
-      if (role === "sys_admin") return "star";
-      if (role === "waterway_admin") return ["fab", "adn"];
-      return "user";
-    }
-  },
-  beforeRouteEnter(to, from, next) {
-    store
-      .dispatch("usermanagement/loadUsers")
-      .then(next)
-      .catch(error => {
-        const { status, data } = error.response;
-        displayError({
-          title: this.$gettext("Backend Error"),
-          message: `${status}: ${data}`
-        });
-      });
-  },
-  beforeRouteLeave(to, from, next) {
-    store.commit("usermanagement/clearCurrentUser");
-    store.commit("usermanagement/setUserDetailsInvisible");
-    next();
-  }
-};
-</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/fairway/Fairwayprofile.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,414 @@
+<template>
+  <div :class="['position-relative', { show: showSplitscreen }]">
+    <button
+      class="rounded-bottom bg-white border-0 position-absolute splitscreen-toggle"
+      @click="$store.commit('application/showSplitscreen', false)"
+      v-if="showSplitscreen"
+    >
+      <font-awesome-icon icon="angle-down" />
+    </button>
+    <button
+      class="rounded-bottom bg-white border-0 position-absolute clear-selection"
+      @click="$store.dispatch('fairwayprofile/clearSelection')"
+      v-if="showSplitscreen"
+    >
+      <font-awesome-icon icon="times" />
+    </button>
+    <div class="profile bg-white position-relative d-flex flex-column">
+      <h5
+        class="headline border-bottom mb-0 py-2"
+        v-if="selectedBottleneck && selectedSurvey"
+      >
+        {{ selectedBottleneck }} ({{ selectedSurvey.date_info }})
+      </h5>
+      <div class="d-flex flex-fill">
+        <div
+          class="loading d-flex justify-content-center align-items-center"
+          v-if="surveysLoading || profileLoading"
+        >
+          <font-awesome-icon icon="spinner" spin />
+        </div>
+        <div class="fairwayprofile m-3 mt-0 bg-white flex-grow-1"></div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.profile {
+  width: 100vw;
+  height: 0;
+  overflow: hidden;
+  z-index: 2;
+}
+
+.splitscreen-toggle,
+.clear-selection {
+  width: 2rem;
+  height: 2rem;
+  margin-top: 8px;
+  z-index: 3;
+  outline: none;
+}
+
+.splitscreen-toggle svg path,
+.clear-selection svg path {
+  fill: #666;
+}
+
+.splitscreen-toggle {
+  right: 2.5rem;
+}
+
+.clear-selection {
+  right: 0.5rem;
+}
+
+.show .profile {
+  height: 50vh;
+}
+
+.loading {
+  background: rgba(255, 255, 255, 0.96);
+  position: absolute;
+  z-index: 99;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+}
+</style>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+import * as d3 from "d3";
+import { mapState, mapGetters } from "vuex";
+import debounce from "debounce";
+
+const GROUND_COLOR = "#4A2F06";
+
+export default {
+  name: "fairwayprofile",
+  data() {
+    return {
+      coordinatesInput: "",
+      coordinatesSelect: null,
+      cutLabel: "",
+      showLabelInput: false,
+      width: null,
+      height: null,
+      margin: {
+        top: 20,
+        right: 40,
+        bottom: 30,
+        left: 40
+      }
+    };
+  },
+  computed: {
+    ...mapGetters("fairwayprofile", ["totalLength"]),
+    ...mapState("application", ["showSplitscreen"]),
+    ...mapState("fairwayprofile", [
+      "startPoint",
+      "endPoint",
+      "currentProfile",
+      "additionalSurvey",
+      "minAlt",
+      "maxAlt",
+      "fairwayCoordinates",
+      "waterLevels",
+      "selectedWaterLevel",
+      "profileLoading"
+    ]),
+    ...mapState("bottlenecks", [
+      "selectedBottleneck",
+      "selectedSurvey",
+      "surveysLoading"
+    ]),
+    currentData() {
+      if (
+        !this.selectedSurvey ||
+        !this.currentProfile.hasOwnProperty(this.selectedSurvey.date_info)
+      )
+        return [];
+      return this.currentProfile[this.selectedSurvey.date_info].points;
+    },
+    additionalData() {
+      if (
+        !this.additionalSurvey ||
+        !this.currentProfile.hasOwnProperty(this.additionalSurvey.date_info)
+      )
+        return [];
+      return this.currentProfile[this.additionalSurvey.date_info].points;
+    },
+    waterColor() {
+      const result = this.waterLevels.find(
+        x => x.level === this.selectedWaterLevel
+      );
+      return result.color;
+    },
+    xScale() {
+      return [0, this.totalLength];
+    },
+    yScaleLeft() {
+      const hi = Math.max(this.maxAlt, this.selectedWaterLevel);
+      return [this.minAlt, hi];
+    },
+    yScaleRight() {
+      const DELTA = this.maxAlt * 1.1 - this.maxAlt;
+      return [this.maxAlt * 1 + DELTA, -DELTA];
+    }
+  },
+  watch: {
+    currentData() {
+      this.drawDiagram();
+    },
+    additionalData() {
+      this.drawDiagram();
+    },
+    width() {
+      this.drawDiagram();
+    },
+    height() {
+      this.drawDiagram();
+    },
+    waterLevels() {
+      this.drawDiagram();
+    },
+    selectedWaterLevel() {
+      this.drawDiagram();
+    },
+    fairwayCoordinates() {
+      this.drawDiagram();
+    }
+  },
+  methods: {
+    drawDiagram() {
+      this.coordinatesSelect = null;
+      const chartDiv = document.querySelector(".fairwayprofile");
+      d3.select(".fairwayprofile svg").remove();
+      this.scaleFairwayProfile();
+      let svg = d3.select(chartDiv).append("svg");
+      svg.attr("width", this.width);
+      svg.attr("height", this.height);
+      const width = this.width - this.margin.right - 1.5 * this.margin.left;
+      const height = this.height - this.margin.top - 2 * this.margin.bottom;
+      const currentData = this.currentData;
+      const additionalData = this.additionalData;
+      const { xScale, yScaleRight, graph } = this.generateCoordinates(
+        svg,
+        height,
+        width
+      );
+      if (!this.height || !this.width) return; // do not try to render when height and width are unknown
+      this.drawWaterlevel({ graph, xScale, yScaleRight, height });
+      this.drawLabels({ graph, height });
+      this.drawFairway({ graph, xScale, yScaleRight });
+      if (currentData) {
+        this.drawProfile({
+          graph,
+          xScale,
+          yScaleRight,
+          currentData,
+          height,
+          color: GROUND_COLOR,
+          strokeColor: "black",
+          opacity: 1
+        });
+      }
+      if (additionalData) {
+        this.drawProfile({
+          graph,
+          xScale,
+          yScaleRight,
+          currentData: additionalData,
+          height,
+          color: GROUND_COLOR,
+          strokeColor: "#943007",
+          opacity: 0.6
+        });
+      }
+    },
+    drawFairway({ graph, xScale, yScaleRight }) {
+      for (let coordinates of this.fairwayCoordinates) {
+        const [startPoint, endPoint, depth] = coordinates;
+        let fairwayArea = d3
+          .area()
+          .x(function(d) {
+            return xScale(d.x);
+          })
+          .y0(yScaleRight(0))
+          .y1(function(d) {
+            return yScaleRight(d.y);
+          });
+        graph
+          .append("path")
+          .datum([{ x: startPoint, y: depth }, { x: endPoint, y: depth }])
+          .attr("fill", "#002AFF")
+          .attr("stroke-opacity", 0.65)
+          .attr("fill-opacity", 0.65)
+          .attr("stroke", "#FFD20D")
+          .attr("d", fairwayArea);
+      }
+    },
+    drawLabels({ graph, height }) {
+      graph
+        .append("text")
+        .attr("transform", ["rotate(-90)"])
+        .attr("y", this.width - 60)
+        .attr("x", -(this.height - this.margin.top - this.margin.bottom) / 2)
+        .attr("dy", "1em")
+        .attr("fill", "black")
+        .style("text-anchor", "middle")
+        .text("Depth [m]");
+      graph
+        .append("text")
+        .attr("y", 0 - this.margin.left)
+        .attr("x", 0 - height / 4)
+        .attr("dy", "1em")
+        .attr("fill", "black")
+        .style("text-anchor", "middle")
+        .attr("transform", [
+          "translate(" + this.width / 2 + "," + this.height + ")",
+          "rotate(0)"
+        ])
+        .text("Width [m]");
+    },
+    generateCoordinates(svg, height, width) {
+      let xScale = d3
+        .scaleLinear()
+        .domain(this.xScale)
+        .rangeRound([0, width]);
+
+      xScale.ticks(5);
+      let yScaleLeft = d3
+        .scaleLinear()
+        .domain(this.yScaleLeft)
+        .rangeRound([height, 0]);
+
+      let yScaleRight = d3
+        .scaleLinear()
+        .domain(this.yScaleRight)
+        .rangeRound([height, 0]);
+
+      let xAxis = d3.axisBottom(xScale);
+      let yAxis2 = d3.axisRight(yScaleRight);
+      let graph = svg
+        .append("g")
+        .attr(
+          "transform",
+          "translate(" + this.margin.left + "," + this.margin.top + ")"
+        );
+      graph
+        .append("g")
+        .attr("transform", "translate(0," + height + ")")
+        .call(xAxis.ticks(5));
+      graph
+        .append("g")
+        .attr("transform", "translate(" + width + ",0)")
+        .call(yAxis2);
+      return { xScale, yScaleLeft, yScaleRight, graph };
+    },
+    drawWaterlevel({ graph, xScale, yScaleRight, height }) {
+      let waterArea = d3
+        .area()
+        .x(function(d) {
+          return xScale(d.x);
+        })
+        .y0(height)
+        .y1(function(d) {
+          return yScaleRight(d.y);
+        });
+      graph
+        .append("path")
+        .datum([{ x: 0, y: 0 }, { x: this.totalLength, y: 0 }])
+        .attr("fill", this.waterColor)
+        .attr("stroke", this.waterColor)
+        .attr("d", waterArea);
+    },
+    drawProfile({
+      graph,
+      xScale,
+      yScaleRight,
+      currentData,
+      height,
+      color,
+      strokeColor,
+      opacity
+    }) {
+      for (let part of currentData) {
+        let profileLine = d3
+          .line()
+          .x(d => {
+            return xScale(d.x);
+          })
+          .y(d => {
+            return yScaleRight(d.y);
+          });
+        let profileArea = d3
+          .area()
+          .x(function(d) {
+            return xScale(d.x);
+          })
+          .y0(height)
+          .y1(function(d) {
+            return yScaleRight(d.y);
+          });
+        graph
+          .append("path")
+          .datum(part)
+          .attr("fill", color)
+          .attr("stroke", color)
+          .attr("stroke-width", 3)
+          .attr("stroke-opacity", opacity)
+          .attr("fill-opacity", opacity)
+          .attr("d", profileArea);
+        graph
+          .append("path")
+          .datum(part)
+          .attr("fill", "none")
+          .attr("stroke", strokeColor)
+          .attr("stroke-linejoin", "round")
+          .attr("stroke-linecap", "round")
+          .attr("stroke-width", 3)
+          .attr("stroke-opacity", opacity)
+          .attr("fill-opacity", opacity)
+          .attr("d", profileLine);
+      }
+    },
+    scaleFairwayProfile() {
+      if (!document.querySelector(".fairwayprofile")) return;
+      const clientHeight = document.querySelector(".fairwayprofile")
+        .clientHeight;
+      const clientWidth = document.querySelector(".fairwayprofile").clientWidth;
+      if (!clientHeight || !clientWidth) return;
+      this.height = clientHeight;
+      this.width = clientWidth;
+    }
+  },
+  created() {
+    window.addEventListener("resize", debounce(this.drawDiagram), 100);
+  },
+  mounted() {
+    this.drawDiagram();
+  },
+  updated() {
+    this.scaleFairwayProfile();
+  },
+  destroyed() {
+    window.removeEventListener("resize", debounce(this.drawDiagram));
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/fairway/Infobar.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,61 @@
+<template>
+  <div
+    v-if="Object.keys(currentProfile).length && !showSplitscreen"
+    class="ui-element shadow-xs infobar rounded bg-white ml-auto mb-3 mr-3"
+  >
+    <div class="d-flex flex-row justify-content-between h-100">
+      <h6 class="my-auto px-2">
+        {{ selectedBottleneck }} ({{ selectedSurvey.date_info }})
+      </h6>
+      <span
+        class="p-2 border-left d-flex align-items-center"
+        @click="$store.commit('application/showSplitscreen', true)"
+      >
+        <font-awesome-icon icon="angle-up"></font-awesome-icon>
+      </span>
+      <span
+        class="p-2 border-left d-flex align-items-center"
+        @click="$store.dispatch('fairwayprofile/clearSelection')"
+      >
+        <font-awesome-icon icon="times"></font-awesome-icon>
+      </span>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.infobar {
+  height: 2.2rem;
+  z-index: 2;
+}
+
+.infobar svg path {
+  fill: #666;
+}
+</style>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState } from "vuex";
+
+export default {
+  name: "infobar",
+  computed: {
+    ...mapState("application", ["showSplitscreen"]),
+    ...mapState("fairwayprofile", ["currentProfile"]),
+    ...mapState("bottlenecks", ["selectedBottleneck", "selectedSurvey"])
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/fairway/Profiles.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,471 @@
+<template>
+  <div
+    :class="[
+      'box ui-element rounded bg-white text-nowrap',
+      { expanded: showProfiles }
+    ]"
+  >
+    <div>
+      <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
+        <font-awesome-icon icon="chart-area" class="mr-2"></font-awesome-icon>
+        <translate>Profiles</translate>
+        <font-awesome-icon
+          icon="times"
+          class="ml-auto text-muted"
+          @click="$store.commit('application/showProfiles', false)"
+        ></font-awesome-icon>
+      </h6>
+      <div
+        class="d-flex flex-column p-3 flex-grow-1 text-left position-relative"
+      >
+        <div
+          class="loading d-flex justify-content-center align-items-center"
+          v-if="surveysLoading || profileLoading"
+        >
+          <font-awesome-icon icon="spinner" spin />
+        </div>
+        <select
+          @click="moveToBottleneck"
+          v-model="selectedBottleneck"
+          class="form-control font-weight-bold"
+        >
+          <option :value="null">
+            <translate>Select Bottleneck</translate>
+          </option>
+          <option
+            v-for="bn in bottlenecks"
+            :key="bn.properties.name"
+            :value="bn.properties.name"
+            >{{ bn.properties.name }}</option
+          >
+        </select>
+        <div v-if="selectedBottleneck">
+          <div class="d-flex mt-2">
+            <div class="flex-fill">
+              <small class="text-muted">
+                <translate>Sounding Result</translate>:
+              </small>
+              <select
+                v-model="selectedSurvey"
+                class="form-control form-control-sm"
+              >
+                <option
+                  v-for="survey in surveys"
+                  :key="survey.date_info"
+                  :value="survey"
+                  >{{ formatSurveyDate(survey.date_info) }}</option
+                >
+              </select>
+            </div>
+            <div
+              class="flex-fill ml-3"
+              v-if="selectedSurvey && surveys.length > 1"
+            >
+              <small class="text-muted mt-1">
+                <translate>Compare with</translate>:
+              </small>
+              <select
+                v-model="additionalSurvey"
+                class="form-control form-control-sm"
+              >
+                <option :value="null">None</option>
+                <option
+                  v-for="survey in additionalSurveys"
+                  :key="survey.date_info"
+                  :value="survey"
+                  >{{ formatSurveyDate(survey.date_info) }}</option
+                >
+              </select>
+            </div>
+          </div>
+          <hr class="w-100 mb-0" />
+          <small class="text-muted d-block mt-2">
+            <translate>Saved cross profiles</translate>:
+          </small>
+          <div class="d-flex">
+            <select
+              :class="[
+                'form-control form-control-sm flex-fill',
+                { 'rounded-left-only': selectedCut }
+              ]"
+              v-model="selectedCut"
+            >
+              <option></option>
+              <option
+                v-for="(cut, index) in previousCuts"
+                :value="cut"
+                :key="index"
+                >{{ cut.label }}</option
+              >
+            </select>
+            <button
+              class="btn btn-sm btn-danger input-button-right"
+              @click="confirmDeleteSelectedCut = true"
+              v-if="selectedCut && !confirmDeleteSelectedCut"
+            >
+              <font-awesome-icon icon="trash" />
+            </button>
+            <button
+              class="btn btn-sm btn-info rounded-0"
+              @click="confirmDeleteSelectedCut = false"
+              v-if="selectedCut && confirmDeleteSelectedCut"
+            >
+              <font-awesome-icon icon="times" />
+            </button>
+            <button
+              class="btn btn-sm btn-danger input-button-right"
+              @click="deleteSelectedCut"
+              v-if="selectedCut && confirmDeleteSelectedCut"
+            >
+              <font-awesome-icon icon="check" />
+            </button>
+          </div>
+          <small class="text-muted d-block mt-2">
+            <translate>Enter coordinates manually</translate>:
+          </small>
+          <div class="position-relative">
+            <input
+              class="form-control form-control-sm pr-5"
+              placeholder="Lat,Lon,Lat,Lon"
+              v-model="coordinatesInput"
+            />
+            <button
+              class="btn btn-sm btn-info position-absolute input-button-right"
+              @click="applyManualCoordinates"
+              style="top: 0; right: 0;"
+              v-if="coordinatesInputIsValid"
+            >
+              <font-awesome-icon icon="check" />
+            </button>
+          </div>
+          <small class="d-flex text-left mt-2" v-if="startPoint && endPoint">
+            <div class="text-nowrap mr-3">
+              <b> <translate>Start</translate>: </b> <br />
+              Lat: {{ startPoint[1] }} <br />
+              Lon: {{ startPoint[0] }}
+            </div>
+            <div class="text-nowrap">
+              <b>End:</b> <br />
+              Lat: {{ endPoint[1] }} <br />
+              Lon: {{ endPoint[0] }}
+            </div>
+            <button
+              v-clipboard:copy="coordinatesForClipboard"
+              v-clipboard:success="onCopyCoordinates"
+              class="btn btn-info btn-sm ml-auto mt-auto"
+            >
+              <font-awesome-icon icon="copy" />
+            </button>
+          </small>
+          <div class="d-flex mt-3">
+            <div
+              class="pr-3 w-50"
+              v-if="startPoint && endPoint && !selectedCut"
+            >
+              <button
+                class="btn btn-info btn-sm w-100"
+                @click="showLabelInput = !showLabelInput"
+              >
+                <font-awesome-icon :icon="showLabelInput ? 'times' : 'check'" />
+                {{ showLabelInput ? "Cancel" : "Save" }}
+              </button>
+            </div>
+            <div
+              :class="startPoint && endPoint && !selectedCut ? 'w-50' : 'w-100'"
+            >
+              <button class="btn btn-info btn-sm w-100" @click="toggleCutTool">
+                <font-awesome-icon
+                  :icon="cutTool && cutTool.getActive() ? 'times' : 'plus'"
+                ></font-awesome-icon>
+                {{ cutTool && cutTool.getActive() ? "Cancel" : "New" }}
+              </button>
+            </div>
+          </div>
+          <div v-if="showLabelInput" class="mt-2">
+            <small class="text-muted">
+              <translate>Enter label for cross profile</translate>:
+            </small>
+            <div class="position-relative">
+              <input
+                class="form-control form-control-sm pr-5"
+                v-model="cutLabel"
+              />
+              <button
+                class="btn btn-sm btn-info position-absolute input-button-right"
+                @click="saveCut"
+                v-if="cutLabel"
+                style="top: 0; right: 0;"
+              >
+                <font-awesome-icon icon="check" />
+              </button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.loading {
+  background: rgba(255, 255, 255, 0.9);
+  position: absolute;
+  z-index: 99;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+}
+
+.input-button-right {
+  border-top-right-radius: $border-radius;
+  border-bottom-right-radius: $border-radius;
+  border-top-left-radius: 0 !important;
+  border-bottom-left-radius: 0 !important;
+}
+
+.rounded-left-only {
+  border-top-right-radius: 0 !important;
+  border-bottom-right-radius: 0 !important;
+  border-top-left-radius: $border-radius;
+  border-bottom-left-radius: $border-radius;
+}
+</style>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState, mapGetters } from "vuex";
+import Feature from "ol/Feature";
+import LineString from "ol/geom/LineString";
+import { displayError, displayInfo } from "@/lib/errors.js";
+import { formatSurveyDate } from "@/lib/date.js";
+
+export default {
+  name: "profiles",
+  data() {
+    return {
+      coordinatesInput: "",
+      cutLabel: "",
+      showLabelInput: false,
+      confirmDeleteSelectedCut: false
+    };
+  },
+  computed: {
+    ...mapGetters("map", ["getVSourceByName"]),
+    ...mapState("application", ["showProfiles"]),
+    ...mapState("map", ["lineTool", "polygonTool", "cutTool"]),
+    ...mapState("bottlenecks", ["bottlenecks", "surveys", "surveysLoading"]),
+    ...mapState("fairwayprofile", [
+      "previousCuts",
+      "startPoint",
+      "endPoint",
+      "profileLoading"
+    ]),
+    selectedBottleneck: {
+      get() {
+        return this.$store.state.bottlenecks.selectedBottleneck;
+      },
+      set(name) {
+        this.$store
+          .dispatch("bottlenecks/setSelectedBottleneck", name)
+          .then(() => {
+            this.$store.commit("bottlenecks/setFirstSurveySelected");
+          });
+      }
+    },
+    selectedSurvey: {
+      get() {
+        return this.$store.state.bottlenecks.selectedSurvey;
+      },
+      set(survey) {
+        this.$store.commit("fairwayprofile/additionalSurvey", null);
+        this.$store.commit("bottlenecks/selectedSurvey", survey);
+      }
+    },
+    additionalSurvey: {
+      get() {
+        return this.$store.state.fairwayprofile.additionalSurvey;
+      },
+      set(survey) {
+        this.$store.commit("fairwayprofile/additionalSurvey", survey);
+      }
+    },
+    selectedCut: {
+      get() {
+        return this.$store.state.fairwayprofile.selectedCut;
+      },
+      set(cut) {
+        this.$store.commit("fairwayprofile/selectedCut", cut);
+        if (!cut) {
+          this.$store.commit("fairwayprofile/clearCurrentProfile");
+          this.$store.commit("application/showSplitscreen", false);
+          this.getVSourceByName("Cut Tool").clear();
+        }
+      }
+    },
+    additionalSurveys() {
+      return this.surveys.filter(survey => survey !== this.selectedSurvey);
+    },
+    coordinatesForClipboard() {
+      return (
+        this.startPoint[1] +
+        "," +
+        this.startPoint[0] +
+        "," +
+        this.endPoint[1] +
+        "," +
+        this.endPoint[0]
+      );
+    },
+    coordinatesInputIsValid() {
+      const coordinates = this.coordinatesInput
+        .split(",")
+        .map(coord => parseFloat(coord.trim()))
+        .filter(c => Number(c) === c);
+      return coordinates.length === 4;
+    }
+  },
+  watch: {
+    selectedBottleneck() {
+      this.$store.dispatch("fairwayprofile/previousCuts");
+      this.cutLabel =
+        this.selectedBottleneck + " (" + new Date().toISOString() + ")";
+    },
+    selectedSurvey(survey) {
+      this.loadProfile(survey);
+    },
+    additionalSurvey(survey) {
+      this.loadProfile(survey);
+    },
+    selectedCut(cut) {
+      if (cut) {
+        this.confirmDeleteSelectedCut = false;
+        this.applyCoordinates(cut.coordinates);
+      }
+    }
+  },
+  methods: {
+    formatSurveyDate(date) {
+      return formatSurveyDate(date);
+    },
+    loadProfile(survey) {
+      if (survey) {
+        this.$store.commit("fairwayprofile/profileLoading", true);
+        this.$store
+          .dispatch("fairwayprofile/loadProfile", survey)
+          .finally(() =>
+            this.$store.commit("fairwayprofile/profileLoading", false)
+          );
+      }
+    },
+    toggleCutTool() {
+      this.cutTool.setActive(!this.cutTool.getActive());
+      this.lineTool.setActive(false);
+      this.polygonTool.setActive(false);
+      this.$store.commit("map/setCurrentMeasurement", null);
+    },
+    onCopyCoordinates() {
+      displayInfo({
+        title: this.$gettext("Success"),
+        message: this.$gettext("Coordinates copied to clipboard!")
+      });
+    },
+    applyManualCoordinates() {
+      const coordinates = this.coordinatesInput
+        .split(",")
+        .map(coord => parseFloat(coord.trim()));
+      this.selectedCut = null;
+      this.coordinatesInput = "";
+      this.applyCoordinates([
+        coordinates[1],
+        coordinates[0],
+        coordinates[3],
+        coordinates[2]
+      ]);
+    },
+    applyCoordinates(coordinates) {
+      // allow only numbers
+      coordinates = coordinates.filter(c => Number(c) === c);
+      if (coordinates.length === 4) {
+        // draw line on map
+        this.getVSourceByName("Cut Tool").clear();
+        const cut = new Feature({
+          geometry: new LineString([
+            [coordinates[0], coordinates[1]],
+            [coordinates[2], coordinates[3]]
+          ]).transform("EPSG:4326", "EPSG:3857")
+        });
+        this.getVSourceByName("Cut Tool").addFeature(cut);
+
+        // draw diagram
+        this.$store.dispatch("fairwayprofile/cut", cut);
+      } else {
+        displayError({
+          title: this.$gettext("Invalid input"),
+          message: this.$gettext(
+            "Please enter correct coordinates in the format: Lat,Lon,Lat,Lon"
+          )
+        });
+      }
+    },
+    saveCut() {
+      const previousCuts =
+        JSON.parse(localStorage.getItem("previousCuts")) || [];
+      const newEntry = {
+        label: this.cutLabel,
+        bottleneckName: this.selectedBottleneck,
+        coordinates: [...this.startPoint, ...this.endPoint],
+        timestamp: new Date().getTime()
+      };
+      const existingEntry = previousCuts.find(cut => {
+        return JSON.stringify(cut) === JSON.stringify(newEntry);
+      });
+      if (!existingEntry) previousCuts.push(newEntry);
+      if (previousCuts.length > 100) previousCuts.shift();
+      localStorage.setItem("previousCuts", JSON.stringify(previousCuts));
+      this.$store.dispatch("fairwayprofile/previousCuts");
+
+      this.showLabelInput = false;
+      displayInfo({
+        title: this.$gettext("Profile saved!"),
+        message: this.$gettext(
+          'You can now select these coordinates from the "Saved cross profiles" menu to restore this cross profile.'
+        )
+      });
+    },
+    deleteSelectedCut() {
+      let previousCuts = JSON.parse(localStorage.getItem("previousCuts")) || [];
+      previousCuts = previousCuts.filter(cut => {
+        return JSON.stringify(cut) !== JSON.stringify(this.selectedCut);
+      });
+      localStorage.setItem("previousCuts", JSON.stringify(previousCuts));
+      this.$store.commit("fairwayprofile/selectedCut", null);
+      this.$store.dispatch("fairwayprofile/previousCuts");
+      displayInfo({ title: this.$gettext("Profile deleted!") });
+    },
+    moveToBottleneck() {
+      const bottleneck = this.bottlenecks.find(
+        bn => bn.properties.name === this.selectedBottleneck
+      );
+      if (!bottleneck) return;
+      this.$store.commit("map/moveMap", {
+        coordinates: bottleneck.geometry.coordinates,
+        zoom: 17,
+        preventZoomOut: true
+      });
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importqueue/Importqueue.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,357 @@
+<template>
+  <div class="d-flex flex-row">
+    <Spacer></Spacer>
+    <div class="mt-3 importqueuecard flex-grow-1">
+      <div class="card shadow-xs">
+        <h6
+          class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
+        >
+          <font-awesome-icon icon="tasks" class="mr-2"></font-awesome-icon>
+          <translate class="headline">Importqueue</translate>
+        </h6>
+        <div class="card-body importcardbody">
+          <div class="card-body importcardbody">
+            <div class="searchandfilter d-flex flex-row">
+              <div class="searchgroup input-group">
+                <div class="input-group-prepend">
+                  <span class="input-group-text" id="search">
+                    <font-awesome-icon icon="search"></font-awesome-icon>
+                  </span>
+                </div>
+                <input
+                  v-model="searchQuery"
+                  type="text"
+                  class="form-control"
+                  placeholder
+                  aria-label="Search"
+                  aria-describedby="search"
+                />
+              </div>
+              <div class="filters">
+                <button
+                  @click="setFilter('successful')"
+                  :class="successfulStyle"
+                >
+                  <translate>Successful</translate>
+                </button>
+                <button @click="setFilter('failed')" :class="failedStyle">
+                  <translate>Failed</translate>
+                </button>
+                <button @click="setFilter('pending')" :class="pendingStyle">
+                  <translate>Pending</translate>
+                </button>
+                <button @click="setFilter('rejected')" :class="rejectedStyle">
+                  <translate>Rejected</translate>
+                </button>
+                <button @click="setFilter('accepted')" :class="acceptedStyle">
+                  <translate>Accepted</translate>
+                </button>
+              </div>
+            </div>
+            <div class="text-left d-flex flex-row border-bottom entries">
+              <div class="header py-1 jobid mr-2">
+                <translate>Id</translate>
+              </div>
+              <div class="header py-1 enqueued mr-2">
+                <translate>Enqueued</translate>
+              </div>
+              <div class="header py-1 kind mr-2">
+                <translate>Kind</translate>
+              </div>
+              <div class="header py-1 user mr-2">
+                <translate>User</translate>
+              </div>
+              <div class="header py-1 signer mr-2">
+                <translate>Signer</translate>
+              </div>
+              <div class="header py-1 state mr-2">
+                <translate>State</translate>
+              </div>
+            </div>
+            <div class="importqueuedetail">
+              <div
+                class="text-left"
+                v-for="job in filteredImports"
+                :key="job.id"
+              >
+                <Importqueuedetail
+                  :reload="reload"
+                  :job="job"
+                ></Importqueuedetail>
+              </div>
+            </div>
+            <div>
+              <button @click="refresh" class="btn btn-info refresh">
+                <translate>Refresh</translate>
+              </button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus@intevation.de>
+ */
+import { displayError } from "@/lib/errors.js";
+import { mapState } from "vuex";
+import { HTTP } from "@/lib/http.js";
+
+export default {
+  name: "importqueue",
+  components: {
+    Importqueuedetail: () => import("./Importqueuedetail"),
+    Spacer: () => import("@/components/Spacer")
+  },
+  data() {
+    return {
+      reload: false,
+      searchQuery: "",
+      successful: false,
+      failed: false,
+      pending: false,
+      rejected: false,
+      accepted: false
+    };
+  },
+  mounted() {
+    this.loadQueue();
+  },
+  methods: {
+    setFilter(name) {
+      this[name] = !this[name];
+      const allSet =
+        this.successful &&
+        this.failed &&
+        this.pending &&
+        this.accepted &&
+        this.rejected;
+      if (allSet) {
+        this.successful = false;
+        this.failed = false;
+        this.pending = false;
+        this.accepted = false;
+        this.rejected = false;
+      }
+    },
+    loadQueue() {
+      this.reload = true;
+      this.$store
+        .dispatch("imports/getImports")
+        .then(() => {
+          this.reload = false;
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    },
+    refresh() {
+      this.loadQueue();
+    },
+    showDetails(id) {
+      HTTP.get("/imports/" + id, {
+        headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+      })
+        .then(response => {
+          const { entries } = response.data;
+          this.entries = entries;
+          this.$modal.show("details");
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    },
+    close() {
+      this.$modal.hide("details");
+    }
+  },
+  computed: {
+    ...mapState("imports", ["imports"]),
+    ...mapState("application", ["showSidebar"]),
+    sortIcon() {
+      return this.sortAsc ? "sort-amount-down" : "sort-amount-up";
+    },
+    filteredImports() {
+      const filtered = this.imports
+        .filter(element => {
+          if (!this.searchQuery) return true;
+          return [(element.kind, element.user, element.enqueued)].some(x => {
+            return x.toLowerCase().includes(this.searchQuery.toLowerCase());
+          });
+        })
+        .filter(y => {
+          if (
+            !this.successful &&
+            !this.failed &&
+            !this.pending &&
+            !this.accepted &&
+            !this.rejected
+          )
+            return true;
+          let filterCriteria = [];
+          if (this.successful) filterCriteria.push("successful");
+          if (this.failed) filterCriteria.push("failed");
+          if (this.pending) filterCriteria.push("pending");
+          if (this.accepted) filterCriteria.push("accepted");
+          if (this.rejected) filterCriteria.push("rejected");
+          const result = filterCriteria.map(selectedState => {
+            return y.state === selectedState;
+          });
+          return result.some(x => x);
+        });
+      return filtered;
+    },
+    successfulStyle() {
+      return {
+        btn: true,
+        "btn-light": !this.successful,
+        "btn-dark": this.successful
+      };
+    },
+    pendingStyle() {
+      return {
+        btn: true,
+        "btn-light": !this.pending,
+        "btn-dark": this.pending
+      };
+    },
+    failedStyle() {
+      return {
+        btn: true,
+        "btn-light": !this.failed,
+        "btn-dark": this.failed
+      };
+    },
+    rejectedStyle() {
+      return {
+        btn: true,
+        "btn-light": !this.rejected,
+        "btn-dark": this.rejected
+      };
+    },
+    acceptedStyle() {
+      return {
+        btn: true,
+        "btn-light": !this.accepted,
+        "btn-dark": this.accepted
+      };
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.importqueuedetail {
+  margin-bottom: 3rem;
+}
+.entries {
+  width: 100%;
+}
+
+.jobid {
+  width: 15%;
+}
+
+.enqueued {
+  width: 20%;
+}
+
+.user {
+  width: 15%;
+}
+
+.signer {
+  width: 20%;
+}
+
+.kind {
+  width: 10%;
+}
+
+.state {
+  width: 20%;
+}
+
+.details thead {
+  display: block;
+}
+.details tbody {
+  display: block;
+}
+
+.details tbody {
+  height: 260px;
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+
+.closebutton {
+  top: $small-offset;
+}
+
+.refresh {
+  position: absolute;
+  right: $offset;
+  bottom: $offset;
+}
+
+.importqueuecard {
+  width: 97%;
+  margin-right: $offset;
+  min-height: 20rem;
+}
+
+.card-body {
+  width: 100%;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+.searchandfilter {
+  position: relative;
+  margin-bottom: $xx-large-offset;
+}
+
+.filters {
+  position: absolute;
+  right: 0;
+}
+
+.filters button {
+  margin-right: $small-offset;
+}
+
+.table td,
+.table th {
+  border-top: 0 !important;
+  text-align: left;
+  padding: $small-offset !important;
+}
+
+.searchgroup {
+  position: absolute;
+  left: 0;
+  width: 45%;
+}
+</style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importqueue/Importqueuedetail.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,307 @@
+<template>
+  <div class="entry d-flex flex-column py-1 border-bottom">
+    <div class="d-flex flex-row position-relative">
+      <div @click="showDetails(job.id)" class="jobid ml-2 mt-1 mr-2">
+        {{ job.id }}
+      </div>
+      <div @click="showDetails(job.id)" class="enqueued mt-1  mr-2">
+        {{ formatDate(job.enqueued) }}
+      </div>
+      <div @click="showDetails(job.id)" class="kind mt-1 mr-2">
+        {{ job.kind }}
+      </div>
+      <div @click="showDetails(job.id)" class="user mt-1 mr-2">
+        {{ job.user }}
+      </div>
+      <div @click="showDetails(job.id)" class="signer mt-1 mr-2">
+        {{ job.signer }}
+      </div>
+      <div @click="showDetails(job.id)" class="state mt-1 mr-2">
+        {{ job.state }}
+      </div>
+      <div @click="showDetails(job.id)" class="mt-1 text-info detailsbutton">
+        <font-awesome-icon
+          v-if="show"
+          icon="angle-up"
+          fixed-width
+        ></font-awesome-icon>
+        <font-awesome-icon
+          v-if="loading"
+          icon="spinner"
+          fixed-width
+        ></font-awesome-icon>
+        <font-awesome-icon
+          v-if="!show && !loading"
+          icon="angle-down"
+          fixed-width
+        ></font-awesome-icon>
+      </div>
+    </div>
+    <div class="detailstable d-flex flex-row">
+      <div :class="collapse">
+        <table class="table table-responsive">
+          <thead>
+            <tr>
+              <th class="type pb-0">
+                <small class="condensed"><translate>Kind</translate></small>
+              </th>
+              <th class="datetime  pb-0">
+                <a href="#" @click="sortAsc = !sortAsc" class="sort-link"
+                  ><small class="condensed"><translate>Date</translate></small>
+                  <small class="condensed"
+                    ><font-awesome-icon
+                      :icon="sortIcon"
+                      class="ml-1"
+                    ></font-awesome-icon></small
+                ></a>
+              </th>
+              <th class="message pb-0">
+                <small class="condensed"><translate>Message</translate></small>
+              </th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr
+              v-for="(entry, index) in sortedEntries"
+              :key="index"
+              class="detailsrow"
+            >
+              <td class="type">
+                <span class="condensed">{{ entry.kind }}</span>
+              </td>
+              <td class="datetime">
+                <span class="condensed">{{ formatDateTime(entry.time) }}</span>
+              </td>
+              <td class="message">
+                <span class="condensed">{{ entry.message }}</span>
+              </td>
+            </tr>
+          </tbody>
+        </table>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+
+import { HTTP } from "@/lib/http.js";
+import { displayError } from "@/lib/errors.js";
+import locale2 from "locale2";
+
+export default {
+  name: "importqueuedetail",
+  props: ["job", "reload"],
+  data() {
+    return {
+      loading: false,
+      show: false,
+      entries: [],
+      sortAsc: true
+    };
+  },
+  watch: {
+    reload() {
+      if (this.reload) {
+        this.entries = [];
+        this.show = false;
+      }
+    }
+  },
+  methods: {
+    formatDate(date) {
+      return date
+        ? new Date(date).toLocaleDateString(locale2, {
+            day: "2-digit",
+            month: "2-digit",
+            year: "numeric"
+          })
+        : "";
+    },
+    formatDateTime(date) {
+      if (!date) return "";
+      const d = new Date(date);
+      return (
+        d.toLocaleDateString(locale2, {
+          day: "2-digit",
+          month: "2-digit",
+          year: "numeric"
+        }) +
+        " - " +
+        d.toLocaleTimeString(locale2, {
+          hour12: false
+        })
+      );
+    },
+    showDetails(id) {
+      if (this.show) {
+        this.show = false;
+        return;
+      }
+      if (this.entries.length === 0) {
+        this.loading = true;
+        HTTP.get("/imports/" + id, {
+          headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+        })
+          .then(response => {
+            const { entries } = response.data;
+            this.entries = entries;
+            this.show = true;
+            this.loading = false;
+          })
+          .catch(error => {
+            const { status, data } = error.response;
+            displayError({
+              title: this.$gettext("Backend Error"),
+              message: `${status}: ${data.message || data}`
+            });
+          });
+      } else {
+        this.show = true;
+      }
+    }
+  },
+  computed: {
+    sortedEntries() {
+      let sorted = this.entries.slice();
+      sorted.sort((r1, r2) => {
+        let d1 = new Date(r1.time);
+        let d2 = new Date(r2.time);
+        if (d2 < d1) {
+          return !this.sortAsc ? -1 : 1;
+        }
+        if (d2 > d1) {
+          return !this.sortAsc ? 1 : -1;
+        }
+        return 0;
+      });
+      return sorted;
+    },
+    sortIcon() {
+      return this.sortAsc ? "sort-amount-down" : "sort-amount-up";
+    },
+    icon() {
+      return {
+        "angle-up": !this.show,
+        "angle-down": this.show
+      };
+    },
+    collapse() {
+      return {
+        details: true,
+        collapse: true,
+        show: this.show,
+        "w-100": true
+      };
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.condensed {
+  font-stretch: condensed;
+}
+
+.entry {
+  background-color: white;
+  cursor: pointer;
+  width: 100%;
+}
+
+.entry:hover {
+  background-color: #efefef;
+  transition: 1.6s;
+}
+
+.detailstable {
+  margin-left: $offset;
+  margin-right: $large-offset;
+}
+
+.detailsbutton {
+  position: absolute;
+  top: 0;
+  right: 0;
+  height: 100%;
+}
+.jobid {
+  width: 15%;
+}
+
+.enqueued {
+  width: 20%;
+}
+
+.user {
+  width: 15%;
+}
+
+.signer {
+  width: 20%;
+}
+
+.kind {
+  width: 10%;
+}
+
+.state {
+  width: 20%;
+}
+
+.details {
+  width: 50%;
+}
+
+.detailsrow {
+  line-height: 0.1em;
+}
+
+.type {
+  width: 65px;
+  white-space: nowrap;
+  padding-left: 0px;
+  border-top: 0px;
+  padding-bottom: $small-offset;
+}
+
+.datetime {
+  width: 200px;
+  white-space: nowrap;
+  padding-left: 0px;
+  border-top: 0px;
+  padding-bottom: $small-offset;
+}
+
+.message {
+  min-width: 700px;
+  white-space: nowrap;
+  padding-left: 0px;
+  border-top: 0px;
+  padding-bottom: $small-offset;
+}
+
+thead,
+tbody {
+  display: block;
+}
+
+tbody {
+  height: 150px;
+  overflow-y: auto;
+  overflow-x: auto;
+}
+</style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importschedule/Importschedule.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,152 @@
+<template>
+  <div class="d-flex flex-row">
+    <Spacer></Spacer>
+    <div class="mt-3 w-100">
+      <div class="card flex-grow-1 schedulecard shadow-xs">
+        <h6
+          class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
+        >
+          <font-awesome-icon icon="clock" class="mr-2"></font-awesome-icon>
+          <translate class="headline">Imports</translate>
+        </h6>
+        <div class="card-body schedulecardbody">
+          <div class="card-body schedulecardbody">
+            <div class="searchandfilter  w-50 d-flex flex-row">
+              <div class="searchgroup input-group">
+                <div class="input-group-prepend">
+                  <span class="input-group-text" id="search">
+                    <font-awesome-icon icon="search"></font-awesome-icon>
+                  </span>
+                </div>
+                <input
+                  v-model="searchQuery"
+                  type="text"
+                  class="form-control"
+                  placeholder
+                  aria-label="Search"
+                  aria-describedby="search"
+                />
+              </div>
+            </div>
+            <table v-if="schedules.length" class="table">
+              <thead>
+                <tr>
+                  <th><translate>Import</translate></th>
+                  <th><translate>Type</translate></th>
+                  <th><translate>Author</translate></th>
+                  <th><translate>Schedule</translate></th>
+                  <th><translate>Email</translate></th>
+                  <th>&nbsp;</th>
+                  <th>&nbsp;</th>
+                </tr>
+              </thead>
+              <tbody>
+                <tr v-for="(schedule, index) in schedules" :key="index">
+                  <td></td>
+                  <td></td>
+                  <td></td>
+                  <td></td>
+                  <td></td>
+                  <td>
+                    <font-awesome-icon
+                      icon="pencil-alt"
+                      fixed-width
+                    ></font-awesome-icon>
+                  </td>
+                  <td>
+                    <font-awesome-icon
+                      @click="deleteSchedule"
+                      icon="trash"
+                      fixed-width
+                    ></font-awesome-icon>
+                  </td>
+                </tr>
+              </tbody>
+            </table>
+            <div v-else class="mt-4 small text-center py-3">
+              <translate>No schedules</translate>
+            </div>
+            <button
+              @click="newImport"
+              class="btn btn-info position-absolute newbutton"
+            >
+              <translate>New Import</translate>
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+    <Importscheduledetail></Importscheduledetail>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+
+import { mapState } from "vuex";
+
+export default {
+  name: "importschedule",
+  components: {
+    Importscheduledetail: () => import("./Importscheduledetail"),
+    Spacer: () => import("@/components/Spacer")
+  },
+  data() {
+    return {
+      searchQuery: ""
+    };
+  },
+  methods: {
+    newImport() {
+      this.$store.commit("imports/setImportScheduleDetailVisible");
+    },
+    deleteSchedule(index) {
+      this.$store.commit("imports/deleteSchedule", index);
+    }
+  },
+  computed: {
+    ...mapState("application", ["showSidebar"]),
+    ...mapState("imports", ["schedules"]),
+    spacerStyle() {
+      return [
+        "spacer ml-3",
+        {
+          "spacer-expanded": this.showSidebar,
+          "spacer-collapsed": !this.showSidebar
+        }
+      ];
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.schedulecard {
+  margin-right: $offset;
+  min-height: 20rem;
+}
+
+.schedulecard-body {
+  width: 100%;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+.newbutton {
+  position: absolute;
+  bottom: $offset;
+  right: $offset;
+}
+</style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/importschedule/Importscheduledetail.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,511 @@
+<template>
+  <div
+    class="importscheduledetails  fadeIn animated"
+    v-if="importScheduleDetailVisible"
+  >
+    <div class="card shadow-xs importscheduledetailscard pb-5">
+      <h6
+        class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
+      >
+        <translate>New import</translate>
+        <span @click="closeDetailview" class="closebutton">
+          <font-awesome-icon icon="times"></font-awesome-icon>
+        </span>
+      </h6>
+      <div class="card-body">
+        <form @submit.prevent="save" class="ml-2 mr-2">
+          <div class="d-flex flex-row w-100">
+            <div class="flex-column w-50 mr-3">
+              <div class="flex-row text-left">
+                <small class="text-muted">
+                  <translate>Imports</translate>
+                </small>
+              </div>
+              <select v-model="import_" class="custom-select" id="importtype">
+                <option :value="$options.IMPORTTYPES.BOTTLENECK"
+                  ><translate>Bottlenecks</translate></option
+                >
+              </select>
+            </div>
+            <div v-if="import_" class="flex-column w-50">
+              <div class="flex-row text-left">
+                <small class="text-muted">
+                  <translate>Importtype</translate>
+                </small>
+              </div>
+              <select
+                :disabled="fixedSource"
+                v-model="importSource"
+                class="custom-select"
+                id="importsource"
+              >
+                <option :value="this.$options.IMPORTSOURCES.SOAP"
+                  ><translate>SOAP</translate></option
+                >
+              </select>
+            </div>
+          </div>
+          <div v-if="isURLRequired">
+            <div class="d-flex flex-row">
+              <div class="flex-column mt-3 mr-3 w-100">
+                <div class="flex-row text-left">
+                  <small class="text-muted"> <translate>URL</translate> </small>
+                </div>
+                <div class="w-100">
+                  <input v-model="url" class="form-control" type="url" />
+                </div>
+              </div>
+              <div class="flex-column mt-3 text-left">
+                <div class="d-flex flex-row">
+                  <small class="text-muted mr-2"
+                    ><translate>Insecure</translate>
+                  </small>
+                </div>
+                <div class="d-flex flex-row">
+                  <toggle-button
+                    v-model="insecure"
+                    class="mt-2"
+                    :speed="100"
+                    :color="{
+                      checked: '#FF0000',
+                      unchecked: '#E9ECEF',
+                      disabled: '#CCCCCC'
+                    }"
+                    :labels="{
+                      checked: this.$options.on,
+                      unchecked: this.$options.off
+                    }"
+                    :width="60"
+                    :height="30"
+                  />
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="flex-column mt-3 w-100 mr-2">
+            <div class="flex-row text-left">
+              <small class="text-muted">
+                <translate>Simple Schedule</translate>
+              </small>
+            </div>
+            <div class="flex-flex-row text-left">
+              <toggle-button
+                v-model="easyCron"
+                class="mt-2"
+                :speed="100"
+                :labels="{
+                  checked: this.$options.on,
+                  unchecked: this.$options.off
+                }"
+                :width="60"
+                :height="30"
+              />
+            </div>
+          </div>
+          <div class="flex-column w-100 mr-2">
+            <div class="flex-row text-left">
+              <small class="text-muted">
+                <translate>Schedule</translate>
+              </small>
+            </div>
+            <div v-if="easyCron" class="text-left w-50">
+              <select v-model="simple" class="form-control"
+                ><option value="weekly"><translate>Weekly</translate></option>
+                <option value="monthly"><translate>Monthly</translate></option>
+              </select>
+            </div>
+            <div v-if="!easyCron" class="text-left w-100">
+              <div class="d-flex flex-row">
+                <h4 class="mt-auto mb-auto mr-2">{{ $options.EVERY }}</h4>
+                <select
+                  style="width: 130px;"
+                  v-model="cronMode"
+                  class="form-control"
+                  @change="clearInputs"
+                >
+                  <option
+                    v-for="(option, key) in $options.CRONMODE"
+                    :value="key"
+                    :key="key"
+                    >{{ option }}</option
+                  >
+                </select>
+                <div v-if="cronMode == 'hour'" class="ml-1 d-flex flex-row">
+                  <h4 class="mt-auto mb-auto">{{ $options.ON }}</h4>
+                  <input
+                    v-model="minutes"
+                    class="cronfield ml-1 mr-1 form-control"
+                    type="number"
+                  />
+                  <h4 class="mt-auto mb-auto">{{ $options.MINUTESPAST }}</h4>
+                </div>
+                <div v-if="cronMode == 'day'" class="ml-1 d-flex flex-row">
+                  <h4 class="mt-auto mb-auto">{{ $options.AT }}</h4>
+                  <input
+                    v-model="hour"
+                    class="cronfield ml-1 mr-1 form-control"
+                    type="number"
+                  />
+                  <input
+                    v-model="minutes"
+                    class="cronfield ml-1 mr-1 form-control"
+                    type="number"
+                  />
+                  <h4 class="mt-auto mb-auto">{{ $options.OCLOCK }}</h4>
+                </div>
+                <div v-if="cronMode == 'week'" class="ml-1 d-flex flex-row">
+                  <h4 class="ml-1 mr-1 mt-auto mb-auto">{{ $options.ON }}</h4>
+                  <select v-model="day" class="form-control">
+                    <option
+                      v-for="(option, key) in $options.DAYSOFWEEK"
+                      :key="key"
+                      :value="key"
+                      >{{ option }}</option
+                    >
+                  </select>
+                  <h4 class="ml-1 mt-auto mb-auto">{{ $options.AT }}</h4>
+                  <input
+                    v-model="hour"
+                    class="cronfield ml-1 mr-1 form-control"
+                    type="number"
+                  />
+                  <input
+                    v-model="minutes"
+                    class="cronfield ml-1 mr-1 form-control"
+                    type="number"
+                  />
+                </div>
+                <div v-if="cronMode == 'month'" class="ml-1 d-flex flex-row">
+                  <h4 class="ml-1 mt-auto mb-auto">{{ $options.ON }}</h4>
+                  <input
+                    v-model="dayOfMonth"
+                    class="cronfield ml-1 mr-1 form-control"
+                    type="number"
+                  />
+                  <h4 class="mt-auto mb-auto">{{ $options.AT }}</h4>
+                  <input
+                    v-model="hour"
+                    class="cronfield ml-1 mr-2 form-control"
+                    type="number"
+                  />
+                  <input
+                    v-model="minutes"
+                    class="cronfield ml-1 mr-2 form-control"
+                    type="number"
+                  />
+                  <h4 class="mt-auto mb-auto">{{ $options.OCLOCK }}</h4>
+                </div>
+                <div v-if="cronMode == 'year'" class="ml-1 d-flex flex-row">
+                  <h4 class="ml-1 mt-auto mb-auto">{{ $options.ON }}</h4>
+                  <input
+                    v-model="dayOfMonth"
+                    class="cronfield ml-1 mr-1 form-control"
+                    type="number"
+                  />
+                  <h4 class="mt-auto mb-auto">{{ $options.OF }}</h4>
+                  <select v-model="month" class="ml-1 mr-1 form-control">
+                    <option
+                      v-for="(option, key) in $options.MONTHS"
+                      :value="key"
+                      :key="key"
+                      >{{ option }}</option
+                    >
+                  </select>
+                  <h4 class="mt-auto mb-auto">{{ $options.ON }}</h4>
+                  <input
+                    v-model="hour"
+                    class="cronfield ml-1 mr-1 form-control"
+                    type="number"
+                  />
+                  <input
+                    v-model="minutes"
+                    class="cronfield ml-1 mr-1 form-control"
+                    type="number"
+                  />
+                </div>
+              </div>
+              <div class="mt-3 w-50 d-flex flex-row">
+                <h5 class="mt-auto mb-auto mr-2">
+                  <translate>Cronstring</translate>
+                </h5>
+                <input class="form-control" :value="cronString" type="text" />
+              </div>
+            </div>
+          </div>
+          <div class="flex-column mt-3 w-100 mr-2">
+            <div class="flex-row text-left">
+              <small class="text-muted">
+                <translate>Email Notification</translate>
+              </small>
+            </div>
+            <div class="flex-flex-row text-left">
+              <toggle-button
+                v-model="eMailNotification"
+                class="mt-2"
+                :speed="100"
+                :labels="{
+                  checked: this.$options.on,
+                  unchecked: this.$options.off
+                }"
+                :width="60"
+                :height="30"
+              />
+            </div>
+          </div>
+          <div class="flex-column w-100 mr-2">
+            <div class="flex-row text-left">
+              <small class="text-muted"><translate>Email</translate> </small>
+            </div>
+            <input
+              :disabled="!eMailNotification"
+              class="form-control"
+              type="email"
+            />
+          </div>
+          <button type="submit" class="shadow-sm btn btn-info submit-button">
+            <translate>Submit</translate>
+          </button>
+          <button
+            @click="triggerManualImport"
+            type="button"
+            class="shadow-sm btn btn-outline-info trigger"
+            :disabled="!triggerActive"
+          >
+            <font-awesome-icon
+              class="fa-fw mr-2"
+              fixed-width
+              icon="play"
+            ></font-awesome-icon
+            ><translate>Trigger import</translate>
+          </button>
+        </form>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { mapState } from "vuex";
+import { displayInfo, displayError } from "@/lib/errors.js";
+import app from "@/main.js";
+import { HTTP } from "@/lib/http.js";
+
+export default {
+  name: "importscheduledetail",
+  data() {
+    return {
+      importType: null,
+      schedule: null,
+      import_: null,
+      importSource: null,
+      eMailNotification: false,
+      easyCron: true,
+      cronMode: "",
+      minutes: null,
+      month: null,
+      hour: null,
+      day: null,
+      dayOfMonth: null,
+      simple: null,
+      url: null,
+      insecure: false,
+      triggerActive: true
+    };
+  },
+  IMPORTTYPES: {
+    BOTTLENECK: "BOTTLENECK"
+  },
+  IMPORTSOURCES: {
+    SOAP: "SOAP"
+  },
+  EVERY: app.$gettext("Every"),
+  MINUTESPAST: app.$gettext("minutes past"),
+  ON: app.$gettext("on"),
+  OF: app.$gettext("of"),
+  AT: app.$gettext("at"),
+  OCLOCK: app.$gettext("o' clock"),
+  CRONMODE: {
+    "15minutes": app.$gettext("15 minutes"),
+    hour: app.$gettext("hour"),
+    day: app.$gettext("day"),
+    week: app.$gettext("week"),
+    month: app.$gettext("month"),
+    year: app.$gettext("year")
+  },
+  DAYSOFWEEK: {
+    1: app.$gettext("Monday"),
+    2: app.$gettext("Tuesday"),
+    3: app.$gettext("Wednesday"),
+    4: app.$gettext("Thursday"),
+    5: app.$gettext("Friday"),
+    6: app.$gettext("Saturday"),
+    0: app.$gettext("Sunday")
+  },
+  MONTHS: {
+    1: app.$gettext("January"),
+    2: app.$gettext("February"),
+    3: app.$gettext("March"),
+    4: app.$gettext("April"),
+    5: app.$gettext("May"),
+    6: app.$gettext("June"),
+    7: app.$gettext("July"),
+    8: app.$gettext("August"),
+    9: app.$gettext("September"),
+    10: app.$gettext("October"),
+    11: app.$gettext("November"),
+    12: app.$gettext("December")
+  },
+  watch: {
+    cronString() {
+      if (this.isWeekly(this.cronString)) {
+        this.simple = "weekly";
+      }
+      if (this.isMonthly(this.cronString)) {
+        this.simple = "monthly";
+      }
+    },
+    import_() {
+      if (this.import_ === this.$options.IMPORTTYPES.BOTTLENECK) {
+        this.importSource = this.$options.IMPORTSOURCES.SOAP;
+      }
+    }
+  },
+  computed: {
+    ...mapState("imports", ["importScheduleDetailVisible"]),
+    isURLRequired() {
+      if (this.import_ === this.$options.IMPORTTYPES.BOTTLENECK) return true;
+      return false;
+    },
+    cronString: {
+      get() {
+        let getValue = value => {
+          return this[value] ? this[value] : "*";
+        };
+        if (this.cronMode === "15minutes") return "*/15 * * * *";
+        const min = getValue("minutes");
+        const h = getValue("hour");
+        const dm = getValue("dayOfMonth");
+        const m = getValue("month");
+        const wd = getValue("day");
+        return `${min} ${h} ${dm} ${m} ${wd}`;
+      }
+    },
+    fixedSource() {
+      if (this.import_ === this.$options.IMPORTTYPES.BOTTLENECK) return true;
+      return false;
+    }
+  },
+  methods: {
+    isWeekly(cron) {
+      return /\d{1,2} \d{1,2} \* \* \d{1}/.test(cron);
+    },
+    isMonthly(cron) {
+      return /\d{1,2} \d{1,2} \d{1,2} \* \*/.test(cron);
+    },
+    initialize() {
+      this.importType = null;
+      this.schedule = null;
+      this.import_ = null;
+      this.importSource = null;
+      this.eMailNotification = false;
+      this.easyCron = true;
+      this.cronMode = "";
+      this.minutes = null;
+      this.month = null;
+      this.hour = null;
+      this.day = null;
+      this.dayOfMonth = null;
+      this.simple = null;
+      this.url = null;
+      this.insecure = false;
+    },
+    clearInputs() {
+      this.minutes = null;
+      this.month = null;
+      this.hour = null;
+      this.day = null;
+      this.dayOfMonth = null;
+    },
+    triggerManualImport() {
+      if (!this.triggerActive) return;
+      let data = {};
+      if (this.import_ === this.$options.IMPORTTYPES.BOTTLENECK) {
+        if (!this.url) return;
+        data["url"] = this.url;
+        data["insecure"] = this.insecure;
+      }
+      const importTypes = {
+        BOTTLENECK: "bottleneck"
+      };
+      this.triggerActive = false;
+      HTTP.post("imports/" + importTypes[this.import_], data, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token")
+        }
+      })
+        .then(response => {
+          const { id } = response.data;
+          displayInfo({
+            title: this.$gettext("Import"),
+            message: this.$gettext("Manually triggered import: #") + id
+          });
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        })
+        .finally(() => {
+          this.triggerActive = true;
+        });
+    },
+    save() {
+      displayInfo({
+        title: this.$gettext("Import"),
+        message: this.$gettext("under construction")
+      });
+    },
+    closeDetailview() {
+      this.initialize();
+      this.$store.commit("imports/setImportScheduleDetailInvisible");
+    }
+  },
+  imports: [],
+  on: "on",
+  off: "off",
+  periods: {
+    DAILY: "daily",
+    MONTHLY: "monthly"
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.cronfield {
+  width: 55px;
+}
+
+.importscheduledetailscard {
+  min-height: 550px;
+}
+
+.importscheduledetails {
+  width: 100%;
+  margin-top: $offset;
+  margin-right: $offset;
+}
+
+.trigger {
+  position: absolute;
+  left: $large-offset;
+  bottom: $offset;
+}
+
+.submit-button {
+  position: absolute;
+  right: $large-offset;
+  bottom: $offset;
+}
+</style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/layers/Layers.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,63 @@
+<template>
+  <div
+    :class="[
+      'box ui-element rounded bg-white text-nowrap',
+      { expanded: showLayers }
+    ]"
+  >
+    <div style="width: 20rem">
+      <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
+        <font-awesome-icon icon="layer-group" class="mr-2"></font-awesome-icon
+        ><translate>Layers</translate>
+        <font-awesome-icon
+          icon="times"
+          class="ml-auto text-muted"
+          @click="$store.commit('application/showLayers', false)"
+        ></font-awesome-icon>
+      </h6>
+      <div class="d-flex flex-column p-3 small">
+        <Layerselect
+          v-for="(layer, index) in layersForLegend"
+          :layerindex="index"
+          :layername="layer.name"
+          :key="layer.name"
+          :isVisible="layer.isVisible"
+          @visibilityToggled="visibilityToggled"
+        ></Layerselect>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapGetters, mapState } from "vuex";
+export default {
+  name: "layers",
+  components: {
+    Layerselect: () => import("./Layerselect")
+  },
+  computed: {
+    ...mapGetters("map", ["layersForLegend"]),
+    ...mapState("application", ["showLayers"])
+  },
+  methods: {
+    visibilityToggled(layer) {
+      this.$store.commit("map/toggleVisibility", layer);
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/layers/Layerselect.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,84 @@
+<template>
+  <div>
+    <div class="form-check d-flex flex-row flex-start selection">
+      <input
+        class="form-check-input"
+        @change="visibilityToggled"
+        :id="layername"
+        type="checkbox"
+        :checked="isVisible"
+      />
+      <LegendElement
+        :layername="layername"
+        :layerindex="layerindex"
+      ></LegendElement>
+      <label class="layername form-check-label" @click="visibilityToggled">{{
+        layername
+      }}</label>
+    </div>
+    <div v-if="isVisible && layername == 'Bottleneck isolines'">
+      <img class="rounded my-1 d-block" :src="isolinesLegendImgUrl" />
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.selection {
+  text-align: left;
+}
+.layername {
+  margin-left: $small-offset;
+}
+</style>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+import { HTTP } from "@/lib/http";
+export default {
+  props: ["layername", "layerindex", "isVisible"],
+  name: "layerselect",
+  data() {
+    return {
+      isolinesLegendImgUrl: ""
+    };
+  },
+  components: {
+    LegendElement: () => import("./LegendElement.vue")
+  },
+  methods: {
+    visibilityToggled() {
+      this.$emit("visibilityToggled", this.layerindex);
+    }
+  },
+  created() {
+    // fetch legend image for bottleneck isolines
+    // TODO: move to store
+    if (this.layername == "Bottleneck isolines") {
+      const src =
+        "/internal/wms?REQUEST=GetLegendGraphic&VERSION=1.0.0&FORMAT=image/png&WIDTH=20&HEIGHT=20&LAYER=sounding_results_contour_lines_geoserver&legend_options=columns:4;fontAntiAliasing:true";
+      HTTP.get(src, {
+        headers: {
+          Accept: "image/png",
+          "X-Gemma-Auth": localStorage.getItem("token")
+        },
+        responseType: "blob"
+      }).then(response => {
+        var urlCreator = window.URL || window.webkitURL;
+        this.isolinesLegendImgUrl = urlCreator.createObjectURL(response.data);
+      });
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/layers/LegendElement.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,124 @@
+<template>
+  <div :id="id" class="legendelement"></div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+import { mapGetters } from "vuex";
+
+import { Map, View } from "ol";
+import Feature from "ol/Feature";
+import { Vector as VectorLayer } from "ol/layer.js";
+import { Vector as VectorSource } from "ol/source.js";
+import LineString from "ol/geom/LineString.js";
+import Point from "ol/geom/Point";
+
+export default {
+  name: "legendelement",
+  props: ["layername", "layerindex"],
+  data: function() {
+    return {
+      myMap: null,
+      mapLayer: null
+    };
+  },
+  computed: {
+    ...mapGetters("map", ["getLayerByName"]),
+    id() {
+      return "legendelement" + this.layerindex;
+    },
+    mstyle() {
+      if (this.mapLayer && this.mapLayer.data.getStyle) {
+        return this.mapLayer.data.getStyle();
+      }
+    }
+  },
+  watch: {
+    mstyle(newStyle, oldStyle) {
+      // only recreate if there already was a style before
+      if (oldStyle) {
+        let vector = this.createVectorLayer();
+
+        this.myMap.removeLayer(this.myMap.getLayers()[0]);
+        this.myMap.addLayer(vector);
+      }
+    }
+  },
+  mounted() {
+    this.mapLayer = this.getLayerByName(this.layername);
+    if (this.mapLayer.data.getType() == "VECTOR") {
+      this.initMap();
+    } else {
+      // TODO other tiles
+    }
+  },
+  methods: {
+    initMap() {
+      let vector = this.createVectorLayer();
+
+      this.myMap = new Map({
+        layers: [vector],
+        target: this.id,
+        controls: [],
+        interactions: [],
+        view: new View({
+          center: [0, 0],
+          zoom: 3,
+          projection: "EPSG:4326"
+        })
+      });
+    },
+    createVectorLayer() {
+      let mapStyle = this.mapLayer.data.getStyle();
+
+      let feature = new Feature({
+        geometry: new LineString([[-1, 0.5], [0, 0], [0.7, 0], [1.3, -0.7]])
+      });
+
+      // 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) {
+          feature.setGeometry(new Point([0, 0]));
+        }
+        mapStyle = this.mapLayer.data.getStyleFunction()(
+          feature,
+          this.mapLayer.forLegendStyle.resolution
+        );
+      }
+
+      // 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.
+      // FIXME, this is a special case for the Fairway Dimensions style
+      feature.set("level_of_service", "");
+      return new VectorLayer({
+        source: new VectorSource({
+          features: [feature],
+          wrapX: false
+        }),
+        style: mapStyle
+      });
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.legendelement {
+  max-height: 1.5rem;
+  width: 2rem;
+}
+</style>
--- a/client/src/components/map/Identify.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,125 +0,0 @@
-<template>
-  <div
-    :class="[
-      'box ui-element rounded bg-white text-nowrap',
-      { expanded: showIdentify }
-    ]"
-  >
-    <div style="width: 20rem">
-      <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
-        <font-awesome-icon icon="info" class="mr-2"></font-awesome-icon>
-        <translate>Identified</translate>
-        <font-awesome-icon
-          icon="times"
-          class="ml-auto text-muted"
-          @click="$store.commit('application/showIdentify', false)"
-        ></font-awesome-icon>
-      </h6>
-      <div class="d-flex flex-column features p-3 flex-grow-1 text-left">
-        <div v-if="currentMeasurement">
-          <b>
-            {{ currentMeasurement.quantity }} ({{
-              currentMeasurement.unitSymbol
-            }}):
-          </b>
-          <br />
-          <small>{{ currentMeasurement.value }}</small>
-        </div>
-        <div v-for="(feature, i) of identifiedFeatures" :key="feature.getId()">
-          <div v-if="feature.getId()" :class="{ 'mt-2': i }">
-            <b
-              >{{
-                feature
-                  .getId()
-                  .replace(
-                    /[.][^.]*$/,
-                    ""
-                  ) /* cut away everything from the last . to the end */
-              }}:</b>
-            <small
-              v-for="(value, key) in prepareProperties(feature)"
-              :key="key"
-            >
-              <div v-if="value">{{ key }}:{{ value }}</div>
-            </small>
-          </div>
-        </div>
-        <div
-          v-if="!currentMeasurement && !identifiedFeatures.length"
-          class="text-muted small text-center my-auto"
-        >
-          <translate>No features identified.</translate>
-        </div>
-      </div>
-      <div class="versioninfo border-top p-3 text-left">
-        <span v-translate="{ license: 'AGPL-3.0-or-later' }">
-          This app uses <i>gemma</i>, which is Free Software under <br />
-          %{ license } without warranty, see docs for details.
-        </span>
-        <br />
-        <a href="https://hg.intevation.de/gemma/file/tip">
-          <translate>source-code</translate>
-        </a>
-        {{ versionStr }} <br />© via donau. &#x24D4; Intevation. <br />
-        <span v-translate="{ name: 'OpenSteetMap' }"
-          >Some data ©
-          <a href="https://www.openstreetmap.org/copyright">%{ name }</a>
-          contributors.
-        </span>
-        <p v-translate="{geoLicense: 'CC-BY-4.0'}">
-           Uses
-          <a href="https://download.geonames.org/export/dump/readme.txt"
-             >GeoNames</a> under %{ geoLicense }.
-        </p>
-      </div>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-.features {
-  max-height: 19rem;
-  overflow-y: auto;
-}
-
-.versioninfo {
-  font-size: 60%;
-  white-space: normal;
-}
-</style>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- * Bernhard E. Reiter <bernhard.reiter@intevation.de>
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import { mapState, mapGetters } from "vuex";
-
-export default {
-  name: "identify",
-  computed: {
-    ...mapGetters("application", ["versionStr"]),
-    ...mapState("application", ["showIdentify"]),
-    ...mapState("map", ["identifiedFeatures", "currentMeasurement"])
-  },
-  methods: {
-    prepareProperties(feature) {
-      // return dict object with propertyname:plainvalue prepared for display
-      var properties = feature.getProperties();
-      delete properties[feature.getGeometryName()];
-      return properties;
-    }
-  }
-};
-</script>
--- a/client/src/components/map/Main.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,33 +0,0 @@
-<template>
-  <div class="main d-flex flex-column">
-    <Maplayer></Maplayer>
-    <FairwayProfile></FairwayProfile>
-  </div>
-</template>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- */
-
-import Maplayer from "./Maplayer";
-import FairwayProfile from "./fairway/Fairwayprofile";
-
-export default {
-  name: "mainview",
-  components: {
-    Maplayer,
-    FairwayProfile
-  }
-};
-</script>
--- a/client/src/components/map/Maplayer.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,375 +0,0 @@
-<template>
-  <div id="map" :class="mapStyle"></div>
-</template>
-
-<style lang="scss" scoped>
-.nocursor {
-  cursor: none;
-}
-
-.mapsplit {
-  height: 50vh;
-}
-
-.mapfull {
-  height: 100vh;
-}
-
-@media print {
-  .mapfull {
-    width: 2000px;
-    height: 2828px;
-  }
-  .mapsplit {
-    width: 2000px;
-    height: 2828px;
-  }
-}
-</style>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * * Thomas Junk <thomas.junk@intevation.de>
- * * Bernhard E. Reiter <bernhard.reiter@intevation.de>
- */
-import { HTTP } from "../../lib/http";
-import { mapGetters, mapState } from "vuex";
-import "ol/ol.css";
-import { Map, View } from "ol";
-import { WFS, GeoJSON } from "ol/format.js";
-import { Stroke, Style, Fill } from "ol/style.js";
-
-/* for the sake of debugging */
-/* eslint-disable no-console */
-export default {
-  name: "maplayer",
-  data() {
-    return {
-      projection: "EPSG:3857"
-    };
-  },
-  computed: {
-    ...mapGetters("map", ["getLayerByName", "getVSourceByName"]),
-    ...mapState("map", [
-      "extent",
-      "layers",
-      "openLayersMap",
-      "lineTool",
-      "polygonTool",
-      "cutTool"
-    ]),
-    ...mapState("bottlenecks", ["selectedSurvey"]),
-    ...mapState("application", ["showSplitscreen"]),
-    mapStyle() {
-      return {
-        mapfull: !this.showSplitscreen,
-        mapsplit: this.showSplitscreen,
-        nocursor: this.hasActiveInteractions
-      };
-    },
-    hasActiveInteractions() {
-      return (
-        (this.lineTool && this.lineTool.getActive()) ||
-        (this.polygonTool && this.polygonTool.getActive()) ||
-        (this.cutTool && this.cutTool.getActive())
-      );
-    }
-  },
-  methods: {
-    buildVectorLoader(featureRequestOptions, endpoint, vectorSource) {
-      // build a function to be used for VectorSource.setLoader()
-      // make use of WFS().writeGetFeature to build the request
-      // and use our HTTP library to actually do it
-      // NOTE: a) the geometryName has to be given in featureRequestOptions,
-      //          because we want to load depending on the bbox
-      //  b) the VectorSource has to have the option strategy: bbox
-      featureRequestOptions["outputFormat"] = "application/json";
-      var loader = function(extent, resolution, projection) {
-        featureRequestOptions["bbox"] = extent;
-        featureRequestOptions["srsName"] = projection.getCode();
-        var featureRequest = new WFS().writeGetFeature(featureRequestOptions);
-        // DEBUG console.log(featureRequest);
-        HTTP.post(
-          endpoint,
-          new XMLSerializer().serializeToString(featureRequest),
-          {
-            headers: {
-              "X-Gemma-Auth": localStorage.getItem("token"),
-              "Content-type": "text/xml; charset=UTF-8"
-            }
-          }
-        )
-          .then(response => {
-            var features = new GeoJSON().readFeatures(
-              JSON.stringify(response.data)
-            );
-            vectorSource.addFeatures(features);
-            // console.log(
-            //   "loaded",
-            //   features.length,
-            //   featureRequestOptions.featureTypes,
-            //   "features"
-            // );
-            // DEBUG console.log("loaded ", features, "for", vectorSource);
-            // eslint-disable-next-line
-          })
-          .catch(() => {
-            vectorSource.removeLoadedExtent(extent);
-          });
-      };
-      return loader;
-    },
-    updateBottleneckFilter(bottleneck_id, datestr) {
-      console.log("updating filter with", bottleneck_id, datestr);
-      const layer = this.getLayerByName("Bottleneck isolines");
-      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 +
-            "'"
-        });
-      }
-      layer.isVisible = exists;
-      layer.data.setVisible(exists);
-    },
-    onBeforePrint(/* evt */) {
-      // console.log("onBeforePrint(", evt ,")");
-      //
-      // the following code shows how to get the current map canvas
-      // and change it, however this does not work well enough, as
-      // another mechanism seems to update the size again before the rendering
-      // for printing is done:
-      // console.log(this.openLayersMap.getViewport());
-      // var canvas = this.openLayersMap.getViewport().getElementsByTagName("canvas")[0];
-      // console.log(canvas);
-      // canvas.width=1000;
-      // canvas.height=1414;
-      //
-      // An experiment which also did not work:
-      // this.openLayersMap.setSize([1000, 1414]); // estimate portait DIN A4
-      //
-      // according to documentation
-      // http://openlayers.org/en/latest/apidoc/module-ol_PluggableMap-PluggableMap.html#updateSize
-      // "Force a recalculation of the map viewport size. This should be called when third-party code changes the size of the map viewport."
-      // but did not help
-      // this.openLayersMap.updateSize();
-    },
-    onAfterPrint(/* evt */) {
-      // could be used to undo changes that have been done for printing
-      // though https://www.tjvantoll.com/2012/06/15/detecting-print-requests-with-javascript/
-      // reported that this was not feasable (back then).
-      // console.log("onAfterPrint(", evt, ")");
-    }
-  },
-  watch: {
-    showSplitscreen() {
-      const map = this.openLayersMap;
-      this.$nextTick(() => {
-        map && map.updateSize();
-      });
-    },
-    selectedSurvey(newSelectedSurvey) {
-      if (newSelectedSurvey) {
-        this.updateBottleneckFilter(
-          newSelectedSurvey.bottleneck_id,
-          newSelectedSurvey.date_info
-        );
-      } else {
-        this.updateBottleneckFilter("does_not_exist", "1999-10-01");
-      }
-    }
-  },
-  mounted() {
-    let map = new Map({
-      layers: [...this.layers.map(x => x.data)],
-      target: "map",
-      controls: [],
-      view: new View({
-        center: [this.extent.lon, this.extent.lat],
-        zoom: this.extent.zoom,
-        projection: this.projection
-      })
-    });
-    map.on("moveend", event => {
-      const center = event.map.getView().getCenter();
-      this.$store.commit("map/extent", {
-        lat: center[1],
-        lon: center[0],
-        zoom: event.map.getView().getZoom()
-      });
-    });
-    this.$store.dispatch("map/openLayersMap", map);
-
-    // TODO make display of layers more dynamic, e.g. from a list
-
-    // loading the full WFS layer, by not setting the loader function
-    // and without bboxStrategy
-    var featureRequest = new WFS().writeGetFeature({
-      srsName: "EPSG:3857",
-      featureNS: "gemma",
-      featurePrefix: "gemma",
-      featureTypes: ["fairway_dimensions"],
-      outputFormat: "application/json"
-    });
-
-    // NOTE: loading the full fairway_dimensions makes sure
-    //       that all are available for the intersection with the profile
-    HTTP.post(
-      "/internal/wfs",
-      new XMLSerializer().serializeToString(featureRequest),
-      {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-type": "text/xml; charset=UTF-8"
-        }
-      }
-    ).then(response => {
-      this.getVSourceByName("Fairway Dimensions").addFeatures(
-        new GeoJSON().readFeatures(JSON.stringify(response.data))
-      );
-      // would scale to the extend of all resulting features
-      // this.openLayersMap.getView().fit(vectorSrc.getExtent());
-    });
-
-    // load following layers with bboxStrategy (using our request builder)
-    var layer = null;
-
-    layer = this.getLayerByName("Waterway Area");
-    layer.data.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featurePrefix: "ws-wamos",
-          featureTypes: ["ienc_wtware"],
-          geometryName: "geom"
-        },
-        "/external/d4d",
-        layer.data.getSource()
-      )
-    );
-
-    layer = this.getLayerByName("Waterway Axis");
-    layer.data.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featurePrefix: "ws-wamos",
-          featureTypes: ["ienc_wtwaxs"],
-          geometryName: "geom"
-        },
-        "/external/d4d",
-        layer.data.getSource()
-      )
-    );
-
-    layer = this.getLayerByName("Distance marks");
-    layer.data.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featurePrefix: "ws-wamos",
-          featureTypes: ["ienc_dismar"],
-          geometryName: "geom" //,
-          /* restrict loading approximately to extend of danube in Austria */
-          // filter: bboxFilter("geom", [13.3, 48.0, 17.1, 48.6], "EPSG:4326")
-        },
-        "/external/d4d",
-        layer.data.getSource()
-      )
-    );
-    layer.data.setVisible(layer.isVisible);
-
-    layer = this.getLayerByName("Distance marks, Axis");
-    layer.data.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureNS: "gemma",
-          featurePrefix: "gemma",
-          featureTypes: ["distance_marks_geoserver"],
-          geometryName: "geom"
-        },
-        "/internal/wfs",
-        layer.data.getSource()
-      )
-    );
-
-    layer = this.getLayerByName("Waterway Area, named");
-    layer.data.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureNS: "gemma",
-          featurePrefix: "gemma",
-          featureTypes: ["hydro_seaare"],
-          geometryName: "geom"
-        },
-        "/external/d4d",
-        layer.data.getSource()
-      )
-    );
-    layer.data.setVisible(layer.isVisible);
-
-    layer = this.getLayerByName("Bottlenecks");
-    layer.data.getSource().setLoader(
-      this.buildVectorLoader(
-        {
-          featureNS: "gemma",
-          featurePrefix: "gemma",
-          featureTypes: ["bottlenecks"],
-          geometryName: "area"
-        },
-        "/internal/wfs",
-        layer.data.getSource()
-      )
-    );
-    HTTP.get("/system/style/Bottlenecks/stroke", {
-      headers: { "X-Gemma-Auth": localStorage.getItem("token") }
-    })
-      .then(response => {
-        this.btlnStrokeC = response.data.code;
-        HTTP.get("/system/style/Bottlenecks/fill", {
-          headers: { "X-Gemma-Auth": localStorage.getItem("token") }
-        })
-          .then(response => {
-            this.btlnFillC = response.data.code;
-            var newstyle = new Style({
-              stroke: new Stroke({
-                color: this.btlnStrokeC,
-                width: 4
-              }),
-              fill: new Fill({
-                color: this.btlnFillC
-              })
-            });
-            layer.data.setStyle(newstyle);
-          })
-          .catch(error => {
-            console.log(error);
-          });
-      })
-      .catch(error => {
-        console.log(error);
-      });
-
-    window.addEventListener("beforeprint", this.onBeforePrint);
-    window.addEventListener("afterprint", this.onAfterPrint);
-
-    // so none is shown
-    this.updateBottleneckFilter("does_not_exist", "1999-10-01");
-    this.$store.dispatch("map/enableIdentifyTool");
-    this.$store.dispatch("bottlenecks/loadBottlenecks");
-  }
-};
-</script>
--- a/client/src/components/map/Pdftool.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,110 +0,0 @@
-<template>
-  <div
-    :class="[
-      'box ui-element rounded bg-white text-nowrap',
-      { expanded: showPdfTool }
-    ]"
-  >
-    <div style="width: 20rem">
-      <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
-        <font-awesome-icon icon="file-pdf" class="mr-2"></font-awesome-icon
-        ><translate>Generate PDF</translate>
-        <font-awesome-icon
-          icon="times"
-          class="ml-auto text-muted"
-          @click="$store.commit('application/showPdfTool', false)"
-        ></font-awesome-icon>
-      </h6>
-      <div class="p-3">
-        <b><translate>Chose format:</translate></b>
-        <select v-model="form.format" class="form-control d-block w-100">
-          <option><translate>landscape</translate></option>
-          <option><translate>portrait</translate></option>
-        </select>
-        <small class="d-block my-2">
-          <input
-            type="radio"
-            id="pdfexport-downloadtype-download"
-            value="download"
-            v-model="form.downloadType"
-            selected
-          />
-          <label for="pdfexport-downloadtype-download" class="ml-1 mr-2"
-            ><translate>Download</translate></label
-          >
-          <input
-            type="radio"
-            id="pdfexport-downloadtype-open"
-            value="open"
-            v-model="form.downloadType"
-          />
-          <label for="pdfexport-downloadtype-open" class="ml-1"
-            ><translate>Open in new window</translate></label
-          >
-        </small>
-        <button
-          @click="download"
-          type="button"
-          class="btn btn-sm btn-info d-block w-100"
-        >
-          <translate>Generate PDF</translate>
-        </button>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import { mapState } from "vuex";
-//import { HTTP } from "../application/lib/http";
-
-export default {
-  name: "pdftool",
-  data() {
-    return {
-      form: {
-        format: "landscape",
-        downloadType: "download"
-      }
-    };
-  },
-  computed: {
-    ...mapState("application", ["showPdfTool"]),
-    ...mapState("bottlenecks", ["selectedSurvey"])
-  },
-  methods: {
-    download() {
-      // generate PDF and open it
-      // TODO: replace this src with an API reponse after actually generating PDFs
-      let src =
-        this.form.format === "landscape"
-          ? "/img/PrintTemplate-Var2-Landscape.pdf"
-          : "/img/PrintTemplate-Var2-Portrait.pdf";
-
-      let a = document.createElement("a");
-      a.href = src;
-
-      if (this.form.downloadType === "download")
-        a.download = src.substr(src.lastIndexOf("/") + 1);
-      else a.target = "_blank";
-
-      document.body.appendChild(a);
-      a.click();
-      document.body.removeChild(a);
-    }
-  }
-};
-</script>
--- a/client/src/components/map/Search.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,292 +0,0 @@
-<template>
-  <div :class="searchbarContainerStyle">
-    <div class="input-group-prepend m-0 d-print-none">
-      <span @click="toggleSearchbar" :class="searchButtonStyle" for="search">
-        <font-awesome-icon icon="search"></font-awesome-icon>
-      </span>
-    </div>
-    <div
-      :class="[
-        'searchgroup',
-        {
-          'searchgroup-collapsed': !showSearchbar,
-          big:
-            showContextBox &&
-            ['bottlenecks', 'staging'].indexOf(contextBoxContent) !== -1
-        }
-      ]"
-    >
-      <input
-        @keyup.enter="takeFirstSearchresult"
-        id="search"
-        v-model="searchQuery"
-        type="text"
-        :class="searchInputStyle"
-      />
-    </div>
-    <div
-      v-if="showSearchbar && searchResults !== null && !showContextBox"
-      class="searchresults border-top ui-element bg-white rounded-bottom d-print-none position-absolute"
-    >
-      <div
-        v-for="entry of searchResults"
-        :key="entry.name"
-        class="border-top text-left"
-      >
-        <a
-          href="#"
-          @click.prevent="moveToSearchResult(entry)"
-          class="p-2 d-block text-nowrap"
-        >
-          <font-awesome-icon
-            icon="ship"
-            v-if="entry.type === 'bottleneck'"
-            class="mr-1"
-            fixed-width
-          />
-          <font-awesome-icon
-            icon="water"
-            v-if="entry.type === 'rhm'"
-            class="mr-1"
-            fixed-width
-          />
-          <font-awesome-icon
-            icon="city"
-            v-if="entry.type === 'city'"
-            class="mr-1"
-            fixed-width
-          />
-          {{ entry.name }}
-        </a>
-      </div>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-.searchcontainer {
-  opacity: 0.96;
-}
-
-.searchcontainer .searchbar {
-  border-top-left-radius: 0 !important;
-  border-bottom-left-radius: 0 !important;
-}
-
-.searchgroup {
-  margin-left: -3px;
-  transition: width 0.3s;
-  width: 300px;
-  overflow: hidden;
-}
-
-.searchgroup.big {
-  width: 571px;
-}
-
-.searchgroup-collapsed {
-  width: 0;
-}
-
-.searchbar {
-  height: 2rem !important;
-  box-shadow: none !important;
-}
-
-.searchbar.rounded-top-right {
-  border-radius: 0 !important;
-  border-top-right-radius: 0.25rem !important;
-}
-
-.searchlabel.rounded-top-left {
-  border-radius: 0 !important;
-  border-top-left-radius: 0.25rem !important;
-}
-
-.input-group-text {
-  height: 2rem;
-  width: 2rem;
-}
-
-.input-group-prepend svg path {
-  fill: #666;
-}
-
-.searchresults {
-  box-shadow: 0 0.1rem 0.5rem rgba(0, 0, 0, 0.2);
-  top: 2rem;
-  left: 0;
-  right: 0;
-  max-height: 24rem;
-  overflow: auto;
-}
-
-.searchresults > div:first-child {
-  border-top: 0 !important;
-}
-
-.searchresults a {
-  text-decoration: none;
-}
-
-.searchresults a:hover {
-  background: #f8f8f8;
-}
-</style>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import debounce from "lodash.debounce";
-import { mapState } from "vuex";
-
-import { displayError } from "../../lib/errors.js";
-import { HTTP } from "../../lib/http";
-
-const setFocus = () => document.querySelector("#search").focus();
-
-export default {
-  name: "search",
-  data() {
-    return {
-      searchQueryIsDirty: false,
-      searchResults: null,
-      isSearching: false
-    };
-  },
-  computed: {
-    ...mapState("application", [
-      "showSearchbar",
-      "showContextBox",
-      "contextBoxContent"
-    ]),
-    searchQuery: {
-      get() {
-        return this.$store.state.application.searchQuery;
-      },
-      set(value) {
-        this.$store.commit("application/searchQuery", value);
-      }
-    },
-    searchIndicator: function() {
-      if (this.isSearching) {
-        return "⟳";
-      } else if (this.searchQueryIsDirty) {
-        return "";
-      } else {
-        return "✓";
-      }
-    },
-    searchbarContainerStyle() {
-      return [
-        "input-group searchcontainer shadow-xs",
-        {
-          "d-flex": this.contextBoxContent !== "imports",
-          "d-none": this.contextBoxContent === "imports" && this.showContextBox
-        }
-      ];
-    },
-    searchInputStyle() {
-      return [
-        "form-control ui-element search searchbar d-print-none border-0",
-        { "rounded-top-right": this.showContextBox || this.searchResults }
-      ];
-    },
-    searchButtonStyle() {
-      return [
-        "ui-element input-group-text p-0 d-flex border-0 justify-content-center searchlabel bg-white d-print-none",
-        {
-          rounded: !this.showSearchbar,
-          "rounded-left": this.showSearchbar,
-          "rounded-top-left":
-            this.showSearchbar && (this.showContextBox || this.searchResults)
-        }
-      ];
-    }
-  },
-  watch: {
-    searchQuery: function() {
-      this.searchQueryIsDirty = true;
-      this.triggerSearch();
-    }
-  },
-  methods: {
-    takeFirstSearchresult() {
-      if (!this.searchResults || this.searchResults.length != 1) return;
-      this.moveToSearchResult(this.searchResults[0]);
-    },
-    triggerSearch: debounce(function() {
-      this.doSearch();
-    }, 500),
-    doSearch() {
-      this.isCalculating = true;
-      this.searchResults = null;
-
-      if (this.searchQuery == "") {
-        return;
-      }
-
-      HTTP.post(
-        "/search",
-        { string: this.searchQuery },
-        {
-          headers: {
-            "X-Gemma-Auth": localStorage.getItem("token"),
-            "Content-type": "text/xml; charset=UTF-8"
-          }
-        }
-      )
-        .then(response => {
-          // console.log("got:", response.data);
-          this.searchResults = response.data;
-        })
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: this.$gettext("Backend Error"),
-            message: `${status}: ${data.message || data}`
-          });
-        });
-
-      this.isCalculating = false;
-      this.searchQueryIsDirty = false;
-    },
-    moveToSearchResult(resultEntry) {
-      // DEBUG console.log("Moving to", resultEntry);
-      if (resultEntry.geom.type == "Point") {
-        let zoom = 11;
-        if (resultEntry.type === "bottleneck") zoom = 17;
-        if (resultEntry.type === "rhm") zoom = 15;
-        if (resultEntry.type === "city") zoom = 13;
-
-        this.$store.commit("map/moveMap", {
-          coordinates: resultEntry.geom.coordinates,
-          zoom,
-          preventZoomOut: true
-        });
-      }
-      // this.searchQuery = ""; // clear search query again
-      this.toggleSearchbar();
-    },
-    toggleSearchbar() {
-      if (!this.showContextBox) {
-        if (!this.showSearchbar) {
-          setTimeout(setFocus, 300);
-        }
-        this.$store.commit("application/showSearchbar", !this.showSearchbar);
-      }
-    }
-  }
-};
-</script>
--- a/client/src/components/map/Zoom.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-<template>
-  <div
-    class="d-flex buttoncontainer shadow-xs mb-3 position-absolute"
-    :style="showSplitscreen ? 'margin-bottom: 51vh !important' : ''"
-  >
-    <button
-      class="zoomButton border-0 bg-white rounded-left ui-element"
-      @click="zoomOut"
-    >
-      <font-awesome-icon icon="minus"></font-awesome-icon>
-    </button>
-    <button
-      class="zoomButton border-0 bg-white rounded-right ui-element border-right"
-      @click="zoomIn"
-    >
-      <font-awesome-icon icon="plus"></font-awesome-icon>
-    </button>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-.buttoncontainer {
-  bottom: 0;
-  left: 50%;
-  margin-left: -$icon-width;
-}
-
-.zoomButton {
-  min-height: $icon-width;
-  min-width: $icon-width;
-  z-index: 1;
-  outline: none;
-  color: #666;
-}
-</style>
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus@intevation.de>
- * Thomas Junk <thomas.junk@intevation.de>
- */
-import { mapState } from "vuex";
-
-export default {
-  name: "zoom",
-  computed: {
-    ...mapState("map", ["openLayersMap"]),
-    ...mapState("application", ["showSplitscreen"]),
-    zoomLevel: {
-      get() {
-        return this.openLayersMap.getView().getZoom();
-      },
-      set(value) {
-        this.openLayersMap.getView().animate({ zoom: value, duration: 300 });
-      }
-    }
-  },
-  methods: {
-    zoomIn() {
-      this.zoomLevel = this.zoomLevel + 1;
-    },
-    zoomOut() {
-      this.zoomLevel = this.zoomLevel - 1;
-    }
-  }
-};
-</script>
--- a/client/src/components/map/contextbox/Bottlenecks.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,304 +0,0 @@
-<template>
-  <div>
-    <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
-      <font-awesome-icon icon="ship" class="mr-2"></font-awesome-icon>
-      <translate>Bottlenecks</translate>
-    </h6>
-    <div class="row p-2 text-left small">
-      <div class="col-5">
-        <a href="#" @click="sortBy('name')" class="sort-link">
-          <translate>Name</translate>
-        </a>
-        <font-awesome-icon
-          :icon="sortIcon"
-          class="ml-1"
-          v-if="sortColumn === 'name'"
-        ></font-awesome-icon>
-      </div>
-      <div class="col-2">
-        <a href="#" @click="sortBy('latestMeasurement')" class="sort-link">
-          <translate>Latest</translate> <br />
-          <translate>Measurement</translate>
-        </a>
-        <font-awesome-icon
-          :icon="sortIcon"
-          class="ml-1"
-          v-if="sortColumn === 'latestMeasurement'"
-        ></font-awesome-icon>
-      </div>
-      <div class="col-3">
-        <a href="#" @click="sortBy('chainage')" class="sort-link">
-          <translate>Chainage</translate>
-        </a>
-        <font-awesome-icon
-          :icon="sortIcon"
-          class="ml-1"
-          v-if="sortColumn === 'chainage'"
-        ></font-awesome-icon>
-      </div>
-      <div class="col-2"></div>
-    </div>
-    <div
-      class="bottleneck-list small text-left"
-      :style="'max-height: ' + (showSplitscreen ? 18 : 35) + 'rem'"
-      v-if="filteredAndSortedBottlenecks().length"
-    >
-      <div
-        v-for="bottleneck in filteredAndSortedBottlenecks()"
-        :key="bottleneck.properties.name"
-        class="border-top row bottleneck-row mx-0"
-      >
-        <div class="col-5 py-2 text-left">
-          <a href="#" @click="selectBottleneck(bottleneck)">{{
-            bottleneck.properties.name
-          }}</a>
-        </div>
-        <div class="col-2 py-2">
-          {{ displayCurrentSurvey(bottleneck.properties.current) }}
-        </div>
-        <div class="col-3 py-2">
-          {{
-            displayCurrentChainage(
-              bottleneck.properties.from,
-              bottleneck.properties.to
-            )
-          }}
-        </div>
-        <div class="col-2 pr-0 text-right">
-          <button
-            type="button"
-            class="btn btn-sm btn-info rounded-0 h-100"
-            @click="loadSurveys(bottleneck.properties.name)"
-            v-if="bottleneck.properties.current"
-          >
-            <font-awesome-icon
-              icon="spinner"
-              fixed-width
-              spin
-              v-if="loading === bottleneck.properties.name"
-            ></font-awesome-icon>
-            <font-awesome-icon
-              icon="angle-down"
-              fixed-width
-              v-if="
-                loading !== bottleneck.properties.name &&
-                  openBottleneck !== bottleneck.properties.name
-              "
-            ></font-awesome-icon>
-            <font-awesome-icon
-              icon="angle-up"
-              fixed-width
-              v-if="
-                loading !== bottleneck.properties.name &&
-                  openBottleneck === bottleneck.properties.name
-              "
-            ></font-awesome-icon>
-          </button>
-        </div>
-        <div
-          :class="[
-            'col-12 p-0',
-            'surveys',
-            { open: openBottleneck === bottleneck.properties.name }
-          ]"
-        >
-          <a
-            href="#"
-            class="d-block px-3 py-2"
-            v-for="(survey, index) in openBottleneckSurveys"
-            :key="index"
-            @click="selectSurvey(survey, bottleneck)"
-            >{{ survey.date_info }}</a
-          >
-        </div>
-      </div>
-    </div>
-    <div v-else class="small text-center py-3 border-top">
-      <translate>No results.</translate>
-    </div>
-  </div>
-</template>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import { mapState } from "vuex";
-import { HTTP } from "../../../lib/http";
-import { displayError } from "../../../lib/errors.js";
-
-export default {
-  name: "bottlenecks",
-  data() {
-    return {
-      sortColumn: "name",
-      sortDirection: "ASC",
-      openBottleneck: null,
-      openBottleneckSurveys: null,
-      loading: null
-    };
-  },
-  computed: {
-    ...mapState("application", [
-      "searchQuery",
-      "showSearchbarLastState",
-      "showSplitscreen"
-    ]),
-    ...mapState("bottlenecks", ["bottlenecks"]),
-    sortIcon() {
-      return this.sortDirection === "ASC"
-        ? "sort-amount-down"
-        : "sort-amount-up";
-    }
-  },
-  methods: {
-    filteredAndSortedBottlenecks() {
-      return this.bottlenecks
-        .filter(bn => {
-          return bn.properties.name
-            .toLowerCase()
-            .includes(this.searchQuery.toLowerCase());
-        })
-        .sort((bnA, bnB) => {
-          switch (this.sortColumn) {
-            case "name":
-              if (
-                bnA.properties.name.toLowerCase() <
-                bnB.properties.name.toLowerCase()
-              )
-                return this.sortDirection === "ASC" ? -1 : 1;
-              if (
-                bnA.properties.name.toLowerCase() >
-                bnB.properties.name.toLowerCase()
-              )
-                return this.sortDirection === "ASC" ? 1 : -1;
-              return 0;
-
-            case "latestMeasurement": {
-              if (
-                (bnA.properties.current || "") < (bnB.properties.current || "")
-              )
-                return this.sortDirection === "ASC" ? -1 : 1;
-              if (
-                (bnA.properties.current || "") > (bnB.properties.current || "")
-              )
-                return this.sortDirection === "ASC" ? 1 : -1;
-              return 0;
-            }
-
-            case "chainage":
-              if (bnA.properties.from < bnB.properties.from)
-                return this.sortDirection === "ASC" ? -1 : 1;
-              if (bnA.properties.from > bnB.properties.from)
-                return this.sortDirection === "ASC" ? 1 : -1;
-              return 0;
-
-            default:
-              return 0;
-          }
-        });
-    },
-    selectSurvey(survey, bottleneck) {
-      this.selectBottleneck(bottleneck);
-      this.$store.commit("bottlenecks/selectedSurvey", survey);
-    },
-    selectBottleneck(bottleneck) {
-      this.$store.dispatch(
-        "bottlenecks/setSelectedBottleneck",
-        bottleneck.properties.name
-      );
-      this.$store.commit("map/moveMap", {
-        coordinates: bottleneck.geometry.coordinates,
-        zoom: 17,
-        preventZoomOut: true
-      });
-    },
-    sortBy(column) {
-      this.sortColumn = column;
-      this.sortDirection = this.sortDirection === "ASC" ? "DESC" : "ASC";
-    },
-    loadSurveys(name) {
-      this.openBottleneckSurveys = null;
-      if (name === this.openBottleneck) {
-        this.openBottleneck = null;
-      } else {
-        this.openBottleneck = name;
-        this.loading = name;
-
-        HTTP.get("/surveys/" + name, {
-          headers: {
-            "X-Gemma-Auth": localStorage.getItem("token"),
-            "Content-type": "text/xml; charset=UTF-8"
-          }
-        })
-          .then(response => {
-            this.openBottleneckSurveys = response.data.surveys.sort((a, b) => {
-              return a.date_info < b.date_info ? 1 : -1;
-            });
-          })
-          .catch(error => {
-            const { status, data } = error.response;
-            displayError({
-              title: this.$gettext("Backend Error"),
-              message: `${status}: ${data.message || data}`
-            });
-          })
-          .finally(() => (this.loading = null));
-      }
-    },
-    displayCurrentSurvey(current) {
-      return current ? current.substr(0, current.length - 1) : "";
-    },
-    displayCurrentChainage(from, to) {
-      return from / 10 + " - " + to / 10;
-    }
-  },
-  mounted() {
-    this.$store.dispatch("bottlenecks/loadBottlenecks");
-  }
-};
-</script>
-
-<style lang="scss" scoped>
-.bottleneck-list {
-  overflow-y: auto;
-}
-
-.bottleneck-list .bottleneck-row a {
-  text-decoration: none;
-}
-
-.bottleneck-list .bottleneck-row:hover {
-  background: #fbfbfb;
-}
-
-.surveys {
-  max-height: 0;
-  min-height: 0;
-  overflow: hidden;
-}
-
-.surveys a:hover {
-  background: #f3f3f3;
-}
-
-.surveys.open {
-  max-height: 250px;
-  overflow: auto;
-}
-
-.sort-link {
-  color: #444;
-  font-weight: bold;
-}
-</style>
--- a/client/src/components/map/contextbox/Contextbox.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,96 +0,0 @@
-<template>
-  <div :class="style">
-    <div @click="close" class="ui-element close-contextbox text-muted">
-      <font-awesome-icon icon="times"></font-awesome-icon>
-    </div>
-    <Bottlenecks v-if="contextBoxContent === 'bottlenecks'"></Bottlenecks>
-    <Importsounding v-if="contextBoxContent === 'imports'"></Importsounding>
-    <Staging v-if="contextBoxContent === 'staging'"></Staging>
-  </div>
-</template>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import { mapState } from "vuex";
-
-export default {
-  name: "contextbox",
-  components: {
-    Bottlenecks: () => import("./Bottlenecks"),
-    Importsounding: () => import("./ImportSoundingresults.vue"),
-    Staging: () => import("./Staging.vue")
-  },
-  computed: {
-    ...mapState("application", [
-      "showSearchbarLastState",
-      "contextBoxContent",
-      "showContextBox"
-    ]),
-    style() {
-      return [
-        "ui-element shadow-xs contextbox",
-        {
-          contextboxcollapsed: !this.showContextBox,
-          contextboxextended: this.showContextBox,
-          "rounded-bottom": this.contextBoxContent !== "imports",
-          rounded: this.contextBoxContent === "imports"
-        }
-      ];
-    }
-  },
-  methods: {
-    close() {
-      this.$store.commit("application/showContextBox", false);
-      this.$store.commit(
-        "application/showSearchbar",
-        this.showSearchbarLastState
-      );
-    }
-  }
-};
-</script>
-
-<style lang="scss" scoped>
-.contextbox {
-  position: relative;
-  background-color: #ffffff;
-  opacity: $slight-transparent;
-  transition: max-width 0.3s, max-height 0.3s;
-  overflow: hidden;
-  background: #fff;
-}
-.contextbox > div:last-child {
-  width: 600px;
-}
-
-.contextboxcollapsed {
-  max-width: 0;
-  max-height: 0;
-}
-
-.contextboxextended {
-  max-width: 600px;
-  max-height: 640px;
-}
-
-.close-contextbox {
-  position: absolute;
-  z-index: 2;
-  right: 0;
-  top: 7px;
-  height: $icon-width;
-  width: $icon-height;
-}
-</style>
--- a/client/src/components/map/contextbox/ImportSoundingresults.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,379 +0,0 @@
-<template>
-  <div>
-    <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
-      <font-awesome-icon icon="upload" class="mr-2"></font-awesome-icon>
-      <translate>Import Soundingresults</translate>
-    </h6>
-    <div v-if="editState" class="ml-auto mr-auto mt-4 w-95">
-      <div class="d-flex flex-column">
-        <div class="d-flex flex-row">
-          <div class="mt-1 text-left w-50 ml-2 mr-4">
-            <small class="text-muted">
-              <translate>Bottleneck</translate>
-            </small>
-            <select v-model="bottleneck" class="custom-select">
-              <option
-                v-for="bottleneck in availableBottlenecks"
-                :key="bottleneck"
-                >{{ bottleneck }}</option
-              >
-            </select>
-            <span class="text-danger">
-              <small v-if="!bottleneck">
-                <translate>Please select a bottleneck</translate>
-              </small>
-            </span>
-          </div>
-          <div class="d-flex flex-column mt-1 text-left w-50 mr-2">
-            <small class="text-muted">
-              <translate>Projection</translate>&nbsp;(EPSG)
-            </small>
-            <input
-              class="form-control"
-              v-model="projection"
-              value="4326"
-              placeholder="e.g. 4326"
-              type="number"
-            />
-            <span class="text-left text-danger">
-              <small v-if="!projection">
-                <translate>Please enter a projection</translate>
-              </small>
-            </span>
-          </div>
-        </div>
-        <div class="d-flex flex-row">
-          <div class="mt-1 text-left w-50 ml-2 mr-4">
-            <small class="text-muted">
-              <translate>Depthreference</translate>
-            </small>
-            <select
-              v-model="depthReference"
-              class="custom-select"
-              id="depthreference"
-            >
-              <option
-                v-for="option in this.$options.depthReferenceOptions"
-                :key="option"
-                >{{ option }}</option
-              >
-            </select>
-            <span class="text-left text-danger">
-              <small v-if="!depthReference">
-                <translate>Please enter a reference</translate>
-              </small>
-            </span>
-          </div>
-          <div class="mt-1 text-left w-50 mr-2">
-            <small class="text-muted"> <translate>Date</translate> </small>
-            <input
-              id="importdate"
-              type="date"
-              class="form-control"
-              placeholder="Date of import"
-              aria-label="bottleneck"
-              aria-describedby="bottlenecklabel"
-              v-model="importDate"
-            />
-            <span class="text-left text-danger">
-              <small v-if="!importDate">
-                <translate>Please enter a date</translate>
-              </small>
-            </span>
-          </div>
-        </div>
-      </div>
-      <div class="ml-2 mt-2 text-left">
-        <small v-for="(message, index) in messages" :key="index">
-          {{ message }}
-        </small>
-      </div>
-    </div>
-    <div class="w-95 ml-auto mr-auto mt-4 mb-4">
-      <div v-if="uploadState" class="d-flex flex-row input-group mb-4">
-        <div class="custom-file">
-          <input
-            accept=".zip"
-            type="file"
-            @change="fileSelected"
-            class="custom-file-input"
-            id="uploadFile"
-          />
-          <label class="custom-file-label" for="uploadFile">
-            {{ uploadLabel }}
-          </label>
-        </div>
-      </div>
-      <div class="buttons text-right">
-        <a
-          v-if="editState"
-          download="meta.json"
-          :href="dataLink"
-          class="btn btn-outline-info pull-left"
-        >
-          <translate>Download Meta.json</translate>
-        </a>
-        <button
-          v-if="editState"
-          @click="deleteTempData"
-          class="btn btn-danger"
-          type="button"
-        >
-          <translate>Cancel Upload</translate>
-        </button>
-        <button
-          :disabled="disableUploadButton"
-          @click="submit"
-          class="btn btn-info"
-          type="button"
-        >
-          {{ uploadState ? Upload : Confirm }}
-        </button>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import { HTTP } from "../../../lib/http";
-import { displayError, displayInfo } from "../../../lib/errors.js";
-import { mapState } from "vuex";
-
-const IMPORTSTATE = { UPLOAD: "UPLOAD", EDIT: "EDIT" };
-
-export default {
-  name: "imports",
-  data() {
-    return {
-      importState: IMPORTSTATE.UPLOAD,
-      depthReference: "",
-      bottleneck: "",
-      projection: "",
-      importDate: "",
-      uploadLabel: this.$gettext("choose .zip- file"),
-      uploadFile: null,
-      disableUpload: false,
-      token: null,
-      messages: []
-    };
-  },
-  methods: {
-    initialState() {
-      this.importState = IMPORTSTATE.UPLOAD;
-      this.depthReference = "";
-      this.bottleneck = "";
-      this.projection = "";
-      this.importDate = "";
-      this.uploadLabel = this.$gettext("choose .zip- file");
-      this.uploadFile = null;
-      this.disableUpload = false;
-      this.token = null;
-      this.messages = [];
-    },
-    fileSelected(e) {
-      const files = e.target.files || e.dataTransfer.files;
-      if (!files) return;
-      this.uploadLabel = files[0].name;
-      this.uploadFile = files[0];
-    },
-    deleteTempData() {
-      HTTP.delete("/imports/soundingresult-upload/" + this.token, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token")
-        }
-      })
-        .then(() => {
-          this.initialState();
-        })
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: this.$gettext("Backend Error"),
-            message: `${status}: ${data.message || data}`
-          });
-        });
-    },
-    submit() {
-      if (!this.uploadFile || this.disableUpload) return;
-      if (this.importState === IMPORTSTATE.UPLOAD) {
-        this.upload();
-      } else {
-        this.confirm();
-      }
-    },
-    upload() {
-      let formData = new FormData();
-      formData.append("soundingresult", this.uploadFile);
-      HTTP.post("/imports/soundingresult-upload", formData, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-Type": "multipart/form-data"
-        }
-      })
-        .then(response => {
-          if (response.data.meta) {
-            const { bottleneck, date, epsg } = response.data.meta;
-            const depthReference = response.data.meta["depth-reference"];
-            this.bottleneck = bottleneck;
-            this.depthReference = depthReference;
-            this.importDate = new Date(date).toISOString().split("T")[0];
-            this.projection = epsg;
-          }
-          this.importState = IMPORTSTATE.EDIT;
-          this.token = response.data.token;
-          this.messages = response.data.messages;
-        })
-        .catch(error => {
-          const { status, data } = error.response;
-          const messages = data.messages ? data.messages.join(", ") : "";
-          displayError({
-            title: this.$gettext("Backend Error"),
-            message: `${status}: ${messages}`
-          });
-        });
-    },
-    confirm() {
-      let formData = new FormData();
-      formData.append("token", this.token);
-      if (this.bottleneck) formData.append("bottleneck", this.bottleneck);
-      if (this.importDate)
-        formData.append("date", this.importDate.split("T")[0]);
-      if (this.depthReference)
-        formData.append("depth-reference", this.depthReference);
-      if (this.projection) formData.append("", this.projection);
-
-      HTTP.post("/imports/soundingresult", formData, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-Type": "multipart/form-data"
-        }
-      })
-        .then(() => {
-          displayInfo({
-            title: this.$gettext("Import"),
-            message: this.$gettext("Starting import for ") + this.bottleneck
-          });
-          this.initialState();
-        })
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: this.$gettext("Backend Error"),
-            message: `${status}: ${data.message || data}`
-          });
-        });
-    }
-  },
-  mounted() {
-    this.$store.dispatch("bottlenecks/loadBottlenecks");
-  },
-  watch: {
-    showContextBox() {
-      if (!this.showContextBox && this.token) this.deleteTempData();
-    }
-  },
-  computed: {
-    ...mapState("application", ["showContextBox"]),
-    ...mapState("bottlenecks", ["bottlenecks"]),
-    disableUploadButton() {
-      if (this.importState === IMPORTSTATE.UPLOAD) return this.disableUpload;
-      if (
-        !this.bottleneck ||
-        !this.importDate ||
-        !this.depthReference ||
-        !this.projection
-      )
-        return true;
-      return this.disableUpload;
-    },
-    availableBottlenecks() {
-      return this.bottlenecks.map(x => x.properties.name);
-    },
-    editState() {
-      return this.importState === IMPORTSTATE.EDIT;
-    },
-    uploadState() {
-      return this.importState === IMPORTSTATE.UPLOAD;
-    },
-    Upload() {
-      return this.$gettext("Upload");
-    },
-    Confirm() {
-      return this.$gettext("Confirm");
-    },
-    dataLink() {
-      return (
-        "data:text/json;charset=utf-8," +
-        encodeURIComponent(
-          JSON.stringify({
-            depthReference: this.depthReference,
-            bottleneck: this.bottleneck,
-            date: this.importDate
-          })
-        )
-      );
-    }
-  },
-  depthReferenceOptions: [
-    "",
-    // "NAP",
-    // "KP",
-    // "FZP",
-    // "ADR",
-    // "TAW",
-    // "PUL",
-    // "NGM",
-    // "ETRS",
-    // "POT",
-    // "LDC",
-    // "HDC",
-    // "ZPG",
-    // "GLW",
-    // "HSW",
-    // "LNW",
-    // "HNW",
-    // "IGN",
-    // "WGS",
-    "RN" //,
-    // "HBO"
-  ]
-};
-</script>
-
-<style lang="scss" scoped>
-.projectionLabel {
-  margin-left: $small-offset;
-}
-
-.depthreferencelabel {
-  margin-left: $small-offset;
-}
-
-.offset-r {
-  margin-right: $small-offset;
-}
-
-.buttons button {
-  margin-left: $offset !important;
-}
-
-.label-text {
-  width: 5rem;
-  text-align: left;
-  line-height: 2.25rem;
-}
-</style>
--- a/client/src/components/map/contextbox/Staging.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,232 +0,0 @@
-<template>
-  <div class="w-90 stagingcard">
-    <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
-      <font-awesome-icon
-        class="mr-2"
-        icon="clipboard-check"
-      ></font-awesome-icon>
-      <translate>Staging Area</translate>
-    </h6>
-    <table class="table">
-      <thead>
-        <tr>
-          <th><translate>Name</translate></th>
-          <th><translate>Type</translate></th>
-          <th><translate>Date</translate></th>
-          <th><translate>Imported</translate></th>
-          <th><translate>Username</translate></th>
-          <th>&nbsp;</th>
-          <th>&nbsp;</th>
-        </tr>
-      </thead>
-      <tbody v-if="filteredData.length">
-        <tr :key="data.id" v-for="data in filteredData">
-          <td>
-            <a @click="zoomTo(data.id)" href="#">{{
-              data.summary.bottleneck
-            }}</a>
-          </td>
-          <td>{{ data.kind.toUpperCase() }}</td>
-          <td>{{ data.summary.date }}</td>
-          <td>{{ data.enqueued.split("T")[0] }}</td>
-          <td>{{ data.user }}</td>
-          <td>
-            <button
-              :class="{
-                btn: true,
-                'btn-sm': true,
-                'btn-outline-success': needsApproval(data) || isRejected(data),
-                'btn-success': isApproved(data)
-              }"
-              @click="toggleApproval(data.id, $options.STATES.APPROVED)"
-            >
-              <font-awesome-icon icon="check"></font-awesome-icon>
-            </button>
-          </td>
-          <td>
-            <button
-              :class="{
-                btn: true,
-                'btn-sm': true,
-                'btn-outline-danger': needsApproval(data) || isApproved(data),
-                'btn-danger': isRejected(data)
-              }"
-              @click="toggleApproval(data.id, $options.STATES.REJECTED)"
-            >
-              <font-awesome-icon icon="times"></font-awesome-icon>
-            </button>
-          </td>
-        </tr>
-      </tbody>
-      <tbody v-else>
-        <tr>
-          <td class="text-center" colspan="6">
-            <translate>No results.</translate>
-          </td>
-        </tr>
-      </tbody>
-    </table>
-    <div class="p-3" v-if="filteredData.length">
-      <button @click="confirmReview" class="confirm-button btn btn-info">
-        <translate>Confirm</translate>
-      </button>
-    </div>
-    <div class="p-3">
-      <button @click="loadData" class="refresh btn btn-dark">Refresh</button>
-    </div>
-  </div>
-</template>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- * Markus Kottländer <markus@intevation.de>
- */
-import { mapState } from "vuex";
-import { HTTP } from "../../../lib/http.js";
-import { STATES } from "../../../store/imports.js";
-import { displayError, displayInfo } from "../../../lib/errors.js";
-
-export default {
-  data() {
-    return {};
-  },
-  mounted() {
-    this.loadData();
-  },
-  computed: {
-    ...mapState("application", ["searchQuery"]),
-    ...mapState("imports", ["staging"]),
-    filteredData() {
-      return this.staging.filter(data => {
-        const result = [data.id + "", data.enqueued, data.kind, data.user].some(
-          x => x.toLowerCase().includes(this.searchQuery.toLowerCase())
-        );
-        return result;
-      });
-    }
-  },
-  STATES: STATES,
-  methods: {
-    loadData() {
-      this.$store.dispatch("imports/getStaging").catch(error => {
-        const { status, data } = error.response;
-        displayError({
-          title: "Backend Error",
-          message: `${status}: ${data.message || data}`
-        });
-      });
-    },
-    confirmReview() {
-      const reviewResults = this.staging
-        .filter(x => x.status !== STATES.NEEDSAPPROVAL)
-        .map(r => {
-          return {
-            id: r.id,
-            state: r.status
-          };
-        });
-      if (!reviewResults.length) return;
-      HTTP.patch("/imports", reviewResults, {
-        headers: {
-          "X-Gemma-Auth": localStorage.getItem("token"),
-          "Content-type": "application/json"
-        }
-      })
-        .then(response => {
-          const messages = response.data
-            .map(x => {
-              if (x.message) return x.message;
-              if (x.error) return x.error;
-            })
-            .join("\n\n");
-          displayInfo({
-            title: "Staging Area",
-            message: messages,
-            options: {
-              timeout: 0,
-              buttons: [{ text: "Ok", action: null, bold: true }]
-            }
-          });
-          this.loadData();
-        })
-        .catch(error => {
-          const { status, data } = error.response;
-          displayError({
-            title: "Backend Error",
-            message: `${status}: ${data.message || data}`
-          });
-        });
-    },
-    needsApproval(item) {
-      return item.status === STATES.NEEDSAPPROVAL;
-    },
-    isRejected(item) {
-      return item.status === STATES.REJECTED;
-    },
-    isApproved(item) {
-      return item.status === STATES.APPROVED;
-    },
-    zoomTo(id) {
-      if (!id) return;
-      const soundingResult = this.filteredData.filter(x => x.id == id)[0];
-      const { lat, lon, bottleneck, date } = soundingResult.summary;
-      const coordinates = [lat, lon];
-
-      this.$store.commit("map/moveMap", {
-        coordinates: coordinates,
-        zoom: 17,
-        preventZoomOut: true
-      });
-      this.$store.dispatch(
-        "bottlenecks/setSelectedBottleneck",
-        bottleneck,
-        date
-      );
-    },
-    toggleApproval(id, newStatus) {
-      this.$store.commit("imports/toggleApproval", {
-        id: id,
-        newStatus: newStatus
-      });
-    }
-  }
-};
-</script>
-
-<style lang="scss" scoped>
-.refresh {
-  position: absolute;
-  left: $offset;
-  bottom: $offset;
-}
-.table th,
-td {
-  font-size: 0.9rem;
-  border-top: 0px !important;
-  border-bottom-width: 1px;
-  text-align: left;
-  padding: 0.5rem !important;
-}
-
-.stagingcard {
-  position: relative;
-  min-height: 150px;
-}
-
-.confirm-button {
-  position: absolute;
-  right: $offset;
-  bottom: $offset;
-}
-</style>
--- a/client/src/components/map/fairway/Fairwayprofile.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,413 +0,0 @@
-<template>
-  <div :class="['position-relative', { show: showSplitscreen }]">
-    <button
-      class="rounded-bottom bg-white border-0 position-absolute splitscreen-toggle"
-      @click="$store.commit('application/showSplitscreen', false)"
-      v-if="showSplitscreen"
-    >
-      <font-awesome-icon icon="angle-down" />
-    </button>
-    <button
-      class="rounded-bottom bg-white border-0 position-absolute clear-selection"
-      @click="$store.dispatch('fairwayprofile/clearSelection')"
-      v-if="showSplitscreen"
-    >
-      <font-awesome-icon icon="times" />
-    </button>
-    <div class="profile bg-white position-relative d-flex flex-column">
-      <h5
-        class="headline border-bottom mb-0 py-2"
-        v-if="selectedBottleneck && selectedSurvey"
-      >
-        {{ selectedBottleneck }} ({{ selectedSurvey.date_info }})
-      </h5>
-      <div class="d-flex flex-fill">
-        <div
-          class="loading d-flex justify-content-center align-items-center"
-          v-if="surveysLoading || profileLoading"
-        >
-          <font-awesome-icon icon="spinner" spin />
-        </div>
-        <div class="fairwayprofile m-3 mt-0 bg-white flex-grow-1"></div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-.profile {
-  width: 100vw;
-  height: 0;
-  overflow: hidden;
-  z-index: 2;
-}
-
-.splitscreen-toggle,
-.clear-selection {
-  width: 2rem;
-  height: 2rem;
-  margin-top: 8px;
-  z-index: 3;
-  outline: none;
-}
-
-.splitscreen-toggle svg path,
-.clear-selection svg path {
-  fill: #666;
-}
-
-.splitscreen-toggle {
-  right: 2.5rem;
-}
-
-.clear-selection {
-  right: 0.5rem;
-}
-
-.show .profile {
-  height: 50vh;
-}
-
-.loading {
-  background: rgba(255, 255, 255, 0.96);
-  position: absolute;
-  z-index: 99;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
-}
-</style>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- */
-import * as d3 from "d3";
-import { mapState, mapGetters } from "vuex";
-import debounce from "debounce";
-
-const GROUND_COLOR = "#4A2F06";
-
-export default {
-  name: "fairwayprofile",
-  data() {
-    return {
-      coordinatesInput: "",
-      coordinatesSelect: null,
-      cutLabel: "",
-      showLabelInput: false,
-      width: null,
-      height: null,
-      margin: {
-        top: 20,
-        right: 40,
-        bottom: 30,
-        left: 40
-      }
-    };
-  },
-  computed: {
-    ...mapGetters("fairwayprofile", ["totalLength"]),
-    ...mapState("application", ["showSplitscreen"]),
-    ...mapState("fairwayprofile", [
-      "startPoint",
-      "endPoint",
-      "currentProfile",
-      "additionalSurvey",
-      "minAlt",
-      "maxAlt",
-      "fairwayCoordinates",
-      "waterLevels",
-      "selectedWaterLevel",
-      "profileLoading"
-    ]),
-    ...mapState("bottlenecks", [
-      "selectedBottleneck",
-      "selectedSurvey",
-      "surveysLoading"
-    ]),
-    currentData() {
-      if (
-        !this.selectedSurvey ||
-        !this.currentProfile.hasOwnProperty(this.selectedSurvey.date_info)
-      )
-        return [];
-      return this.currentProfile[this.selectedSurvey.date_info].points;
-    },
-    additionalData() {
-      if (
-        !this.additionalSurvey ||
-        !this.currentProfile.hasOwnProperty(this.additionalSurvey.date_info)
-      )
-        return [];
-      return this.currentProfile[this.additionalSurvey.date_info].points;
-    },
-    waterColor() {
-      const result = this.waterLevels.find(
-        x => x.level === this.selectedWaterLevel
-      );
-      return result.color;
-    },
-    xScale() {
-      return [0, this.totalLength];
-    },
-    yScaleLeft() {
-      const hi = Math.max(this.maxAlt, this.selectedWaterLevel);
-      return [this.minAlt, hi];
-    },
-    yScaleRight() {
-      const DELTA = this.maxAlt * 1.1 - this.maxAlt;
-      return [this.maxAlt * 1 + DELTA, -DELTA];
-    }
-  },
-  watch: {
-    currentData() {
-      this.drawDiagram();
-    },
-    additionalData() {
-      this.drawDiagram();
-    },
-    width() {
-      this.drawDiagram();
-    },
-    height() {
-      this.drawDiagram();
-    },
-    waterLevels() {
-      this.drawDiagram();
-    },
-    selectedWaterLevel() {
-      this.drawDiagram();
-    },
-    fairwayCoordinates() {
-      this.drawDiagram();
-    }
-  },
-  methods: {
-    drawDiagram() {
-      this.coordinatesSelect = null;
-      const chartDiv = document.querySelector(".fairwayprofile");
-      d3.select(".fairwayprofile svg").remove();
-      this.scaleFairwayProfile();
-      let svg = d3.select(chartDiv).append("svg");
-      svg.attr("width", this.width);
-      svg.attr("height", this.height);
-      const width = this.width - this.margin.right - 1.5 * this.margin.left;
-      const height = this.height - this.margin.top - 2 * this.margin.bottom;
-      const currentData = this.currentData;
-      const additionalData = this.additionalData;
-      const { xScale, yScaleRight, graph } = this.generateCoordinates(
-        svg,
-        height,
-        width
-      );
-      this.drawWaterlevel({ graph, xScale, yScaleRight, height });
-      this.drawLabels({ graph, height });
-      this.drawFairway({ graph, xScale, yScaleRight });
-      if (currentData) {
-        this.drawProfile({
-          graph,
-          xScale,
-          yScaleRight,
-          currentData,
-          height,
-          color: GROUND_COLOR,
-          strokeColor: "black",
-          opacity: 1
-        });
-      }
-      if (additionalData) {
-        this.drawProfile({
-          graph,
-          xScale,
-          yScaleRight,
-          currentData: additionalData,
-          height,
-          color: GROUND_COLOR,
-          strokeColor: "#943007",
-          opacity: 0.6
-        });
-      }
-    },
-    drawFairway({ graph, xScale, yScaleRight }) {
-      for (let coordinates of this.fairwayCoordinates) {
-        const [startPoint, endPoint, depth] = coordinates;
-        let fairwayArea = d3
-          .area()
-          .x(function(d) {
-            return xScale(d.x);
-          })
-          .y0(yScaleRight(0))
-          .y1(function(d) {
-            return yScaleRight(d.y);
-          });
-        graph
-          .append("path")
-          .datum([{ x: startPoint, y: depth }, { x: endPoint, y: depth }])
-          .attr("fill", "#002AFF")
-          .attr("stroke-opacity", 0.65)
-          .attr("fill-opacity", 0.65)
-          .attr("stroke", "#FFD20D")
-          .attr("d", fairwayArea);
-      }
-    },
-    drawLabels({ graph, height }) {
-      graph
-        .append("text")
-        .attr("transform", ["rotate(-90)"])
-        .attr("y", this.width - 60)
-        .attr("x", -(this.height - this.margin.top - this.margin.bottom) / 2)
-        .attr("dy", "1em")
-        .attr("fill", "black")
-        .style("text-anchor", "middle")
-        .text("Depth [m]");
-      graph
-        .append("text")
-        .attr("y", 0 - this.margin.left)
-        .attr("x", 0 - height / 4)
-        .attr("dy", "1em")
-        .attr("fill", "black")
-        .style("text-anchor", "middle")
-        .attr("transform", [
-          "translate(" + this.width / 2 + "," + this.height + ")",
-          "rotate(0)"
-        ])
-        .text("Width [m]");
-    },
-    generateCoordinates(svg, height, width) {
-      let xScale = d3
-        .scaleLinear()
-        .domain(this.xScale)
-        .rangeRound([0, width]);
-
-      xScale.ticks(5);
-      let yScaleLeft = d3
-        .scaleLinear()
-        .domain(this.yScaleLeft)
-        .rangeRound([height, 0]);
-
-      let yScaleRight = d3
-        .scaleLinear()
-        .domain(this.yScaleRight)
-        .rangeRound([height, 0]);
-
-      let xAxis = d3.axisBottom(xScale);
-      let yAxis2 = d3.axisRight(yScaleRight);
-      let graph = svg
-        .append("g")
-        .attr(
-          "transform",
-          "translate(" + this.margin.left + "," + this.margin.top + ")"
-        );
-      graph
-        .append("g")
-        .attr("transform", "translate(0," + height + ")")
-        .call(xAxis.ticks(5));
-      graph
-        .append("g")
-        .attr("transform", "translate(" + width + ",0)")
-        .call(yAxis2);
-      return { xScale, yScaleLeft, yScaleRight, graph };
-    },
-    drawWaterlevel({ graph, xScale, yScaleRight, height }) {
-      let waterArea = d3
-        .area()
-        .x(function(d) {
-          return xScale(d.x);
-        })
-        .y0(height)
-        .y1(function(d) {
-          return yScaleRight(d.y);
-        });
-      graph
-        .append("path")
-        .datum([{ x: 0, y: 0 }, { x: this.totalLength, y: 0 }])
-        .attr("fill", this.waterColor)
-        .attr("stroke", this.waterColor)
-        .attr("d", waterArea);
-    },
-    drawProfile({
-      graph,
-      xScale,
-      yScaleRight,
-      currentData,
-      height,
-      color,
-      strokeColor,
-      opacity
-    }) {
-      for (let part of currentData) {
-        let profileLine = d3
-          .line()
-          .x(d => {
-            return xScale(d.x);
-          })
-          .y(d => {
-            return yScaleRight(d.y);
-          });
-        let profileArea = d3
-          .area()
-          .x(function(d) {
-            return xScale(d.x);
-          })
-          .y0(height)
-          .y1(function(d) {
-            return yScaleRight(d.y);
-          });
-        graph
-          .append("path")
-          .datum(part)
-          .attr("fill", color)
-          .attr("stroke", color)
-          .attr("stroke-width", 3)
-          .attr("stroke-opacity", opacity)
-          .attr("fill-opacity", opacity)
-          .attr("d", profileArea);
-        graph
-          .append("path")
-          .datum(part)
-          .attr("fill", "none")
-          .attr("stroke", strokeColor)
-          .attr("stroke-linejoin", "round")
-          .attr("stroke-linecap", "round")
-          .attr("stroke-width", 3)
-          .attr("stroke-opacity", opacity)
-          .attr("fill-opacity", opacity)
-          .attr("d", profileLine);
-      }
-    },
-    scaleFairwayProfile() {
-      if (!document.querySelector(".fairwayprofile")) return;
-      const clientHeight = document.querySelector(".fairwayprofile")
-        .clientHeight;
-      const clientWidth = document.querySelector(".fairwayprofile").clientWidth;
-      if (!clientHeight || !clientWidth) return;
-      this.height = clientHeight;
-      this.width = clientWidth;
-    }
-  },
-  created() {
-    window.addEventListener("resize", debounce(this.drawDiagram), 100);
-  },
-  mounted() {
-    this.drawDiagram();
-  },
-  updated() {
-    this.scaleFairwayProfile();
-  },
-  destroyed() {
-    window.removeEventListener("resize", debounce(this.drawDiagram));
-  }
-};
-</script>
--- a/client/src/components/map/fairway/Infobar.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-<template>
-  <div
-    v-if="Object.keys(currentProfile).length && !showSplitscreen"
-    class="ui-element shadow-xs infobar rounded bg-white ml-auto mb-3 mr-3"
-  >
-    <div class="d-flex flex-row justify-content-between h-100">
-      <h6 class="my-auto px-2">
-        {{ selectedBottleneck }} ({{ selectedSurvey.date_info }})
-      </h6>
-      <span
-        class="p-2 border-left d-flex align-items-center"
-        @click="$store.commit('application/showSplitscreen', true)"
-      >
-        <font-awesome-icon icon="angle-up"></font-awesome-icon>
-      </span>
-      <span
-        class="p-2 border-left d-flex align-items-center"
-        @click="$store.dispatch('fairwayprofile/clearSelection')"
-      >
-        <font-awesome-icon icon="times"></font-awesome-icon>
-      </span>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-.infobar {
-  height: 2.2rem;
-  z-index: 2;
-}
-
-.infobar svg path {
-  fill: #666;
-}
-</style>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import { mapState } from "vuex";
-
-export default {
-  name: "infobar",
-  computed: {
-    ...mapState("application", ["showSplitscreen"]),
-    ...mapState("fairwayprofile", ["currentProfile"]),
-    ...mapState("bottlenecks", ["selectedBottleneck", "selectedSurvey"])
-  }
-};
-</script>
--- a/client/src/components/map/fairway/Profiles.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,463 +0,0 @@
-<template>
-  <div
-    :class="[
-      'box ui-element rounded bg-white text-nowrap',
-      { expanded: showProfiles }
-    ]"
-  >
-    <div style="width: 20rem">
-      <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
-        <font-awesome-icon icon="chart-area" class="mr-2"></font-awesome-icon>
-        <translate>Profiles</translate>
-        <font-awesome-icon
-          icon="times"
-          class="ml-auto text-muted"
-          @click="$store.commit('application/showProfiles', false)"
-        ></font-awesome-icon>
-      </h6>
-      <div
-        class="d-flex flex-column p-3 flex-grow-1 text-left position-relative"
-      >
-        <div
-          class="loading d-flex justify-content-center align-items-center"
-          v-if="surveysLoading || profileLoading"
-        >
-          <font-awesome-icon icon="spinner" spin />
-        </div>
-        <select
-          @click="moveToBottleneck"
-          v-model="selectedBottleneck"
-          class="form-control font-weight-bold"
-        >
-          <option :value="null">
-            <translate>Select Bottleneck</translate>
-          </option>
-          <option
-            v-for="bn in bottlenecks"
-            :key="bn.properties.name"
-            :value="bn.properties.name"
-            >{{ bn.properties.name }}</option
-          >
-        </select>
-        <div v-if="selectedBottleneck">
-          <div class="d-flex mt-2">
-            <div class="flex-fill">
-              <small class="text-muted">
-                <translate>Sounding Result</translate>:
-              </small>
-              <select
-                v-model="selectedSurvey"
-                class="form-control form-control-sm"
-              >
-                <option
-                  v-for="survey in surveys"
-                  :key="survey.date_info"
-                  :value="survey"
-                  >{{ survey.date_info }}</option
-                >
-              </select>
-            </div>
-            <div
-              class="flex-fill ml-3"
-              v-if="selectedSurvey && surveys.length > 1"
-            >
-              <small class="text-muted mt-1">
-                <translate>Compare with</translate>:
-              </small>
-              <select
-                v-model="additionalSurvey"
-                class="form-control form-control-sm"
-              >
-                <option :value="null">None</option>
-                <option
-                  v-for="survey in additionalSurveys"
-                  :key="survey.date_info"
-                  :value="survey"
-                  >{{ survey.date_info }}</option
-                >
-              </select>
-            </div>
-          </div>
-          <hr class="w-100 mb-0" />
-          <small class="text-muted d-block mt-2">
-            <translate>Saved cross profiles</translate>:
-          </small>
-          <div class="d-flex">
-            <select
-              :class="[
-                'form-control form-control-sm flex-fill',
-                { 'rounded-left-only': selectedCut }
-              ]"
-              v-model="selectedCut"
-            >
-              <option></option>
-              <option
-                v-for="(cut, index) in previousCuts"
-                :value="cut"
-                :key="index"
-                >{{ cut.label }}</option
-              >
-            </select>
-            <button
-              class="btn btn-sm btn-danger input-button-right"
-              @click="confirmDeleteSelectedCut = true"
-              v-if="selectedCut && !confirmDeleteSelectedCut"
-            >
-              <font-awesome-icon icon="trash" />
-            </button>
-            <button
-              class="btn btn-sm btn-info rounded-0"
-              @click="confirmDeleteSelectedCut = false"
-              v-if="selectedCut && confirmDeleteSelectedCut"
-            >
-              <font-awesome-icon icon="times" />
-            </button>
-            <button
-              class="btn btn-sm btn-danger input-button-right"
-              @click="deleteSelectedCut"
-              v-if="selectedCut && confirmDeleteSelectedCut"
-            >
-              <font-awesome-icon icon="check" />
-            </button>
-          </div>
-          <small class="text-muted d-block mt-2">
-            <translate>Enter coordinates manually</translate>:
-          </small>
-          <div class="position-relative">
-            <input
-              class="form-control form-control-sm pr-5"
-              placeholder="Lat,Lon,Lat,Lon"
-              v-model="coordinatesInput"
-            />
-            <button
-              class="btn btn-sm btn-info position-absolute input-button-right"
-              @click="applyManualCoordinates"
-              style="top: 0; right: 0;"
-              v-if="coordinatesInputIsValid"
-            >
-              <font-awesome-icon icon="check" />
-            </button>
-          </div>
-          <small class="d-flex text-left mt-2" v-if="startPoint && endPoint">
-            <div class="text-nowrap mr-3">
-              <b> <translate>Start</translate>: </b> <br />
-              Lat: {{ startPoint[1] }} <br />
-              Lon: {{ startPoint[0] }}
-            </div>
-            <div class="text-nowrap">
-              <b>End:</b> <br />
-              Lat: {{ endPoint[1] }} <br />
-              Lon: {{ endPoint[0] }}
-            </div>
-            <button
-              v-clipboard:copy="coordinatesForClipboard"
-              v-clipboard:success="onCopyCoordinates"
-              class="btn btn-info btn-sm ml-auto mt-auto"
-            >
-              <font-awesome-icon icon="copy" />
-            </button>
-          </small>
-          <div class="d-flex mt-3">
-            <div
-              class="pr-3 w-50"
-              v-if="startPoint && endPoint && !selectedCut"
-            >
-              <button
-                class="btn btn-info btn-sm w-100"
-                @click="showLabelInput = !showLabelInput"
-              >
-                <font-awesome-icon :icon="showLabelInput ? 'times' : 'check'" />
-                {{ showLabelInput ? "Cancel" : "Save" }}
-              </button>
-            </div>
-            <div
-              :class="startPoint && endPoint && !selectedCut ? 'w-50' : 'w-100'"
-            >
-              <button class="btn btn-info btn-sm w-100" @click="toggleCutTool">
-                <font-awesome-icon
-                  :icon="cutTool && cutTool.getActive() ? 'times' : 'plus'"
-                ></font-awesome-icon>
-                {{ cutTool && cutTool.getActive() ? "Cancel" : "New" }}
-              </button>
-            </div>
-          </div>
-          <div v-if="showLabelInput" class="mt-2">
-            <small class="text-muted">
-              <translate>Enter label for cross profile</translate>:
-            </small>
-            <div class="position-relative">
-              <input
-                class="form-control form-control-sm pr-5"
-                v-model="cutLabel"
-              />
-              <button
-                class="btn btn-sm btn-info position-absolute input-button-right"
-                @click="saveCut"
-                v-if="cutLabel"
-                style="top: 0; right: 0;"
-              >
-                <font-awesome-icon icon="check" />
-              </button>
-            </div>
-          </div>
-        </div>
-      </div>
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-.loading {
-  background: rgba(255, 255, 255, 0.9);
-  position: absolute;
-  z-index: 99;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
-}
-
-.input-button-right {
-  border-top-right-radius: $border-radius;
-  border-bottom-right-radius: $border-radius;
-  border-top-left-radius: 0 !important;
-  border-bottom-left-radius: 0 !important;
-}
-
-.rounded-left-only {
-  border-top-right-radius: 0 !important;
-  border-bottom-right-radius: 0 !important;
-  border-top-left-radius: $border-radius;
-  border-bottom-left-radius: $border-radius;
-}
-</style>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import { mapState, mapGetters } from "vuex";
-import Feature from "ol/Feature";
-import LineString from "ol/geom/LineString";
-import { displayError, displayInfo } from "../../../lib/errors.js";
-
-export default {
-  name: "profiles",
-  data() {
-    return {
-      coordinatesInput: "",
-      cutLabel: "",
-      showLabelInput: false,
-      confirmDeleteSelectedCut: false
-    };
-  },
-  computed: {
-    ...mapGetters("map", ["getVSourceByName"]),
-    ...mapState("application", ["showProfiles"]),
-    ...mapState("map", ["lineTool", "polygonTool", "cutTool"]),
-    ...mapState("bottlenecks", ["bottlenecks", "surveys", "surveysLoading"]),
-    ...mapState("fairwayprofile", [
-      "previousCuts",
-      "startPoint",
-      "endPoint",
-      "profileLoading"
-    ]),
-    selectedBottleneck: {
-      get() {
-        return this.$store.state.bottlenecks.selectedBottleneck;
-      },
-      set(name) {
-        this.$store.dispatch("bottlenecks/setSelectedBottleneck", name);
-      }
-    },
-    selectedSurvey: {
-      get() {
-        return this.$store.state.bottlenecks.selectedSurvey;
-      },
-      set(survey) {
-        this.$store.commit("fairwayprofile/additionalSurvey", null);
-        this.$store.commit("bottlenecks/selectedSurvey", survey);
-      }
-    },
-    additionalSurvey: {
-      get() {
-        return this.$store.state.fairwayprofile.additionalSurvey;
-      },
-      set(survey) {
-        this.$store.commit("fairwayprofile/additionalSurvey", survey);
-      }
-    },
-    selectedCut: {
-      get() {
-        return this.$store.state.fairwayprofile.selectedCut;
-      },
-      set(cut) {
-        this.$store.commit("fairwayprofile/selectedCut", cut);
-        if (!cut) {
-          this.$store.commit("fairwayprofile/clearCurrentProfile");
-          this.$store.commit("application/showSplitscreen", false);
-          this.getVSourceByName("Cut Tool").clear();
-        }
-      }
-    },
-    additionalSurveys() {
-      return this.surveys.filter(survey => survey !== this.selectedSurvey);
-    },
-    coordinatesForClipboard() {
-      return (
-        this.startPoint[1] +
-        "," +
-        this.startPoint[0] +
-        "," +
-        this.endPoint[1] +
-        "," +
-        this.endPoint[0]
-      );
-    },
-    coordinatesInputIsValid() {
-      const coordinates = this.coordinatesInput
-        .split(",")
-        .map(coord => parseFloat(coord.trim()))
-        .filter(c => Number(c) === c);
-      return coordinates.length === 4;
-    }
-  },
-  watch: {
-    selectedBottleneck() {
-      this.$store.dispatch("fairwayprofile/previousCuts");
-      this.cutLabel =
-        this.selectedBottleneck + " (" + new Date().toISOString() + ")";
-    },
-    selectedSurvey(survey) {
-      this.loadProfile(survey);
-    },
-    additionalSurvey(survey) {
-      this.loadProfile(survey);
-    },
-    selectedCut(cut) {
-      if (cut) {
-        this.confirmDeleteSelectedCut = false;
-        this.applyCoordinates(cut.coordinates);
-      }
-    }
-  },
-  methods: {
-    loadProfile(survey) {
-      if (survey) {
-        this.$store.commit("fairwayprofile/profileLoading", true);
-        this.$store
-          .dispatch("fairwayprofile/loadProfile", survey)
-          .finally(() =>
-            this.$store.commit("fairwayprofile/profileLoading", false)
-          );
-      }
-    },
-    toggleCutTool() {
-      this.cutTool.setActive(!this.cutTool.getActive());
-      this.lineTool.setActive(false);
-      this.polygonTool.setActive(false);
-      this.$store.commit("map/setCurrentMeasurement", null);
-    },
-    onCopyCoordinates() {
-      displayInfo({
-        title: this.$gettext("Success"),
-        message: this.$gettext("Coordinates copied to clipboard!")
-      });
-    },
-    applyManualCoordinates() {
-      const coordinates = this.coordinatesInput
-        .split(",")
-        .map(coord => parseFloat(coord.trim()));
-      this.selectedCut = null;
-      this.coordinatesInput = "";
-      this.applyCoordinates([
-        coordinates[1],
-        coordinates[0],
-        coordinates[3],
-        coordinates[2]
-      ]);
-    },
-    applyCoordinates(coordinates) {
-      // allow only numbers
-      coordinates = coordinates.filter(c => Number(c) === c);
-      if (coordinates.length === 4) {
-        // draw line on map
-        this.getVSourceByName("Cut Tool").clear();
-        const cut = new Feature({
-          geometry: new LineString([
-            [coordinates[0], coordinates[1]],
-            [coordinates[2], coordinates[3]]
-          ]).transform("EPSG:4326", "EPSG:3857")
-        });
-        this.getVSourceByName("Cut Tool").addFeature(cut);
-
-        // draw diagram
-        this.$store.dispatch("fairwayprofile/cut", cut);
-      } else {
-        displayError({
-          title: this.$gettext("Invalid input"),
-          message: this.$gettext(
-            "Please enter correct coordinates in the format: Lat,Lon,Lat,Lon"
-          )
-        });
-      }
-    },
-    saveCut() {
-      const previousCuts =
-        JSON.parse(localStorage.getItem("previousCuts")) || [];
-      const newEntry = {
-        label: this.cutLabel,
-        bottleneckName: this.selectedBottleneck,
-        coordinates: [...this.startPoint, ...this.endPoint],
-        timestamp: new Date().getTime()
-      };
-      const existingEntry = previousCuts.find(cut => {
-        return JSON.stringify(cut) === JSON.stringify(newEntry);
-      });
-      if (!existingEntry) previousCuts.push(newEntry);
-      if (previousCuts.length > 100) previousCuts.shift();
-      localStorage.setItem("previousCuts", JSON.stringify(previousCuts));
-      this.$store.dispatch("fairwayprofile/previousCuts");
-
-      this.showLabelInput = false;
-      displayInfo({
-        title: this.$gettext("Profile saved!"),
-        message: this.$gettext(
-          'You can now select these coordinates from the "Saved cross profiles" menu to restore this cross profile.'
-        )
-      });
-    },
-    deleteSelectedCut() {
-      let previousCuts = JSON.parse(localStorage.getItem("previousCuts")) || [];
-      previousCuts = previousCuts.filter(cut => {
-        return JSON.stringify(cut) !== JSON.stringify(this.selectedCut);
-      });
-      localStorage.setItem("previousCuts", JSON.stringify(previousCuts));
-      this.$store.commit("fairwayprofile/selectedCut", null);
-      this.$store.dispatch("fairwayprofile/previousCuts");
-      displayInfo({ title: this.$gettext("Profile deleted!") });
-    },
-    moveToBottleneck() {
-      const bottleneck = this.bottlenecks.find(
-        bn => bn.properties.name === this.selectedBottleneck
-      );
-      if (!bottleneck) return;
-      this.$store.commit("map/moveMap", {
-        coordinates: bottleneck.geometry.coordinates,
-        zoom: 17,
-        preventZoomOut: true
-      });
-    }
-  }
-};
-</script>
--- a/client/src/components/map/layers/Layers.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-<template>
-  <div
-    :class="[
-      'box ui-element rounded bg-white text-nowrap',
-      { expanded: showLayers }
-    ]"
-  >
-    <div style="width: 20rem">
-      <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
-        <font-awesome-icon icon="layer-group" class="mr-2"></font-awesome-icon
-        ><translate>Layers</translate>
-        <font-awesome-icon
-          icon="times"
-          class="ml-auto text-muted"
-          @click="$store.commit('application/showLayers', false)"
-        ></font-awesome-icon>
-      </h6>
-      <div class="d-flex flex-column p-3 small">
-        <Layerselect
-          v-for="(layer, index) in layersForLegend"
-          :layerindex="index"
-          :layername="layer.name"
-          :key="layer.name"
-          :isVisible="layer.isVisible"
-          @visibilityToggled="visibilityToggled"
-        ></Layerselect>
-      </div>
-    </div>
-  </div>
-</template>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import Layerselect from "./Layerselect";
-import { mapGetters, mapState } from "vuex";
-export default {
-  name: "layers",
-  components: {
-    Layerselect
-  },
-  computed: {
-    ...mapGetters("map", ["layersForLegend"]),
-    ...mapState("application", ["showLayers"])
-  },
-  methods: {
-    visibilityToggled(layer) {
-      this.$store.commit("map/toggleVisibility", layer);
-    }
-  }
-};
-</script>
--- a/client/src/components/map/layers/Layerselect.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +0,0 @@
-<template>
-  <div>
-    <div class="form-check d-flex flex-row flex-start selection">
-      <input
-        class="form-check-input"
-        @change="visibilityToggled"
-        :id="layername"
-        type="checkbox"
-        :checked="isVisible"
-      />
-      <LegendElement
-        :layername="layername"
-        :layerindex="layerindex"
-      ></LegendElement>
-      <label class="layername form-check-label" @click="visibilityToggled">{{
-        layername
-      }}</label>
-    </div>
-    <div v-if="isVisible && layername == 'Bottleneck isolines'">
-      <img class="rounded my-1 d-block" :src="isolinesLegendImgUrl" />
-    </div>
-  </div>
-</template>
-
-<style lang="scss" scoped>
-.selection {
-  text-align: left;
-}
-.layername {
-  margin-left: $small-offset;
-}
-</style>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- */
-import { HTTP } from "../../../lib/http";
-import LegendElement from "./LegendElement.vue";
-export default {
-  props: ["layername", "layerindex", "isVisible"],
-  name: "layerselect",
-  data() {
-    return {
-      isolinesLegendImgUrl: ""
-    };
-  },
-  components: {
-    LegendElement
-  },
-  methods: {
-    visibilityToggled() {
-      this.$emit("visibilityToggled", this.layerindex);
-    }
-  },
-  created() {
-    // fetch legend image for bottleneck isolines
-    // TODO: move to store
-    if (this.layername == "Bottleneck isolines") {
-      const src =
-        "/internal/wms?REQUEST=GetLegendGraphic&VERSION=1.0.0&FORMAT=image/png&WIDTH=20&HEIGHT=20&LAYER=sounding_results_contour_lines_geoserver&legend_options=columns:4;fontAntiAliasing:true";
-      HTTP.get(src, {
-        headers: {
-          Accept: "image/png",
-          "X-Gemma-Auth": localStorage.getItem("token")
-        },
-        responseType: "blob"
-      }).then(response => {
-        var urlCreator = window.URL || window.webkitURL;
-        this.isolinesLegendImgUrl = urlCreator.createObjectURL(response.data);
-      });
-    }
-  }
-};
-</script>
--- a/client/src/components/map/layers/LegendElement.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +0,0 @@
-<template>
-  <div :id="id" class="legendelement"></div>
-</template>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Thomas Junk <thomas.junk@intevation.de>
- */
-import { mapGetters } from "vuex";
-
-import { Map, View } from "ol";
-import Feature from "ol/Feature";
-import { Vector as VectorLayer } from "ol/layer.js";
-import { Vector as VectorSource } from "ol/source.js";
-import LineString from "ol/geom/LineString.js";
-import Point from "ol/geom/Point";
-
-export default {
-  name: "legendelement",
-  props: ["layername", "layerindex"],
-  data: function() {
-    return {
-      myMap: null,
-      mapLayer: null
-    };
-  },
-  computed: {
-    ...mapGetters("map", ["getLayerByName"]),
-    id() {
-      return "legendelement" + this.layerindex;
-    },
-    mstyle() {
-      if (this.mapLayer && this.mapLayer.data.getStyle) {
-        return this.mapLayer.data.getStyle();
-      }
-    }
-  },
-  watch: {
-    mstyle(newStyle, oldStyle) {
-      // only recreate if there already was a style before
-      if (oldStyle) {
-        let vector = this.createVectorLayer();
-
-        this.myMap.removeLayer(this.myMap.getLayers()[0]);
-        this.myMap.addLayer(vector);
-      }
-    }
-  },
-  mounted() {
-    this.mapLayer = this.getLayerByName(this.layername);
-    if (this.mapLayer.data.getType() == "VECTOR") {
-      this.initMap();
-    } else {
-      // TODO other tiles
-    }
-  },
-  methods: {
-    initMap() {
-      let vector = this.createVectorLayer();
-
-      this.myMap = new Map({
-        layers: [vector],
-        target: this.id,
-        controls: [],
-        interactions: [],
-        view: new View({
-          center: [0, 0],
-          zoom: 3,
-          projection: "EPSG:4326"
-        })
-      });
-    },
-    createVectorLayer() {
-      let mapStyle = this.mapLayer.data.getStyle();
-
-      let feature = new Feature({
-        geometry: new LineString([[-1, 0.5], [0, 0], [0.7, 0], [1.3, -0.7]])
-      });
-
-      // 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) {
-          feature.setGeometry(new Point([0, 0]));
-        }
-        mapStyle = this.mapLayer.data.getStyleFunction()(
-          feature,
-          this.mapLayer.forLegendStyle.resolution
-        );
-      }
-
-      // 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.
-      // FIXME, this is a special case for the Fairway Dimensions style
-      feature.set("level_of_service", "");
-      return new VectorLayer({
-        source: new VectorSource({
-          features: [feature],
-          wrapX: false
-        }),
-        style: mapStyle
-      });
-    }
-  }
-};
-</script>
-
-<style lang="scss" scoped>
-.legendelement {
-  max-height: 1.5rem;
-  width: 2rem;
-}
-</style>
--- a/client/src/components/map/toolbar/Identify.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,50 +0,0 @@
-<template>
-  <div
-    @click="$store.commit('application/showIdentify', !showIdentify)"
-    class="toolbar-button"
-  >
-    <font-awesome-icon
-      icon="info"
-      :class="{ 'text-info': showIdentify }"
-    ></font-awesome-icon>
-    <span
-      :class="[
-        'indicator',
-        {
-          show:
-            !showIdentify && (identifiedFeatures.length || currentMeasurement)
-        }
-      ]"
-    >
-      {{ badgeCount }}
-    </span>
-  </div>
-</template>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import { mapState } from "vuex";
-
-export default {
-  name: "identify",
-  computed: {
-    ...mapState("application", ["showIdentify"]),
-    ...mapState("map", ["identifiedFeatures", "currentMeasurement"]),
-    badgeCount() {
-      return this.identifiedFeatures.length + !!this.currentMeasurement;
-    }
-  }
-};
-</script>
--- a/client/src/components/map/toolbar/Layers.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-<template>
-  <div
-    @click="$store.commit('application/showLayers', !showLayers)"
-    class="toolbar-button"
-  >
-    <font-awesome-icon
-      icon="layer-group"
-      :class="{ 'text-info': showLayers }"
-    ></font-awesome-icon>
-  </div>
-</template>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import { mapState } from "vuex";
-
-export default {
-  name: "layers",
-  computed: {
-    ...mapState("application", ["showLayers"])
-  }
-};
-</script>
--- a/client/src/components/map/toolbar/Linetool.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-<template>
-  <div @click="toggleLineTool" class="toolbar-button">
-    <font-awesome-icon
-      icon="ruler"
-      :class="{ 'text-info': lineTool && lineTool.getActive() }"
-    ></font-awesome-icon>
-  </div>
-</template>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import { mapState, mapGetters } from "vuex";
-
-export default {
-  name: "linetool",
-  computed: {
-    ...mapGetters("map", ["getLayerByName"]),
-    ...mapState("map", ["lineTool", "polygonTool", "cutTool"])
-  },
-  methods: {
-    toggleLineTool() {
-      this.lineTool.setActive(!this.lineTool.getActive());
-      this.polygonTool.setActive(false);
-      this.cutTool.setActive(false);
-      this.$store.commit("map/setCurrentMeasurement", null);
-      this.getVSourceByName("Draw Tool").clear();
-    }
-  }
-};
-</script>
--- a/client/src/components/map/toolbar/Pdftool.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-<template>
-  <div
-    @click="$store.commit('application/showPdfTool', !showPdfTool)"
-    class="toolbar-button"
-  >
-    <font-awesome-icon
-      icon="file-pdf"
-      :class="{ 'text-info': showPdfTool }"
-    ></font-awesome-icon>
-  </div>
-</template>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import { mapState } from "vuex";
-
-export default {
-  name: "pdftool",
-  computed: {
-    ...mapState("application", ["showPdfTool"])
-  }
-};
-</script>
--- a/client/src/components/map/toolbar/Polygontool.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-<template>
-  <div @click="togglePolygonTool" class="toolbar-button">
-    <font-awesome-icon
-      icon="draw-polygon"
-      :class="{ 'text-info': polygonTool && polygonTool.getActive() }"
-    ></font-awesome-icon>
-  </div>
-</template>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import { mapState, mapGetters } from "vuex";
-
-export default {
-  name: "polygontool",
-  computed: {
-    ...mapGetters("map", ["getLayerByName"]),
-    ...mapState("map", ["lineTool", "polygonTool", "cutTool"])
-  },
-  methods: {
-    togglePolygonTool() {
-      this.polygonTool.setActive(!this.polygonTool.getActive());
-      this.lineTool.setActive(false);
-      this.cutTool.setActive(false);
-      this.$store.commit("map/setCurrentMeasurement", null);
-      this.getVSourceByName("Draw Tool").clear();
-    }
-  }
-};
-</script>
--- a/client/src/components/map/toolbar/Profiles.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-<template>
-  <div
-    @click="$store.commit('application/showProfiles', !showProfiles)"
-    class="toolbar-button"
-  >
-    <font-awesome-icon
-      icon="chart-area"
-      :class="{ 'text-info': showProfiles }"
-    ></font-awesome-icon>
-  </div>
-</template>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import { mapState } from "vuex";
-
-export default {
-  name: "profiles",
-  computed: {
-    ...mapState("application", ["showProfiles"])
-  }
-};
-</script>
--- a/client/src/components/map/toolbar/Toolbar.vue	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,142 +0,0 @@
-<template>
-  <div class="ml-2">
-    <div
-      :class="
-        'rounded-top toolbar toolbar-' +
-          (expandToolbar ? 'expanded' : 'collapsed')
-      "
-    >
-      <Identify></Identify>
-      <Layers></Layers>
-      <Profiles></Profiles>
-      <Linetool></Linetool>
-      <Polygontool></Polygontool>
-      <Pdftool></Pdftool>
-    </div>
-    <div
-      @click="$store.commit('application/expandToolbar', !expandToolbar)"
-      class="toolbar-button toolbar-toggle rounded-bottom bg-info text-white"
-    >
-      <font-awesome-icon
-        :icon="expandToolbar ? 'angle-up' : 'angle-down'"
-      ></font-awesome-icon>
-    </div>
-  </div>
-</template>
-
-<style lang="scss">
-// not scoped to affect nested components
-// doen't work when put in application/assets/application.sass... why??? o_O
-.toolbar {
-  box-shadow: 0 0.1rem 0.5rem rgba(0, 0, 0, 0.2);
-  overflow: hidden;
-  transition: max-height 0.4s;
-  margin-bottom: auto;
-}
-
-.toolbar-collapsed {
-  max-height: 6rem;
-}
-
-.toolbar-expanded {
-  max-height: 100%;
-}
-
-.toolbar-button {
-  opacity: 0.96;
-  color: #666;
-  height: 2rem;
-  width: 2rem;
-  align-items: center;
-  justify-content: center;
-  display: flex;
-  background: #fff;
-  border-bottom: 1px solid #dee2e6;
-  z-index: 2;
-  pointer-events: auto;
-  position: relative;
-  overflow: hidden;
-}
-
-.toolbar-button:last-child {
-  border-bottom: none;
-}
-
-.toolbar-button .inverted {
-  color: #17a2b8;
-}
-
-.toolbar-button .grey {
-  color: #ddd;
-}
-
-.toolbar-button .indicator {
-  color: #fff;
-  background: #17a2b8;
-  position: absolute;
-  bottom: -14px;
-  left: -14px;
-  padding: 2px 4px 1px;
-  font-size: 11px;
-  line-height: 11px;
-  border-top-right-radius: 0.25rem;
-  transition: bottom 0.3s, left 0.3s;
-}
-
-.toolbar-button .indicator.show {
-  left: 0;
-  bottom: 0;
-}
-
-.toolbar-toggle {
-  height: 1.2rem;
-  border-bottom: none;
-}
-</style>
-
-<script>
-/* This is Free Software under GNU Affero General Public License v >= 3.0
- * without warranty, see README.md and license for details.
- *
- * SPDX-License-Identifier: AGPL-3.0-or-later
- * License-Filename: LICENSES/AGPL-3.0.txt
- *
- * Copyright (C) 2018 by via donau
- *   – Österreichische Wasserstraßen-Gesellschaft mbH
- * Software engineering by Intevation GmbH
- *
- * Author(s):
- * Markus Kottländer <markus.kottlaender@intevation.de>
- */
-import { mapState, mapGetters } from "vuex";
-
-export default {
-  name: "toolbar",
-  components: {
-    Identify: () => import("./Identify.vue"),
-    Layers: () => import("./Layers.vue"),
-    Linetool: () => import("./Linetool.vue"),
-    Polygontool: () => import("./Polygontool.vue"),
-    Profiles: () => import("./Profiles.vue"),
-    Pdftool: () => import("./Pdftool.vue")
-  },
-  computed: {
-    ...mapGetters("map", ["getVSourceByName"]),
-    ...mapState("map", ["lineTool", "polygonTool", "cutTool"]),
-    ...mapState("application", ["expandToolbar"])
-  },
-  mounted() {
-    window.addEventListener("keydown", e => {
-      // Escape
-      if (e.keyCode === 27) {
-        this.lineTool.setActive(false);
-        this.polygonTool.setActive(false);
-        this.cutTool.setActive(false);
-        this.$store.commit("map/setCurrentMeasurement", null);
-        this.$store.dispatch("map/enableIdentifyTool");
-        this.getVSourceByName("Draw Tool").clear();
-      }
-    });
-  }
-};
-</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/staging/Staging.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,191 @@
+<template>
+  <div class="w-90 stagingcard">
+    <h6 class="mb-0 py-2 px-3 border-bottom d-flex align-items-center">
+      <font-awesome-icon
+        class="mr-2"
+        icon="clipboard-check"
+      ></font-awesome-icon>
+      <translate>Staging Area</translate>
+    </h6>
+    <div class="mt-3 pl-3 pr-3">
+      <div class="mt-3 text-left flex-row d-flex border-bottom">
+        <div class="header text-left name"><translate>Name</translate></div>
+        <div class="header text-left type"><translate>Type</translate></div>
+        <div class="header text-left date"><translate>Date</translate></div>
+        <div class="header text-left imported">
+          <translate>Imported</translate>
+        </div>
+        <div class="header text-left username">
+          <translate>Username</translate>
+        </div>
+        <div class="ml-3 controls"></div>
+      </div>
+      <div class="mt-3" v-if="filteredData.length > 0">
+        <StagingDetail
+          class="mb-3 border-bottom"
+          :key="data.id"
+          v-for="data in filteredData"
+          :data="data"
+        ></StagingDetail>
+      </div>
+    </div>
+    <div class="mt-3 p-3" v-if="filteredData.length > 0">
+      <button @click="confirmReview" class="confirm-button btn btn-info">
+        <translate>Confirm</translate>
+      </button>
+    </div>
+    <div v-else class="mr-auto ml-auto"><translate>No results.</translate></div>
+    <div class="mt-1 p-3">
+      <button @click="loadData" class="refresh btn btn-dark">Refresh</button>
+    </div>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ * Markus Kottländer <markus@intevation.de>
+ */
+import { mapState } from "vuex";
+import { HTTP } from "@/lib/http.js";
+import { displayError, displayInfo } from "@/lib/errors.js";
+import { STATES } from "@/store/imports.js";
+
+export default {
+  data() {
+    return {};
+  },
+  components: {
+    StagingDetail: () => import("./StagingDetail")
+  },
+  mounted() {
+    this.loadData();
+  },
+  computed: {
+    ...mapState("application", ["searchQuery"]),
+    ...mapState("imports", ["staging"]),
+    filteredData() {
+      return this.staging.filter(data => {
+        const result = [data.id + "", data.enqueued, data.kind, data.user].some(
+          x => x.toLowerCase().includes(this.searchQuery.toLowerCase())
+        );
+        return result;
+      });
+    }
+  },
+  methods: {
+    loadData() {
+      this.$store.dispatch("imports/getStaging").catch(error => {
+        const { status, data } = error.response;
+        displayError({
+          title: "Backend Error",
+          message: `${status}: ${data.message || data}`
+        });
+      });
+    },
+    confirmReview() {
+      const reviewResults = this.staging
+        .filter(x => x.status !== STATES.NEEDSAPPROVAL)
+        .map(r => {
+          return {
+            id: r.id,
+            state: r.status
+          };
+        });
+      if (!reviewResults.length) return;
+      HTTP.patch("/imports", reviewResults, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-type": "application/json"
+        }
+      })
+        .then(response => {
+          const messages = response.data
+            .map(x => {
+              if (x.message) return x.message;
+              if (x.error) return x.error;
+            })
+            .join("\n\n");
+          displayInfo({
+            title: "Staging Area",
+            message: messages,
+            options: {
+              timeout: 0,
+              buttons: [{ text: "Ok", action: null, bold: true }]
+            }
+          });
+          this.loadData();
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: "Backend Error",
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    }
+  },
+  STATES: STATES
+};
+</script>
+
+<style lang="scss" scoped>
+.name {
+  width: 180px;
+}
+
+.date {
+  width: 90px;
+}
+
+.type {
+  width: 40px;
+}
+
+.imported {
+  width: 90px;
+}
+
+.username {
+  width: 150px;
+}
+
+.controls {
+  width: 60px;
+}
+
+.refresh {
+  position: absolute;
+  left: $offset;
+  bottom: $offset;
+}
+.table th,
+td {
+  font-size: 0.9rem;
+  border-top: 0px !important;
+  border-bottom-width: 1px;
+  text-align: left;
+  padding: 0.5rem !important;
+}
+
+.stagingcard {
+  position: relative;
+  min-height: 150px;
+}
+
+.confirm-button {
+  position: absolute;
+  right: $offset;
+  bottom: $offset;
+}
+</style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/staging/StagingDetail.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,334 @@
+<template>
+  <div :class="detail">
+    <div class="d-flex flex-row">
+      <div class="mt-auto d-flex flex-row mb-auto small name text-left">
+        <a
+          v-if="!isBottleneck(data.kind.toUpperCase())"
+          @click="zoomTo()"
+          href="#"
+          >{{ data.summary.bottleneck }}</a
+        >
+        <span v-else class="text-left"
+          ><translate>Bottlenecks</translate> ({{
+            data.summary.bottlenecks.length
+          }})</span
+        >
+      </div>
+      <div class="mt-auto mb-auto small text-left type">
+        {{ data.kind.toUpperCase() }}
+      </div>
+      <div class="mt-auto mb-auto small text-left date">
+        {{ formatSurveyDate(data.summary.date) }}
+      </div>
+      <div class="mt-auto mb-auto small text-left imported">
+        {{ formatSurveyDate(data.enqueued.split("T")[0]) }}
+      </div>
+      <div class="mt-auto mb-auto small text-left username">
+        {{ data.user }}
+      </div>
+      <div class="controls d-flex flex-row justify-content-end">
+        <div>
+          <button
+            :class="{
+              'ml-3': true,
+              'mr-3': true,
+              btn: true,
+              'btn-sm': true,
+              'btn-outline-success': needsApproval(data) || isRejected(data),
+              'btn-success': isApproved(data)
+            }"
+            @click="toggleApproval(data.id, $options.STATES.APPROVED)"
+          >
+            <font-awesome-icon icon="check"></font-awesome-icon>
+          </button>
+        </div>
+        <div>
+          <button
+            :class="{
+              'mr-3': true,
+              btn: true,
+              'btn-sm': true,
+              'btn-outline-danger': needsApproval(data) || isApproved(data),
+              'btn-danger': isRejected(data)
+            }"
+            @click="toggleApproval(data.id, $options.STATES.REJECTED)"
+          >
+            <font-awesome-icon icon="times"></font-awesome-icon>
+          </button>
+        </div>
+        <div v-if="isBottleneck(data.kind.toUpperCase())">
+          <div
+            @click="showDetails()"
+            class="mt-auto mb-auto text-info text-left"
+          >
+            <font-awesome-icon
+              v-if="show"
+              icon="angle-up"
+              fixed-width
+            ></font-awesome-icon>
+            <font-awesome-icon
+              v-if="loading"
+              icon="spinner"
+              fixed-width
+            ></font-awesome-icon>
+            <font-awesome-icon
+              v-if="!show && !loading"
+              icon="angle-down"
+              fixed-width
+            ></font-awesome-icon>
+          </div>
+        </div>
+        <div v-else class="empty"></div>
+      </div>
+    </div>
+    <div
+      v-for="(bottleneck, index) in bottlenecks"
+      :key="index"
+      class="d-flex flex-row"
+      v-if="show && bottlenecks.length > 0"
+    >
+      <div class="d-flex flex-column">
+        <div class="d-flex flex-row">
+          <a @click="moveToBottleneck(index)" class="small" href="#">{{
+            bottleneck.properties.objnam
+          }}</a>
+          <div
+            @click="showFull = !showFull"
+            class="small mt-auto mb-auto text-info text-left"
+          >
+            <font-awesome-icon
+              v-if="showFull"
+              icon="angle-up"
+              fixed-width
+            ></font-awesome-icon>
+            <font-awesome-icon
+              v-if="!showFull"
+              icon="angle-down"
+              fixed-width
+            ></font-awesome-icon>
+          </div>
+        </div>
+
+        <div class="d-flex flex-row" v-if="showFull">
+          <table>
+            <tr
+              v-for="(info, index) in Object.keys(bottleneck.properties)"
+              :key="index"
+              class="mr-1 small text-muted"
+            >
+              <td class="condensed text-left">{{ info }}</td>
+              <td class="condensed pl-3 text-left">
+                {{ bottleneck.properties[info] }}
+              </td>
+            </tr>
+          </table>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+
+import { formatSurveyDate } from "@/lib/date.js";
+import { STATES } from "@/store/imports.js";
+import { HTTP } from "@/lib/http";
+import { WFS } from "ol/format.js";
+import { or as orFilter, equalTo as equalToFilter } from "ol/format/filter.js";
+import { displayError } from "@/lib/errors.js";
+import { mapState } from "vuex";
+import center from "@turf/center";
+
+export default {
+  name: "stagingdetail",
+  props: ["data"],
+  data() {
+    return {
+      showFull: false,
+      show: false,
+      loading: false,
+      bottlenecks: []
+    };
+  },
+  mounted() {
+    this.bottlenecks = [];
+    if (this.open) this.showDetails();
+  },
+  computed: {
+    ...mapState("imports", ["importToReview"]),
+    open() {
+      return this.importToReview == this.data.id;
+    },
+    detail() {
+      return [
+        "pb-2",
+        "pt-2",
+        "d-flex",
+        "flex-column",
+        "w-100",
+        {
+          highlight: this.open && this.needsApproval(this.data)
+        }
+      ];
+    }
+  },
+  watch: {
+    open() {
+      const { review } = this.$route.query;
+      if (review) {
+        this.showDetails();
+      }
+    }
+  },
+  methods: {
+    showDetails() {
+      if (this.data.kind.toUpperCase() !== "BN") return;
+      if (this.show) {
+        this.show = false;
+        return;
+      }
+      if (this.bottlenecks.length > 0) {
+        this.show = true;
+        return;
+      }
+      this.loading = true;
+      const generateFilter = () => {
+        const { bottlenecks } = this.data.summary;
+        if (bottlenecks.length === 1)
+          return equalToFilter("bottleneck_id", bottlenecks[0]);
+        const orExpressions = bottlenecks.map(x => {
+          equalToFilter("bottleneck_id", x);
+        });
+        return orFilter(...orExpressions);
+      };
+      const filterExpression = generateFilter();
+      const bottleneckFeatureCollectionRequest = new WFS().writeGetFeature({
+        srsName: "EPSG:4326",
+        featureNS: "gemma",
+        featurePrefix: "gemma",
+        featureTypes: ["bottlenecks"],
+        outputFormat: "application/json",
+        filter: filterExpression
+      });
+      HTTP.post(
+        "/internal/wfs",
+        new XMLSerializer().serializeToString(
+          bottleneckFeatureCollectionRequest
+        ),
+        {
+          headers: {
+            "X-Gemma-Auth": localStorage.getItem("token"),
+            "Content-type": "text/xml; charset=UTF-8"
+          }
+        }
+      )
+        .then(response => {
+          this.bottlenecks = response.data.features;
+          this.show = true;
+          this.loading = false;
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    },
+    isBottleneck(kind) {
+      return kind === "BN";
+    },
+    formatSurveyDate(date) {
+      return formatSurveyDate(date);
+    },
+    needsApproval(item) {
+      return item.status === STATES.NEEDSAPPROVAL;
+    },
+    isRejected(item) {
+      return item.status === STATES.REJECTED;
+    },
+    isApproved(item) {
+      return item.status === STATES.APPROVED;
+    },
+    moveToBottleneck(index) {
+      const { coordinates } = center(this.bottlenecks[index]).geometry;
+      this.moveMap(coordinates);
+    },
+    moveMap(coordinates) {
+      this.$store.commit("map/moveMap", {
+        coordinates: coordinates,
+        zoom: 17,
+        preventZoomOut: true
+      });
+    },
+    zoomTo() {
+      const { lat, lon, bottleneck, date } = this.data.summary;
+      const coordinates = [lat, lon];
+      this.moveMap(coordinates);
+      this.$store
+        .dispatch("bottlenecks/setSelectedBottleneck", bottleneck)
+        .then(() => {
+          this.$store.commit("bottlenecks/setSelectedSurveyByDate", date);
+        });
+    },
+    toggleApproval(id, newStatus) {
+      this.$store.commit("imports/toggleApproval", {
+        id: id,
+        newStatus: newStatus
+      });
+    }
+  },
+  STATES: STATES
+};
+</script>
+
+<style lang="scss" scoped>
+.highlight {
+  background-color: #f9f9f9;
+}
+
+.condensed {
+  font-stretch: condensed;
+}
+
+.empty {
+  margin-right: 20px;
+}
+
+.name {
+  width: 180px;
+}
+
+.date {
+  width: 90px;
+}
+
+.type {
+  width: 40px;
+}
+
+.imported {
+  width: 90px;
+}
+
+.username {
+  width: 150px;
+}
+
+.controls {
+  width: 60px;
+}
+</style>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/toolbar/Identify.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,50 @@
+<template>
+  <div
+    @click="$store.commit('application/showIdentify', !showIdentify)"
+    class="toolbar-button"
+  >
+    <font-awesome-icon
+      icon="info"
+      :class="{ 'text-info': showIdentify }"
+    ></font-awesome-icon>
+    <span
+      :class="[
+        'indicator',
+        {
+          show:
+            !showIdentify && (identifiedFeatures.length || currentMeasurement)
+        }
+      ]"
+    >
+      {{ badgeCount }}
+    </span>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState } from "vuex";
+
+export default {
+  name: "identify",
+  computed: {
+    ...mapState("application", ["showIdentify"]),
+    ...mapState("map", ["identifiedFeatures", "currentMeasurement"]),
+    badgeCount() {
+      return this.identifiedFeatures.length + !!this.currentMeasurement;
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/toolbar/Layers.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,35 @@
+<template>
+  <div
+    @click="$store.commit('application/showLayers', !showLayers)"
+    class="toolbar-button"
+  >
+    <font-awesome-icon
+      icon="layer-group"
+      :class="{ 'text-info': showLayers }"
+    ></font-awesome-icon>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState } from "vuex";
+
+export default {
+  name: "layers",
+  computed: {
+    ...mapState("application", ["showLayers"])
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/toolbar/Linetool.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,42 @@
+<template>
+  <div @click="toggleLineTool" class="toolbar-button">
+    <font-awesome-icon
+      icon="ruler"
+      :class="{ 'text-info': lineTool && lineTool.getActive() }"
+    ></font-awesome-icon>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState, mapGetters } from "vuex";
+
+export default {
+  name: "linetool",
+  computed: {
+    ...mapGetters("map", ["getLayerByName"]),
+    ...mapState("map", ["lineTool", "polygonTool", "cutTool"])
+  },
+  methods: {
+    toggleLineTool() {
+      this.lineTool.setActive(!this.lineTool.getActive());
+      this.polygonTool.setActive(false);
+      this.cutTool.setActive(false);
+      this.$store.commit("map/setCurrentMeasurement", null);
+      this.getVSourceByName("Draw Tool").clear();
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/toolbar/Pdftool.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,35 @@
+<template>
+  <div
+    @click="$store.commit('application/showPdfTool', !showPdfTool)"
+    class="toolbar-button"
+  >
+    <font-awesome-icon
+      icon="file-pdf"
+      :class="{ 'text-info': showPdfTool }"
+    ></font-awesome-icon>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState } from "vuex";
+
+export default {
+  name: "pdftool",
+  computed: {
+    ...mapState("application", ["showPdfTool"])
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/toolbar/Polygontool.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,42 @@
+<template>
+  <div @click="togglePolygonTool" class="toolbar-button">
+    <font-awesome-icon
+      icon="draw-polygon"
+      :class="{ 'text-info': polygonTool && polygonTool.getActive() }"
+    ></font-awesome-icon>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState, mapGetters } from "vuex";
+
+export default {
+  name: "polygontool",
+  computed: {
+    ...mapGetters("map", ["getLayerByName"]),
+    ...mapState("map", ["lineTool", "polygonTool", "cutTool"])
+  },
+  methods: {
+    togglePolygonTool() {
+      this.polygonTool.setActive(!this.polygonTool.getActive());
+      this.lineTool.setActive(false);
+      this.cutTool.setActive(false);
+      this.$store.commit("map/setCurrentMeasurement", null);
+      this.getVSourceByName("Draw Tool").clear();
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/toolbar/Profiles.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,35 @@
+<template>
+  <div
+    @click="$store.commit('application/showProfiles', !showProfiles)"
+    class="toolbar-button"
+  >
+    <font-awesome-icon
+      icon="chart-area"
+      :class="{ 'text-info': showProfiles }"
+    ></font-awesome-icon>
+  </div>
+</template>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState } from "vuex";
+
+export default {
+  name: "profiles",
+  computed: {
+    ...mapState("application", ["showProfiles"])
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/toolbar/Toolbar.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,142 @@
+<template>
+  <div class="ml-2">
+    <div
+      :class="
+        'rounded-top toolbar toolbar-' +
+          (expandToolbar ? 'expanded' : 'collapsed')
+      "
+    >
+      <Identify></Identify>
+      <Layers></Layers>
+      <Profiles></Profiles>
+      <Linetool></Linetool>
+      <Polygontool></Polygontool>
+      <Pdftool></Pdftool>
+    </div>
+    <div
+      @click="$store.commit('application/expandToolbar', !expandToolbar)"
+      class="toolbar-button toolbar-toggle rounded-bottom bg-info text-white"
+    >
+      <font-awesome-icon
+        :icon="expandToolbar ? 'angle-up' : 'angle-down'"
+      ></font-awesome-icon>
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+// not scoped to affect nested components
+// doen't work when put in application/assets/application.sass... why??? o_O
+.toolbar {
+  box-shadow: 0 0.1rem 0.5rem rgba(0, 0, 0, 0.2);
+  overflow: hidden;
+  transition: max-height 0.4s;
+  margin-bottom: auto;
+}
+
+.toolbar-collapsed {
+  max-height: 6rem;
+}
+
+.toolbar-expanded {
+  max-height: 100%;
+}
+
+.toolbar-button {
+  opacity: 0.96;
+  color: #666;
+  height: 2rem;
+  width: 2rem;
+  align-items: center;
+  justify-content: center;
+  display: flex;
+  background: #fff;
+  border-bottom: 1px solid #dee2e6;
+  z-index: 2;
+  pointer-events: auto;
+  position: relative;
+  overflow: hidden;
+}
+
+.toolbar-button:last-child {
+  border-bottom: none;
+}
+
+.toolbar-button .inverted {
+  color: #17a2b8;
+}
+
+.toolbar-button .grey {
+  color: #ddd;
+}
+
+.toolbar-button .indicator {
+  color: #fff;
+  background: #17a2b8;
+  position: absolute;
+  bottom: -14px;
+  left: -14px;
+  padding: 2px 4px 1px;
+  font-size: 11px;
+  line-height: 11px;
+  border-top-right-radius: 0.25rem;
+  transition: bottom 0.3s, left 0.3s;
+}
+
+.toolbar-button .indicator.show {
+  left: 0;
+  bottom: 0;
+}
+
+.toolbar-toggle {
+  height: 1.2rem;
+  border-bottom: none;
+}
+</style>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Markus Kottländer <markus.kottlaender@intevation.de>
+ */
+import { mapState, mapGetters } from "vuex";
+
+export default {
+  name: "toolbar",
+  components: {
+    Identify: () => import("./Identify.vue"),
+    Layers: () => import("./Layers.vue"),
+    Linetool: () => import("./Linetool.vue"),
+    Polygontool: () => import("./Polygontool.vue"),
+    Profiles: () => import("./Profiles.vue"),
+    Pdftool: () => import("./Pdftool.vue")
+  },
+  computed: {
+    ...mapGetters("map", ["getVSourceByName"]),
+    ...mapState("map", ["lineTool", "polygonTool", "cutTool"]),
+    ...mapState("application", ["expandToolbar"])
+  },
+  mounted() {
+    window.addEventListener("keydown", e => {
+      // Escape
+      if (e.keyCode === 27) {
+        this.lineTool.setActive(false);
+        this.polygonTool.setActive(false);
+        this.cutTool.setActive(false);
+        this.$store.commit("map/setCurrentMeasurement", null);
+        this.$store.dispatch("map/enableIdentifyTool");
+        this.getVSourceByName("Draw Tool").clear();
+      }
+    });
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/usermanagement/Passwordfield.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,73 @@
+<template>
+  <div class="w-100">
+    <div class="d-flex flex-row">
+      <label for="password">{{ this.label }}</label>
+    </div>
+    <div class="d-flex d-row">
+      <input
+        :type="isPasswordVisible"
+        @change="fieldChanged"
+        class="form-control"
+        :placeholder="placeholder"
+        :required="required"
+      />
+      <span class="input-group-text" @click="showPassword">
+        <font-awesome-icon
+          :icon="readablePassword ? 'eye-slash' : 'eye'"
+        ></font-awesome-icon>
+      </span>
+    </div>
+    <div v-show="passworderrors" class="text-danger">
+      <small>
+        <font-awesome-icon icon="exclamation-triangle"></font-awesome-icon>
+        {{ this.passworderrors }}
+      </small>
+    </div>
+  </div>
+</template>
+
+<style>
+/* FIXME does not work here, unclear why, so added to Login.vue
+input[type="password"]::-ms-reveal {
+  display: none;
+} */
+</style>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+export default {
+  name: "passwordfield",
+  props: ["model", "placeholder", "label", "passworderrors", "required"],
+  data() {
+    return {
+      password: "",
+      readablePassword: false
+    };
+  },
+  methods: {
+    showPassword() {
+      this.readablePassword = !this.readablePassword;
+    },
+    fieldChanged(e) {
+      this.$emit("fieldchange", e.target.value);
+    }
+  },
+  computed: {
+    isPasswordVisible() {
+      return this.readablePassword ? "text" : "password";
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/usermanagement/Userdetail.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,392 @@
+<template>
+  <div class="userdetails mt-3 shadow fadeIn animated card">
+    <h6
+      class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
+    >
+      {{ this.cardHeader }}
+      <span @click="closeDetailview" class="closebutton">
+        <font-awesome-icon icon="times"></font-awesome-icon>
+      </span>
+    </h6>
+    <div class="card-body">
+      <form @submit.prevent="save" class="ml-3">
+        <div class="formfields">
+          <div v-if="currentUser.isNew" class="form-group row">
+            <label for="user"> <translate>Username</translate> </label>
+            <input
+              type="user"
+              :placeholder="userNamePlaceholder"
+              class="form-control form-control-sm"
+              id="user"
+              aria-describedby="userHelp"
+              v-model="currentUser.user"
+            />
+            <div v-show="errors.user" class="text-danger">
+              <small>
+                <font-awesome-icon
+                  icon="exclamation-triangle"
+                ></font-awesome-icon>
+                {{ errors.user }}
+              </small>
+            </div>
+          </div>
+          <div class="form-group row">
+            <label for="country"> <translate>Country</translate> </label>
+            <select
+              class="form-control form-control-sm"
+              v-on:change="validateCountry"
+              v-model="currentUser.country"
+            >
+              <option disabled value>
+                <translate>Please select one</translate>
+              </option>
+              <option
+                v-for="country in countries"
+                v-bind:value="country"
+                v-bind:key="country"
+                >{{ country }}</option
+              >
+            </select>
+            <div v-show="errors.country" class="text-danger">
+              <small>
+                <font-awesome-icon
+                  icon="exclamation-triangle"
+                ></font-awesome-icon>
+                {{ errors.country }}
+              </small>
+            </div>
+          </div>
+          <div class="form-group row">
+            <label for="email"> <translate>Email address</translate> </label>
+            <input
+              type="email"
+              v-on:change="validateEmailaddress"
+              class="form-control form-control-sm"
+              id="email"
+              aria-describedby="emailHelp"
+              v-model="currentUser.email"
+            />
+            <div v-show="errors.email" class="text-danger">
+              <small>
+                <font-awesome-icon
+                  icon="exclamation-triangle"
+                ></font-awesome-icon>
+                {{ errors.email }}
+              </small>
+            </div>
+          </div>
+          <div class="form-group row">
+            <label for="role"> <translate>Role</translate> </label>
+            <select
+              class="form-control form-control-sm"
+              v-on:change="validateRole"
+              v-model="currentUser.role"
+            >
+              <option disabled value>
+                <translate>Please select one</translate>
+              </option>
+              <option value="sys_admin">
+                <translate>Sysadmin</translate>
+              </option>
+              <option value="waterway_admin">
+                <translate>Waterway Admin</translate>
+              </option>
+              <option value="waterway_user">
+                <translate>Waterway User</translate>
+              </option>
+            </select>
+            <div v-show="errors.role" class="text-danger">
+              <small>
+                <font-awesome-icon
+                  icon="exclamation-triangle"
+                ></font-awesome-icon>
+                {{ errors.role }}
+              </small>
+            </div>
+          </div>
+          <div class="form-group row">
+            <PasswordField
+              @fieldchange="passwordChanged"
+              :placeholder="passwordPlaceholder"
+              :label="passwordLabel"
+              :passworderrors="errors.password"
+            ></PasswordField>
+          </div>
+          <div class="form-group row">
+            <PasswordField
+              @fieldchange="passwordReChanged"
+              :placeholder="passwordRePlaceholder"
+              :label="passwordReLabel"
+              :passworderrors="errors.passwordre"
+            ></PasswordField>
+          </div>
+        </div>
+        <div>
+          <button
+            type="submit"
+            :disabled="submitted"
+            class="shadow-sm btn btn-info submit-button"
+          >
+            <translate>Submit</translate>
+          </button>
+        </div>
+        <div
+          v-if="currentUser.role != 'waterway_user'"
+          class="form-group row d-flex flex-row justify-content-start mailbutton"
+        >
+          <a @click="sendTestMail" class="btn btn-light">
+            <font-awesome-icon icon="paper-plane"></font-awesome-icon>
+            <translate>Send testmail</translate>
+          </a>
+          <div v-if="mailsent"><translate>Mail was sent</translate></div>
+        </div>
+      </form>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.submit-button {
+  position: absolute;
+  right: $offset;
+  bottom: $offset;
+}
+.mailbutton {
+  width: 12vw;
+  position: absolute;
+  left: $large-offset;
+  bottom: 0;
+}
+
+.formfields {
+  width: 60%;
+}
+
+.userdetails {
+  height: 600px;
+  margin-top: $offset;
+  margin-left: $offset;
+  margin-right: $offset;
+}
+
+form {
+  font-size: $smaller;
+}
+</style>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+import { HTTP } from "@/lib/http";
+import { displayError } from "@/lib/errors.js";
+import { mapState } from "vuex";
+
+const emptyErrormessages = () => {
+  return {
+    email: "",
+    country: "",
+    role: "",
+    password: "",
+    passwordre: ""
+  };
+};
+
+const isEmailValid = email => {
+  /**
+   *
+   * For convenience purposes the same regex used as in the go code
+   * cf. types.go
+   *
+   */
+  // eslint-disable-next-line
+  return /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(
+    email
+  );
+};
+
+const violatedPasswordRules = password => {
+  return (
+    // rules according to issue 70
+    password.length < 7 ||
+    /\W/.test(password) == false ||
+    /\d/.test(password) == false
+  );
+};
+
+export default {
+  name: "userdetail",
+  components: {
+    PasswordField: () => import("./Passwordfield")
+  },
+  data() {
+    return {
+      mailsent: false,
+      passwordLabel: this.$gettext("Password"),
+      passwordReLabel: this.$gettext("Repeat Password"),
+      passwordPlaceholder: this.$gettext("password"),
+      passwordRePlaceholder: this.$gettext("password again"),
+      password: "",
+      passwordre: "",
+      currentUser: {},
+      path: null,
+      submitted: false,
+      errors: {
+        email: "",
+        country: "",
+        role: "",
+        password: "",
+        passwordre: ""
+      }
+    };
+  },
+  mounted() {
+    this.currentUser = { ...this.user };
+    this.path = this.user.name;
+  },
+  watch: {
+    user() {
+      this.currentUser = { ...this.user };
+      this.path = this.user.name;
+      this.clearPassword();
+      this.clearErrors();
+    }
+  },
+  computed: {
+    cardHeader() {
+      if (this.currentUser.isNew) return "N.N";
+      return this.currentUser.user;
+    },
+    userNamePlaceholder() {
+      if (this.currentUser.isNew) return "N.N";
+      return "";
+    },
+    ...mapState("application", ["countries"]),
+    user() {
+      return this.$store.getters["usermanagement/currentUser"];
+    },
+    isFormValid() {
+      return (
+        isEmailValid(this.currentUser.email) &&
+        this.currentUser.country &&
+        this.password === this.passwordre &&
+        (this.password === "" || !violatedPasswordRules(this.password))
+      );
+    }
+  },
+  methods: {
+    sendTestMail() {
+      if (this.mailsent) return;
+      HTTP.get("/testmail/" + this.currentUser.user, {
+        headers: {
+          "X-Gemma-Auth": localStorage.getItem("token"),
+          "Content-type": "text/xml; charset=UTF-8"
+        }
+      })
+        .then(() => {
+          this.mailsent = true;
+        })
+        .catch(error => {
+          this.loginFailed = true;
+          this.submitted = false;
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    },
+    passwordChanged(value) {
+      this.password = value;
+      this.validatePassword();
+    },
+    passwordReChanged(value) {
+      this.passwordre = value;
+      this.validatePassword();
+    },
+    clearErrors() {
+      this.errors = emptyErrormessages();
+    },
+    clearPassword() {
+      this.password = "";
+      this.passwordre = "";
+    },
+    closeDetailview() {
+      this.$store.commit("usermanagement/clearCurrentUser");
+      this.$store.commit("usermanagement/setUserDetailsInvisible");
+    },
+    validateCountry() {
+      this.errors.country = this.currentUser.country
+        ? ""
+        : this.$gettext("Please choose a country");
+    },
+    validateRole() {
+      this.errors.role = this.currentUser.role
+        ? ""
+        : this.$gettext("Please choose a role");
+    },
+    validatePassword() {
+      this.errors.passwordre =
+        this.password === this.passwordre
+          ? ""
+          : this.$gettext("Passwords do not match!");
+      this.errors.password =
+        this.password === "" || !violatedPasswordRules(this.password)
+          ? ""
+          : this.$gettext(
+              "Password should at least be 8 char long including 1 digit and 1 special char like $"
+            );
+    },
+    validateEmailaddress() {
+      this.errors.email = isEmailValid(this.currentUser.email)
+        ? ""
+        : this.$gettext("invalid email");
+    },
+    validate() {
+      this.validateCountry();
+      this.validateRole();
+      this.validatePassword();
+      this.validateEmailaddress();
+    },
+    save() {
+      this.validate();
+      if (!this.isFormValid) return;
+      if (this.password) this.currentUser.password = this.password;
+      this.submitted = true;
+      this.$store
+        .dispatch("usermanagement/saveCurrentUser", {
+          path: this.user.user,
+          user: this.currentUser
+        })
+        .then(() => {
+          this.submitted = false;
+          this.$store.dispatch("usermanagement/loadUsers").catch(error => {
+            const { status, data } = error.response;
+            displayError({
+              title: this.$gettext("Backend Error"),
+              message: `${status}: ${data.message || data}`
+            });
+          });
+        })
+        .catch(error => {
+          this.submitted = false;
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Error while saving user"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    }
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/components/usermanagement/Usermanagement.vue	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,351 @@
+<template>
+  <div class="main d-flex flex-row">
+    <Spacer></Spacer>
+    <div class="d-flex content flex-column">
+      <div class="d-flex flex-row">
+        <div :class="userlistStyle">
+          <div class="card">
+            <h6
+              class="mb-0 py-2 px-3 border-bottom d-flex text-info align-items-center"
+            >
+              <font-awesome-icon
+                icon="users-cog"
+                class="mr-2 fa-fw"
+              ></font-awesome-icon>
+              <translate class="headline">Users</translate>
+            </h6>
+            <div class="card-body">
+              <table id="datatable" :class="tableStyle">
+                <thead>
+                  <tr>
+                    <th scope="col" @click="sortBy('user')">
+                      <span
+                        >Username&nbsp;
+                        <font-awesome-icon
+                          v-if="sortCriterion == 'user'"
+                          icon="angle-down"
+                        ></font-awesome-icon>
+                      </span>
+                    </th>
+                    <th scope="col" @click="sortBy('country')">
+                      <span
+                        >Country&nbsp;
+                        <font-awesome-icon
+                          v-if="sortCriterion == 'country'"
+                          icon="angle-down"
+                        ></font-awesome-icon>
+                      </span>
+                    </th>
+                    <th scope="col" @click="sortBy('email')">
+                      <span
+                        >Email&nbsp;
+                        <font-awesome-icon
+                          v-if="sortCriterion == 'email'"
+                          icon="angle-down"
+                        ></font-awesome-icon>
+                      </span>
+                    </th>
+                    <th scope="col" @click="sortBy('role')">
+                      <span
+                        >Role&nbsp;
+                        <font-awesome-icon
+                          v-if="sortCriterion == 'role'"
+                          icon="angle-down"
+                        ></font-awesome-icon>
+                      </span>
+                    </th>
+                    <th scope="col"></th>
+                  </tr>
+                </thead>
+                <tbody>
+                  <tr
+                    v-for="user in users"
+                    :key="user.user"
+                    @click="selectUser(user.user)"
+                  >
+                    <td>{{ user.user }}</td>
+                    <td>{{ user.country }}</td>
+                    <td>{{ user.email }}</td>
+                    <td>
+                      <font-awesome-icon
+                        v-tooltip="roleLabel(user.role)"
+                        :icon="roleIcon(user.role)"
+                        @click="deleteUser(user.user)"
+                      ></font-awesome-icon>
+                    </td>
+                    <td>
+                      <font-awesome-icon
+                        icon="trash"
+                        @click="deleteUser(user.user)"
+                      ></font-awesome-icon>
+                    </td>
+                  </tr>
+                </tbody>
+              </table>
+            </div>
+            <div class="d-flex mx-auto align-items-center">
+              <button
+                @click="prevPage"
+                v-if="this.currentPage !== 1"
+                class="mr-2 btn btn-sm btn-light align-self-center"
+              >
+                <font-awesome-icon icon="angle-left"></font-awesome-icon>
+              </button>
+              {{ this.currentPage }} / {{ this.pages }}
+              <button
+                @click="nextPage"
+                v-if="this.currentPage !== this.pages"
+                class="ml-2 btn btn-sm btn-light align-self-center"
+              >
+                <font-awesome-icon icon="angle-right"></font-awesome-icon>
+              </button>
+            </div>
+            <div class="mr-3 pb-3">
+              <button @click="addUser" class="btn btn-info addbutton shadow-sm">
+                <translate>Add User</translate>
+              </button>
+            </div>
+          </div>
+        </div>
+        <Userdetail
+          class="d-flex userdetails"
+          v-if="isUserDetailsVisible"
+        ></Userdetail>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss">
+@import "@/assets/tooltip.scss";
+
+.addbutton {
+  position: absolute;
+  bottom: $offset;
+  right: $offset;
+}
+
+.content {
+  width: 100%;
+}
+
+.userdetails {
+  width: 50%;
+}
+
+.main {
+  height: 100vh;
+}
+
+.icon {
+  font-size: large;
+}
+
+.userlist {
+  min-width: 520px;
+  height: 100%;
+}
+
+.userlistsmall {
+  width: 100%;
+}
+
+.userlistextended {
+  width: 100%;
+}
+
+.table {
+  width: 90% !important;
+  margin: auto;
+}
+
+.table th {
+  cursor: pointer;
+}
+
+.table th,
+td {
+  font-size: $smaller;
+  border-top: 0px !important;
+  text-align: left;
+  padding: $small-offset !important;
+}
+
+.table td {
+  font-size: $smaller;
+  cursor: pointer;
+}
+
+tr span {
+  display: flex;
+}
+</style>
+
+<script>
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+import store from "@/store";
+import { mapGetters, mapState } from "vuex";
+import { displayError } from "@/lib/errors.js";
+import Vue from "vue";
+import { VTooltip, VPopover, VClosePopover } from "v-tooltip";
+
+Vue.directive("tooltip", VTooltip);
+Vue.directive("close-popover", VClosePopover);
+Vue.component("v-popover", VPopover);
+
+export default {
+  name: "userview",
+  data() {
+    return {
+      sortCriterion: "user",
+      pageSize: 20,
+      currentPage: 1
+    };
+  },
+  components: {
+    Userdetail: () => import("./Userdetail"),
+    Spacer: () => import("@/components/Spacer")
+  },
+  computed: {
+    ...mapGetters("usermanagement", ["isUserDetailsVisible"]),
+    ...mapState("application", ["showSidebar"]),
+    users() {
+      let users = [...this.$store.getters["usermanagement/users"]];
+      users.sort((a, b) => {
+        if (
+          a[this.sortCriterion].toLowerCase() <
+          b[this.sortCriterion].toLowerCase()
+        )
+          return -1;
+        if (
+          a[this.sortCriterion].toLowerCase() >
+          b[this.sortCriterion].toLowerCase()
+        )
+          return 1;
+        return 0;
+      });
+      const start = (this.currentPage - 1) * this.pageSize;
+      return users.slice(start, start + this.pageSize);
+    },
+    pages() {
+      let users = [...this.$store.getters["usermanagement/users"]];
+      return Math.ceil(users.length / this.pageSize);
+    },
+    tableStyle() {
+      return {
+        table: true,
+        "table-hover": true,
+        "table-sm": this.isUserDetailsVisible,
+        fadeIn: true,
+        animated: true
+      };
+    },
+    userlistStyle() {
+      return [
+        "userlist mt-3 mr-3 shadow-xs",
+        {
+          userlistsmall: this.isUserDetailsVisible,
+          userlistextended: !this.isUserDetailsVisible
+        }
+      ];
+    }
+  },
+  methods: {
+    tween() {},
+    nextPage() {
+      if (this.currentPage < this.pages) {
+        document.querySelector("#datatable").classList.add("fadeOut");
+        setTimeout(() => {
+          document.querySelector("#datatable").classList.remove("fadeOut");
+          this.currentPage += 1;
+        }, 10);
+      }
+      return;
+    },
+    prevPage() {
+      if (this.currentPage > 0) {
+        document.querySelector("#datatable").classList.add("fadeOut");
+        setTimeout(() => {
+          document.querySelector("#datatable").classList.remove("fadeOut");
+          this.currentPage -= 1;
+        }, 10);
+      }
+      return;
+    },
+    sortBy(criterion) {
+      this.sortCriterion = criterion;
+    },
+    deleteUser(name) {
+      this.$store
+        .dispatch("usermanagement/deleteUser", { name: name })
+        .then(() => {
+          this.submitted = false;
+          this.$store.dispatch("usermanagement/loadUsers").catch(error => {
+            const { status, data } = error.response;
+            displayError({
+              title: this.$gettext("Backend Error"),
+              message: `${status}: ${data.message || data}`
+            });
+          });
+        })
+        .catch(error => {
+          const { status, data } = error.response;
+          displayError({
+            title: this.$gettext("Backend Error"),
+            message: `${status}: ${data.message || data}`
+          });
+        });
+    },
+    addUser() {
+      this.$store.commit("usermanagement/clearCurrentUser");
+      this.$store.commit("usermanagement/setUserDetailsVisible");
+    },
+    selectUser(name) {
+      const user = this.$store.getters["usermanagement/getUserByName"](name);
+      this.$store.commit("usermanagement/setCurrentUser", user);
+    },
+    roleIcon(role) {
+      if (role === "sys_admin") return "star";
+      if (role === "waterway_admin") return ["fab", "adn"];
+      return "user";
+    },
+    roleLabel(role) {
+      const labels = {
+        sys_admin: this.$gettext("System-Administrator"),
+        waterway_admin: this.$gettext("Waterway Admin"),
+        waterway_user: this.$gettext("Waterway User")
+      };
+      return labels[role];
+    }
+  },
+  beforeRouteEnter(to, from, next) {
+    store
+      .dispatch("usermanagement/loadUsers")
+      .then(next)
+      .catch(error => {
+        const { status, data } = error.response;
+        displayError({
+          title: this.$gettext("Backend Error"),
+          message: `${status}: ${data}`
+        });
+      });
+  },
+  beforeRouteLeave(to, from, next) {
+    store.commit("usermanagement/clearCurrentUser");
+    store.commit("usermanagement/setUserDetailsInvisible");
+    next();
+  }
+};
+</script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/fontawesome.js	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,100 @@
+import Vue from "vue";
+import { library } from "@fortawesome/fontawesome-svg-core";
+import {
+  faAngleDown,
+  faAngleLeft,
+  faAngleRight,
+  faAngleUp,
+  faBars,
+  faBook,
+  faChartArea,
+  faCheck,
+  faCity,
+  faClipboardCheck,
+  faClock,
+  faCloudUploadAlt,
+  faCopy,
+  faDrawPolygon,
+  faExclamationTriangle,
+  faEye,
+  faEyeSlash,
+  faFilePdf,
+  faFolderPlus,
+  faInfo,
+  faLayerGroup,
+  faMapMarkedAlt,
+  faMinus,
+  faPaperPlane,
+  faPencilAlt,
+  faPlay,
+  faPlus,
+  faPowerOff,
+  faRuler,
+  faSearch,
+  faShip,
+  faSortAmountDown,
+  faSortAmountUp,
+  faSpinner,
+  faStar,
+  faTasks,
+  faTimes,
+  faTrash,
+  faUpload,
+  faUser,
+  faUsersCog,
+  faWater,
+  faWrench
+} from "@fortawesome/free-solid-svg-icons";
+import { faAdn } from "@fortawesome/free-brands-svg-icons";
+
+library.add(
+  faAdn,
+  faAngleDown,
+  faAngleLeft,
+  faAngleRight,
+  faAngleUp,
+  faBars,
+  faBook,
+  faChartArea,
+  faCheck,
+  faCity,
+  faClipboardCheck,
+  faClock,
+  faCloudUploadAlt,
+  faCopy,
+  faDrawPolygon,
+  faExclamationTriangle,
+  faEye,
+  faEyeSlash,
+  faFilePdf,
+  faFolderPlus,
+  faInfo,
+  faLayerGroup,
+  faMapMarkedAlt,
+  faMinus,
+  faPaperPlane,
+  faPencilAlt,
+  faPlay,
+  faPlus,
+  faPowerOff,
+  faRuler,
+  faSearch,
+  faShip,
+  faSortAmountDown,
+  faSortAmountUp,
+  faSpinner,
+  faStar,
+  faTasks,
+  faTimes,
+  faTrash,
+  faUpload,
+  faUser,
+  faUsersCog,
+  faWater,
+  faWrench
+);
+
+import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
+Vue.component("font-awesome-icon", FontAwesomeIcon);
+
+export { Vue };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/src/lib/date.js	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,27 @@
+/* This is Free Software under GNU Affero General Public License v >= 3.0
+ * without warranty, see README.md and license for details.
+ *
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ * License-Filename: LICENSES/AGPL-3.0.txt
+ *
+ * Copyright (C) 2018 by via donau
+ *   – Österreichische Wasserstraßen-Gesellschaft mbH
+ * Software engineering by Intevation GmbH
+ *
+ * Author(s):
+ * Thomas Junk <thomas.junk@intevation.de>
+ */
+
+import locale2 from "locale2";
+
+const formatSurveyDate = current => {
+  return current
+    ? new Date(current).toLocaleDateString(locale2, {
+        day: "2-digit",
+        month: "2-digit",
+        year: "numeric"
+      })
+    : "";
+};
+
+export { formatSurveyDate };
--- a/client/src/locale/bg_BG/LC_MESSAGES/app.po	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/locale/bg_BG/LC_MESSAGES/app.po	Sat Dec 29 16:07:40 2018 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: gemmajs 1.99.0-dev\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-12-05 14:14+0100\n"
+"POT-Creation-Date: 2018-12-10 15:30+0100\n"
 "PO-Revision-Date: 2018-12-05 12:23+0100\n"
 "Last-Translator: Automatically generated\n"
 "Language-Team: none\n"
@@ -25,7 +25,7 @@
 msgid "Accesslog"
 msgstr ""
 
-#: src/components/admin/usermanagement/Usermanagement.vue:106
+#: src/components/admin/usermanagement/Usermanagement.vue:103
 msgid "Add User"
 msgstr ""
 
@@ -38,17 +38,17 @@
 msgstr ""
 
 #: src/components/admin/Importqueue.vue:136
-#: src/components/admin/Systemconfiguration.vue:133
-#: src/components/admin/Systemconfiguration.vue:148
-#: src/components/admin/Systemconfiguration.vue:167
-#: src/components/admin/Systemconfiguration.vue:184
-#: src/components/admin/usermanagement/Userdetail.vue:296
-#: src/components/admin/usermanagement/Userdetail.vue:368
-#: src/components/admin/usermanagement/Usermanagement.vue:300
-#: src/components/admin/usermanagement/Usermanagement.vue:308
-#: src/components/admin/usermanagement/Usermanagement.vue:334
+#: src/components/admin/Systemconfiguration.vue:134
+#: src/components/admin/Systemconfiguration.vue:149
+#: src/components/admin/Systemconfiguration.vue:168
+#: src/components/admin/Systemconfiguration.vue:185
+#: src/components/admin/usermanagement/Userdetail.vue:305
+#: src/components/admin/usermanagement/Userdetail.vue:377
+#: src/components/admin/usermanagement/Usermanagement.vue:313
+#: src/components/admin/usermanagement/Usermanagement.vue:321
+#: src/components/admin/usermanagement/Usermanagement.vue:347
 #: src/components/map/Search.vue:257
-#: src/components/map/contextbox/Bottlenecks.vue:252
+#: src/components/map/contextbox/Bottlenecks.vue:275
 #: src/components/map/contextbox/ImportSoundingresults.vue:205
 #: src/components/map/contextbox/ImportSoundingresults.vue:244
 #: src/components/map/contextbox/ImportSoundingresults.vue:275
@@ -67,7 +67,7 @@
 msgid "Bottleneck Areas stroke-color"
 msgstr ""
 
-#: src/components/Sidebar.vue:22
+#: src/components/Sidebar.vue:27
 #: src/components/map/contextbox/Bottlenecks.vue:4
 msgid "Bottlenecks"
 msgstr ""
@@ -93,7 +93,7 @@
 msgid "Compare with"
 msgstr ""
 
-#: src/components/Sidebar.vue:58
+#: src/components/Sidebar.vue:76
 msgid "Configuration"
 msgstr ""
 
@@ -102,11 +102,11 @@
 msgid "Confirm"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:374
+#: src/components/map/fairway/Profiles.vue:378
 msgid "Coordinates copied to clipboard!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:34
+#: src/components/admin/usermanagement/Userdetail.vue:33
 msgid "Country"
 msgstr ""
 
@@ -128,13 +128,18 @@
 msgstr ""
 
 #: src/components/admin/importschedule/Importschedule.vue:37
+#: src/components/admin/importschedule/Importscheduledetail.vue:80
 msgid "Email"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:60
+#: src/components/admin/usermanagement/Userdetail.vue:59
 msgid "Email address"
 msgstr ""
 
+#: src/components/admin/importschedule/Importscheduledetail.vue:61
+msgid "Email Notification"
+msgstr ""
+
 #: src/components/admin/Importqueue.vue:53
 msgid "Enqueued"
 msgstr ""
@@ -155,7 +160,7 @@
 msgid "Enter username"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:377
+#: src/components/admin/usermanagement/Userdetail.vue:386
 msgid "Error while saving user"
 msgstr ""
 
@@ -184,7 +189,7 @@
 msgid "Import"
 msgstr ""
 
-#: src/components/Sidebar.vue:31
+#: src/components/Sidebar.vue:40
 msgid "Import soundingresults"
 msgstr ""
 
@@ -196,20 +201,28 @@
 msgid "Imported"
 msgstr ""
 
-#: src/components/Sidebar.vue:66 src/components/admin/Importqueue.vue:9
+#: src/components/Sidebar.vue:92 src/components/admin/Importqueue.vue:9
 msgid "Importqueue"
 msgstr ""
 
-#: src/components/Sidebar.vue:70
+#: src/components/admin/importschedule/Importscheduledetail.vue:20
+msgid "Imports"
+msgstr ""
+
+#: src/components/Sidebar.vue:100
 #: src/components/admin/importschedule/Importschedule.vue:9
 msgid "Importschedule"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:345
+#: src/components/admin/importschedule/Importscheduledetail.vue:34
+msgid "Importtype"
+msgstr ""
+
+#: src/components/admin/usermanagement/Userdetail.vue:354
 msgid "invalid email"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:408
+#: src/components/map/fairway/Profiles.vue:412
 msgid "Invalid input"
 msgstr ""
 
@@ -241,19 +254,19 @@
 msgid "Login failed"
 msgstr ""
 
-#: src/components/Sidebar.vue:76
+#: src/components/Sidebar.vue:110
 msgid "Logout"
 msgstr ""
 
-#: src/components/Sidebar.vue:62 src/components/admin/Logs.vue:9
+#: src/components/Sidebar.vue:84 src/components/admin/Logs.vue:9
 msgid "Logs"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:141
+#: src/components/admin/usermanagement/Userdetail.vue:140
 msgid "Mail was sent"
 msgstr ""
 
-#: src/components/Sidebar.vue:14
+#: src/components/Sidebar.vue:15
 msgid "Map"
 msgstr ""
 
@@ -266,7 +279,7 @@
 msgid "Name"
 msgstr ""
 
-#: src/components/admin/importschedule/Importscheduledetail.vue:6
+#: src/components/admin/importschedule/Importscheduledetail.vue:9
 msgid "New import"
 msgstr ""
 
@@ -274,7 +287,7 @@
 msgid "New Import"
 msgstr ""
 
-#: src/components/map/Identify.vue:50
+#: src/components/map/Identify.vue:47
 msgid "No features identified."
 msgstr ""
 
@@ -291,15 +304,15 @@
 msgid "Open in new window"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:229
+#: src/components/admin/usermanagement/Userdetail.vue:238
 msgid "password"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:227
+#: src/components/admin/usermanagement/Userdetail.vue:236
 msgid "Password"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:230
+#: src/components/admin/usermanagement/Userdetail.vue:239
 msgid "password again"
 msgstr ""
 
@@ -307,12 +320,12 @@
 msgid "Password reset requested!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:338
-#: src/components/admin/usermanagement/Userdetail.vue:339
+#: src/components/admin/usermanagement/Userdetail.vue:347
+#: src/components/admin/usermanagement/Userdetail.vue:348
 msgid "Password should at least be 8 char long including 1 digit and 1 special char like $"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:334
+#: src/components/admin/usermanagement/Userdetail.vue:343
 msgid "Passwords do not match!"
 msgstr ""
 
@@ -320,11 +333,11 @@
 msgid "Pending"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:323
+#: src/components/admin/usermanagement/Userdetail.vue:332
 msgid "Please choose a country"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:328
+#: src/components/admin/usermanagement/Userdetail.vue:337
 msgid "Please choose a role"
 msgstr ""
 
@@ -340,8 +353,8 @@
 msgid "Please enter a reference"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:409
-#: src/components/map/fairway/Profiles.vue:410
+#: src/components/map/fairway/Profiles.vue:413
+#: src/components/map/fairway/Profiles.vue:414
 msgid "Please enter correct coordinates in the format: Lat,Lon,Lat,Lon"
 msgstr ""
 
@@ -349,8 +362,8 @@
 msgid "Please select a bottleneck"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:41
-#: src/components/admin/usermanagement/Userdetail.vue:86
+#: src/components/admin/usermanagement/Userdetail.vue:40
+#: src/components/admin/usermanagement/Userdetail.vue:85
 msgid "Please select one"
 msgstr ""
 
@@ -358,11 +371,11 @@
 msgid "portrait"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:448
+#: src/components/map/fairway/Profiles.vue:452
 msgid "Profile deleted!"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:434
+#: src/components/map/fairway/Profiles.vue:438
 msgid "Profile saved!"
 msgstr ""
 
@@ -382,7 +395,7 @@
 msgid "Rejected"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:228
+#: src/components/admin/usermanagement/Userdetail.vue:237
 msgid "Repeat Password"
 msgstr ""
 
@@ -390,7 +403,7 @@
 msgid "Request password reset!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:79
+#: src/components/admin/usermanagement/Userdetail.vue:78
 msgid "Role"
 msgstr ""
 
@@ -399,6 +412,7 @@
 msgstr ""
 
 #: src/components/admin/importschedule/Importschedule.vue:36
+#: src/components/admin/importschedule/Importscheduledetail.vue:48
 msgid "Schedule"
 msgstr ""
 
@@ -410,7 +424,7 @@
 msgid "Send"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:139
+#: src/components/admin/usermanagement/Userdetail.vue:138
 msgid "Send testmail"
 msgstr ""
 
@@ -418,7 +432,7 @@
 msgid "Signer"
 msgstr ""
 
-#: src/components/map/Identify.vue:63
+#: src/components/map/Identify.vue:60
 msgid ""
 "Some data ©\n"
 "        <a href=\"https://www.openstreetmap.org/copyright\">%{ name }</a>\n"
@@ -429,11 +443,11 @@
 msgid "Sounding Result"
 msgstr ""
 
-#: src/components/map/Identify.vue:60
+#: src/components/map/Identify.vue:57
 msgid "source-code"
 msgstr ""
 
-#: src/components/Sidebar.vue:44
+#: src/components/Sidebar.vue:54
 msgid "Staging area"
 msgstr ""
 
@@ -453,11 +467,12 @@
 msgid "State"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:130
+#: src/components/admin/importschedule/Importscheduledetail.vue:85
+#: src/components/admin/usermanagement/Userdetail.vue:129
 msgid "Submit"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:373
+#: src/components/map/fairway/Profiles.vue:377
 msgid "Success"
 msgstr ""
 
@@ -465,11 +480,11 @@
 msgid "Successful"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:89
+#: src/components/admin/usermanagement/Userdetail.vue:88
 msgid "Sysadmin"
 msgstr ""
 
-#: src/components/Sidebar.vue:47
+#: src/components/Sidebar.vue:57
 msgid "Systemadministration"
 msgstr ""
 
@@ -477,7 +492,7 @@
 msgid "Systemconfiguration"
 msgstr ""
 
-#: src/components/map/Identify.vue:54
+#: src/components/map/Identify.vue:51
 msgid ""
 "This app uses <i>gemma</i>, which is Free Software under <br/>\n"
 "        %{ license } without warranty, see docs for details."
@@ -496,31 +511,40 @@
 msgid "User"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:15
+#: src/components/admin/usermanagement/Userdetail.vue:14
 #: src/components/map/contextbox/Staging.vue:16
 msgid "Username"
 msgstr ""
 
-#: src/components/Sidebar.vue:52
+#: src/components/Sidebar.vue:66
 #: src/components/admin/usermanagement/Usermanagement.vue:14
 msgid "Users"
 msgstr ""
 
-#: src/components/map/Identify.vue:68
+#: src/components/map/Identify.vue:65
 msgid ""
 "Uses\n"
-"        <a href=\"https://download.geonames.org/export/dump/readme.txt\">GeoNames</a> under %{ geoLicense }."
+"        <a href=\"https://download.geonames.org/export/dump/readme.txt\">GeoNames</a>\n"
+"        under %{ geoLicense }."
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:92
+#: src/components/admin/usermanagement/Userdetail.vue:91
 msgid "Waterway Admin"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:95
+#: src/components/admin/usermanagement/Userdetail.vue:94
 msgid "Waterway User"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:435
-#: src/components/map/fairway/Profiles.vue:436
+#: src/components/map/fairway/Profiles.vue:439
+#: src/components/map/fairway/Profiles.vue:440
 msgid "You can now select these coordinates from the \"Saved cross profiles\" menu to restore this cross profile."
 msgstr ""
+
+#: src/store/map.js:415
+msgid "Length"
+msgstr ""
+
+#: src/store/map.js:436
+msgid "Area"
+msgstr ""
--- a/client/src/locale/de_AT/LC_MESSAGES/app.po	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/locale/de_AT/LC_MESSAGES/app.po	Sat Dec 29 16:07:40 2018 +0100
@@ -8,10 +8,11 @@
 msgstr ""
 "Project-Id-Version: wamosjs 0.1.0\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-12-05 14:14+0100\n"
-"PO-Revision-Date: 2018-12-05 13:13+0000\n"
+"POT-Creation-Date: 2018-12-10 15:30+0100\n"
+"PO-Revision-Date: 2018-12-11 17:08+0000\n"
 "Last-Translator: Sascha L. Teichmann <sascha.teichmann@intevation.de>\n"
-"Language-Team: Austrian German <https://hosted.weblate.org/projects/gemma/client/de_AT/>\n"
+"Language-Team: Austrian German <https://hosted.weblate.org/projects/gemma/"
+"client/de_AT/>\n"
 "Language: de_AT\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
@@ -27,7 +28,7 @@
 msgid "Accesslog"
 msgstr "Zugriffs-Protokoll"
 
-#: src/components/admin/usermanagement/Usermanagement.vue:106
+#: src/components/admin/usermanagement/Usermanagement.vue:103
 msgid "Add User"
 msgstr "Benutzer hinzufügen"
 
@@ -40,17 +41,17 @@
 msgstr "zurück zur Anmeldung"
 
 #: src/components/admin/Importqueue.vue:136
-#: src/components/admin/Systemconfiguration.vue:133
-#: src/components/admin/Systemconfiguration.vue:148
-#: src/components/admin/Systemconfiguration.vue:167
-#: src/components/admin/Systemconfiguration.vue:184
-#: src/components/admin/usermanagement/Userdetail.vue:296
-#: src/components/admin/usermanagement/Userdetail.vue:368
-#: src/components/admin/usermanagement/Usermanagement.vue:300
-#: src/components/admin/usermanagement/Usermanagement.vue:308
-#: src/components/admin/usermanagement/Usermanagement.vue:334
+#: src/components/admin/Systemconfiguration.vue:134
+#: src/components/admin/Systemconfiguration.vue:149
+#: src/components/admin/Systemconfiguration.vue:168
+#: src/components/admin/Systemconfiguration.vue:185
+#: src/components/admin/usermanagement/Userdetail.vue:305
+#: src/components/admin/usermanagement/Userdetail.vue:377
+#: src/components/admin/usermanagement/Usermanagement.vue:313
+#: src/components/admin/usermanagement/Usermanagement.vue:321
+#: src/components/admin/usermanagement/Usermanagement.vue:347
 #: src/components/map/Search.vue:257
-#: src/components/map/contextbox/Bottlenecks.vue:252
+#: src/components/map/contextbox/Bottlenecks.vue:275
 #: src/components/map/contextbox/ImportSoundingresults.vue:205
 #: src/components/map/contextbox/ImportSoundingresults.vue:244
 #: src/components/map/contextbox/ImportSoundingresults.vue:275
@@ -69,7 +70,7 @@
 msgid "Bottleneck Areas stroke-color"
 msgstr "Flächenumrandungsfarbe Seichtstelle"
 
-#: src/components/Sidebar.vue:22
+#: src/components/Sidebar.vue:27
 #: src/components/map/contextbox/Bottlenecks.vue:4
 msgid "Bottlenecks"
 msgstr "Seichtstellen"
@@ -80,7 +81,7 @@
 
 #: src/components/map/contextbox/Bottlenecks.vue:30
 msgid "Chainage"
-msgstr "Verkettung"
+msgstr "Stationierung"
 
 #: src/components/map/contextbox/ImportSoundingresults.vue:167
 #: src/components/map/contextbox/ImportSoundingresults.vue:181
@@ -95,7 +96,7 @@
 msgid "Compare with"
 msgstr "Vergleiche mit"
 
-#: src/components/Sidebar.vue:58
+#: src/components/Sidebar.vue:76
 msgid "Configuration"
 msgstr "Konfiguration"
 
@@ -104,11 +105,11 @@
 msgid "Confirm"
 msgstr "Bestätigen"
 
-#: src/components/map/fairway/Profiles.vue:374
+#: src/components/map/fairway/Profiles.vue:378
 msgid "Coordinates copied to clipboard!"
 msgstr "Koordinaten auf die Zwischenablage kopiert!"
 
-#: src/components/admin/usermanagement/Userdetail.vue:34
+#: src/components/admin/usermanagement/Userdetail.vue:33
 msgid "Country"
 msgstr "Land"
 
@@ -130,13 +131,18 @@
 msgstr "Meta.json Herunterladen"
 
 #: src/components/admin/importschedule/Importschedule.vue:37
+#: src/components/admin/importschedule/Importscheduledetail.vue:80
 msgid "Email"
 msgstr "E-Mail"
 
-#: src/components/admin/usermanagement/Userdetail.vue:60
+#: src/components/admin/usermanagement/Userdetail.vue:59
 msgid "Email address"
 msgstr "E-Mail Adresse"
 
+#: src/components/admin/importschedule/Importscheduledetail.vue:61
+msgid "Email Notification"
+msgstr "E-Mail Benachrichtigung"
+
 #: src/components/admin/Importqueue.vue:53
 msgid "Enqueued"
 msgstr "Hinzugefügt"
@@ -157,7 +163,7 @@
 msgid "Enter username"
 msgstr "Benutzername eingeben"
 
-#: src/components/admin/usermanagement/Userdetail.vue:377
+#: src/components/admin/usermanagement/Userdetail.vue:386
 msgid "Error while saving user"
 msgstr "Während des Speicherns der Nutzerdaten trat ein Fehler auf"
 
@@ -186,7 +192,7 @@
 msgid "Import"
 msgstr "Daten-Import"
 
-#: src/components/Sidebar.vue:31
+#: src/components/Sidebar.vue:40
 msgid "Import soundingresults"
 msgstr "Seichtstellenmessungen importieren"
 
@@ -198,20 +204,28 @@
 msgid "Imported"
 msgstr "Importiert"
 
-#: src/components/Sidebar.vue:66 src/components/admin/Importqueue.vue:9
+#: src/components/Sidebar.vue:92 src/components/admin/Importqueue.vue:9
 msgid "Importqueue"
 msgstr "Import-Warteschlange"
 
-#: src/components/Sidebar.vue:70
+#: src/components/admin/importschedule/Importscheduledetail.vue:20
+msgid "Imports"
+msgstr "Daten-Import"
+
+#: src/components/Sidebar.vue:100
 #: src/components/admin/importschedule/Importschedule.vue:9
 msgid "Importschedule"
 msgstr "Import-Zeitplan"
 
-#: src/components/admin/usermanagement/Userdetail.vue:345
+#: src/components/admin/importschedule/Importscheduledetail.vue:34
+msgid "Importtype"
+msgstr "Art des Imports"
+
+#: src/components/admin/usermanagement/Userdetail.vue:354
 msgid "invalid email"
 msgstr "Ungültige E-Mail"
 
-#: src/components/map/fairway/Profiles.vue:408
+#: src/components/map/fairway/Profiles.vue:412
 msgid "Invalid input"
 msgstr "Ungültige Eingabe"
 
@@ -229,7 +243,7 @@
 
 #: src/components/map/contextbox/Bottlenecks.vue:19
 msgid "Latest"
-msgstr "Neustes"
+msgstr "Neuste"
 
 #: src/components/map/layers/Layers.vue:10
 msgid "Layers"
@@ -243,19 +257,19 @@
 msgid "Login failed"
 msgstr "Login fehlgeschlagen"
 
-#: src/components/Sidebar.vue:76
+#: src/components/Sidebar.vue:110
 msgid "Logout"
 msgstr "Abmelden"
 
-#: src/components/Sidebar.vue:62 src/components/admin/Logs.vue:9
+#: src/components/Sidebar.vue:84 src/components/admin/Logs.vue:9
 msgid "Logs"
 msgstr "Protokolle"
 
-#: src/components/admin/usermanagement/Userdetail.vue:141
+#: src/components/admin/usermanagement/Userdetail.vue:140
 msgid "Mail was sent"
 msgstr "E-Mail wurde gesendet"
 
-#: src/components/Sidebar.vue:14
+#: src/components/Sidebar.vue:15
 msgid "Map"
 msgstr "Karte"
 
@@ -268,7 +282,7 @@
 msgid "Name"
 msgstr "Name"
 
-#: src/components/admin/importschedule/Importscheduledetail.vue:6
+#: src/components/admin/importschedule/Importscheduledetail.vue:9
 msgid "New import"
 msgstr "Neuer Import"
 
@@ -276,7 +290,7 @@
 msgid "New Import"
 msgstr "Neuer Import"
 
-#: src/components/map/Identify.vue:50
+#: src/components/map/Identify.vue:47
 msgid "No features identified."
 msgstr "Keine Objekte identifiziert."
 
@@ -293,15 +307,15 @@
 msgid "Open in new window"
 msgstr "In neuem Fenster öffnen"
 
-#: src/components/admin/usermanagement/Userdetail.vue:229
+#: src/components/admin/usermanagement/Userdetail.vue:238
 msgid "password"
 msgstr "Passwort"
 
-#: src/components/admin/usermanagement/Userdetail.vue:227
+#: src/components/admin/usermanagement/Userdetail.vue:236
 msgid "Password"
 msgstr "Passwort"
 
-#: src/components/admin/usermanagement/Userdetail.vue:230
+#: src/components/admin/usermanagement/Userdetail.vue:239
 msgid "password again"
 msgstr "Noch einmal das Passwort"
 
@@ -309,12 +323,12 @@
 msgid "Password reset requested!"
 msgstr "Passwort Zurücksetzung angefragt!"
 
-#: src/components/admin/usermanagement/Userdetail.vue:338
-#: src/components/admin/usermanagement/Userdetail.vue:339
+#: src/components/admin/usermanagement/Userdetail.vue:347
+#: src/components/admin/usermanagement/Userdetail.vue:348
 msgid "Password should at least be 8 char long including 1 digit and 1 special char like $"
 msgstr "Das Passwort sollte mindestens 8 Zeichen lang sein, eine Zahlenziffer und ein Sonderzeichen wie etwa $ enthalten"
 
-#: src/components/admin/usermanagement/Userdetail.vue:334
+#: src/components/admin/usermanagement/Userdetail.vue:343
 msgid "Passwords do not match!"
 msgstr "Die Passwörter stimmen nicht überein!"
 
@@ -322,11 +336,11 @@
 msgid "Pending"
 msgstr "Ausstehend"
 
-#: src/components/admin/usermanagement/Userdetail.vue:323
+#: src/components/admin/usermanagement/Userdetail.vue:332
 msgid "Please choose a country"
 msgstr "Bitte wählen Sie ein Land aus"
 
-#: src/components/admin/usermanagement/Userdetail.vue:328
+#: src/components/admin/usermanagement/Userdetail.vue:337
 msgid "Please choose a role"
 msgstr "Bitte wählen Sie eine Rolle aus"
 
@@ -342,8 +356,8 @@
 msgid "Please enter a reference"
 msgstr "Bitte ein Höhenreferenzsystem eingeben"
 
-#: src/components/map/fairway/Profiles.vue:409
-#: src/components/map/fairway/Profiles.vue:410
+#: src/components/map/fairway/Profiles.vue:413
+#: src/components/map/fairway/Profiles.vue:414
 msgid "Please enter correct coordinates in the format: Lat,Lon,Lat,Lon"
 msgstr "Bitte geben Sie die Koordinaten in folgendem Format an: Lat,Lon,Lat,Lon"
 
@@ -351,8 +365,8 @@
 msgid "Please select a bottleneck"
 msgstr "Bitte eine Seichtstelle wählen"
 
-#: src/components/admin/usermanagement/Userdetail.vue:41
-#: src/components/admin/usermanagement/Userdetail.vue:86
+#: src/components/admin/usermanagement/Userdetail.vue:40
+#: src/components/admin/usermanagement/Userdetail.vue:85
 msgid "Please select one"
 msgstr "Bitte auswählen"
 
@@ -360,11 +374,11 @@
 msgid "portrait"
 msgstr "Hochformat"
 
-#: src/components/map/fairway/Profiles.vue:448
+#: src/components/map/fairway/Profiles.vue:452
 msgid "Profile deleted!"
 msgstr "Profil gelöscht!"
 
-#: src/components/map/fairway/Profiles.vue:434
+#: src/components/map/fairway/Profiles.vue:438
 msgid "Profile saved!"
 msgstr "Profil gespeichert!"
 
@@ -384,7 +398,7 @@
 msgid "Rejected"
 msgstr "Abgelehnt"
 
-#: src/components/admin/usermanagement/Userdetail.vue:228
+#: src/components/admin/usermanagement/Userdetail.vue:237
 msgid "Repeat Password"
 msgstr "Passwort erneut eingeben"
 
@@ -392,7 +406,7 @@
 msgid "Request password reset!"
 msgstr "Passwort-Zurücksetzung anfragen!"
 
-#: src/components/admin/usermanagement/Userdetail.vue:79
+#: src/components/admin/usermanagement/Userdetail.vue:78
 msgid "Role"
 msgstr "Rolle"
 
@@ -401,26 +415,27 @@
 msgstr "Gespeicherte Profile"
 
 #: src/components/admin/importschedule/Importschedule.vue:36
+#: src/components/admin/importschedule/Importscheduledetail.vue:48
 msgid "Schedule"
 msgstr "Zeitplan"
 
 #: src/components/map/fairway/Profiles.vue:32
 msgid "Select Bottleneck"
-msgstr "Wähle Engstelle"
+msgstr "Wähle Seichtstelle"
 
 #: src/components/admin/Systemconfiguration.vue:25
 msgid "Send"
 msgstr "Absenden"
 
-#: src/components/admin/usermanagement/Userdetail.vue:139
+#: src/components/admin/usermanagement/Userdetail.vue:138
 msgid "Send testmail"
 msgstr "Test-E-Mail versenden"
 
 #: src/components/admin/Importqueue.vue:56
 msgid "Signer"
-msgstr ""
+msgstr "Überprüft durch"
 
-#: src/components/map/Identify.vue:63
+#: src/components/map/Identify.vue:60
 msgid ""
 "Some data ©\n"
 "        <a href=\"https://www.openstreetmap.org/copyright\">%{ name }</a>\n"
@@ -429,13 +444,13 @@
 
 #: src/components/map/fairway/Profiles.vue:45
 msgid "Sounding Result"
-msgstr "Seichtstellenvermessungsergebnisse"
+msgstr "Seichtstellenvermessung"
 
-#: src/components/map/Identify.vue:60
+#: src/components/map/Identify.vue:57
 msgid "source-code"
 msgstr "Quelltext"
 
-#: src/components/Sidebar.vue:44
+#: src/components/Sidebar.vue:54
 msgid "Staging area"
 msgstr "Import-Überprüfung"
 
@@ -455,11 +470,12 @@
 msgid "State"
 msgstr "Zustand"
 
-#: src/components/admin/usermanagement/Userdetail.vue:130
+#: src/components/admin/importschedule/Importscheduledetail.vue:85
+#: src/components/admin/usermanagement/Userdetail.vue:129
 msgid "Submit"
 msgstr "Abschicken"
 
-#: src/components/map/fairway/Profiles.vue:373
+#: src/components/map/fairway/Profiles.vue:377
 msgid "Success"
 msgstr "Erfolg"
 
@@ -467,11 +483,11 @@
 msgid "Successful"
 msgstr "Erfolgreich"
 
-#: src/components/admin/usermanagement/Userdetail.vue:89
+#: src/components/admin/usermanagement/Userdetail.vue:88
 msgid "Sysadmin"
 msgstr "Sys-Admin"
 
-#: src/components/Sidebar.vue:47
+#: src/components/Sidebar.vue:57
 msgid "Systemadministration"
 msgstr "System-Administration"
 
@@ -479,7 +495,7 @@
 msgid "Systemconfiguration"
 msgstr "System-Konfiguation"
 
-#: src/components/map/Identify.vue:54
+#: src/components/map/Identify.vue:51
 msgid ""
 "This app uses <i>gemma</i>, which is Free Software under <br/>\n"
 "        %{ license } without warranty, see docs for details."
@@ -498,31 +514,40 @@
 msgid "User"
 msgstr "Benutzer"
 
-#: src/components/admin/usermanagement/Userdetail.vue:15
+#: src/components/admin/usermanagement/Userdetail.vue:14
 #: src/components/map/contextbox/Staging.vue:16
 msgid "Username"
 msgstr "Benutzername"
 
-#: src/components/Sidebar.vue:52
+#: src/components/Sidebar.vue:66
 #: src/components/admin/usermanagement/Usermanagement.vue:14
 msgid "Users"
 msgstr "Benutzer"
 
-#: src/components/map/Identify.vue:68
+#: src/components/map/Identify.vue:65
 msgid ""
 "Uses\n"
-"        <a href=\"https://download.geonames.org/export/dump/readme.txt\">GeoNames</a> under %{ geoLicense }."
+"        <a href=\"https://download.geonames.org/export/dump/readme.txt\">GeoNames</a>\n"
+"        under %{ geoLicense }."
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:92
+#: src/components/admin/usermanagement/Userdetail.vue:91
 msgid "Waterway Admin"
 msgstr "Waterway-Admin"
 
-#: src/components/admin/usermanagement/Userdetail.vue:95
+#: src/components/admin/usermanagement/Userdetail.vue:94
 msgid "Waterway User"
 msgstr "Waterway-Benutzer"
 
-#: src/components/map/fairway/Profiles.vue:435
-#: src/components/map/fairway/Profiles.vue:436
+#: src/components/map/fairway/Profiles.vue:439
+#: src/components/map/fairway/Profiles.vue:440
 msgid "You can now select these coordinates from the \"Saved cross profiles\" menu to restore this cross profile."
 msgstr "Sie können diese Koordinaten aus dem \"Gespeicherte Profile\"-Menü auswählen, um diesen Profilschnitt wieder herzustellen."
+
+#: src/store/map.js:415
+msgid "Length"
+msgstr "Länge"
+
+#: src/store/map.js:436
+msgid "Area"
+msgstr "Fläche"
--- a/client/src/locale/en_GB/LC_MESSAGES/app.po	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/locale/en_GB/LC_MESSAGES/app.po	Sat Dec 29 16:07:40 2018 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: wamosjs 0.1.0\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-12-05 14:14+0100\n"
+"POT-Creation-Date: 2018-12-10 15:30+0100\n"
 "PO-Revision-Date: 2018-07-03 17:18+0200\n"
 "Last-Translator: Automatically generated\n"
 "Language-Team: none\n"
@@ -25,7 +25,7 @@
 msgid "Accesslog"
 msgstr ""
 
-#: src/components/admin/usermanagement/Usermanagement.vue:106
+#: src/components/admin/usermanagement/Usermanagement.vue:103
 msgid "Add User"
 msgstr ""
 
@@ -38,17 +38,17 @@
 msgstr ""
 
 #: src/components/admin/Importqueue.vue:136
-#: src/components/admin/Systemconfiguration.vue:133
-#: src/components/admin/Systemconfiguration.vue:148
-#: src/components/admin/Systemconfiguration.vue:167
-#: src/components/admin/Systemconfiguration.vue:184
-#: src/components/admin/usermanagement/Userdetail.vue:296
-#: src/components/admin/usermanagement/Userdetail.vue:368
-#: src/components/admin/usermanagement/Usermanagement.vue:300
-#: src/components/admin/usermanagement/Usermanagement.vue:308
-#: src/components/admin/usermanagement/Usermanagement.vue:334
+#: src/components/admin/Systemconfiguration.vue:134
+#: src/components/admin/Systemconfiguration.vue:149
+#: src/components/admin/Systemconfiguration.vue:168
+#: src/components/admin/Systemconfiguration.vue:185
+#: src/components/admin/usermanagement/Userdetail.vue:305
+#: src/components/admin/usermanagement/Userdetail.vue:377
+#: src/components/admin/usermanagement/Usermanagement.vue:313
+#: src/components/admin/usermanagement/Usermanagement.vue:321
+#: src/components/admin/usermanagement/Usermanagement.vue:347
 #: src/components/map/Search.vue:257
-#: src/components/map/contextbox/Bottlenecks.vue:252
+#: src/components/map/contextbox/Bottlenecks.vue:275
 #: src/components/map/contextbox/ImportSoundingresults.vue:205
 #: src/components/map/contextbox/ImportSoundingresults.vue:244
 #: src/components/map/contextbox/ImportSoundingresults.vue:275
@@ -67,7 +67,7 @@
 msgid "Bottleneck Areas stroke-color"
 msgstr ""
 
-#: src/components/Sidebar.vue:22
+#: src/components/Sidebar.vue:27
 #: src/components/map/contextbox/Bottlenecks.vue:4
 msgid "Bottlenecks"
 msgstr ""
@@ -93,7 +93,7 @@
 msgid "Compare with"
 msgstr ""
 
-#: src/components/Sidebar.vue:58
+#: src/components/Sidebar.vue:76
 msgid "Configuration"
 msgstr ""
 
@@ -102,11 +102,11 @@
 msgid "Confirm"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:374
+#: src/components/map/fairway/Profiles.vue:378
 msgid "Coordinates copied to clipboard!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:34
+#: src/components/admin/usermanagement/Userdetail.vue:33
 msgid "Country"
 msgstr ""
 
@@ -128,13 +128,18 @@
 msgstr ""
 
 #: src/components/admin/importschedule/Importschedule.vue:37
+#: src/components/admin/importschedule/Importscheduledetail.vue:80
 msgid "Email"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:60
+#: src/components/admin/usermanagement/Userdetail.vue:59
 msgid "Email address"
 msgstr ""
 
+#: src/components/admin/importschedule/Importscheduledetail.vue:61
+msgid "Email Notification"
+msgstr ""
+
 #: src/components/admin/Importqueue.vue:53
 msgid "Enqueued"
 msgstr ""
@@ -155,7 +160,7 @@
 msgid "Enter username"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:377
+#: src/components/admin/usermanagement/Userdetail.vue:386
 msgid "Error while saving user"
 msgstr ""
 
@@ -184,7 +189,7 @@
 msgid "Import"
 msgstr ""
 
-#: src/components/Sidebar.vue:31
+#: src/components/Sidebar.vue:40
 msgid "Import soundingresults"
 msgstr ""
 
@@ -196,20 +201,28 @@
 msgid "Imported"
 msgstr ""
 
-#: src/components/Sidebar.vue:66 src/components/admin/Importqueue.vue:9
+#: src/components/Sidebar.vue:92 src/components/admin/Importqueue.vue:9
 msgid "Importqueue"
 msgstr ""
 
-#: src/components/Sidebar.vue:70
+#: src/components/admin/importschedule/Importscheduledetail.vue:20
+msgid "Imports"
+msgstr ""
+
+#: src/components/Sidebar.vue:100
 #: src/components/admin/importschedule/Importschedule.vue:9
 msgid "Importschedule"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:345
+#: src/components/admin/importschedule/Importscheduledetail.vue:34
+msgid "Importtype"
+msgstr ""
+
+#: src/components/admin/usermanagement/Userdetail.vue:354
 msgid "invalid email"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:408
+#: src/components/map/fairway/Profiles.vue:412
 msgid "Invalid input"
 msgstr ""
 
@@ -241,19 +254,19 @@
 msgid "Login failed"
 msgstr ""
 
-#: src/components/Sidebar.vue:76
+#: src/components/Sidebar.vue:110
 msgid "Logout"
 msgstr ""
 
-#: src/components/Sidebar.vue:62 src/components/admin/Logs.vue:9
+#: src/components/Sidebar.vue:84 src/components/admin/Logs.vue:9
 msgid "Logs"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:141
+#: src/components/admin/usermanagement/Userdetail.vue:140
 msgid "Mail was sent"
 msgstr ""
 
-#: src/components/Sidebar.vue:14
+#: src/components/Sidebar.vue:15
 msgid "Map"
 msgstr ""
 
@@ -266,7 +279,7 @@
 msgid "Name"
 msgstr ""
 
-#: src/components/admin/importschedule/Importscheduledetail.vue:6
+#: src/components/admin/importschedule/Importscheduledetail.vue:9
 msgid "New import"
 msgstr ""
 
@@ -274,7 +287,7 @@
 msgid "New Import"
 msgstr ""
 
-#: src/components/map/Identify.vue:50
+#: src/components/map/Identify.vue:47
 msgid "No features identified."
 msgstr ""
 
@@ -291,15 +304,15 @@
 msgid "Open in new window"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:229
+#: src/components/admin/usermanagement/Userdetail.vue:238
 msgid "password"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:227
+#: src/components/admin/usermanagement/Userdetail.vue:236
 msgid "Password"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:230
+#: src/components/admin/usermanagement/Userdetail.vue:239
 msgid "password again"
 msgstr ""
 
@@ -307,12 +320,12 @@
 msgid "Password reset requested!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:338
-#: src/components/admin/usermanagement/Userdetail.vue:339
+#: src/components/admin/usermanagement/Userdetail.vue:347
+#: src/components/admin/usermanagement/Userdetail.vue:348
 msgid "Password should at least be 8 char long including 1 digit and 1 special char like $"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:334
+#: src/components/admin/usermanagement/Userdetail.vue:343
 msgid "Passwords do not match!"
 msgstr ""
 
@@ -320,11 +333,11 @@
 msgid "Pending"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:323
+#: src/components/admin/usermanagement/Userdetail.vue:332
 msgid "Please choose a country"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:328
+#: src/components/admin/usermanagement/Userdetail.vue:337
 msgid "Please choose a role"
 msgstr ""
 
@@ -340,8 +353,8 @@
 msgid "Please enter a reference"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:409
-#: src/components/map/fairway/Profiles.vue:410
+#: src/components/map/fairway/Profiles.vue:413
+#: src/components/map/fairway/Profiles.vue:414
 msgid "Please enter correct coordinates in the format: Lat,Lon,Lat,Lon"
 msgstr ""
 
@@ -349,8 +362,8 @@
 msgid "Please select a bottleneck"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:41
-#: src/components/admin/usermanagement/Userdetail.vue:86
+#: src/components/admin/usermanagement/Userdetail.vue:40
+#: src/components/admin/usermanagement/Userdetail.vue:85
 msgid "Please select one"
 msgstr ""
 
@@ -358,11 +371,11 @@
 msgid "portrait"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:448
+#: src/components/map/fairway/Profiles.vue:452
 msgid "Profile deleted!"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:434
+#: src/components/map/fairway/Profiles.vue:438
 msgid "Profile saved!"
 msgstr ""
 
@@ -382,7 +395,7 @@
 msgid "Rejected"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:228
+#: src/components/admin/usermanagement/Userdetail.vue:237
 msgid "Repeat Password"
 msgstr ""
 
@@ -390,7 +403,7 @@
 msgid "Request password reset!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:79
+#: src/components/admin/usermanagement/Userdetail.vue:78
 msgid "Role"
 msgstr ""
 
@@ -399,6 +412,7 @@
 msgstr ""
 
 #: src/components/admin/importschedule/Importschedule.vue:36
+#: src/components/admin/importschedule/Importscheduledetail.vue:48
 msgid "Schedule"
 msgstr ""
 
@@ -410,7 +424,7 @@
 msgid "Send"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:139
+#: src/components/admin/usermanagement/Userdetail.vue:138
 msgid "Send testmail"
 msgstr ""
 
@@ -418,7 +432,7 @@
 msgid "Signer"
 msgstr ""
 
-#: src/components/map/Identify.vue:63
+#: src/components/map/Identify.vue:60
 msgid ""
 "Some data ©\n"
 "        <a href=\"https://www.openstreetmap.org/copyright\">%{ name }</a>\n"
@@ -429,11 +443,11 @@
 msgid "Sounding Result"
 msgstr ""
 
-#: src/components/map/Identify.vue:60
+#: src/components/map/Identify.vue:57
 msgid "source-code"
 msgstr ""
 
-#: src/components/Sidebar.vue:44
+#: src/components/Sidebar.vue:54
 msgid "Staging area"
 msgstr ""
 
@@ -453,11 +467,12 @@
 msgid "State"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:130
+#: src/components/admin/importschedule/Importscheduledetail.vue:85
+#: src/components/admin/usermanagement/Userdetail.vue:129
 msgid "Submit"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:373
+#: src/components/map/fairway/Profiles.vue:377
 msgid "Success"
 msgstr ""
 
@@ -465,11 +480,11 @@
 msgid "Successful"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:89
+#: src/components/admin/usermanagement/Userdetail.vue:88
 msgid "Sysadmin"
 msgstr ""
 
-#: src/components/Sidebar.vue:47
+#: src/components/Sidebar.vue:57
 msgid "Systemadministration"
 msgstr ""
 
@@ -477,7 +492,7 @@
 msgid "Systemconfiguration"
 msgstr ""
 
-#: src/components/map/Identify.vue:54
+#: src/components/map/Identify.vue:51
 msgid ""
 "This app uses <i>gemma</i>, which is Free Software under <br/>\n"
 "        %{ license } without warranty, see docs for details."
@@ -496,31 +511,40 @@
 msgid "User"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:15
+#: src/components/admin/usermanagement/Userdetail.vue:14
 #: src/components/map/contextbox/Staging.vue:16
 msgid "Username"
 msgstr ""
 
-#: src/components/Sidebar.vue:52
+#: src/components/Sidebar.vue:66
 #: src/components/admin/usermanagement/Usermanagement.vue:14
 msgid "Users"
 msgstr ""
 
-#: src/components/map/Identify.vue:68
+#: src/components/map/Identify.vue:65
 msgid ""
 "Uses\n"
-"        <a href=\"https://download.geonames.org/export/dump/readme.txt\">GeoNames</a> under %{ geoLicense }."
+"        <a href=\"https://download.geonames.org/export/dump/readme.txt\">GeoNames</a>\n"
+"        under %{ geoLicense }."
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:92
+#: src/components/admin/usermanagement/Userdetail.vue:91
 msgid "Waterway Admin"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:95
+#: src/components/admin/usermanagement/Userdetail.vue:94
 msgid "Waterway User"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:435
-#: src/components/map/fairway/Profiles.vue:436
+#: src/components/map/fairway/Profiles.vue:439
+#: src/components/map/fairway/Profiles.vue:440
 msgid "You can now select these coordinates from the \"Saved cross profiles\" menu to restore this cross profile."
 msgstr ""
+
+#: src/store/map.js:415
+msgid "Length"
+msgstr ""
+
+#: src/store/map.js:436
+msgid "Area"
+msgstr ""
--- a/client/src/locale/hr_HR/LC_MESSAGES/app.po	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/locale/hr_HR/LC_MESSAGES/app.po	Sat Dec 29 16:07:40 2018 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: gemmajs 1.99.0-dev\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-12-05 14:14+0100\n"
+"POT-Creation-Date: 2018-12-10 15:30+0100\n"
 "PO-Revision-Date: 2018-12-05 12:23+0100\n"
 "Last-Translator: Automatically generated\n"
 "Language-Team: none\n"
@@ -25,7 +25,7 @@
 msgid "Accesslog"
 msgstr ""
 
-#: src/components/admin/usermanagement/Usermanagement.vue:106
+#: src/components/admin/usermanagement/Usermanagement.vue:103
 msgid "Add User"
 msgstr ""
 
@@ -38,17 +38,17 @@
 msgstr ""
 
 #: src/components/admin/Importqueue.vue:136
-#: src/components/admin/Systemconfiguration.vue:133
-#: src/components/admin/Systemconfiguration.vue:148
-#: src/components/admin/Systemconfiguration.vue:167
-#: src/components/admin/Systemconfiguration.vue:184
-#: src/components/admin/usermanagement/Userdetail.vue:296
-#: src/components/admin/usermanagement/Userdetail.vue:368
-#: src/components/admin/usermanagement/Usermanagement.vue:300
-#: src/components/admin/usermanagement/Usermanagement.vue:308
-#: src/components/admin/usermanagement/Usermanagement.vue:334
+#: src/components/admin/Systemconfiguration.vue:134
+#: src/components/admin/Systemconfiguration.vue:149
+#: src/components/admin/Systemconfiguration.vue:168
+#: src/components/admin/Systemconfiguration.vue:185
+#: src/components/admin/usermanagement/Userdetail.vue:305
+#: src/components/admin/usermanagement/Userdetail.vue:377
+#: src/components/admin/usermanagement/Usermanagement.vue:313
+#: src/components/admin/usermanagement/Usermanagement.vue:321
+#: src/components/admin/usermanagement/Usermanagement.vue:347
 #: src/components/map/Search.vue:257
-#: src/components/map/contextbox/Bottlenecks.vue:252
+#: src/components/map/contextbox/Bottlenecks.vue:275
 #: src/components/map/contextbox/ImportSoundingresults.vue:205
 #: src/components/map/contextbox/ImportSoundingresults.vue:244
 #: src/components/map/contextbox/ImportSoundingresults.vue:275
@@ -67,7 +67,7 @@
 msgid "Bottleneck Areas stroke-color"
 msgstr ""
 
-#: src/components/Sidebar.vue:22
+#: src/components/Sidebar.vue:27
 #: src/components/map/contextbox/Bottlenecks.vue:4
 msgid "Bottlenecks"
 msgstr ""
@@ -93,7 +93,7 @@
 msgid "Compare with"
 msgstr ""
 
-#: src/components/Sidebar.vue:58
+#: src/components/Sidebar.vue:76
 msgid "Configuration"
 msgstr ""
 
@@ -102,11 +102,11 @@
 msgid "Confirm"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:374
+#: src/components/map/fairway/Profiles.vue:378
 msgid "Coordinates copied to clipboard!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:34
+#: src/components/admin/usermanagement/Userdetail.vue:33
 msgid "Country"
 msgstr ""
 
@@ -128,13 +128,18 @@
 msgstr ""
 
 #: src/components/admin/importschedule/Importschedule.vue:37
+#: src/components/admin/importschedule/Importscheduledetail.vue:80
 msgid "Email"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:60
+#: src/components/admin/usermanagement/Userdetail.vue:59
 msgid "Email address"
 msgstr ""
 
+#: src/components/admin/importschedule/Importscheduledetail.vue:61
+msgid "Email Notification"
+msgstr ""
+
 #: src/components/admin/Importqueue.vue:53
 msgid "Enqueued"
 msgstr ""
@@ -155,7 +160,7 @@
 msgid "Enter username"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:377
+#: src/components/admin/usermanagement/Userdetail.vue:386
 msgid "Error while saving user"
 msgstr ""
 
@@ -184,7 +189,7 @@
 msgid "Import"
 msgstr ""
 
-#: src/components/Sidebar.vue:31
+#: src/components/Sidebar.vue:40
 msgid "Import soundingresults"
 msgstr ""
 
@@ -196,20 +201,28 @@
 msgid "Imported"
 msgstr ""
 
-#: src/components/Sidebar.vue:66 src/components/admin/Importqueue.vue:9
+#: src/components/Sidebar.vue:92 src/components/admin/Importqueue.vue:9
 msgid "Importqueue"
 msgstr ""
 
-#: src/components/Sidebar.vue:70
+#: src/components/admin/importschedule/Importscheduledetail.vue:20
+msgid "Imports"
+msgstr ""
+
+#: src/components/Sidebar.vue:100
 #: src/components/admin/importschedule/Importschedule.vue:9
 msgid "Importschedule"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:345
+#: src/components/admin/importschedule/Importscheduledetail.vue:34
+msgid "Importtype"
+msgstr ""
+
+#: src/components/admin/usermanagement/Userdetail.vue:354
 msgid "invalid email"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:408
+#: src/components/map/fairway/Profiles.vue:412
 msgid "Invalid input"
 msgstr ""
 
@@ -241,19 +254,19 @@
 msgid "Login failed"
 msgstr ""
 
-#: src/components/Sidebar.vue:76
+#: src/components/Sidebar.vue:110
 msgid "Logout"
 msgstr ""
 
-#: src/components/Sidebar.vue:62 src/components/admin/Logs.vue:9
+#: src/components/Sidebar.vue:84 src/components/admin/Logs.vue:9
 msgid "Logs"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:141
+#: src/components/admin/usermanagement/Userdetail.vue:140
 msgid "Mail was sent"
 msgstr ""
 
-#: src/components/Sidebar.vue:14
+#: src/components/Sidebar.vue:15
 msgid "Map"
 msgstr ""
 
@@ -266,7 +279,7 @@
 msgid "Name"
 msgstr ""
 
-#: src/components/admin/importschedule/Importscheduledetail.vue:6
+#: src/components/admin/importschedule/Importscheduledetail.vue:9
 msgid "New import"
 msgstr ""
 
@@ -274,7 +287,7 @@
 msgid "New Import"
 msgstr ""
 
-#: src/components/map/Identify.vue:50
+#: src/components/map/Identify.vue:47
 msgid "No features identified."
 msgstr ""
 
@@ -291,15 +304,15 @@
 msgid "Open in new window"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:229
+#: src/components/admin/usermanagement/Userdetail.vue:238
 msgid "password"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:227
+#: src/components/admin/usermanagement/Userdetail.vue:236
 msgid "Password"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:230
+#: src/components/admin/usermanagement/Userdetail.vue:239
 msgid "password again"
 msgstr ""
 
@@ -307,12 +320,12 @@
 msgid "Password reset requested!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:338
-#: src/components/admin/usermanagement/Userdetail.vue:339
+#: src/components/admin/usermanagement/Userdetail.vue:347
+#: src/components/admin/usermanagement/Userdetail.vue:348
 msgid "Password should at least be 8 char long including 1 digit and 1 special char like $"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:334
+#: src/components/admin/usermanagement/Userdetail.vue:343
 msgid "Passwords do not match!"
 msgstr ""
 
@@ -320,11 +333,11 @@
 msgid "Pending"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:323
+#: src/components/admin/usermanagement/Userdetail.vue:332
 msgid "Please choose a country"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:328
+#: src/components/admin/usermanagement/Userdetail.vue:337
 msgid "Please choose a role"
 msgstr ""
 
@@ -340,8 +353,8 @@
 msgid "Please enter a reference"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:409
-#: src/components/map/fairway/Profiles.vue:410
+#: src/components/map/fairway/Profiles.vue:413
+#: src/components/map/fairway/Profiles.vue:414
 msgid "Please enter correct coordinates in the format: Lat,Lon,Lat,Lon"
 msgstr ""
 
@@ -349,8 +362,8 @@
 msgid "Please select a bottleneck"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:41
-#: src/components/admin/usermanagement/Userdetail.vue:86
+#: src/components/admin/usermanagement/Userdetail.vue:40
+#: src/components/admin/usermanagement/Userdetail.vue:85
 msgid "Please select one"
 msgstr ""
 
@@ -358,11 +371,11 @@
 msgid "portrait"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:448
+#: src/components/map/fairway/Profiles.vue:452
 msgid "Profile deleted!"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:434
+#: src/components/map/fairway/Profiles.vue:438
 msgid "Profile saved!"
 msgstr ""
 
@@ -382,7 +395,7 @@
 msgid "Rejected"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:228
+#: src/components/admin/usermanagement/Userdetail.vue:237
 msgid "Repeat Password"
 msgstr ""
 
@@ -390,7 +403,7 @@
 msgid "Request password reset!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:79
+#: src/components/admin/usermanagement/Userdetail.vue:78
 msgid "Role"
 msgstr ""
 
@@ -399,6 +412,7 @@
 msgstr ""
 
 #: src/components/admin/importschedule/Importschedule.vue:36
+#: src/components/admin/importschedule/Importscheduledetail.vue:48
 msgid "Schedule"
 msgstr ""
 
@@ -410,7 +424,7 @@
 msgid "Send"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:139
+#: src/components/admin/usermanagement/Userdetail.vue:138
 msgid "Send testmail"
 msgstr ""
 
@@ -418,7 +432,7 @@
 msgid "Signer"
 msgstr ""
 
-#: src/components/map/Identify.vue:63
+#: src/components/map/Identify.vue:60
 msgid ""
 "Some data ©\n"
 "        <a href=\"https://www.openstreetmap.org/copyright\">%{ name }</a>\n"
@@ -429,11 +443,11 @@
 msgid "Sounding Result"
 msgstr ""
 
-#: src/components/map/Identify.vue:60
+#: src/components/map/Identify.vue:57
 msgid "source-code"
 msgstr ""
 
-#: src/components/Sidebar.vue:44
+#: src/components/Sidebar.vue:54
 msgid "Staging area"
 msgstr ""
 
@@ -453,11 +467,12 @@
 msgid "State"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:130
+#: src/components/admin/importschedule/Importscheduledetail.vue:85
+#: src/components/admin/usermanagement/Userdetail.vue:129
 msgid "Submit"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:373
+#: src/components/map/fairway/Profiles.vue:377
 msgid "Success"
 msgstr ""
 
@@ -465,11 +480,11 @@
 msgid "Successful"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:89
+#: src/components/admin/usermanagement/Userdetail.vue:88
 msgid "Sysadmin"
 msgstr ""
 
-#: src/components/Sidebar.vue:47
+#: src/components/Sidebar.vue:57
 msgid "Systemadministration"
 msgstr ""
 
@@ -477,7 +492,7 @@
 msgid "Systemconfiguration"
 msgstr ""
 
-#: src/components/map/Identify.vue:54
+#: src/components/map/Identify.vue:51
 msgid ""
 "This app uses <i>gemma</i>, which is Free Software under <br/>\n"
 "        %{ license } without warranty, see docs for details."
@@ -496,31 +511,40 @@
 msgid "User"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:15
+#: src/components/admin/usermanagement/Userdetail.vue:14
 #: src/components/map/contextbox/Staging.vue:16
 msgid "Username"
 msgstr ""
 
-#: src/components/Sidebar.vue:52
+#: src/components/Sidebar.vue:66
 #: src/components/admin/usermanagement/Usermanagement.vue:14
 msgid "Users"
 msgstr ""
 
-#: src/components/map/Identify.vue:68
+#: src/components/map/Identify.vue:65
 msgid ""
 "Uses\n"
-"        <a href=\"https://download.geonames.org/export/dump/readme.txt\">GeoNames</a> under %{ geoLicense }."
+"        <a href=\"https://download.geonames.org/export/dump/readme.txt\">GeoNames</a>\n"
+"        under %{ geoLicense }."
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:92
+#: src/components/admin/usermanagement/Userdetail.vue:91
 msgid "Waterway Admin"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:95
+#: src/components/admin/usermanagement/Userdetail.vue:94
 msgid "Waterway User"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:435
-#: src/components/map/fairway/Profiles.vue:436
+#: src/components/map/fairway/Profiles.vue:439
+#: src/components/map/fairway/Profiles.vue:440
 msgid "You can now select these coordinates from the \"Saved cross profiles\" menu to restore this cross profile."
 msgstr ""
+
+#: src/store/map.js:415
+msgid "Length"
+msgstr ""
+
+#: src/store/map.js:436
+msgid "Area"
+msgstr ""
--- a/client/src/locale/hu_HU/LC_MESSAGES/app.po	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/locale/hu_HU/LC_MESSAGES/app.po	Sat Dec 29 16:07:40 2018 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: gemmajs 1.99.0-dev\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-12-05 14:14+0100\n"
+"POT-Creation-Date: 2018-12-10 15:30+0100\n"
 "PO-Revision-Date: 2018-12-05 12:23+0100\n"
 "Last-Translator: Automatically generated\n"
 "Language-Team: none\n"
@@ -25,7 +25,7 @@
 msgid "Accesslog"
 msgstr ""
 
-#: src/components/admin/usermanagement/Usermanagement.vue:106
+#: src/components/admin/usermanagement/Usermanagement.vue:103
 msgid "Add User"
 msgstr ""
 
@@ -38,17 +38,17 @@
 msgstr ""
 
 #: src/components/admin/Importqueue.vue:136
-#: src/components/admin/Systemconfiguration.vue:133
-#: src/components/admin/Systemconfiguration.vue:148
-#: src/components/admin/Systemconfiguration.vue:167
-#: src/components/admin/Systemconfiguration.vue:184
-#: src/components/admin/usermanagement/Userdetail.vue:296
-#: src/components/admin/usermanagement/Userdetail.vue:368
-#: src/components/admin/usermanagement/Usermanagement.vue:300
-#: src/components/admin/usermanagement/Usermanagement.vue:308
-#: src/components/admin/usermanagement/Usermanagement.vue:334
+#: src/components/admin/Systemconfiguration.vue:134
+#: src/components/admin/Systemconfiguration.vue:149
+#: src/components/admin/Systemconfiguration.vue:168
+#: src/components/admin/Systemconfiguration.vue:185
+#: src/components/admin/usermanagement/Userdetail.vue:305
+#: src/components/admin/usermanagement/Userdetail.vue:377
+#: src/components/admin/usermanagement/Usermanagement.vue:313
+#: src/components/admin/usermanagement/Usermanagement.vue:321
+#: src/components/admin/usermanagement/Usermanagement.vue:347
 #: src/components/map/Search.vue:257
-#: src/components/map/contextbox/Bottlenecks.vue:252
+#: src/components/map/contextbox/Bottlenecks.vue:275
 #: src/components/map/contextbox/ImportSoundingresults.vue:205
 #: src/components/map/contextbox/ImportSoundingresults.vue:244
 #: src/components/map/contextbox/ImportSoundingresults.vue:275
@@ -67,7 +67,7 @@
 msgid "Bottleneck Areas stroke-color"
 msgstr ""
 
-#: src/components/Sidebar.vue:22
+#: src/components/Sidebar.vue:27
 #: src/components/map/contextbox/Bottlenecks.vue:4
 msgid "Bottlenecks"
 msgstr ""
@@ -93,7 +93,7 @@
 msgid "Compare with"
 msgstr ""
 
-#: src/components/Sidebar.vue:58
+#: src/components/Sidebar.vue:76
 msgid "Configuration"
 msgstr ""
 
@@ -102,11 +102,11 @@
 msgid "Confirm"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:374
+#: src/components/map/fairway/Profiles.vue:378
 msgid "Coordinates copied to clipboard!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:34
+#: src/components/admin/usermanagement/Userdetail.vue:33
 msgid "Country"
 msgstr ""
 
@@ -128,13 +128,18 @@
 msgstr ""
 
 #: src/components/admin/importschedule/Importschedule.vue:37
+#: src/components/admin/importschedule/Importscheduledetail.vue:80
 msgid "Email"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:60
+#: src/components/admin/usermanagement/Userdetail.vue:59
 msgid "Email address"
 msgstr ""
 
+#: src/components/admin/importschedule/Importscheduledetail.vue:61
+msgid "Email Notification"
+msgstr ""
+
 #: src/components/admin/Importqueue.vue:53
 msgid "Enqueued"
 msgstr ""
@@ -155,7 +160,7 @@
 msgid "Enter username"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:377
+#: src/components/admin/usermanagement/Userdetail.vue:386
 msgid "Error while saving user"
 msgstr ""
 
@@ -184,7 +189,7 @@
 msgid "Import"
 msgstr ""
 
-#: src/components/Sidebar.vue:31
+#: src/components/Sidebar.vue:40
 msgid "Import soundingresults"
 msgstr ""
 
@@ -196,20 +201,28 @@
 msgid "Imported"
 msgstr ""
 
-#: src/components/Sidebar.vue:66 src/components/admin/Importqueue.vue:9
+#: src/components/Sidebar.vue:92 src/components/admin/Importqueue.vue:9
 msgid "Importqueue"
 msgstr ""
 
-#: src/components/Sidebar.vue:70
+#: src/components/admin/importschedule/Importscheduledetail.vue:20
+msgid "Imports"
+msgstr ""
+
+#: src/components/Sidebar.vue:100
 #: src/components/admin/importschedule/Importschedule.vue:9
 msgid "Importschedule"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:345
+#: src/components/admin/importschedule/Importscheduledetail.vue:34
+msgid "Importtype"
+msgstr ""
+
+#: src/components/admin/usermanagement/Userdetail.vue:354
 msgid "invalid email"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:408
+#: src/components/map/fairway/Profiles.vue:412
 msgid "Invalid input"
 msgstr ""
 
@@ -241,19 +254,19 @@
 msgid "Login failed"
 msgstr ""
 
-#: src/components/Sidebar.vue:76
+#: src/components/Sidebar.vue:110
 msgid "Logout"
 msgstr ""
 
-#: src/components/Sidebar.vue:62 src/components/admin/Logs.vue:9
+#: src/components/Sidebar.vue:84 src/components/admin/Logs.vue:9
 msgid "Logs"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:141
+#: src/components/admin/usermanagement/Userdetail.vue:140
 msgid "Mail was sent"
 msgstr ""
 
-#: src/components/Sidebar.vue:14
+#: src/components/Sidebar.vue:15
 msgid "Map"
 msgstr ""
 
@@ -266,7 +279,7 @@
 msgid "Name"
 msgstr ""
 
-#: src/components/admin/importschedule/Importscheduledetail.vue:6
+#: src/components/admin/importschedule/Importscheduledetail.vue:9
 msgid "New import"
 msgstr ""
 
@@ -274,7 +287,7 @@
 msgid "New Import"
 msgstr ""
 
-#: src/components/map/Identify.vue:50
+#: src/components/map/Identify.vue:47
 msgid "No features identified."
 msgstr ""
 
@@ -291,15 +304,15 @@
 msgid "Open in new window"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:229
+#: src/components/admin/usermanagement/Userdetail.vue:238
 msgid "password"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:227
+#: src/components/admin/usermanagement/Userdetail.vue:236
 msgid "Password"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:230
+#: src/components/admin/usermanagement/Userdetail.vue:239
 msgid "password again"
 msgstr ""
 
@@ -307,12 +320,12 @@
 msgid "Password reset requested!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:338
-#: src/components/admin/usermanagement/Userdetail.vue:339
+#: src/components/admin/usermanagement/Userdetail.vue:347
+#: src/components/admin/usermanagement/Userdetail.vue:348
 msgid "Password should at least be 8 char long including 1 digit and 1 special char like $"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:334
+#: src/components/admin/usermanagement/Userdetail.vue:343
 msgid "Passwords do not match!"
 msgstr ""
 
@@ -320,11 +333,11 @@
 msgid "Pending"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:323
+#: src/components/admin/usermanagement/Userdetail.vue:332
 msgid "Please choose a country"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:328
+#: src/components/admin/usermanagement/Userdetail.vue:337
 msgid "Please choose a role"
 msgstr ""
 
@@ -340,8 +353,8 @@
 msgid "Please enter a reference"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:409
-#: src/components/map/fairway/Profiles.vue:410
+#: src/components/map/fairway/Profiles.vue:413
+#: src/components/map/fairway/Profiles.vue:414
 msgid "Please enter correct coordinates in the format: Lat,Lon,Lat,Lon"
 msgstr ""
 
@@ -349,8 +362,8 @@
 msgid "Please select a bottleneck"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:41
-#: src/components/admin/usermanagement/Userdetail.vue:86
+#: src/components/admin/usermanagement/Userdetail.vue:40
+#: src/components/admin/usermanagement/Userdetail.vue:85
 msgid "Please select one"
 msgstr ""
 
@@ -358,11 +371,11 @@
 msgid "portrait"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:448
+#: src/components/map/fairway/Profiles.vue:452
 msgid "Profile deleted!"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:434
+#: src/components/map/fairway/Profiles.vue:438
 msgid "Profile saved!"
 msgstr ""
 
@@ -382,7 +395,7 @@
 msgid "Rejected"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:228
+#: src/components/admin/usermanagement/Userdetail.vue:237
 msgid "Repeat Password"
 msgstr ""
 
@@ -390,7 +403,7 @@
 msgid "Request password reset!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:79
+#: src/components/admin/usermanagement/Userdetail.vue:78
 msgid "Role"
 msgstr ""
 
@@ -399,6 +412,7 @@
 msgstr ""
 
 #: src/components/admin/importschedule/Importschedule.vue:36
+#: src/components/admin/importschedule/Importscheduledetail.vue:48
 msgid "Schedule"
 msgstr ""
 
@@ -410,7 +424,7 @@
 msgid "Send"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:139
+#: src/components/admin/usermanagement/Userdetail.vue:138
 msgid "Send testmail"
 msgstr ""
 
@@ -418,7 +432,7 @@
 msgid "Signer"
 msgstr ""
 
-#: src/components/map/Identify.vue:63
+#: src/components/map/Identify.vue:60
 msgid ""
 "Some data ©\n"
 "        <a href=\"https://www.openstreetmap.org/copyright\">%{ name }</a>\n"
@@ -429,11 +443,11 @@
 msgid "Sounding Result"
 msgstr ""
 
-#: src/components/map/Identify.vue:60
+#: src/components/map/Identify.vue:57
 msgid "source-code"
 msgstr ""
 
-#: src/components/Sidebar.vue:44
+#: src/components/Sidebar.vue:54
 msgid "Staging area"
 msgstr ""
 
@@ -453,11 +467,12 @@
 msgid "State"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:130
+#: src/components/admin/importschedule/Importscheduledetail.vue:85
+#: src/components/admin/usermanagement/Userdetail.vue:129
 msgid "Submit"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:373
+#: src/components/map/fairway/Profiles.vue:377
 msgid "Success"
 msgstr ""
 
@@ -465,11 +480,11 @@
 msgid "Successful"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:89
+#: src/components/admin/usermanagement/Userdetail.vue:88
 msgid "Sysadmin"
 msgstr ""
 
-#: src/components/Sidebar.vue:47
+#: src/components/Sidebar.vue:57
 msgid "Systemadministration"
 msgstr ""
 
@@ -477,7 +492,7 @@
 msgid "Systemconfiguration"
 msgstr ""
 
-#: src/components/map/Identify.vue:54
+#: src/components/map/Identify.vue:51
 msgid ""
 "This app uses <i>gemma</i>, which is Free Software under <br/>\n"
 "        %{ license } without warranty, see docs for details."
@@ -496,31 +511,40 @@
 msgid "User"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:15
+#: src/components/admin/usermanagement/Userdetail.vue:14
 #: src/components/map/contextbox/Staging.vue:16
 msgid "Username"
 msgstr ""
 
-#: src/components/Sidebar.vue:52
+#: src/components/Sidebar.vue:66
 #: src/components/admin/usermanagement/Usermanagement.vue:14
 msgid "Users"
 msgstr ""
 
-#: src/components/map/Identify.vue:68
+#: src/components/map/Identify.vue:65
 msgid ""
 "Uses\n"
-"        <a href=\"https://download.geonames.org/export/dump/readme.txt\">GeoNames</a> under %{ geoLicense }."
+"        <a href=\"https://download.geonames.org/export/dump/readme.txt\">GeoNames</a>\n"
+"        under %{ geoLicense }."
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:92
+#: src/components/admin/usermanagement/Userdetail.vue:91
 msgid "Waterway Admin"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:95
+#: src/components/admin/usermanagement/Userdetail.vue:94
 msgid "Waterway User"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:435
-#: src/components/map/fairway/Profiles.vue:436
+#: src/components/map/fairway/Profiles.vue:439
+#: src/components/map/fairway/Profiles.vue:440
 msgid "You can now select these coordinates from the \"Saved cross profiles\" menu to restore this cross profile."
 msgstr ""
+
+#: src/store/map.js:415
+msgid "Length"
+msgstr ""
+
+#: src/store/map.js:436
+msgid "Area"
+msgstr ""
--- a/client/src/locale/ro_RO/LC_MESSAGES/app.po	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/locale/ro_RO/LC_MESSAGES/app.po	Sat Dec 29 16:07:40 2018 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: gemmajs 1.99.0-dev\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-12-05 14:14+0100\n"
+"POT-Creation-Date: 2018-12-10 15:30+0100\n"
 "PO-Revision-Date: 2018-12-05 12:23+0100\n"
 "Last-Translator: Automatically generated\n"
 "Language-Team: none\n"
@@ -25,7 +25,7 @@
 msgid "Accesslog"
 msgstr ""
 
-#: src/components/admin/usermanagement/Usermanagement.vue:106
+#: src/components/admin/usermanagement/Usermanagement.vue:103
 msgid "Add User"
 msgstr ""
 
@@ -38,17 +38,17 @@
 msgstr ""
 
 #: src/components/admin/Importqueue.vue:136
-#: src/components/admin/Systemconfiguration.vue:133
-#: src/components/admin/Systemconfiguration.vue:148
-#: src/components/admin/Systemconfiguration.vue:167
-#: src/components/admin/Systemconfiguration.vue:184
-#: src/components/admin/usermanagement/Userdetail.vue:296
-#: src/components/admin/usermanagement/Userdetail.vue:368
-#: src/components/admin/usermanagement/Usermanagement.vue:300
-#: src/components/admin/usermanagement/Usermanagement.vue:308
-#: src/components/admin/usermanagement/Usermanagement.vue:334
+#: src/components/admin/Systemconfiguration.vue:134
+#: src/components/admin/Systemconfiguration.vue:149
+#: src/components/admin/Systemconfiguration.vue:168
+#: src/components/admin/Systemconfiguration.vue:185
+#: src/components/admin/usermanagement/Userdetail.vue:305
+#: src/components/admin/usermanagement/Userdetail.vue:377
+#: src/components/admin/usermanagement/Usermanagement.vue:313
+#: src/components/admin/usermanagement/Usermanagement.vue:321
+#: src/components/admin/usermanagement/Usermanagement.vue:347
 #: src/components/map/Search.vue:257
-#: src/components/map/contextbox/Bottlenecks.vue:252
+#: src/components/map/contextbox/Bottlenecks.vue:275
 #: src/components/map/contextbox/ImportSoundingresults.vue:205
 #: src/components/map/contextbox/ImportSoundingresults.vue:244
 #: src/components/map/contextbox/ImportSoundingresults.vue:275
@@ -67,7 +67,7 @@
 msgid "Bottleneck Areas stroke-color"
 msgstr ""
 
-#: src/components/Sidebar.vue:22
+#: src/components/Sidebar.vue:27
 #: src/components/map/contextbox/Bottlenecks.vue:4
 msgid "Bottlenecks"
 msgstr ""
@@ -93,7 +93,7 @@
 msgid "Compare with"
 msgstr ""
 
-#: src/components/Sidebar.vue:58
+#: src/components/Sidebar.vue:76
 msgid "Configuration"
 msgstr ""
 
@@ -102,11 +102,11 @@
 msgid "Confirm"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:374
+#: src/components/map/fairway/Profiles.vue:378
 msgid "Coordinates copied to clipboard!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:34
+#: src/components/admin/usermanagement/Userdetail.vue:33
 msgid "Country"
 msgstr ""
 
@@ -128,13 +128,18 @@
 msgstr ""
 
 #: src/components/admin/importschedule/Importschedule.vue:37
+#: src/components/admin/importschedule/Importscheduledetail.vue:80
 msgid "Email"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:60
+#: src/components/admin/usermanagement/Userdetail.vue:59
 msgid "Email address"
 msgstr ""
 
+#: src/components/admin/importschedule/Importscheduledetail.vue:61
+msgid "Email Notification"
+msgstr ""
+
 #: src/components/admin/Importqueue.vue:53
 msgid "Enqueued"
 msgstr ""
@@ -155,7 +160,7 @@
 msgid "Enter username"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:377
+#: src/components/admin/usermanagement/Userdetail.vue:386
 msgid "Error while saving user"
 msgstr ""
 
@@ -184,7 +189,7 @@
 msgid "Import"
 msgstr ""
 
-#: src/components/Sidebar.vue:31
+#: src/components/Sidebar.vue:40
 msgid "Import soundingresults"
 msgstr ""
 
@@ -196,20 +201,28 @@
 msgid "Imported"
 msgstr ""
 
-#: src/components/Sidebar.vue:66 src/components/admin/Importqueue.vue:9
+#: src/components/Sidebar.vue:92 src/components/admin/Importqueue.vue:9
 msgid "Importqueue"
 msgstr ""
 
-#: src/components/Sidebar.vue:70
+#: src/components/admin/importschedule/Importscheduledetail.vue:20
+msgid "Imports"
+msgstr ""
+
+#: src/components/Sidebar.vue:100
 #: src/components/admin/importschedule/Importschedule.vue:9
 msgid "Importschedule"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:345
+#: src/components/admin/importschedule/Importscheduledetail.vue:34
+msgid "Importtype"
+msgstr ""
+
+#: src/components/admin/usermanagement/Userdetail.vue:354
 msgid "invalid email"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:408
+#: src/components/map/fairway/Profiles.vue:412
 msgid "Invalid input"
 msgstr ""
 
@@ -241,19 +254,19 @@
 msgid "Login failed"
 msgstr ""
 
-#: src/components/Sidebar.vue:76
+#: src/components/Sidebar.vue:110
 msgid "Logout"
 msgstr ""
 
-#: src/components/Sidebar.vue:62 src/components/admin/Logs.vue:9
+#: src/components/Sidebar.vue:84 src/components/admin/Logs.vue:9
 msgid "Logs"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:141
+#: src/components/admin/usermanagement/Userdetail.vue:140
 msgid "Mail was sent"
 msgstr ""
 
-#: src/components/Sidebar.vue:14
+#: src/components/Sidebar.vue:15
 msgid "Map"
 msgstr ""
 
@@ -266,7 +279,7 @@
 msgid "Name"
 msgstr ""
 
-#: src/components/admin/importschedule/Importscheduledetail.vue:6
+#: src/components/admin/importschedule/Importscheduledetail.vue:9
 msgid "New import"
 msgstr ""
 
@@ -274,7 +287,7 @@
 msgid "New Import"
 msgstr ""
 
-#: src/components/map/Identify.vue:50
+#: src/components/map/Identify.vue:47
 msgid "No features identified."
 msgstr ""
 
@@ -291,15 +304,15 @@
 msgid "Open in new window"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:229
+#: src/components/admin/usermanagement/Userdetail.vue:238
 msgid "password"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:227
+#: src/components/admin/usermanagement/Userdetail.vue:236
 msgid "Password"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:230
+#: src/components/admin/usermanagement/Userdetail.vue:239
 msgid "password again"
 msgstr ""
 
@@ -307,12 +320,12 @@
 msgid "Password reset requested!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:338
-#: src/components/admin/usermanagement/Userdetail.vue:339
+#: src/components/admin/usermanagement/Userdetail.vue:347
+#: src/components/admin/usermanagement/Userdetail.vue:348
 msgid "Password should at least be 8 char long including 1 digit and 1 special char like $"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:334
+#: src/components/admin/usermanagement/Userdetail.vue:343
 msgid "Passwords do not match!"
 msgstr ""
 
@@ -320,11 +333,11 @@
 msgid "Pending"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:323
+#: src/components/admin/usermanagement/Userdetail.vue:332
 msgid "Please choose a country"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:328
+#: src/components/admin/usermanagement/Userdetail.vue:337
 msgid "Please choose a role"
 msgstr ""
 
@@ -340,8 +353,8 @@
 msgid "Please enter a reference"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:409
-#: src/components/map/fairway/Profiles.vue:410
+#: src/components/map/fairway/Profiles.vue:413
+#: src/components/map/fairway/Profiles.vue:414
 msgid "Please enter correct coordinates in the format: Lat,Lon,Lat,Lon"
 msgstr ""
 
@@ -349,8 +362,8 @@
 msgid "Please select a bottleneck"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:41
-#: src/components/admin/usermanagement/Userdetail.vue:86
+#: src/components/admin/usermanagement/Userdetail.vue:40
+#: src/components/admin/usermanagement/Userdetail.vue:85
 msgid "Please select one"
 msgstr ""
 
@@ -358,11 +371,11 @@
 msgid "portrait"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:448
+#: src/components/map/fairway/Profiles.vue:452
 msgid "Profile deleted!"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:434
+#: src/components/map/fairway/Profiles.vue:438
 msgid "Profile saved!"
 msgstr ""
 
@@ -382,7 +395,7 @@
 msgid "Rejected"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:228
+#: src/components/admin/usermanagement/Userdetail.vue:237
 msgid "Repeat Password"
 msgstr ""
 
@@ -390,7 +403,7 @@
 msgid "Request password reset!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:79
+#: src/components/admin/usermanagement/Userdetail.vue:78
 msgid "Role"
 msgstr ""
 
@@ -399,6 +412,7 @@
 msgstr ""
 
 #: src/components/admin/importschedule/Importschedule.vue:36
+#: src/components/admin/importschedule/Importscheduledetail.vue:48
 msgid "Schedule"
 msgstr ""
 
@@ -410,7 +424,7 @@
 msgid "Send"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:139
+#: src/components/admin/usermanagement/Userdetail.vue:138
 msgid "Send testmail"
 msgstr ""
 
@@ -418,7 +432,7 @@
 msgid "Signer"
 msgstr ""
 
-#: src/components/map/Identify.vue:63
+#: src/components/map/Identify.vue:60
 msgid ""
 "Some data ©\n"
 "        <a href=\"https://www.openstreetmap.org/copyright\">%{ name }</a>\n"
@@ -429,11 +443,11 @@
 msgid "Sounding Result"
 msgstr ""
 
-#: src/components/map/Identify.vue:60
+#: src/components/map/Identify.vue:57
 msgid "source-code"
 msgstr ""
 
-#: src/components/Sidebar.vue:44
+#: src/components/Sidebar.vue:54
 msgid "Staging area"
 msgstr ""
 
@@ -453,11 +467,12 @@
 msgid "State"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:130
+#: src/components/admin/importschedule/Importscheduledetail.vue:85
+#: src/components/admin/usermanagement/Userdetail.vue:129
 msgid "Submit"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:373
+#: src/components/map/fairway/Profiles.vue:377
 msgid "Success"
 msgstr ""
 
@@ -465,11 +480,11 @@
 msgid "Successful"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:89
+#: src/components/admin/usermanagement/Userdetail.vue:88
 msgid "Sysadmin"
 msgstr ""
 
-#: src/components/Sidebar.vue:47
+#: src/components/Sidebar.vue:57
 msgid "Systemadministration"
 msgstr ""
 
@@ -477,7 +492,7 @@
 msgid "Systemconfiguration"
 msgstr ""
 
-#: src/components/map/Identify.vue:54
+#: src/components/map/Identify.vue:51
 msgid ""
 "This app uses <i>gemma</i>, which is Free Software under <br/>\n"
 "        %{ license } without warranty, see docs for details."
@@ -496,31 +511,40 @@
 msgid "User"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:15
+#: src/components/admin/usermanagement/Userdetail.vue:14
 #: src/components/map/contextbox/Staging.vue:16
 msgid "Username"
 msgstr ""
 
-#: src/components/Sidebar.vue:52
+#: src/components/Sidebar.vue:66
 #: src/components/admin/usermanagement/Usermanagement.vue:14
 msgid "Users"
 msgstr ""
 
-#: src/components/map/Identify.vue:68
+#: src/components/map/Identify.vue:65
 msgid ""
 "Uses\n"
-"        <a href=\"https://download.geonames.org/export/dump/readme.txt\">GeoNames</a> under %{ geoLicense }."
+"        <a href=\"https://download.geonames.org/export/dump/readme.txt\">GeoNames</a>\n"
+"        under %{ geoLicense }."
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:92
+#: src/components/admin/usermanagement/Userdetail.vue:91
 msgid "Waterway Admin"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:95
+#: src/components/admin/usermanagement/Userdetail.vue:94
 msgid "Waterway User"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:435
-#: src/components/map/fairway/Profiles.vue:436
+#: src/components/map/fairway/Profiles.vue:439
+#: src/components/map/fairway/Profiles.vue:440
 msgid "You can now select these coordinates from the \"Saved cross profiles\" menu to restore this cross profile."
 msgstr ""
+
+#: src/store/map.js:415
+msgid "Length"
+msgstr ""
+
+#: src/store/map.js:436
+msgid "Area"
+msgstr ""
--- a/client/src/locale/sk_SK/LC_MESSAGES/app.po	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/locale/sk_SK/LC_MESSAGES/app.po	Sat Dec 29 16:07:40 2018 +0100
@@ -7,7 +7,7 @@
 msgstr ""
 "Project-Id-Version: wamosjs 0.1.0\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-12-05 14:14+0100\n"
+"POT-Creation-Date: 2018-12-10 15:30+0100\n"
 "PO-Revision-Date: 2018-07-03 17:18+0200\n"
 "Last-Translator: Automatically generated\n"
 "Language-Team: none\n"
@@ -25,7 +25,7 @@
 msgid "Accesslog"
 msgstr ""
 
-#: src/components/admin/usermanagement/Usermanagement.vue:106
+#: src/components/admin/usermanagement/Usermanagement.vue:103
 msgid "Add User"
 msgstr ""
 
@@ -38,17 +38,17 @@
 msgstr ""
 
 #: src/components/admin/Importqueue.vue:136
-#: src/components/admin/Systemconfiguration.vue:133
-#: src/components/admin/Systemconfiguration.vue:148
-#: src/components/admin/Systemconfiguration.vue:167
-#: src/components/admin/Systemconfiguration.vue:184
-#: src/components/admin/usermanagement/Userdetail.vue:296
-#: src/components/admin/usermanagement/Userdetail.vue:368
-#: src/components/admin/usermanagement/Usermanagement.vue:300
-#: src/components/admin/usermanagement/Usermanagement.vue:308
-#: src/components/admin/usermanagement/Usermanagement.vue:334
+#: src/components/admin/Systemconfiguration.vue:134
+#: src/components/admin/Systemconfiguration.vue:149
+#: src/components/admin/Systemconfiguration.vue:168
+#: src/components/admin/Systemconfiguration.vue:185
+#: src/components/admin/usermanagement/Userdetail.vue:305
+#: src/components/admin/usermanagement/Userdetail.vue:377
+#: src/components/admin/usermanagement/Usermanagement.vue:313
+#: src/components/admin/usermanagement/Usermanagement.vue:321
+#: src/components/admin/usermanagement/Usermanagement.vue:347
 #: src/components/map/Search.vue:257
-#: src/components/map/contextbox/Bottlenecks.vue:252
+#: src/components/map/contextbox/Bottlenecks.vue:275
 #: src/components/map/contextbox/ImportSoundingresults.vue:205
 #: src/components/map/contextbox/ImportSoundingresults.vue:244
 #: src/components/map/contextbox/ImportSoundingresults.vue:275
@@ -67,7 +67,7 @@
 msgid "Bottleneck Areas stroke-color"
 msgstr ""
 
-#: src/components/Sidebar.vue:22
+#: src/components/Sidebar.vue:27
 #: src/components/map/contextbox/Bottlenecks.vue:4
 msgid "Bottlenecks"
 msgstr ""
@@ -93,7 +93,7 @@
 msgid "Compare with"
 msgstr ""
 
-#: src/components/Sidebar.vue:58
+#: src/components/Sidebar.vue:76
 msgid "Configuration"
 msgstr ""
 
@@ -102,11 +102,11 @@
 msgid "Confirm"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:374
+#: src/components/map/fairway/Profiles.vue:378
 msgid "Coordinates copied to clipboard!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:34
+#: src/components/admin/usermanagement/Userdetail.vue:33
 msgid "Country"
 msgstr ""
 
@@ -128,13 +128,18 @@
 msgstr ""
 
 #: src/components/admin/importschedule/Importschedule.vue:37
+#: src/components/admin/importschedule/Importscheduledetail.vue:80
 msgid "Email"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:60
+#: src/components/admin/usermanagement/Userdetail.vue:59
 msgid "Email address"
 msgstr ""
 
+#: src/components/admin/importschedule/Importscheduledetail.vue:61
+msgid "Email Notification"
+msgstr ""
+
 #: src/components/admin/Importqueue.vue:53
 msgid "Enqueued"
 msgstr ""
@@ -155,7 +160,7 @@
 msgid "Enter username"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:377
+#: src/components/admin/usermanagement/Userdetail.vue:386
 msgid "Error while saving user"
 msgstr ""
 
@@ -184,7 +189,7 @@
 msgid "Import"
 msgstr ""
 
-#: src/components/Sidebar.vue:31
+#: src/components/Sidebar.vue:40
 msgid "Import soundingresults"
 msgstr ""
 
@@ -196,20 +201,28 @@
 msgid "Imported"
 msgstr ""
 
-#: src/components/Sidebar.vue:66 src/components/admin/Importqueue.vue:9
+#: src/components/Sidebar.vue:92 src/components/admin/Importqueue.vue:9
 msgid "Importqueue"
 msgstr ""
 
-#: src/components/Sidebar.vue:70
+#: src/components/admin/importschedule/Importscheduledetail.vue:20
+msgid "Imports"
+msgstr ""
+
+#: src/components/Sidebar.vue:100
 #: src/components/admin/importschedule/Importschedule.vue:9
 msgid "Importschedule"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:345
+#: src/components/admin/importschedule/Importscheduledetail.vue:34
+msgid "Importtype"
+msgstr ""
+
+#: src/components/admin/usermanagement/Userdetail.vue:354
 msgid "invalid email"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:408
+#: src/components/map/fairway/Profiles.vue:412
 msgid "Invalid input"
 msgstr ""
 
@@ -241,19 +254,19 @@
 msgid "Login failed"
 msgstr ""
 
-#: src/components/Sidebar.vue:76
+#: src/components/Sidebar.vue:110
 msgid "Logout"
 msgstr ""
 
-#: src/components/Sidebar.vue:62 src/components/admin/Logs.vue:9
+#: src/components/Sidebar.vue:84 src/components/admin/Logs.vue:9
 msgid "Logs"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:141
+#: src/components/admin/usermanagement/Userdetail.vue:140
 msgid "Mail was sent"
 msgstr ""
 
-#: src/components/Sidebar.vue:14
+#: src/components/Sidebar.vue:15
 msgid "Map"
 msgstr ""
 
@@ -266,7 +279,7 @@
 msgid "Name"
 msgstr ""
 
-#: src/components/admin/importschedule/Importscheduledetail.vue:6
+#: src/components/admin/importschedule/Importscheduledetail.vue:9
 msgid "New import"
 msgstr ""
 
@@ -274,7 +287,7 @@
 msgid "New Import"
 msgstr ""
 
-#: src/components/map/Identify.vue:50
+#: src/components/map/Identify.vue:47
 msgid "No features identified."
 msgstr ""
 
@@ -291,15 +304,15 @@
 msgid "Open in new window"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:229
+#: src/components/admin/usermanagement/Userdetail.vue:238
 msgid "password"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:227
+#: src/components/admin/usermanagement/Userdetail.vue:236
 msgid "Password"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:230
+#: src/components/admin/usermanagement/Userdetail.vue:239
 msgid "password again"
 msgstr ""
 
@@ -307,12 +320,12 @@
 msgid "Password reset requested!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:338
-#: src/components/admin/usermanagement/Userdetail.vue:339
+#: src/components/admin/usermanagement/Userdetail.vue:347
+#: src/components/admin/usermanagement/Userdetail.vue:348
 msgid "Password should at least be 8 char long including 1 digit and 1 special char like $"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:334
+#: src/components/admin/usermanagement/Userdetail.vue:343
 msgid "Passwords do not match!"
 msgstr ""
 
@@ -320,11 +333,11 @@
 msgid "Pending"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:323
+#: src/components/admin/usermanagement/Userdetail.vue:332
 msgid "Please choose a country"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:328
+#: src/components/admin/usermanagement/Userdetail.vue:337
 msgid "Please choose a role"
 msgstr ""
 
@@ -340,8 +353,8 @@
 msgid "Please enter a reference"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:409
-#: src/components/map/fairway/Profiles.vue:410
+#: src/components/map/fairway/Profiles.vue:413
+#: src/components/map/fairway/Profiles.vue:414
 msgid "Please enter correct coordinates in the format: Lat,Lon,Lat,Lon"
 msgstr ""
 
@@ -349,8 +362,8 @@
 msgid "Please select a bottleneck"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:41
-#: src/components/admin/usermanagement/Userdetail.vue:86
+#: src/components/admin/usermanagement/Userdetail.vue:40
+#: src/components/admin/usermanagement/Userdetail.vue:85
 msgid "Please select one"
 msgstr ""
 
@@ -358,11 +371,11 @@
 msgid "portrait"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:448
+#: src/components/map/fairway/Profiles.vue:452
 msgid "Profile deleted!"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:434
+#: src/components/map/fairway/Profiles.vue:438
 msgid "Profile saved!"
 msgstr ""
 
@@ -382,7 +395,7 @@
 msgid "Rejected"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:228
+#: src/components/admin/usermanagement/Userdetail.vue:237
 msgid "Repeat Password"
 msgstr ""
 
@@ -390,7 +403,7 @@
 msgid "Request password reset!"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:79
+#: src/components/admin/usermanagement/Userdetail.vue:78
 msgid "Role"
 msgstr ""
 
@@ -399,6 +412,7 @@
 msgstr ""
 
 #: src/components/admin/importschedule/Importschedule.vue:36
+#: src/components/admin/importschedule/Importscheduledetail.vue:48
 msgid "Schedule"
 msgstr ""
 
@@ -410,7 +424,7 @@
 msgid "Send"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:139
+#: src/components/admin/usermanagement/Userdetail.vue:138
 msgid "Send testmail"
 msgstr ""
 
@@ -418,7 +432,7 @@
 msgid "Signer"
 msgstr ""
 
-#: src/components/map/Identify.vue:63
+#: src/components/map/Identify.vue:60
 msgid ""
 "Some data ©\n"
 "        <a href=\"https://www.openstreetmap.org/copyright\">%{ name }</a>\n"
@@ -429,11 +443,11 @@
 msgid "Sounding Result"
 msgstr ""
 
-#: src/components/map/Identify.vue:60
+#: src/components/map/Identify.vue:57
 msgid "source-code"
 msgstr ""
 
-#: src/components/Sidebar.vue:44
+#: src/components/Sidebar.vue:54
 msgid "Staging area"
 msgstr ""
 
@@ -453,11 +467,12 @@
 msgid "State"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:130
+#: src/components/admin/importschedule/Importscheduledetail.vue:85
+#: src/components/admin/usermanagement/Userdetail.vue:129
 msgid "Submit"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:373
+#: src/components/map/fairway/Profiles.vue:377
 msgid "Success"
 msgstr ""
 
@@ -465,11 +480,11 @@
 msgid "Successful"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:89
+#: src/components/admin/usermanagement/Userdetail.vue:88
 msgid "Sysadmin"
 msgstr ""
 
-#: src/components/Sidebar.vue:47
+#: src/components/Sidebar.vue:57
 msgid "Systemadministration"
 msgstr ""
 
@@ -477,7 +492,7 @@
 msgid "Systemconfiguration"
 msgstr ""
 
-#: src/components/map/Identify.vue:54
+#: src/components/map/Identify.vue:51
 msgid ""
 "This app uses <i>gemma</i>, which is Free Software under <br/>\n"
 "        %{ license } without warranty, see docs for details."
@@ -496,31 +511,40 @@
 msgid "User"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:15
+#: src/components/admin/usermanagement/Userdetail.vue:14
 #: src/components/map/contextbox/Staging.vue:16
 msgid "Username"
 msgstr ""
 
-#: src/components/Sidebar.vue:52
+#: src/components/Sidebar.vue:66
 #: src/components/admin/usermanagement/Usermanagement.vue:14
 msgid "Users"
 msgstr ""
 
-#: src/components/map/Identify.vue:68
+#: src/components/map/Identify.vue:65
 msgid ""
 "Uses\n"
-"        <a href=\"https://download.geonames.org/export/dump/readme.txt\">GeoNames</a> under %{ geoLicense }."
+"        <a href=\"https://download.geonames.org/export/dump/readme.txt\">GeoNames</a>\n"
+"        under %{ geoLicense }."
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:92
+#: src/components/admin/usermanagement/Userdetail.vue:91
 msgid "Waterway Admin"
 msgstr ""
 
-#: src/components/admin/usermanagement/Userdetail.vue:95
+#: src/components/admin/usermanagement/Userdetail.vue:94
 msgid "Waterway User"
 msgstr ""
 
-#: src/components/map/fairway/Profiles.vue:435
-#: src/components/map/fairway/Profiles.vue:436
+#: src/components/map/fairway/Profiles.vue:439
+#: src/components/map/fairway/Profiles.vue:440
 msgid "You can now select these coordinates from the \"Saved cross profiles\" menu to restore this cross profile."
 msgstr ""
+
+#: src/store/map.js:415
+msgid "Length"
+msgstr ""
+
+#: src/store/map.js:436
+msgid "Area"
+msgstr ""
--- a/client/src/locale/translations.json	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/locale/translations.json	Sat Dec 29 16:07:40 2018 +0100
@@ -1,1 +1,1 @@
-{"bg_BG":{},"de_AT":{"Accepted":"Akzeptiert","Accesslog":"Zugriffs-Protokoll","Add User":"Benutzer hinzufügen","Author":"Autor","back to login":"zurück zur Anmeldung","Backend Error":"Server-Fehler","Bottleneck":"Seichtstelle","Bottleneck Areas fill-color":"Flächenfüllfarbe Seichtstelle","Bottleneck Areas stroke-color":"Flächenumrandungsfarbe Seichtstelle","Bottlenecks":"Seichtstellen","Cancel Upload":"Hochladen abbrechen","Chainage":"Verkettung","choose .zip- file":"Wählen Sie eine .zip Datei","Chose format:":"Format wählen:","Compare with":"Vergleiche mit","Configuration":"Konfiguration","Confirm":"Bestätigen","Coordinates copied to clipboard!":"Koordinaten auf die Zwischenablage kopiert!","Country":"Land","Date":"Datum","Depthreference":"Tiefenreferenz","Download":"Herunterladen","Download Meta.json":"Meta.json Herunterladen","Email":"E-Mail","Email address":"E-Mail Adresse","Enqueued":"Hinzugefügt","Enter coordinates manually":"Manuelle Koordinateneingabe","Enter label for cross profile":"Namen für Profilschnitt eingeben","Enter passphrase":"Passphrase eingeben","Enter username":"Benutzername eingeben","Error while saving user":"Während des Speicherns der Nutzerdaten trat ein Fehler auf","Errorlog":"Fehlerprotokoll","Failed":"Fehlgeschlagen","Forgot password":"Passwort vergessen","Generate PDF":"PDF generieren","Identified":"Identifiziert","Import":"Daten-Import","Import soundingresults":"Seichtstellenmessungen importieren","Import Soundingresults":"Seichtstellenmessungen importieren","Imported":"Importiert","Importqueue":"Import-Warteschlange","Importschedule":"Import-Zeitplan","invalid email":"Ungültige E-Mail","Invalid input":"Ungültige Eingabe","Kind":"Art","landscape":"Querformat","Last refresh:":"Letzter Abgleich:","Latest":"Neustes","Layers":"Ebenen","Login":"Login","Login failed":"Login fehlgeschlagen","Logout":"Abmelden","Logs":"Protokolle","Mail was sent":"E-Mail wurde gesendet","Map":"Karte","Measurement":"Messung","Name":"Name","New import":"Neuer Import","New Import":"Neuer Import","No features identified.":"Keine Objekte identifiziert.","No results.":"Keine Ergebnisse.","No schedules":"Keine Pläne","Open in new window":"In neuem Fenster öffnen","password":"Passwort","Password":"Passwort","password again":"Noch einmal das Passwort","Password reset requested!":"Passwort Zurücksetzung angefragt!","Password should at least be 8 char long including 1 digit and 1 special char like $":"Das Passwort sollte mindestens 8 Zeichen lang sein, eine Zahlenziffer und ein Sonderzeichen wie etwa $ enthalten","Passwords do not match!":"Die Passwörter stimmen nicht überein!","Pending":"Ausstehend","Please choose a country":"Bitte wählen Sie ein Land aus","Please choose a role":"Bitte wählen Sie eine Rolle aus","Please enter a date":"Bitte ein Datum eingeben","Please enter a projection":"Bitte eine Projektion eingeben","Please enter a reference":"Bitte ein Höhenreferenzsystem eingeben","Please enter correct coordinates in the format: Lat,Lon,Lat,Lon":"Bitte geben Sie die Koordinaten in folgendem Format an: Lat,Lon,Lat,Lon","Please select a bottleneck":"Bitte eine Seichtstelle wählen","Please select one":"Bitte auswählen","portrait":"Hochformat","Profile deleted!":"Profil gelöscht!","Profile saved!":"Profil gespeichert!","Profiles":"Profile","Projection":"Projektion","Refresh":"Aktualisieren","Rejected":"Abgelehnt","Repeat Password":"Passwort erneut eingeben","Request password reset!":"Passwort-Zurücksetzung anfragen!","Role":"Rolle","Saved cross profiles":"Gespeicherte Profile","Schedule":"Zeitplan","Select Bottleneck":"Wähle Engstelle","Send":"Absenden","Send testmail":"Test-E-Mail versenden","Sounding Result":"Seichtstellenvermessungsergebnisse","source-code":"Quelltext","Staging area":"Import-Überprüfung","Staging Area":"Import-Überprüfung","Start":"Start","Starting import for ":"Import gestartet ","State":"Zustand","Submit":"Abschicken","Success":"Erfolg","Successful":"Erfolgreich","Sysadmin":"Sys-Admin","Systemadministration":"System-Administration","Systemconfiguration":"System-Konfiguation","Type":"Typ","Upload":"Hochladen","User":"Benutzer","Username":"Benutzername","Users":"Benutzer","Waterway Admin":"Waterway-Admin","Waterway User":"Waterway-Benutzer","You can now select these coordinates from the \"Saved cross profiles\" menu to restore this cross profile.":"Sie können diese Koordinaten aus dem \"Gespeicherte Profile\"-Menü auswählen, um diesen Profilschnitt wieder herzustellen."},"en_GB":{},"hr_HR":{},"hu_HU":{},"ro_RO":{},"sk_SK":{}}
\ No newline at end of file
+{"bg_BG":{},"de_AT":{"Accepted":"Akzeptiert","Accesslog":"Zugriffs-Protokoll","Add User":"Benutzer hinzufügen","Author":"Autor","back to login":"zurück zur Anmeldung","Backend Error":"Server-Fehler","Bottleneck":"Seichtstelle","Bottleneck Areas fill-color":"Flächenfüllfarbe Seichtstelle","Bottleneck Areas stroke-color":"Flächenumrandungsfarbe Seichtstelle","Bottlenecks":"Seichtstellen","Cancel Upload":"Hochladen abbrechen","Chainage":"Stationierung","choose .zip- file":"Wählen Sie eine .zip Datei","Chose format:":"Format wählen:","Compare with":"Vergleiche mit","Configuration":"Konfiguration","Confirm":"Bestätigen","Coordinates copied to clipboard!":"Koordinaten auf die Zwischenablage kopiert!","Country":"Land","Date":"Datum","Depthreference":"Tiefenreferenz","Download":"Herunterladen","Download Meta.json":"Meta.json Herunterladen","Email":"E-Mail","Email address":"E-Mail Adresse","Enqueued":"Hinzugefügt","Enter coordinates manually":"Manuelle Koordinateneingabe","Enter label for cross profile":"Namen für Profilschnitt eingeben","Enter passphrase":"Passphrase eingeben","Enter username":"Benutzername eingeben","Error while saving user":"Während des Speicherns der Nutzerdaten trat ein Fehler auf","Errorlog":"Fehlerprotokoll","Failed":"Fehlgeschlagen","Forgot password":"Passwort vergessen","Generate PDF":"PDF generieren","Identified":"Identifiziert","Import":"Daten-Import","Import soundingresults":"Seichtstellenmessungen importieren","Import Soundingresults":"Seichtstellenmessungen importieren","Imported":"Importiert","Importqueue":"Import-Warteschlange","Importschedule":"Import-Zeitplan","invalid email":"Ungültige E-Mail","Invalid input":"Ungültige Eingabe","Kind":"Art","landscape":"Querformat","Last refresh:":"Letzter Abgleich:","Latest":"Neuste","Layers":"Ebenen","Login":"Login","Login failed":"Login fehlgeschlagen","Logout":"Abmelden","Logs":"Protokolle","Mail was sent":"E-Mail wurde gesendet","Map":"Karte","Measurement":"Messung","Name":"Name","New import":"Neuer Import","New Import":"Neuer Import","No features identified.":"Keine Objekte identifiziert.","No results.":"Keine Ergebnisse.","No schedules":"Keine Pläne","Open in new window":"In neuem Fenster öffnen","password":"Passwort","Password":"Passwort","password again":"Noch einmal das Passwort","Password reset requested!":"Passwort Zurücksetzung angefragt!","Password should at least be 8 char long including 1 digit and 1 special char like $":"Das Passwort sollte mindestens 8 Zeichen lang sein, eine Zahlenziffer und ein Sonderzeichen wie etwa $ enthalten","Passwords do not match!":"Die Passwörter stimmen nicht überein!","Pending":"Ausstehend","Please choose a country":"Bitte wählen Sie ein Land aus","Please choose a role":"Bitte wählen Sie eine Rolle aus","Please enter a date":"Bitte ein Datum eingeben","Please enter a projection":"Bitte eine Projektion eingeben","Please enter a reference":"Bitte ein Höhenreferenzsystem eingeben","Please enter correct coordinates in the format: Lat,Lon,Lat,Lon":"Bitte geben Sie die Koordinaten in folgendem Format an: Lat,Lon,Lat,Lon","Please select a bottleneck":"Bitte eine Seichtstelle wählen","Please select one":"Bitte auswählen","portrait":"Hochformat","Profile deleted!":"Profil gelöscht!","Profile saved!":"Profil gespeichert!","Profiles":"Profile","Projection":"Projektion","Refresh":"Aktualisieren","Rejected":"Abgelehnt","Repeat Password":"Passwort erneut eingeben","Request password reset!":"Passwort-Zurücksetzung anfragen!","Role":"Rolle","Saved cross profiles":"Gespeicherte Profile","Schedule":"Zeitplan","Select Bottleneck":"Wähle Seichtstelle","Send":"Absenden","Send testmail":"Test-E-Mail versenden","Sounding Result":"Seichtstellenvermessungsergebnisse","source-code":"Quelltext","Staging area":"Import-Überprüfung","Staging Area":"Import-Überprüfung","Start":"Start","Starting import for ":"Import gestartet ","State":"Zustand","Submit":"Abschicken","Success":"Erfolg","Successful":"Erfolgreich","Sysadmin":"Sys-Admin","Systemadministration":"System-Administration","Systemconfiguration":"System-Konfiguation","Type":"Typ","Upload":"Hochladen","User":"Benutzer","Username":"Benutzername","Users":"Benutzer","Waterway Admin":"Waterway-Admin","Waterway User":"Waterway-Benutzer","You can now select these coordinates from the \"Saved cross profiles\" menu to restore this cross profile.":"Sie können diese Koordinaten aus dem \"Gespeicherte Profile\"-Menü auswählen, um diesen Profilschnitt wieder herzustellen."},"en_GB":{},"hr_HR":{},"hu_HU":{},"ro_RO":{},"sk_SK":{}}
\ No newline at end of file
--- a/client/src/main.js	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/main.js	Sat Dec 29 16:07:40 2018 +0100
@@ -26,102 +26,11 @@
 import "../node_modules/ol/ol.css";
 import "../node_modules/highlight.js/styles/paraiso-dark.css";
 import "../node_modules/vue-snotify/styles/material.css";
-import VTooltip from "v-tooltip";
-import { library } from "@fortawesome/fontawesome-svg-core";
-import {
-  faAngleDown,
-  faAngleLeft,
-  faAngleRight,
-  faAngleUp,
-  faBars,
-  faBook,
-  faChartArea,
-  faCheck,
-  faCity,
-  faClipboardCheck,
-  faClock,
-  faCopy,
-  faDrawPolygon,
-  faExclamationTriangle,
-  faEye,
-  faEyeSlash,
-  faFilePdf,
-  faFolderPlus,
-  faInfo,
-  faLayerGroup,
-  faMapMarkedAlt,
-  faMinus,
-  faPaperPlane,
-  faPencilAlt,
-  faPlus,
-  faPowerOff,
-  faRuler,
-  faSearch,
-  faShip,
-  faSortAmountDown,
-  faSortAmountUp,
-  faSpinner,
-  faStar,
-  faTasks,
-  faTimes,
-  faTrash,
-  faUpload,
-  faUser,
-  faUsersCog,
-  faWater,
-  faWrench
-} from "@fortawesome/free-solid-svg-icons";
-import { faAdn } from "@fortawesome/free-brands-svg-icons";
-import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
 import VueClipboard from "vue-clipboard2";
 
-library.add(
-  faAdn,
-  faAngleDown,
-  faAngleLeft,
-  faAngleRight,
-  faAngleUp,
-  faBars,
-  faBook,
-  faChartArea,
-  faCheck,
-  faCity,
-  faClipboardCheck,
-  faClock,
-  faCopy,
-  faDrawPolygon,
-  faExclamationTriangle,
-  faEye,
-  faEyeSlash,
-  faFilePdf,
-  faFolderPlus,
-  faInfo,
-  faLayerGroup,
-  faMapMarkedAlt,
-  faMinus,
-  faPaperPlane,
-  faPencilAlt,
-  faPlus,
-  faPowerOff,
-  faRuler,
-  faSearch,
-  faShip,
-  faSortAmountDown,
-  faSortAmountUp,
-  faSpinner,
-  faStar,
-  faTasks,
-  faTimes,
-  faTrash,
-  faUpload,
-  faUser,
-  faUsersCog,
-  faWater,
-  faWrench
-);
-Vue.component("font-awesome-icon", FontAwesomeIcon);
+import ToggleButton from "vue-js-toggle-button";
 
-Vue.use(VTooltip);
+Vue.use(ToggleButton);
 
 const options = {
   toast: {
@@ -135,50 +44,29 @@
 
 let browserLanguage = locale2;
 
-// planned also SK, HU, HR, RS, BiH, BG, RO, UA
+// planned also RS, BiH, UA
 const supportedLanguages = {
   en_GB: "British English",
   de_AT: "Deutsch",
   sk_SK: "slovenčina",
-  hu_HU: "Magyat",
+  hu_HU: "Magyar",
   hr_HR: "Hrvatska",
   bg_BG: "български",
   ro_RO: "Română"
 };
 
-if (
-  browserLanguage === "de-DE" ||
-  browserLanguage === "de-LI" ||
-  browserLanguage === "de-LU" ||
-  browserLanguage === "de-CH" ||
-  browserLanguage === "de"
-) {
-  browserLanguage = "de-AT"; // map german,liechtenstein,luxenburg and switzerland to austrian variant for now
-}
+let isAvailableLanguage = Object.keys(supportedLanguages).filter(language => {
+  return browserLanguage.replace("-", "_") === language;
+});
 
-if (browserLanguage === "sk") {
-  browserLanguage = "sk_SK";
+if (isAvailableLanguage.length === 0) {
+  isAvailableLanguage = Object.keys(supportedLanguages).filter(language => {
+    return language.substr(0, 2) === browserLanguage.substr(0, 2);
+  });
 }
 
-if (browserLanguage === "hu") {
-  browserLanguage = "hu_HU";
-}
-
-if (browserLanguage === "hr") {
-  browserLanguage = "hr_HR";
-}
-
-if (browserLanguage === "bg") {
-  browserLanguage = "bg_BG";
-}
-
-if (browserLanguage === "ro") {
-  browserLanguage = "ro_RO";
-}
-
-const language = browserLanguage.replace("-", "_");
-const isLanguageAvailable = supportedLanguages[language];
-let defaultLanguage = isLanguageAvailable ? language : "en_GB";
+let defaultLanguage =
+  isAvailableLanguage.length > 0 ? isAvailableLanguage[0] : "en_GB";
 
 Vue.use(GetTextPlugin, {
   translations: translations,
@@ -187,11 +75,13 @@
 });
 
 Vue.config.productionTip = false;
-
-const app = new Vue({
-  router,
-  store,
-  render: h => h(App)
-}).$mount("#app");
+let app;
+import("./fontawesome").then(() => {
+  app = new Vue({
+    router,
+    store,
+    render: h => h(App)
+  }).$mount("#app");
+});
 
 export default app;
--- a/client/src/router.js	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/router.js	Sat Dec 29 16:07:40 2018 +0100
@@ -20,15 +20,6 @@
 
 /*  facilitate codesplitting */
 const Login = () => import("./components/Login.vue");
-const Main = () => import("./components/map/Main.vue");
-const Usermanagement = () =>
-  import("./components/admin/usermanagement/Usermanagement.vue");
-const Logs = () => import("./components/admin/Logs.vue");
-const Importqueue = () => import("./components/admin/Importqueue.vue");
-const Importschedule = () =>
-  import("./components/admin/importschedule/Importschedule.vue");
-const Systemconfiguration = () =>
-  import("./components/admin/Systemconfiguration.vue");
 
 Vue.use(Router);
 
@@ -42,7 +33,7 @@
     {
       path: "/usermanagement",
       name: "usermanagement",
-      component: Usermanagement,
+      component: () => import("./components/usermanagement/Usermanagement.vue"),
       meta: {
         requiresAuth: true
       },
@@ -58,7 +49,7 @@
     {
       path: "/logs",
       name: "logs",
-      component: Logs,
+      component: () => import("./components/Logs.vue"),
       meta: {
         requiresAuth: true
       },
@@ -74,7 +65,7 @@
     {
       path: "/systemconfiguration",
       name: "systemconfiguration",
-      component: Systemconfiguration,
+      component: () => import("./components/Systemconfiguration.vue"),
       meta: {
         requiresAuth: true
       },
@@ -90,7 +81,39 @@
     {
       path: "/importqueue",
       name: "importqueue",
-      component: Importqueue,
+      component: () => import("./components/importqueue/Importqueue.vue"),
+      meta: {
+        requiresAuth: true
+      },
+      beforeEnter: (to, from, next) => {
+        const isWaterwayAdmin = store.getters["user/isWaterwayAdmin"];
+        if (!isWaterwayAdmin) {
+          next("/");
+        } else {
+          next();
+        }
+      }
+    },
+    {
+      path: "/importsoundingresults",
+      name: "importsoundingresults",
+      component: () => import("./components/ImportSoundingresults.vue"),
+      meta: {
+        requiresAuth: true
+      },
+      beforeEnter: (to, from, next) => {
+        const isWaterwayAdmin = store.getters["user/isWaterwayAdmin"];
+        if (!isWaterwayAdmin) {
+          next("/");
+        } else {
+          next();
+        }
+      }
+    },
+    {
+      path: "/importstretches",
+      name: "importstretches",
+      component: () => import("./components/ImportStretches.vue"),
       meta: {
         requiresAuth: true
       },
@@ -106,7 +129,7 @@
     {
       path: "/importschedule",
       name: "importschedule",
-      component: Importschedule,
+      component: () => import("./components/importschedule/Importschedule.vue"),
       meta: {
         requiresAuth: true
       },
@@ -122,7 +145,7 @@
     {
       path: "/",
       name: "mainview",
-      component: Main,
+      component: () => import("./components/Main.vue"),
       meta: {
         requiresAuth: true
       },
--- a/client/src/store/application.js	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/store/application.js	Sat Dec 29 16:07:40 2018 +0100
@@ -55,7 +55,10 @@
           state.version.includes("beta") ||
           state.version.includes("alpha"))
       )
-        versionStr += " " + process.env.VUE_APP_HGREV;
+        // a '+' according to semver 2.0.0 starts a build meta info section
+        // which shall only have [0-9A-Za-z-] chars
+        // and is to be ignored when determining the order
+        versionStr += "+" + process.env.VUE_APP_HGREV;
 
       return versionStr;
     }
--- a/client/src/store/bottlenecks.js	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/store/bottlenecks.js	Sat Dec 29 16:07:40 2018 +0100
@@ -12,9 +12,9 @@
  * Markus Kottländer <markuks.kottlaender@intevation.de>
  * Thomas Junk <thomas.junk@intevation.de>
  */
-import { HTTP } from "../lib/http";
+import { HTTP } from "@/lib/http";
 import { WFS } from "ol/format.js";
-import { displayError } from "../lib/errors.js";
+import { displayError } from "@/lib/errors.js";
 
 // initial state
 const init = () => {
@@ -41,6 +41,13 @@
     setSurveys(state, surveys) {
       state.surveys = surveys;
     },
+    setSelectedSurveyByDate(state, date) {
+      const survey = state.surveys.filter(x => x.date_info === date)[0];
+      state.selectedSurvey = survey;
+    },
+    setFirstSurveySelected(state) {
+      state.selectedSurvey = state.surveys[0];
+    },
     selectedSurvey(state, survey) {
       state.selectedSurvey = survey;
     },
@@ -49,83 +56,80 @@
     }
   },
   actions: {
-    setSelectedBottleneck(
-      { state, commit, dispatch, rootState, rootGetters },
-      name,
-      date
-    ) {
-      if (name !== state.selectedBottleneck) {
-        commit("selectedSurvey", null);
-        commit("fairwayprofile/clearCurrentProfile", null, { root: true });
-        commit("application/showSplitscreen", false, { root: true });
-        rootState.map.cutTool.setActive(false);
-        rootGetters["map/getVSourceByName"]("Cut Tool").clear();
-      }
-      if (name) {
-        commit("application/showProfiles", true, { root: true });
-      }
-      commit("setSelectedBottleneck", name);
-      dispatch("querySurveys", name, date);
+    setSelectedBottleneck({ state, commit, rootState, rootGetters }, name) {
+      return new Promise((resolve, reject) => {
+        if (name !== state.selectedBottleneck) {
+          commit("selectedSurvey", null);
+          commit("fairwayprofile/clearCurrentProfile", null, { root: true });
+          commit("application/showSplitscreen", false, { root: true });
+          rootState.map.cutTool.setActive(false);
+          rootGetters["map/getVSourceByName"]("Cut Tool").clear();
+        }
+        if (name) {
+          commit("application/showProfiles", true, { root: true });
+        }
+        commit("setSelectedBottleneck", name);
+        if (name) {
+          commit("surveysLoading", true);
+          HTTP.get("/surveys/" + name, {
+            headers: {
+              "X-Gemma-Auth": localStorage.getItem("token"),
+              "Content-type": "text/xml; charset=UTF-8"
+            }
+          })
+            .then(response => {
+              const surveys = response.data.surveys.sort((a, b) =>
+                a.date_info < b.date_info ? 1 : -1
+              );
+              commit("setSurveys", surveys);
+              resolve(response);
+            })
+            .catch(error => {
+              commit("setSurveys", []);
+              commit("selectedSurvey", null);
+              const { status, data } = error.response;
+              displayError({
+                title: "Backend Error",
+                message: `${status}: ${data.message || data}`
+              });
+              reject(error);
+            })
+            .finally(() => commit("surveysLoading", false));
+        } else {
+          commit("setSurveys", []);
+          resolve();
+        }
+      });
     },
     loadBottlenecks({ commit }) {
-      var bottleneckFeatureCollectionRequest = new WFS().writeGetFeature({
-        srsName: "EPSG:4326",
-        featureNS: "gemma",
-        featurePrefix: "gemma",
-        featureTypes: ["bottleneck_overview"],
-        outputFormat: "application/json"
-      });
-
-      HTTP.post(
-        "/internal/wfs",
-        new XMLSerializer().serializeToString(
-          bottleneckFeatureCollectionRequest
-        ),
-        {
-          headers: {
-            "X-Gemma-Auth": localStorage.getItem("token"),
-            "Content-type": "text/xml; charset=UTF-8"
+      return new Promise((resolve, reject) => {
+        var bottleneckFeatureCollectionRequest = new WFS().writeGetFeature({
+          srsName: "EPSG:4326",
+          featureNS: "gemma",
+          featurePrefix: "gemma",
+          featureTypes: ["bottleneck_overview"],
+          outputFormat: "application/json"
+        });
+        HTTP.post(
+          "/internal/wfs",
+          new XMLSerializer().serializeToString(
+            bottleneckFeatureCollectionRequest
+          ),
+          {
+            headers: {
+              "X-Gemma-Auth": localStorage.getItem("token"),
+              "Content-type": "text/xml; charset=UTF-8"
+            }
           }
-        }
-      ).then(response => {
-        commit("setBottlenecks", response.data.features);
-      });
-    },
-    querySurveys({ commit }, name, date) {
-      if (name) {
-        commit("surveysLoading", true);
-        HTTP.get("/surveys/" + name, {
-          headers: {
-            "X-Gemma-Auth": localStorage.getItem("token"),
-            "Content-type": "text/xml; charset=UTF-8"
-          }
-        })
+        )
           .then(response => {
-            const surveys = response.data.surveys.sort((a, b) =>
-              a.date_info < b.date_info ? 1 : -1
-            );
-            if (date) {
-              const survey = surveys.filter(x => x.date_info === date)[0];
-              commit("selectedSurvey", survey);
-            } else {
-              commit("selectedSurvey", surveys[0]);
-            }
-
-            commit("setSurveys", surveys);
+            commit("setBottlenecks", response.data.features);
+            resolve(response);
           })
           .catch(error => {
-            commit("setSurveys", []);
-            commit("selectedSurvey", null);
-            const { status, data } = error.response;
-            displayError({
-              title: "Backend Error",
-              message: `${status}: ${data.message || data}`
-            });
-          })
-          .finally(() => commit("surveysLoading", false));
-      } else {
-        commit("setSurveys", []);
-      }
+            reject(error);
+          });
+      });
     }
   }
 };
--- a/client/src/store/imports.js	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/store/imports.js	Sat Dec 29 16:07:40 2018 +0100
@@ -33,18 +33,8 @@
     imports: [],
     staging: [],
     schedules: [],
-    importScheduleDetail: null,
-    importScheduleDetailVisible: false
-  };
-};
-
-const newImportScheduleDetail = () => {
-  return {
-    import: "",
-    type: "",
-    author: "",
-    schedule: "",
-    emailNotification: null
+    importScheduleDetailVisible: false,
+    importToReview: null
   };
 };
 
@@ -53,9 +43,6 @@
   namespaced: true,
   state: init(),
   mutations: {
-    clearImportScheduleDetail: state => {
-      state.importScheduleDetail = newImportScheduleDetail();
-    },
     deleteSchedule: (state, index) => {
       state.schedules.splice(index, 1);
     },
@@ -77,7 +64,11 @@
       });
       state.staging = enriched;
     },
-
+    setImportToReview: (state, id) => {
+      if (!isNaN(parseFloat(id)) && isFinite(id)) {
+        state.importToReview = id;
+      }
+    },
     toggleApproval: (state, change) => {
       const { id, newStatus } = change;
       const stagedResult = state.staging.find(e => {
--- a/client/src/store/map.js	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/store/map.js	Sat Dec 29 16:07:40 2018 +0100
@@ -28,6 +28,7 @@
 import { getLength, getArea } from "ol/sphere.js";
 import { unByKey } from "ol/Observable";
 import { getCenter } from "ol/extent";
+import app from "../main";
 
 // initial state
 const init = () => {
@@ -58,7 +59,7 @@
         data: new TileLayer({
           source: new TileWMS({
             preload: 1,
-            url: "https://demo.d4d-portal.info/wms",
+            url: "https://service.d4d-portal.info/wms/",
             params: { LAYERS: "d4d", VERSION: "1.1.1", TILED: true }
           })
         }),
@@ -411,7 +412,7 @@
       });
       lineTool.on("drawend", event => {
         commit("setCurrentMeasurement", {
-          quantity: "Length",
+          quantity: app.$gettext("Length"),
           unitSymbol: "m",
           value: Math.round(getLength(event.feature.getGeometry()) * 10) / 10
         });
@@ -432,7 +433,7 @@
       polygonTool.on("drawend", event => {
         const areaSize = getArea(event.feature.getGeometry());
         commit("setCurrentMeasurement", {
-          quantity: "Area",
+          quantity: app.$gettext("Area"),
           unitSymbol: areaSize > 100000 ? "km²" : "m²",
           value:
             areaSize > 100000
@@ -512,7 +513,9 @@
                       "bottlenecks/setSelectedBottleneck",
                       feature.get("objnam"),
                       { root: true }
-                    );
+                    ).then(() => {
+                      this.commit("bottlenecks/setFirstSurveySelected");
+                    });
                     commit("moveMap", {
                       coordinates: getCenter(
                         feature
--- a/client/src/store/user.js	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/src/store/user.js	Sat Dec 29 16:07:40 2018 +0100
@@ -14,6 +14,7 @@
  */
 
 import { HTTP } from "../lib/http";
+import { toMillisFromString } from "../lib/session";
 
 const init = () => {
   return {
@@ -71,11 +72,21 @@
     login({ commit }, user) {
       // using POST is a bit more secure than GET
       return new Promise((resolve, reject) => {
+        const handleResponse = response => {
+          const { expires } = response.data;
+          const renew =
+            (new Date(toMillisFromString(expires)) - new Date()) / 1010;
+          commit("authSuccess", response.data);
+          resolve(response);
+          setTimeout(() => {
+            HTTP.get("/renew", {
+              headers: { "X-Gemma-Auth": localStorage.getItem("token") }
+            }).then(handleResponse);
+          }, renew);
+        };
+
         HTTP.post("/login", user)
-          .then(response => {
-            commit("authSuccess", response.data);
-            resolve(response);
-          })
+          .then(handleResponse)
           .catch(error => {
             commit("reset", null, { root: true });
             commit("clearAuth");
--- a/client/vue.config.js	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/vue.config.js	Sat Dec 29 16:07:40 2018 +0100
@@ -1,7 +1,9 @@
 const CopyWebpackPlugin = require("copy-webpack-plugin");
-
 module.exports = {
   outputDir: "../web",
+  configureWebpack: {
+    devtool: "source-map"
+  },
   chainWebpack: config => {
     let vendorImgPath = process.env.VUE_APP_VENDOR_IMG_PATH;
     if (!vendorImgPath) return;
@@ -10,6 +12,11 @@
       .use(CopyWebpackPlugin, [[{ from: vendorImgPath, to: "img" }]], {
         copyUnmodified: true
       });
+    if (process.env.ANALYZE) {
+      var BundleAnalyzerPlugin = require("webpack-bundle-analyzer")
+        .BundleAnalyzerPlugin;
+      config.plugin("BundleAnalyzerPlugin").use(BundleAnalyzerPlugin, []);
+    }
   },
   css: {
     loaderOptions: {
--- a/client/yarn.lock	Sat Dec 29 16:06:54 2018 +0100
+++ b/client/yarn.lock	Sat Dec 29 16:07:40 2018 +0100
@@ -710,7 +710,7 @@
   resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
   integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
 
-"@turf/bbox@*":
+"@turf/bbox@*", "@turf/bbox@6.x":
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/@turf/bbox/-/bbox-6.0.1.tgz#b966075771475940ee1c16be2a12cf389e6e923a"
   integrity sha512-EGgaRLettBG25Iyx7VyUINsPpVj1x3nFQFiGS3ER8KCI1MximzNLsam3eXRabqQDjyAKyAE1bJ4EZEpGvspQxw==
@@ -718,6 +718,14 @@
     "@turf/helpers" "6.x"
     "@turf/meta" "6.x"
 
+"@turf/center@^6.0.1":
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/@turf/center/-/center-6.0.1.tgz#40a17d0a170df5bba09ad93e133b904d8eb14601"
+  integrity sha512-bh/SLBwRC2QYcbVOxMFBtiARuMzMzfh4YuVtguYAjyBEIA4HXnnEZT+yZlzfcG3oikG7XgV8vg9eegcmwQe+MQ==
+  dependencies:
+    "@turf/bbox" "6.x"
+    "@turf/helpers" "6.x"
+
 "@turf/distance@^6.0.1":
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/@turf/distance/-/distance-6.0.1.tgz#0761f28784286e7865a427c4e7e3593569c2dea8"
@@ -8555,7 +8563,7 @@
   resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281"
   integrity sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==
 
-prettier@^1.13.0, prettier@^1.15.2:
+prettier@^1.13.0, prettier@^1.15.2, prettier@^1.15.3:
   version "1.15.3"
   resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.3.tgz#1feaac5bdd181237b54dbe65d874e02a1472786a"
   integrity sha512-gAU9AGAPMaKb3NNSUUuhhFAS7SCO4ALTN4nRIn6PJ075Qd28Yn2Ig2ahEJWdJwJmlEBTUfC7mMUSFy8MwsOCfg==
@@ -8576,7 +8584,7 @@
     ansi-regex "^3.0.0"
     ansi-styles "^3.2.0"
 
-pretty-quick@^1.6.0:
+pretty-quick@^1.8.0:
   version "1.8.0"
   resolved "https://registry.yarnpkg.com/pretty-quick/-/pretty-quick-1.8.0.tgz#067ebe744ddb4e1ed4e1ee1af9648815121f78fc"
   integrity sha512-qV25sQF/ivJpdZ5efwemQYkQJa7sp3HqT/Vf/7z5vGYMcq1VrT2lDpFKAxJPf6219N1YAdR8mGkIhPAZ1odTmQ==
@@ -10768,6 +10776,11 @@
     tsconfig "^7.0.0"
     vue-template-es2015-compiler "^1.6.0"
 
+vue-js-toggle-button@^1.3.0:
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/vue-js-toggle-button/-/vue-js-toggle-button-1.3.0.tgz#59240f215fd502f54f0c210c5fac878960b0131c"
+  integrity sha512-lnRy+D7gHlvEyv1WKnvYWkxx42obCmeST5eAUwCnyIS+dC1l9Cu4AuWfw9XrRdNRpY7UZgu2TblZcOOF1H661A==
+
 vue-loader@^15.4.2:
   version "15.4.2"
   resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.4.2.tgz#812bb26e447dd3b84c485eb634190d914ce125e2"
--- a/cmd/bottlenecks/main.go	Sat Dec 29 16:06:54 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,269 +0,0 @@
-// This is Free Software under GNU Affero General Public License v >= 3.0
-// without warranty, see README.md and license for details.
-//
-// SPDX-License-Identifier: AGPL-3.0-or-later
-// License-Filename: LICENSES/AGPL-3.0.txt
-//
-// Copyright (C) 2018 by via donau
-//   – Österreichische Wasserstraßen-Gesellschaft mbH
-// Software engineering by Intevation GmbH
-//
-// Author(s):
-//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
-
-package main
-
-import (
-	"database/sql"
-	"flag"
-	"fmt"
-	"log"
-	"regexp"
-	"strconv"
-	"strings"
-
-	"github.com/jackc/pgx"
-	"github.com/jackc/pgx/stdlib"
-
-	"gemma.intevation.de/gemma/pkg/soap/ifbn"
-)
-
-const insertSQL = `INSERT INTO waterway.bottlenecks (
-  bottleneck_id,
-  fk_g_fid,
-  objnam,
-  nobjnm,
-  stretch,
-  area,
-  rb,
-  lb,
-  responsible_country,
-  revisiting_time,
-  limiting,
-  date_info,
-  source_organization
-) VALUES(
-  $1,
-  isrs_fromText($2),
-  $3,
-  $4,
-  isrsrange(isrs_fromText($5), isrs_fromText($6)),
-  ST_MakePolygon(ST_ExteriorRing(ST_Buffer(ST_SetSRID(ST_Makepoint(13.05501, 47.80949), 4326), 0.01)))::Geography,
-  $7,
-  $8,
-  $9,
-  $10,
-  $11,
-  $12,
-  $13
-) ON CONFLICT (bottleneck_id) DO NOTHING`
-
-const insertDumpSQL = `INSERT INTO waterway.bottlenecks (
-  bottleneck_id,
-  fk_g_fid,
-  objnam,
-  nobjnm,
-  stretch,
-  area,
-  rb,
-  lb,
-  responsible_country,
-  revisiting_time,
-  limiting,
-  date_info,
-  source_organization
-) VALUES(
-  %s,
-  isrs_fromText(%s),
-  %s,
-  %s,
-  isrsrange(isrs_fromText(%s), isrs_fromText(%s)),
-  ST_MakePolygon(ST_ExteriorRing(ST_Buffer(ST_SetSRID(ST_Makepoint(13.05501, 47.80949), 4326), 0.01)))::Geography,
-  %s,
-  %s,
-  %s,
-  %d,
-  %s,
-  %s::timestamp with time zone,
-  %s
-) ON CONFLICT (bottleneck_id) DO NOTHING;
-`
-
-var (
-	url  = flag.String("url", "", "the IFBN service")
-	dump = flag.Bool("dump", false, "dump SQL insert statements")
-
-	insecure   = flag.Bool("insecure", false, "skip SSL verification")
-	dbhost     = flag.String("dbhost", "localhost", "database host")
-	dbport     = flag.Uint("dbport", 5432, "database port")
-	dbname     = flag.String("dbname", "gemma", "database user")
-	dbuser     = flag.String("dbuser", "scott", "database user")
-	dbpassword = flag.String("dbpw", "tiger", "database password")
-	dbssl      = flag.String("dbssl", "prefer", "database SSL mode")
-)
-
-func run(fn func(*sql.DB) error) error {
-
-	// To ease SSL config ride a bit on parsing.
-	cc, err := pgx.ParseConnectionString("sslmode=" + *dbssl)
-	if err != nil {
-		return err
-	}
-
-	// Do the rest manually to allow whitespace in user/password.
-	cc.Host = *dbhost
-	cc.Port = uint16(*dbport)
-	cc.User = *dbuser
-	cc.Password = *dbpassword
-	cc.Database = *dbname
-
-	db := stdlib.OpenDB(cc)
-	defer db.Close()
-
-	return fn(db)
-}
-
-var rblbRe = regexp.MustCompile(`(..)_(..)`)
-
-func splitRBLB(s string) (string, string) {
-	m := rblbRe.FindStringSubmatch(s)
-	if len(m) == 0 {
-		return "", ""
-	}
-	return m[1], m[2]
-}
-
-func revisitingTime(s string) int {
-	v, err := strconv.Atoi(s)
-	if err != nil {
-		v = 0
-	}
-	return v
-}
-
-func quote(s string) string {
-	return "'" + strings.Replace(s, "'", "'''", -1) + "'"
-}
-
-func dumpSQLStatements(bns []*ifbn.BottleNeckType) error {
-
-	fmt.Println("BEGIN;")
-
-	for i := range bns {
-		bn := bns[i]
-		rb, lb := splitRBLB(bn.Rb_lb)
-
-		var limiting, country string
-
-		if bn.Limiting_factor != nil {
-			limiting = string(*bn.Limiting_factor)
-		}
-
-		if bn.Responsible_country != nil {
-			country = string(*bn.Responsible_country)
-		}
-
-		if _, err := fmt.Printf(insertDumpSQL,
-			quote(bn.Bottleneck_id),
-			quote(bn.Fk_g_fid),
-			quote(bn.OBJNAM),
-			quote(bn.NOBJNM),
-			quote(bn.From_ISRS), quote(bn.To_ISRS),
-			quote(rb),
-			quote(lb),
-			quote(country),
-			revisitingTime(bn.Revisiting_time),
-			quote(limiting),
-			quote(bn.Date_Info.Format("2006-01-02 15:04:05.999 MST")),
-			quote(bn.Source),
-		); err != nil {
-			return err
-		}
-	}
-	_, err := fmt.Println("COMMIT;")
-	return err
-}
-
-func storeInDatabase(bns []*ifbn.BottleNeckType) error {
-	return run(func(db *sql.DB) error {
-
-		stmt, err := db.Prepare(insertSQL)
-		if err != nil {
-			return err
-		}
-		defer stmt.Close()
-
-		tx, err := db.Begin()
-		if err != nil {
-			return err
-		}
-
-		st := tx.Stmt(stmt)
-
-		for i := range bns {
-			bn := bns[i]
-			rb, lb := splitRBLB(bn.Rb_lb)
-
-			var limiting, country string
-
-			if bn.Limiting_factor != nil {
-				limiting = string(*bn.Limiting_factor)
-			}
-
-			if bn.Responsible_country != nil {
-				country = string(*bn.Responsible_country)
-			}
-
-			fmt.Printf("%s '%s' %s %s\n", bn.Fk_g_fid, bn.OBJNAM, bn.From_ISRS, bn.To_ISRS)
-
-			if _, err := st.Exec(
-				bn.Bottleneck_id,
-				bn.Fk_g_fid,
-				bn.OBJNAM,
-				bn.NOBJNM,
-				bn.From_ISRS, bn.To_ISRS,
-				rb,
-				lb,
-				country,
-				revisitingTime(bn.Revisiting_time),
-				limiting,
-				bn.Date_Info,
-				bn.Source,
-			); err != nil {
-				tx.Rollback()
-				return err
-			}
-		}
-
-		return tx.Commit()
-	})
-}
-
-func main() {
-
-	flag.Parse()
-
-	client := ifbn.NewIBottleneckService(*url, *insecure, nil)
-
-	req := &ifbn.Export_bn_by_isrs{}
-
-	resp, err := client.Export_bn_by_isrs(req)
-	if err != nil {
-		log.Fatalf("error: %v\n", err)
-	}
-
-	if resp.Export_bn_by_isrsResult == nil {
-		return
-	}
-	bns := resp.Export_bn_by_isrsResult.BottleNeckType
-
-	if *dump {
-		err = dumpSQLStatements(bns)
-	} else {
-		err = storeInDatabase(bns)
-	}
-
-	if err != nil {
-		log.Fatalf("error: %v\n", err)
-	}
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmd/wfs/dump.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,187 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package main
+
+import (
+	"bufio"
+	"errors"
+	"fmt"
+	"io"
+	"log"
+	"os"
+
+	"gemma.intevation.de/gemma/pkg/wfs"
+)
+
+func parseFeatures(urls []string, defaultCRS string) error {
+
+	return wfs.DownloadURLs(urls, func(r io.Reader) error {
+		rfc, err := wfs.ParseRawFeatureCollection(r)
+		if err != nil {
+			return err
+		}
+		var crsName string
+		if rfc.Features == nil {
+			return errors.New("no features given")
+		}
+		if rfc.CRS != nil {
+			crsName = rfc.CRS.Properties.Name
+		} else {
+			crsName = defaultCRS
+		}
+		fmt.Printf("CRS: %s\n", crsName)
+		epsg, err := wfs.CRSToEPSG(crsName)
+		if err != nil {
+			log.Printf("error: %v\n", err)
+		} else {
+			fmt.Printf("EPSG: %d\n", epsg)
+		}
+		types := map[string]int{}
+		for _, feature := range rfc.Features {
+			types[feature.Geometry.Type]++
+		}
+		fmt.Printf("found types in %d features:\n", len(rfc.Features))
+		for typ, cnt := range types {
+			fmt.Printf("\t%s: %d\n", typ, cnt)
+		}
+		return nil
+	})
+}
+
+func dumpURLs(urls []string) error {
+	out := bufio.NewWriter(os.Stdout)
+	if err := wfs.DownloadURLs(urls, func(r io.Reader) error {
+		_, err := io.Copy(out, r)
+		return err
+	}); err != nil {
+		return err
+	}
+	return out.Flush()
+}
+
+func dump(caps *wfs.Capabilities) {
+	fmt.Println("service identification")
+	fmt.Println("----------------------")
+	fmt.Printf("title: %s\n", caps.ServiceIdentification.Title)
+	var abstract string
+	if len(caps.ServiceIdentification.Abstract) > 40 {
+		abstract = fmt.Sprintf("%.40s...", caps.ServiceIdentification.Abstract)
+	} else {
+		abstract = caps.ServiceIdentification.Abstract
+	}
+	fmt.Printf("abstract: %s\n", abstract)
+	if len(caps.ServiceIdentification.Keywords.Keywords) > 0 {
+		fmt.Println("keywords:")
+		for _, kw := range caps.ServiceIdentification.Keywords.Keywords {
+			fmt.Printf("\t%s\n", kw.Value)
+		}
+	}
+	fmt.Printf("type: %s\n", caps.ServiceIdentification.ServiceType)
+	fmt.Printf("version: %s\n", caps.ServiceIdentification.ServiceTypeVersion)
+	fmt.Println()
+	fmt.Println("operations meta data")
+	fmt.Println("--------------------")
+	if len(caps.OperationsMetadata.Operations) > 0 {
+		fmt.Println("operations:")
+		for _, operation := range caps.OperationsMetadata.Operations {
+			fmt.Printf("\t%s\n", operation.Name)
+			if operation.DCP.HTTP.Get != nil {
+				fmt.Printf("\t\tGet: %s\n", operation.DCP.HTTP.Get.HRef)
+			}
+			if operation.DCP.HTTP.Post != nil {
+				fmt.Printf("\t\tPost: %s\n", operation.DCP.HTTP.Post.HRef)
+			}
+
+			if len(operation.Parameters) > 0 {
+				fmt.Println("\t\tparameters:")
+				for _, p := range operation.Parameters {
+					fmt.Printf("\t\t\tparameter: %s\n", p.Name)
+					for _, av := range p.AllowedValues.Values {
+						fmt.Printf("\t\t\t\t%s\n", av.Value)
+					}
+				}
+			}
+			if len(operation.Constraints) > 0 {
+				fmt.Println("\t\tconstraints:")
+				for _, c := range operation.Constraints {
+					fmt.Printf("\t\t\tname: %s\n", c.Name)
+					if c.DefaultValue != nil {
+						fmt.Printf("\t\t\t\tdefault: %s\n", c.DefaultValue.Value)
+					}
+					if len(c.AllowedValues.Values) > 0 {
+						fmt.Println("\t\t\tallowed values:")
+						for _, av := range c.AllowedValues.Values {
+							fmt.Printf("\t\t\t\t%s", av.Value)
+						}
+					}
+				}
+			}
+		}
+	}
+	if len(caps.OperationsMetadata.Constraints) > 0 {
+		fmt.Println("constraints:")
+		for _, c := range caps.OperationsMetadata.Constraints {
+			fmt.Printf("\tname: %s\n", c.Name)
+			if c.DefaultValue != nil {
+				fmt.Printf("\t\tdefault: %s\n", c.DefaultValue.Value)
+			}
+			if len(c.AllowedValues.Values) > 0 {
+				fmt.Println("\tallowed values:")
+				for _, av := range c.AllowedValues.Values {
+					fmt.Printf("\t\t%s\n", av.Value)
+				}
+			}
+		}
+	}
+	fmt.Println()
+	fmt.Println("feature type list")
+	fmt.Println("------------------")
+	if len(caps.FeatureTypeList.FeatureTypes) > 0 {
+		fmt.Println("features:")
+		for _, ft := range caps.FeatureTypeList.FeatureTypes {
+			fmt.Printf("\tname: %s\n", ft.Name)
+			fmt.Printf("\ttitle: %s\n", ft.Title)
+			var abstract string
+			if len(ft.Abstract) > 40 {
+				abstract = fmt.Sprintf("%.40s...", ft.Abstract)
+			} else {
+				abstract = ft.Abstract
+			}
+			fmt.Printf("\tabstract: %s\n", abstract)
+			fmt.Printf("\tdefault CRS: %s\n", ft.DefaultCRS)
+			if len(ft.OtherCRSs) > 0 {
+				fmt.Println("\tother CRSs:")
+				for _, crs := range ft.OtherCRSs {
+					fmt.Printf("\t\t%s\n", crs)
+				}
+			}
+			if ft.WGS84BoundingBox != nil {
+				fmt.Printf("\tWGS84 bounding box: (%s) - (%s)\n",
+					ft.WGS84BoundingBox.LowerCorner, ft.WGS84BoundingBox.UpperCorner)
+			}
+			if len(ft.Keywords.Keywords) > 0 {
+				fmt.Println("\tkeywords:")
+				for _, kw := range ft.Keywords.Keywords {
+					fmt.Printf("\t\t%s\n", kw.Value)
+				}
+			}
+			if len(ft.Namespaces) > 0 {
+				fmt.Println("\tnamespaces:")
+				for _, ns := range ft.Namespaces {
+					fmt.Printf("\t\t%s:%s\n", ns.Space, ns.Local)
+				}
+			}
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cmd/wfs/main.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,65 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package main
+
+import (
+	"flag"
+	"log"
+
+	"gemma.intevation.de/gemma/pkg/wfs"
+)
+
+func check(err error) {
+	if err != nil {
+		log.Fatalf("error: %v\n", err)
+	}
+}
+
+func main() {
+	var (
+		dumpCaps     = flag.Bool("dump-caps", false, "Dump capabilities document")
+		dumpFeatures = flag.Bool("dump-features", false, "Dump features")
+		featureType  = flag.String("features", "", "feature to get")
+		sortBy       = flag.String("sortby", "", "Sort features by this property")
+	)
+	flag.Parse()
+
+	for _, arg := range flag.Args() {
+		caps, err := wfs.GetCapabilities(arg)
+		check(err)
+		if *dumpCaps {
+			dump(caps)
+		}
+
+		if *featureType == "" {
+			continue
+		}
+
+		feature := caps.FindFeatureType(*featureType)
+		if feature == nil {
+			log.Fatalf("Unknown feature type '%s'\n", *featureType)
+		}
+
+		urls, err := wfs.GetFeaturesGET(
+			caps, *featureType, "application/json", *sortBy)
+		check(err)
+
+		log.Printf("urls: %v\n", urls)
+		if *dumpFeatures {
+			check(dumpURLs(urls))
+		}
+
+		parseFeatures(urls, feature.DefaultCRS)
+	}
+}
--- a/docker/Dockerfile.backend	Sat Dec 29 16:06:54 2018 +0100
+++ b/docker/Dockerfile.backend	Sat Dec 29 16:07:40 2018 +0100
@@ -7,7 +7,8 @@
 
 RUN apt-get update &&\
     apt-get -y install --no-install-recommends \
-            make git golang-go golang-github-gorilla-context-dev
+            make git golang-go \
+            ca-certificates
 
 WORKDIR /opt/gemma
 
--- a/docker/Dockerfile.db	Sat Dec 29 16:06:54 2018 +0100
+++ b/docker/Dockerfile.db	Sat Dec 29 16:07:40 2018 +0100
@@ -3,14 +3,16 @@
 LABEL description="Contains software from gemma, for right holders and\
  licensing infos, see https://hg.intevation.de/gemma ."
 
+ENV DEBIAN_FRONTEND noninteractive
+
 RUN apt-get update &&\
-    apt-get -y install --no-install-recommends curl gnupg
+    apt-get -y install --no-install-recommends curl ca-certificates gnupg
 
 # Add PostgreSQL's repository for current PostgreSQL release and extensions:
 RUN echo 'deb http://apt.postgresql.org/pub/repos/apt/ bionic-pgdg main' \
     >> /etc/apt/sources.list &&\
     curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | \
-    sudo apt-key add - &&\
+    apt-key add - &&\
     apt-get update &&\
     apt-get -y install postgresql-11-postgis-2.5 postgresql-11-pgtap
 
--- a/docker/Dockerfile.spa	Sat Dec 29 16:06:54 2018 +0100
+++ b/docker/Dockerfile.spa	Sat Dec 29 16:07:40 2018 +0100
@@ -6,10 +6,11 @@
 RUN sed -i 's/\(deb.*\)$/\1 universe/' /etc/apt/sources.list
 
 RUN apt-get update &&\
-    apt-get -y install --no-install-recommends curl gnupg nodejs make mercurial
+    apt-get -y install --no-install-recommends \
+            curl ca-certificates gnupg nodejs make mercurial
 
 # Install yarn
-RUN curl https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - &&\
+RUN curl https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - &&\
     echo 'deb https://dl.yarnpkg.com/debian/ stable main' >> \
          /etc/apt/sources.list &&\
     apt-get update &&\
--- a/example_conf.toml	Sat Dec 29 16:06:54 2018 +0100
+++ b/example_conf.toml	Sat Dec 29 16:07:40 2018 +0100
@@ -56,6 +56,10 @@
 # Proxy settings for external OGC services
 #proxy-key = "SECRET"
 #proxy-prefix = "http://localhost:8000"
+#
+
+# Server is known on the outside as:
+# external-url = "http://localhost:8000"
 
 # ----------------------------------------------------------------------
 # CORS setup:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/common/json.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,34 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package common
+
+import (
+	"encoding/json"
+	"strings"
+)
+
+// FromJSONString revives data from a JSON string.
+func FromJSONString(data string, dst interface{}) error {
+	return json.NewDecoder(strings.NewReader(data)).Decode(dst)
+}
+
+// ToJSONString serializes src into a string to
+// be revived by FromJSONString.
+func ToJSONString(src interface{}) (string, error) {
+	var b strings.Builder
+	if err := json.NewEncoder(&b).Encode(src); err != nil {
+		return "", err
+	}
+	return b.String(), nil
+}
--- a/pkg/config/config.go	Sat Dec 29 16:06:54 2018 +0100
+++ b/pkg/config/config.go	Sat Dec 29 16:07:40 2018 +0100
@@ -106,11 +106,12 @@
 func TmpDir() string { return viper.GetString("tmp-dir") }
 
 var (
-	proxyKeyOnce sync.Once
-	proxyKey     []byte
-
+	proxyKeyOnce    sync.Once
+	proxyKey        []byte
 	proxyPrefixOnce sync.Once
 	proxyPrefix     string
+	externalURLOnce sync.Once
+	externalURL     string
 )
 
 // ProxyKey is a crytographic key to sign the URLs generated by the proxy.
@@ -142,13 +143,29 @@
 func ProxyPrefix() string {
 	fetchPrefix := func() {
 		if proxyPrefix == "" {
-			proxyPrefix = fmt.Sprintf("http://%s:%d", WebHost(), WebPort())
+			if proxyPrefix = viper.GetString("proxy-prefix"); proxyPrefix == "" {
+				proxyPrefix = fmt.Sprintf("http://%s:%d", WebHost(), WebPort())
+			}
 		}
 	}
 	proxyPrefixOnce.Do(fetchPrefix)
 	return proxyPrefix
 }
 
+// ExternalURL is the URL to find this server from the outside.
+// It defauls to http://${WebHost}:${WebPort}".
+func ExternalURL() string {
+	fetchExternal := func() {
+		if externalURL == "" {
+			if externalURL = viper.GetString("external-url"); externalURL == "" {
+				externalURL = fmt.Sprintf("http://%s:%d", WebHost(), WebPort())
+			}
+		}
+	}
+	externalURLOnce.Do(fetchExternal)
+	return externalURL
+}
+
 // RootCmd is cobra command to be bound th the cobra/viper infrastructure.
 var RootCmd = &cobra.Command{
 	Use:   "gemma",
@@ -219,10 +236,16 @@
 	str("geoserver-password", "geoserver", "GeoServer password")
 	bl("geoserver-clean", false, "Clean GeoServer setup")
 
-	str("proxy-key", "", `signing key for proxy URLs. Defaults to random key.`)
-	str("proxy-prefix", "", `URL prefix of proxy. Defaults to "http://${web-host}:${web-port}"`)
+	str("proxy-key", "", "signing key for proxy URLs.\n"+
+		"Defaults to random key.")
+	str("proxy-prefix", "", "URL prefix of proxy.\n"+
+		"Defaults to 'http://${web-host}:${web-port}'")
 
-	str("tmp-dir", "", "Temp directory of gemma server. Defaults to system temp directory.")
+	str("external-url", "", "URL to find the server from the outside.\n"+
+		"Defaults to 'http://${web-host}:${web-port}'")
+
+	str("tmp-dir", "", "Temp directory of gemma server.\n"+
+		"Defaults to system temp directory.")
 }
 
 var (
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/controllers/importconfig.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,397 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package controllers
+
+import (
+	"database/sql"
+	"errors"
+	"fmt"
+	"net/http"
+	"strconv"
+
+	"github.com/gorilla/mux"
+
+	"gemma.intevation.de/gemma/pkg/auth"
+	"gemma.intevation.de/gemma/pkg/imports"
+	"gemma.intevation.de/gemma/pkg/scheduler"
+)
+
+const (
+	selectImportConfigurationPrefix = `
+SELECT
+  id,
+  username,
+  kind,
+  send_email,
+  auto_accept,
+  cron,
+  url
+FROM waterway.import_configuration`
+
+	selectImportConfigurationSQL = selectImportConfigurationPrefix + `
+ORDER by id`
+
+	selectImportConfigurationIDSQL = selectImportConfigurationPrefix + `
+WHERE id = $1`
+
+	insertImportConfigurationSQL = `
+INSERT INTO waterway.import_configuration
+(username, kind, cron, send_email, auto_accept, url)
+VALUES ($1, $2, $3, $4, $5, $6)
+RETURNING id`
+
+	hasImportConfigurationSQL = `
+SELECT true FROM waterway.import_configuration
+WHERE id = $1`
+
+	deleteImportConfiguationSQL = `
+DELETE FROM waterway.import_configuration
+WHERE id = $1`
+
+	updateImportConfigurationSQL = `
+UPDATE waterway.import_configuration SET
+  username = $2
+  kind = $3,
+  cron = $4,
+  url = $5,
+  send_email = $6,
+  auto_accept = $7
+WHERE id = $1
+`
+)
+
+func modifyImportConfig(
+	input interface{},
+	req *http.Request,
+	conn *sql.Conn,
+) (jr JSONResult, err error) {
+
+	ctx := req.Context()
+
+	importConfig := input.(*imports.Config)
+
+	id, _ := strconv.ParseInt(mux.Vars(req)["id"], 10, 64)
+
+	var tx *sql.Tx
+
+	if tx, err = conn.BeginTx(ctx, nil); err != nil {
+		return
+	}
+	defer tx.Rollback()
+
+	var (
+		entry imports.IDConfig
+		kind  string
+		cron  sql.NullString
+		url   sql.NullString
+	)
+
+	err = conn.QueryRowContext(ctx, selectImportConfigurationIDSQL, id).Scan(
+		&entry.ID,
+		&entry.User,
+		&kind,
+		&entry.SendEMail,
+		&entry.AutoAccept,
+		&cron,
+		&url,
+	)
+
+	switch {
+	case err == sql.ErrNoRows:
+		err = JSONError{
+			Code:    http.StatusNotFound,
+			Message: fmt.Sprintf("No schedule %d found", id),
+		}
+		return
+	case err != nil:
+		return
+	}
+
+	session, _ := auth.GetSession(req)
+
+	entry.SendEMail = importConfig.SendEMail
+	entry.AutoAccept = importConfig.AutoAccept
+
+	if importConfig.Cron != nil {
+		cron = sql.NullString{String: string(*importConfig.Cron), Valid: true}
+	}
+
+	if importConfig.URL != nil {
+		url = sql.NullString{String: *importConfig.URL, Valid: true}
+	}
+
+	if _, err = tx.ExecContext(ctx, updateImportConfigurationSQL,
+		id,
+		session.User,
+		string(importConfig.Kind),
+		cron,
+		url,
+		importConfig.SendEMail,
+		importConfig.AutoAccept,
+	); err != nil {
+		return
+	}
+
+	scheduler.UnbindByID(id)
+
+	if cron.Valid {
+		if err = scheduler.BindAction(
+			string(importConfig.Kind),
+			cron.String,
+			id,
+		); err != nil {
+			return
+		}
+	}
+
+	if err = tx.Commit(); err != nil {
+		return
+	}
+
+	var result = struct {
+		ID int64 `json:"id"`
+	}{
+		ID: id,
+	}
+
+	jr = JSONResult{Result: &result}
+	return
+}
+
+func infoImportConfig(
+	_ interface{},
+	req *http.Request,
+	conn *sql.Conn,
+) (jr JSONResult, err error) {
+
+	ctx := req.Context()
+
+	id, _ := strconv.ParseInt(mux.Vars(req)["id"], 10, 64)
+
+	var (
+		entry imports.IDConfig
+		kind  string
+		cron  sql.NullString
+		url   sql.NullString
+	)
+
+	err = conn.QueryRowContext(ctx, selectImportConfigurationIDSQL, id).Scan(
+		&entry.ID,
+		&entry.User,
+		&kind,
+		&entry.SendEMail,
+		&entry.AutoAccept,
+		&cron,
+		&url,
+	)
+
+	switch {
+	case err == sql.ErrNoRows:
+		err = JSONError{
+			Code:    http.StatusNotFound,
+			Message: fmt.Sprintf("No schedule %d found", id),
+		}
+		return
+	case err != nil:
+		return
+	}
+
+	entry.Kind = imports.ImportKind(kind)
+	if cron.Valid {
+		cs := imports.CronSpec(cron.String)
+		entry.Cron = &cs
+	}
+	if url.Valid {
+		entry.URL = &url.String
+	}
+
+	jr = JSONResult{Result: &entry}
+	return
+}
+
+func deleteImportConfig(
+	_ interface{},
+	req *http.Request,
+	conn *sql.Conn,
+) (jr JSONResult, err error) {
+
+	ctx := req.Context()
+
+	id, _ := strconv.ParseInt(mux.Vars(req)["id"], 10, 64)
+
+	var tx *sql.Tx
+	if tx, err = conn.BeginTx(ctx, nil); err != nil {
+		return
+	}
+	defer tx.Rollback()
+
+	var found bool
+	err = tx.QueryRowContext(ctx, hasImportConfigurationSQL, id).Scan(&found)
+	switch {
+	case err == sql.ErrNoRows:
+		err = JSONError{
+			Code:    http.StatusNotFound,
+			Message: fmt.Sprintf("No schedule %d found", id),
+		}
+		return
+	case err != nil:
+		return
+	case !found:
+		err = errors.New("Unexpected result")
+		return
+	}
+
+	if _, err = tx.ExecContext(ctx, deleteImportConfiguationSQL, id); err != nil {
+		return
+	}
+
+	// Remove from running scheduler.
+	scheduler.UnbindByID(id)
+
+	if err = tx.Commit(); err != nil {
+		return
+	}
+
+	var result = struct {
+		ID int64 `json:"id"`
+	}{
+		ID: id,
+	}
+
+	jr = JSONResult{Result: &result}
+	return
+}
+
+func addImportConfig(
+	input interface{},
+	req *http.Request,
+	conn *sql.Conn,
+) (jr JSONResult, err error) {
+
+	importConfig := input.(*imports.Config)
+
+	session, _ := auth.GetSession(req)
+
+	var cron, url sql.NullString
+
+	if importConfig.Cron != nil {
+		cron = sql.NullString{String: string(*importConfig.Cron), Valid: true}
+	}
+	if importConfig.URL != nil {
+		url = sql.NullString{String: *importConfig.URL, Valid: true}
+	}
+
+	ctx := req.Context()
+
+	var tx *sql.Tx
+
+	if tx, err = conn.BeginTx(ctx, nil); err != nil {
+		return
+	}
+	defer tx.Rollback()
+
+	var id int64
+	if err = tx.QueryRowContext(
+		ctx,
+		insertImportConfigurationSQL,
+		session.User,
+		string(importConfig.Kind),
+		cron,
+		importConfig.SendEMail,
+		importConfig.AutoAccept,
+		url,
+	).Scan(&id); err != nil {
+		return
+	}
+
+	// Need to start a scheduler job right away?
+	if importConfig.Cron != nil {
+		if err = scheduler.BindAction(
+			string(importConfig.Kind),
+			string(*importConfig.Cron),
+			id,
+		); err != nil {
+			return
+		}
+	}
+
+	if err = tx.Commit(); err != nil {
+		scheduler.UnbindByID(id)
+	}
+
+	var result = struct {
+		ID int64 `json:"id"`
+	}{
+		ID: id,
+	}
+
+	jr = JSONResult{
+		Code:   http.StatusCreated,
+		Result: &result,
+	}
+	return
+}
+
+func listImportConfigs(
+	_ interface{},
+	req *http.Request,
+	conn *sql.Conn,
+) (jr JSONResult, err error) {
+
+	ctx := req.Context()
+	var rows *sql.Rows
+
+	if rows, err = conn.QueryContext(ctx, selectImportConfigurationSQL); err != nil {
+		return
+	}
+	defer rows.Close()
+
+	list := []*imports.IDConfig{}
+
+	for rows.Next() {
+		var (
+			entry imports.IDConfig
+			kind  string
+			cron  sql.NullString
+			url   sql.NullString
+		)
+		if err = rows.Scan(
+			&entry.ID,
+			&entry.User,
+			&kind,
+			&entry.SendEMail,
+			&entry.AutoAccept,
+			&cron,
+			&url,
+		); err != nil {
+			return
+		}
+		entry.Kind = imports.ImportKind(kind)
+		if cron.Valid {
+			cs := imports.CronSpec(cron.String)
+			entry.Cron = &cs
+		}
+		if url.Valid {
+			entry.URL = &url.String
+		}
+		list = append(list, &entry)
+	}
+
+	if err = rows.Err(); err != nil {
+		return
+	}
+
+	jr = JSONResult{Result: list}
+	return
+}
--- a/pkg/controllers/importqueue.go	Sat Dec 29 16:06:54 2018 +0100
+++ b/pkg/controllers/importqueue.go	Sat Dec 29 16:07:40 2018 +0100
@@ -66,6 +66,25 @@
 DELETE FROM waterway.imports WHERE id = $1`
 )
 
+func toInt8Array(txt string) *pgtype.Int8Array {
+	parts := strings.Split(txt, ",")
+	var ints []int64
+	for _, part := range parts {
+		part = strings.TrimSpace(part)
+		v, err := strconv.ParseInt(part, 10, 64)
+		if err != nil {
+			continue
+		}
+		ints = append(ints, v)
+	}
+	var ia pgtype.Int8Array
+	if err := ia.Set(ints); err != nil {
+		log.Printf("warn: %v\n", err)
+		return nil
+	}
+	return &ia
+}
+
 func toTextArray(txt string, allowed []string) *pgtype.TextArray {
 	parts := strings.Split(txt, ",")
 	var accepted []string
@@ -101,6 +120,7 @@
 		args   []interface{}
 		states *pgtype.TextArray
 		kinds  *pgtype.TextArray
+		ids    *pgtype.Int8Array
 	)
 
 	arg := func(format string, v interface{}) {
@@ -116,8 +136,12 @@
 		kinds = toTextArray(ks, imports.ImportKindNames())
 	}
 
+	if idss := req.FormValue("ids"); idss != "" {
+		ids = toInt8Array(idss)
+	}
+
 	stmt.WriteString(selectImportsSQL)
-	if states != nil || kinds != nil {
+	if states != nil || kinds != nil || ids != nil {
 		stmt.WriteString(" WHERE ")
 	}
 
@@ -125,7 +149,7 @@
 		arg(" state = ANY($%d) ", states)
 	}
 
-	if states != nil && kinds != nil {
+	if states != nil && (kinds != nil || ids != nil) {
 		stmt.WriteString("AND")
 	}
 
@@ -133,6 +157,14 @@
 		arg(" kind = ANY($%d) ", kinds)
 	}
 
+	if (states != nil || kinds != nil) && ids != nil {
+		stmt.WriteString("AND")
+	}
+
+	if ids != nil {
+		arg(" id = ANY($%d) ", ids)
+	}
+
 	stmt.WriteString(" ORDER BY enqueued DESC ")
 
 	if lim := req.FormValue("limit"); lim != "" {
--- a/pkg/controllers/json.go	Sat Dec 29 16:06:54 2018 +0100
+++ b/pkg/controllers/json.go	Sat Dec 29 16:07:40 2018 +0100
@@ -31,10 +31,13 @@
 	Result interface{}
 }
 
+const JSONDefaultLimit = 2048
+
 type JSONHandler struct {
 	Input  func() interface{}
 	Handle func(interface{}, *http.Request, *sql.Conn) (JSONResult, error)
 	NoConn bool
+	Limit  int64
 }
 
 type JSONError struct {
@@ -52,7 +55,16 @@
 	if j.Input != nil {
 		input = j.Input()
 		defer req.Body.Close()
-		if err := json.NewDecoder(req.Body).Decode(input); err != nil {
+		var r io.Reader
+		switch {
+		case j.Limit == 0:
+			r = io.LimitReader(req.Body, JSONDefaultLimit)
+		case j.Limit > 0:
+			r = io.LimitReader(req.Body, j.Limit)
+		default:
+			r = req.Body
+		}
+		if err := json.NewDecoder(r).Decode(input); err != nil {
 			http.Error(rw, "error: "+err.Error(), http.StatusBadRequest)
 			return
 		}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/controllers/manualimports.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,106 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+//  * Raimund Renkert <raimund.renkert@intevation.de>
+
+package controllers
+
+import (
+	"database/sql"
+	"log"
+	"net/http"
+
+	"gemma.intevation.de/gemma/pkg/auth"
+	"gemma.intevation.de/gemma/pkg/common"
+	"gemma.intevation.de/gemma/pkg/imports"
+	"gemma.intevation.de/gemma/pkg/models"
+)
+
+func importBottleneck(input interface{}) (interface{}, bool, bool) {
+	bi := input.(*models.BottleneckImport)
+	bn := &imports.Bottleneck{
+		URL:      bi.URL,
+		Insecure: bi.Insecure,
+	}
+	return bn, bi.SendEmail, false
+}
+
+func importGaugeMeasurement(input interface{}) (interface{}, bool, bool) {
+	gi := input.(*models.GaugeMeasurementImport)
+	gm := &imports.GaugeMeasurement{
+		URL:      gi.URL,
+		Insecure: gi.Insecure,
+	}
+	return gm, gi.SendEmail, true
+}
+
+func importFairwayAvailability(input interface{}) (interface{}, bool, bool) {
+	fai := input.(*models.FairwayAvailabilityImport)
+	fa := &imports.FairwayAvailability{
+		URL:      fai.URL,
+		Insecure: fai.Insecure,
+	}
+	return fa, fai.SendEmail, true
+}
+
+func importWaterwayAxis(input interface{}) (interface{}, bool, bool) {
+	wxi := input.(*models.WaterwayAxisImport)
+	wx := &imports.WaterwayAxis{
+		URL:         wxi.URL,
+		FeatureType: wxi.FeatureType,
+		SortBy:      wxi.SortBy,
+	}
+	return wx, wxi.SendEmail, true
+}
+
+func manualImport(
+	kind imports.JobKind,
+	setup func(interface{}) (interface{}, bool, bool),
+) func(interface{}, *http.Request, *sql.Conn) (JSONResult, error) {
+
+	return func(input interface{}, req *http.Request, _ *sql.Conn) (
+		jr JSONResult, err error) {
+
+		what, sendEmail, autoAccept := setup(input)
+
+		var serialized string
+		if serialized, err = common.ToJSONString(what); err != nil {
+			return
+		}
+
+		session, _ := auth.GetSession(req)
+
+		var jobID int64
+		if jobID, err = imports.AddJob(
+			kind,
+			session.User,
+			sendEmail, autoAccept,
+			serialized,
+		); err != nil {
+			return
+		}
+
+		log.Printf("info: added import #%d to queue\n", jobID)
+
+		result := struct {
+			ID int64 `json:"id"`
+		}{
+			ID: jobID,
+		}
+
+		jr = JSONResult{
+			Code:   http.StatusCreated,
+			Result: &result,
+		}
+		return
+	}
+}
--- a/pkg/controllers/routes.go	Sat Dec 29 16:06:54 2018 +0100
+++ b/pkg/controllers/routes.go	Sat Dec 29 16:07:40 2018 +0100
@@ -21,6 +21,7 @@
 	"github.com/gorilla/mux"
 
 	"gemma.intevation.de/gemma/pkg/auth"
+	"gemma.intevation.de/gemma/pkg/imports"
 	"gemma.intevation.de/gemma/pkg/middleware"
 	"gemma.intevation.de/gemma/pkg/models"
 )
@@ -170,6 +171,57 @@
 	api.Handle("/imports/soundingresult", waterwayAdmin(
 		http.HandlerFunc(importSoundingResult))).Methods(http.MethodPost)
 
+	api.Handle("/imports/bottleneck", waterwayAdmin(&JSONHandler{
+		Input:  func() interface{} { return new(models.BottleneckImport) },
+		Handle: manualImport(imports.BNJobKind, importBottleneck),
+		NoConn: true,
+	})).Methods(http.MethodPost)
+
+	api.Handle("/imports/gaugemeasurement", waterwayAdmin(&JSONHandler{
+		Input:  func() interface{} { return new(models.GaugeMeasurementImport) },
+		Handle: manualImport(imports.GMJobKind, importGaugeMeasurement),
+		NoConn: true,
+	})).Methods(http.MethodPost)
+
+	api.Handle("/imports/fairwayavailability", waterwayAdmin(&JSONHandler{
+		Input:  func() interface{} { return new(models.FairwayAvailabilityImport) },
+		Handle: manualImport(imports.FAJobKind, importFairwayAvailability),
+		NoConn: true,
+	})).Methods(http.MethodPost)
+
+	api.Handle("/imports/waterwayaxis", waterwayAdmin(&JSONHandler{
+		Input:  func() interface{} { return new(models.WaterwayAxisImport) },
+		Handle: manualImport(imports.WXJobKind, importWaterwayAxis),
+		NoConn: true,
+	})).Methods(http.MethodPost)
+
+	// Import scheduler configuration
+	api.Handle("/imports/config/{id:[0-9]+}",
+		waterwayAdmin(&JSONHandler{
+			Handle: modifyImportConfig,
+		})).Methods(http.MethodPatch)
+
+	api.Handle("/imports/config/{id:[0-9]+}",
+		waterwayAdmin(&JSONHandler{
+			Handle: deleteImportConfig,
+		})).Methods(http.MethodDelete)
+
+	api.Handle("/imports/config/{id:[0-9]+}",
+		waterwayAdmin(&JSONHandler{
+			Handle: infoImportConfig,
+		})).Methods(http.MethodGet)
+
+	api.Handle("/imports/config",
+		waterwayAdmin(&JSONHandler{
+			Input:  func() interface{} { return new(imports.Config) },
+			Handle: addImportConfig,
+		})).Methods(http.MethodPost)
+
+	api.Handle("/imports/config",
+		waterwayAdmin(&JSONHandler{
+			Handle: listImportConfigs,
+		})).Methods(http.MethodGet)
+
 	// Import queue
 	lsImports := waterwayAdmin(&JSONHandler{
 		Handle: listImports,
--- a/pkg/controllers/srimports.go	Sat Dec 29 16:06:54 2018 +0100
+++ b/pkg/controllers/srimports.go	Sat Dec 29 16:07:40 2018 +0100
@@ -152,7 +152,7 @@
 	}
 	sr.Dir = dir
 
-	serialized, err := sr.ToString()
+	serialized, err := common.ToJSONString(sr)
 	if err != nil {
 		log.Printf("error: %v\n", err)
 		http.Error(rw, "error: "+err.Error(), http.StatusInternalServerError)
@@ -161,7 +161,14 @@
 
 	session, _ := auth.GetSession(req)
 
-	jobID, err := imports.AddJob(imports.SRJobKind, session.User, serialized)
+	sendEmail := req.FormValue("bottleneck") != ""
+
+	jobID, err := imports.AddJob(
+		imports.SRJobKind,
+		session.User,
+		sendEmail, false,
+		serialized)
+
 	if err != nil {
 		log.Printf("error: %v\n", err)
 		http.Error(rw, "error: "+err.Error(), http.StatusInternalServerError)
@@ -217,12 +224,14 @@
 		Messages []string    `json:"messages,omitempty"`
 	}
 
-	var noXYZ bool
-	if noXYZ = common.FindInZIP(zr, ".xyz") == nil; noXYZ {
-		messages = append(messages, "no .xyz file found.")
+	find := func(ext string) *zip.File { return common.FindInZIP(zr, ext) }
+
+	noXYZ := find(".xyz") == nil && find(".txt") == nil
+	if noXYZ {
+		messages = append(messages, "no .xyz or .txt file found.")
 	}
 
-	if mj := common.FindInZIP(zr, "meta.json"); mj == nil {
+	if mj := find("meta.json"); mj == nil {
 		messages = append(messages, "no 'meta.json' file found.")
 	} else {
 		if meta, err := loadMeta(mj); err != nil {
--- a/pkg/controllers/surveys.go	Sat Dec 29 16:06:54 2018 +0100
+++ b/pkg/controllers/surveys.go	Sat Dec 29 16:07:40 2018 +0100
@@ -23,10 +23,13 @@
 )
 
 const (
-	listSurveysSQL = `SELECT s.bottleneck_id,
-                                 s.date_info::text
-FROM waterway.bottlenecks b, waterway.sounding_results s
-WHERE b.objnam=$1 AND s.bottleneck_id = b.bottleneck_id;`
+	listSurveysSQL = `
+SELECT
+  s.bottleneck_id,
+  s.date_info::text
+FROM waterway.bottlenecks b JOIN waterway.sounding_results s
+ON b.id = s.bottleneck_id
+WHERE b.objnam=$1`
 )
 
 func listSurveys(
--- a/pkg/controllers/user.go	Sat Dec 29 16:06:54 2018 +0100
+++ b/pkg/controllers/user.go	Sat Dec 29 16:07:40 2018 +0100
@@ -29,6 +29,7 @@
 	"gemma.intevation.de/gemma/pkg/auth"
 	"gemma.intevation.de/gemma/pkg/misc"
 	"gemma.intevation.de/gemma/pkg/models"
+	"gemma.intevation.de/gemma/pkg/scheduler"
 )
 
 const (
@@ -112,9 +113,21 @@
 		return
 	}
 
+	ctx := req.Context()
+
+	// Remove scheduled tasks.
+	ids, err2 := scheduler.ScheduledUserIDs(ctx, db, user)
+	if err2 == nil {
+		if len(ids) > 0 {
+			go func() { scheduler.UnbindByIDs(ids) }()
+		}
+	} else {
+		log.Printf("error: %v\n", err2)
+	}
+
 	var res sql.Result
 
-	if res, err = db.ExecContext(req.Context(), deleteUserSQL, user); err != nil {
+	if res, err = db.ExecContext(ctx, deleteUserSQL, user); err != nil {
 		return
 	}
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/imports/bn.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,265 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package imports
+
+import (
+	"context"
+	"database/sql"
+	"errors"
+	"regexp"
+	"strconv"
+	"time"
+
+	"gemma.intevation.de/gemma/pkg/common"
+	"gemma.intevation.de/gemma/pkg/soap/ifbn"
+)
+
+type Bottleneck struct {
+	URL      string `json:"url"`
+	Insecure bool   `json:"insecure"`
+}
+
+const BNJobKind JobKind = "bn"
+
+const (
+	hasBottleneckSQL = `
+SELECT true FROM waterway.bottlenecks WHERE bottleneck_id = $1`
+
+	insertBottleneckSQL = `
+INSERT INTO waterway.bottlenecks (
+  bottleneck_id,
+  fk_g_fid,
+  objnam,
+  nobjnm,
+  stretch,
+  area,
+  rb,
+  lb,
+  responsible_country,
+  revisiting_time,
+  limiting,
+  date_info,
+  source_organization
+) VALUES(
+  $1,
+  isrs_fromText($2),
+  $3,
+  $4,
+  isrsrange(isrs_fromText($5), isrs_fromText($6)),
+  ISRSrange_area(
+    isrsrange(isrs_fromText($5), isrs_fromText($6)),
+    (SELECT ST_Union(CAST(area AS geometry))
+        FROM waterway.fairway_dimensions
+        WHERE level_of_service = 3)),
+  $7,
+  $8,
+  $9,
+  $10,
+  $11,
+  $12,
+  $13
+)
+RETURNING id`
+)
+
+type bnJobCreator struct{}
+
+func init() {
+	RegisterJobCreator(BNJobKind, bnJobCreator{})
+}
+
+func (bnJobCreator) Description() string {
+	return "bottlenecks"
+}
+
+func (bnJobCreator) Create(_ JobKind, data string) (Job, error) {
+	bn := new(Bottleneck)
+	if err := common.FromJSONString(data, bn); err != nil {
+		return nil, err
+	}
+	return bn, nil
+}
+
+func (bnJobCreator) Depends() []string {
+	return []string{
+		"gauges",
+		"bottlenecks",
+	}
+}
+
+const (
+	bnStageDoneSQL = `
+UPDATE waterway.bottlenecks SET staging_done = true
+WHERE id IN (
+  SELECT key from waterway.track_imports
+  WHERE import_id = $1 AND
+        relation = 'waterway.bottlenecks'::regclass)`
+)
+
+// StageDone moves the imported bottleneck out of the staging area.
+func (bnJobCreator) StageDone(
+	ctx context.Context,
+	tx *sql.Tx,
+	id int64,
+) error {
+	_, err := tx.ExecContext(ctx, bnStageDoneSQL, id)
+	return err
+}
+
+// CleanUp of a bottleneck import is a NOP.
+func (*Bottleneck) CleanUp() error { return nil }
+
+var rblbRe = regexp.MustCompile(`(..)_(..)`)
+
+func splitRBLB(s string) (string, string) {
+	m := rblbRe.FindStringSubmatch(s)
+	if len(m) == 0 {
+		return "", ""
+	}
+	return m[1], m[2]
+}
+
+func revisitingTime(s string) int {
+	v, err := strconv.Atoi(s)
+	if err != nil {
+		v = 0
+	}
+	return v
+}
+
+// Do executes the actual bottleneck import.
+func (bn *Bottleneck) Do(
+	ctx context.Context,
+	importID int64,
+	conn *sql.Conn,
+	feedback Feedback,
+) (interface{}, error) {
+	client := ifbn.NewIBottleneckService(bn.URL, bn.Insecure, nil)
+
+	req := &ifbn.Export_bn_by_isrs{}
+
+	resp, err := client.Export_bn_by_isrs(req)
+	if err != nil {
+		feedback.Error("%v", err)
+		return nil, err
+	}
+
+	if resp.Export_bn_by_isrsResult == nil {
+		err := errors.New("no Bottlenecks found")
+		feedback.Error("%v", err)
+		return nil, err
+	}
+
+	bns := resp.Export_bn_by_isrsResult.BottleNeckType
+	feedback.Info("Found %d bottlenecks for import", len(bns))
+
+	tx, err := conn.BeginTx(ctx, nil)
+	if err != nil {
+		return nil, err
+	}
+	defer tx.Rollback()
+
+	var hasStmt, insertStmt, trackStmt *sql.Stmt
+
+	for _, x := range []struct {
+		sql  string
+		stmt **sql.Stmt
+	}{
+		{hasBottleneckSQL, &hasStmt},
+		{insertBottleneckSQL, &insertStmt},
+		{trackImportSQL, &trackStmt},
+	} {
+		var err error
+		if *x.stmt, err = tx.PrepareContext(ctx, x.sql); err != nil {
+			return nil, err
+		}
+		defer (*x.stmt).Close()
+	}
+
+	var nids []string
+
+	start := time.Now()
+
+nextBN:
+	for _, bn := range bns {
+
+		var found bool
+		err := hasStmt.QueryRowContext(ctx, bn.Bottleneck_id).Scan(&found)
+		switch {
+		case err == sql.ErrNoRows:
+			// This is good.
+		case err != nil:
+			return nil, err
+		case found:
+			// TODO: Deep comparison database vs. SOAP.
+			continue nextBN
+		}
+
+		rb, lb := splitRBLB(bn.Rb_lb)
+
+		var limiting, country string
+
+		if bn.Limiting_factor != nil {
+			limiting = string(*bn.Limiting_factor)
+		}
+
+		if bn.Responsible_country != nil {
+			country = string(*bn.Responsible_country)
+		}
+
+		var nid int64
+
+		err = insertStmt.QueryRowContext(
+			ctx,
+			bn.Bottleneck_id,
+			bn.Fk_g_fid,
+			bn.OBJNAM,
+			bn.NOBJNM,
+			bn.From_ISRS, bn.To_ISRS,
+			rb,
+			lb,
+			country,
+			revisitingTime(bn.Revisiting_time),
+			limiting,
+			bn.Date_Info,
+			bn.Source,
+		).Scan(&nid)
+		if err != nil {
+			return nil, err
+		}
+		nids = append(nids, bn.Bottleneck_id)
+		if _, err := trackStmt.ExecContext(
+			ctx, importID, "waterway.bottlenecks", nid,
+		); err != nil {
+			return nil, err
+		}
+		feedback.Info("Inserted '%s' into database", bn.OBJNAM)
+	}
+	if len(nids) == 0 {
+		feedback.Error("No new bottlenecks found")
+		return nil, errors.New("No new bottlenecks found")
+	}
+
+	feedback.Info("Storing %d bottlenecks took %s", len(nids), time.Since(start))
+	if err = tx.Commit(); err == nil {
+		feedback.Info("Import of bottlenecks was successful")
+	}
+
+	summary := struct {
+		Bottlenecks []string `json:"bottlenecks"`
+	}{
+		Bottlenecks: nids,
+	}
+	return &summary, err
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/imports/config.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,129 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package imports
+
+import (
+	"context"
+	"database/sql"
+	"encoding/json"
+	"fmt"
+
+	"gemma.intevation.de/gemma/pkg/auth"
+	"github.com/robfig/cron"
+)
+
+type (
+	CronSpec   string
+	ImportKind string
+
+	Config struct {
+		Kind       ImportKind `json:"kind"`
+		SendEMail  bool       `json:"send-email"`
+		AutoAccept bool       `json:"auto-accept"`
+		Cron       *CronSpec  `json:"cron"`
+		URL        *string    `json:"url"`
+	}
+
+	IDConfig struct {
+		ID         int64      `json:"id"`
+		User       string     `json:"user"`
+		Kind       ImportKind `json:"kind"`
+		SendEMail  bool       `json:"send-email"`
+		AutoAccept bool       `json:"auto-accept"`
+		Cron       *CronSpec  `json:"cron,omitempty"`
+		URL        *string    `json:"url,omitempty"`
+	}
+)
+
+func (ik *ImportKind) UnmarshalJSON(data []byte) error {
+	var s string
+	if err := json.Unmarshal(data, &s); err != nil {
+		return err
+	}
+
+	if !HasImportKindName(s) {
+		return fmt.Errorf("Unknown kind '%s'", s)
+	}
+
+	*ik = ImportKind(s)
+
+	return nil
+}
+
+func (cs *CronSpec) UnmarshalJSON(data []byte) error {
+	var spec string
+	if err := json.Unmarshal(data, &spec); err != nil {
+		return err
+	}
+	if _, err := cron.Parse(spec); err != nil {
+		return err
+	}
+	*cs = CronSpec(spec)
+
+	return nil
+}
+
+const (
+	configUser = "sys_admin"
+
+	loadConfigSQL = `
+SELECT
+  username,
+  kind,
+  send_email,
+  auto_accept,
+  cron,
+  url
+FROM waterway.import_configuration
+WHERE id = $1`
+)
+
+func loadIDConfig(id int64) (*IDConfig, error) {
+
+	ctx := context.Background()
+	cfg := &IDConfig{ID: id}
+
+	err := auth.RunAs(ctx, configUser, func(conn *sql.Conn) error {
+		var kind ImportKind
+		var cron, url sql.NullString
+		if err := conn.QueryRowContext(ctx, loadConfigSQL, id).Scan(
+			&cfg.User,
+			&kind,
+			&cfg.SendEMail,
+			&cfg.AutoAccept,
+			&cron,
+			&url,
+		); err != nil {
+			return err
+		}
+		cfg.Kind = ImportKind(kind)
+		if cron.Valid {
+			c := CronSpec(cron.String)
+			cfg.Cron = &c
+		}
+		if url.Valid {
+			cfg.URL = &url.String
+		}
+		return nil
+	})
+
+	switch {
+	case err == sql.ErrNoRows:
+		return nil, nil
+	case err != nil:
+		return nil, err
+	}
+
+	return cfg, nil
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/imports/email.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,91 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package imports
+
+import (
+	"context"
+	"database/sql"
+	"log"
+	"strings"
+	"text/template"
+
+	"gemma.intevation.de/gemma/pkg/auth"
+	"gemma.intevation.de/gemma/pkg/config"
+	"gemma.intevation.de/gemma/pkg/misc"
+)
+
+const (
+	selectEmailSQL = `SELECT email_address FROM users.list_users WHERE username = $1`
+
+	importNotificationMailSubject = `import notification mail`
+)
+
+var (
+	importNotificationMailTmpl = template.Must(
+		template.New("notification").Parse(`Dear {{ .User }},
+
+a {{ .Description }} import on server {{ .Server }} triggered
+this email notification.
+
+{{ if eq .State "accepted" }}The imported data were successfully integrated into the database.{{ end -}}
+{{ if eq .State "failed" }}The import failed for some reasons.{{ end -}}
+{{ if eq .State "pending" }}The imported data could be integrated into the database
+but your final decision is needed.{{ end }}
+
+Please follow this link to have a closer look at the details:
+
+{{ .Server }}/#/?{{ if eq .State "pending" }}review{{ else }}importlog{{ end }}={{ .ID }}
+
+Best regards
+    Your service team`))
+)
+
+func sendNotificationMail(user, description, state string, id int64) {
+	config.WaitReady()
+
+	ctx := context.Background()
+	var email string
+	if err := auth.RunAs(ctx, user,
+		func(conn *sql.Conn) error {
+			return conn.QueryRowContext(ctx, selectEmailSQL, user).Scan(&email)
+		},
+	); err != nil {
+		log.Printf("error: %v\n", err)
+		return
+	}
+
+	data := struct {
+		User        string
+		Description string
+		Server      string
+		State       string
+		ID          int64
+	}{
+		User:        user,
+		Description: description,
+		Server:      config.ExternalURL(),
+		State:       state,
+		ID:          id,
+	}
+
+	var body strings.Builder
+	if err := importNotificationMailTmpl.Execute(&body, &data); err != nil {
+		log.Printf("error: %v\n", err)
+		return
+	}
+
+	if err := misc.SendMail(email, importNotificationMailSubject, body.String()); err != nil {
+		log.Printf("error: %v\n", err)
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/imports/fa.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,363 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Raimund Renkert <raimund.renkert@intevation.de>
+package imports
+
+import (
+	"context"
+	"database/sql"
+	"errors"
+	"time"
+
+	"github.com/jackc/pgx/pgtype"
+
+	"gemma.intevation.de/gemma/pkg/common"
+	"gemma.intevation.de/gemma/pkg/models"
+	"gemma.intevation.de/gemma/pkg/soap/ifaf"
+)
+
+type FairwayAvailability struct {
+	URL      string `json:"url"`
+	Insecure bool   `json:"insecure"`
+}
+
+const FAJobKind JobKind = "fa"
+
+const (
+	listBottlenecksSQL = `
+SELECT
+  bottleneck_id,
+  responsible_country
+FROM waterway.bottlenecks
+WHERE responsible_country = users.current_user_country()
+  AND staging_done = true
+`
+	insertFASQL = `
+INSERT INTO waterway.fairway_availability (
+  position_code,
+  bottleneck_id,
+  surdat,
+  critical,
+  date_info,
+  source_organization
+) VALUES (
+  $1,
+  (SELECT id FROM waterway.bottlenecks WHERE bottleneck_id = $2),
+  $3,
+  $4,
+  $5,
+  $6
+)
+RETURNING id`
+
+	insertBnPdfsSQL = `
+INSERT INTO waterway.bottleneck_pdfs (
+  fairway_availability_id,
+  profile_pdf_filename,
+  profile_pdf_url,
+  pdf_generation_date,
+  source_organization
+) VALUES (
+  $1,
+  $2,
+  $3,
+  $4,
+  $5
+)`
+	insertEFASQL = `
+INSERT INTO waterway.effective_fairway_availability (
+  fairway_availability_id,
+  measure_date,
+  level_of_service,
+  available_depth_value,
+  available_width_value,
+  water_level_value,
+  measure_type,
+  source_organization,
+  forecast_generation_time,
+  value_lifetime
+) VALUES (
+  $1,
+  $2,
+  (SELECT
+    level_of_service
+  FROM levels_of_service
+  WHERE name = $3),
+  $4,
+  $5,
+  $6,
+  $7,
+  $8,
+  $9,
+  $10
+)`
+	insertFAVSQL = `
+INSERT INTO waterway.fa_reference_values (
+  fairway_availability_id,
+  level_of_service,
+  fairway_depth,
+  fairway_width,
+  fairway_radius,
+  shallowest_spot
+) VALUES (
+  $1,
+  (SELECT
+    level_of_service
+  FROM levels_of_service
+  WHERE name = $2),
+  $3,
+  $4,
+  $5,
+  ST_MakePoint($6, $7)::geography
+)`
+)
+
+type faJobCreator struct{}
+
+func init() {
+	RegisterJobCreator(FAJobKind, faJobCreator{})
+}
+
+func (faJobCreator) Description() string {
+	return "fairway availability"
+}
+
+func (faJobCreator) Create(_ JobKind, data string) (Job, error) {
+	fa := new(FairwayAvailability)
+	if err := common.FromJSONString(data, fa); err != nil {
+		return nil, err
+	}
+	return fa, nil
+}
+
+func (faJobCreator) Depends() []string {
+	return []string{
+		"bottlenecks",
+		"fairway_availability",
+		"bottleneck_pdfs",
+		"effective_fairway_availability",
+		"fa_reference_values",
+		"levels_of_service",
+	}
+}
+
+// StageDone moves the imported fairway availablities out of the staging area.
+// Currently doing nothing.
+func (faJobCreator) StageDone(context.Context, *sql.Tx, int64) error {
+	return nil
+}
+
+// CleanUp of a fairway availablities import is a NOP.
+func (*FairwayAvailability) CleanUp() error { return nil }
+
+// Do executes the actual fairway availability import.
+func (fa *FairwayAvailability) Do(
+	ctx context.Context,
+	importID int64,
+	conn *sql.Conn,
+	feedback Feedback,
+) (interface{}, error) {
+
+	// Get available bottlenecks from database for use as filter in SOAP request
+	var rows *sql.Rows
+
+	rows, err := conn.QueryContext(ctx, listBottlenecksSQL)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+
+	bottlenecks := []models.Bottleneck{}
+
+	for rows.Next() {
+		var bn models.Bottleneck
+		if err = rows.Scan(
+			&bn.ID,
+			&bn.ResponsibleCountry,
+		); err != nil {
+			return nil, err
+		}
+		bottlenecks = append(bottlenecks, bn)
+	}
+
+	if err = rows.Err(); err != nil {
+		return nil, err
+	}
+
+	faids, err := fa.doForFAs(ctx, bottlenecks, conn, feedback)
+	if err != nil {
+		feedback.Error("Error processing data: %s", err)
+	}
+	if len(faids) == 0 {
+		feedback.Info("No new fairway availablity data found")
+		return nil, nil
+	}
+	feedback.Info("Processed %d of %d bottlenecks", len(faids), len(bottlenecks))
+	// TODO: needs to be filled more useful.
+	summary := struct {
+		FairwayAvailabilities []string `json:"fairwayAvailabilities"`
+	}{
+		FairwayAvailabilities: faids,
+	}
+	return &summary, err
+}
+
+func (fa *FairwayAvailability) doForFAs(
+	ctx context.Context,
+	bottlenecks []models.Bottleneck,
+	conn *sql.Conn,
+	feedback Feedback,
+) ([]string, error) {
+	start := time.Now()
+
+	client := ifaf.NewFairwayAvailabilityService(fa.URL, fa.Insecure, nil)
+
+	var bnIds []string
+	for _, bn := range bottlenecks {
+		bnIds = append(bnIds, bn.ID)
+	}
+
+	ids := ifaf.ArrayOfString{
+		String: bnIds,
+	}
+
+	// TODO: Filter by period. Period should start after latest measurement date.
+	req := &ifaf.Get_bottleneck_fa{
+		Bottleneck_id: &ids,
+	}
+	resp, err := client.Get_bottleneck_fa(req)
+	if err != nil {
+		feedback.Error("%v", err)
+		return nil, err
+	}
+
+	if resp.Get_bottleneck_faResult == nil {
+		err := errors.New("no fairway availabilities found")
+		return nil, err
+	}
+
+	result := resp.Get_bottleneck_faResult
+
+	tx, err := conn.BeginTx(ctx, nil)
+	if err != nil {
+		return nil, err
+	}
+	defer tx.Rollback()
+
+	insertFAStmt, err := tx.PrepareContext(ctx, insertFASQL)
+	if err != nil {
+		return nil, err
+	}
+	defer insertFAStmt.Close()
+	insertBnPdfsStmt, err := tx.PrepareContext(ctx, insertBnPdfsSQL)
+	if err != nil {
+		return nil, err
+	}
+	defer insertBnPdfsStmt.Close()
+	insertEFAStmt, err := tx.PrepareContext(ctx, insertEFASQL)
+	if err != nil {
+		return nil, err
+	}
+	defer insertEFAStmt.Close()
+	insertFAVStmt, err := tx.PrepareContext(ctx, insertFAVSQL)
+	if err != nil {
+		return nil, err
+	}
+	defer insertFAVStmt.Close()
+
+	var faids []string
+	var faId int64
+	feedback.Info("Found %d fairway availabilities", len(result.FairwayAvailability))
+	for _, faRes := range result.FairwayAvailability {
+		// TODO: high frequent requests lead to "duplicate key value violates unique constraint "fairway_availability_bottleneck_id_surdat_key"
+		// in the database. This has to be resolved.
+		// All data subsets can also ocure as duplicates!
+		err = insertFAStmt.QueryRowContext(
+			ctx,
+			faRes.POSITION,
+			faRes.Bottleneck_id,
+			faRes.SURDAT,
+			faRes.Critical,
+			faRes.Date_Info,
+			faRes.Source,
+		).Scan(&faId)
+		if err != nil {
+			return nil, err
+		}
+		feedback.Info("Processing for Bottleneck %s", faRes.Bottleneck_id)
+		faids = append(faids, faRes.Bottleneck_id)
+		for _, bnPdfs := range faRes.Bottleneck_PDFs.PdfInfo {
+			_, err = insertBnPdfsStmt.ExecContext(
+				ctx,
+				faId,
+				bnPdfs.ProfilePdfFilename,
+				bnPdfs.ProfilePdfURL,
+				bnPdfs.PDF_Generation_Date,
+				bnPdfs.Source,
+			)
+			if err != nil {
+				return nil, err
+			}
+			feedback.Info("Add %d Pdfs", len(faRes.Bottleneck_PDFs.PdfInfo))
+		}
+		for _, efa := range faRes.Effective_fairway_availability.EffectiveFairwayAvailability {
+			los := efa.Level_of_Service
+			fgt := efa.Forecast_generation_time
+			if efa.Forecast_generation_time.Status == pgtype.Undefined {
+				fgt = pgtype.Timestamp{
+					Status: pgtype.Null,
+				}
+			}
+			_, err = insertEFAStmt.ExecContext(
+				ctx,
+				faId,
+				efa.Measure_date,
+				string(*los),
+				efa.Available_depth_value,
+				efa.Available_width_value,
+				efa.Water_level_value,
+				efa.Measure_type,
+				efa.Source,
+				fgt,
+				efa.Value_lifetime,
+			)
+			if err != nil {
+				return nil, err
+			}
+			feedback.Info("Add %d Effective Fairway Availability", len(
+				faRes.Effective_fairway_availability.EffectiveFairwayAvailability))
+		}
+		for _, fav := range faRes.Reference_values.ReferenceValue {
+			_, err = insertFAVStmt.ExecContext(
+				ctx,
+				faId,
+				fav.Level_of_Service,
+				fav.Fairway_depth,
+				fav.Fairway_width,
+				fav.Fairway_radius,
+				fav.Shallowest_spot_Lat,
+				fav.Shallowest_spot_Lon,
+			)
+			if err != nil {
+				return nil, err
+			}
+			feedback.Info("Add %d Reference Values",
+				len(faRes.Reference_values.ReferenceValue))
+		}
+	}
+	feedback.Info("Storing fairway availabilities took %s", time.Since(start))
+	if err = tx.Commit(); err == nil {
+		feedback.Info("Import of fairway availabilities was successful")
+	}
+
+	return faids, nil
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/imports/gm.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,264 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Raimund Renkert <raimund.renkert@intevation.de>
+package imports
+
+import (
+	"context"
+	"database/sql"
+	"errors"
+	"time"
+
+	"gemma.intevation.de/gemma/pkg/common"
+	"gemma.intevation.de/gemma/pkg/models"
+	"gemma.intevation.de/gemma/pkg/soap/nts"
+)
+
+type GaugeMeasurement struct {
+	URL      string `json:"url"`
+	Insecure bool   `json:"insecure"`
+}
+
+const GMJobKind JobKind = "gm"
+
+const (
+	listGaugesSQL = `
+SELECT
+  (location).country_code,
+  (location).locode,
+  (location).fairway_section,
+  (location).orc,
+  (location).hectometre
+FROM waterway.gauges
+WHERE (location).country_code = users.current_user_country()`
+
+	hasGaugeMeasurementSQL = `
+SELECT true FROM waterway.gauge_measurements WHERE fk_gauge_id = $1`
+
+	insertGMSQL = `
+INSERT INTO waterway.gauge_measurements (
+  fk_gauge_id,
+  measure_date,
+  sender,
+  language_code,
+  date_issue,
+  water_level,
+  predicted,
+  is_waterlevel,
+  value_min,
+  value_max,
+  date_info,
+  source_organization
+) VALUES(
+  ($1, $2, $3, $4, $5),
+  $6,
+  $7,
+  $8,
+  $9,
+  $10,
+  $11,
+  $12,
+  $13,
+  $14,
+  $15,
+  $16
+)
+RETURNING id`
+)
+
+type gmJobCreator struct{}
+
+func init() {
+	RegisterJobCreator(GMJobKind, gmJobCreator{})
+}
+
+func (gmJobCreator) Description() string {
+	return "gauge measurements"
+}
+
+func (gmJobCreator) Create(_ JobKind, data string) (Job, error) {
+	gm := new(GaugeMeasurement)
+	if err := common.FromJSONString(data, gm); err != nil {
+		return nil, err
+	}
+	return gm, nil
+}
+
+func (gmJobCreator) Depends() []string {
+	return []string{
+		"gauges",
+		"gauge_measurements",
+	}
+}
+
+// StageDone moves the imported gauge measurement out of the staging area.
+// Currently doing nothing.
+func (gmJobCreator) StageDone(context.Context, *sql.Tx, int64) error {
+	return nil
+}
+
+// CleanUp of a gauge measurement import is a NOP.
+func (*GaugeMeasurement) CleanUp() error { return nil }
+
+// Do executes the actual bottleneck import.
+func (gm *GaugeMeasurement) Do(
+	ctx context.Context,
+	importID int64,
+	conn *sql.Conn,
+	feedback Feedback,
+) (interface{}, error) {
+
+	// Get available gauges from database for use as filter in SOAP request
+	var rows *sql.Rows
+
+	rows, err := conn.QueryContext(ctx, listGaugesSQL)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+
+	gauges := []models.GaugeMeasurement{}
+
+	for rows.Next() {
+		var g models.GaugeMeasurement
+		if err = rows.Scan(
+			&g.Gauge.CountryCode,
+			&g.Gauge.LoCode,
+			&g.Gauge.FairwaySection,
+			&g.Gauge.Orc,
+			&g.Gauge.Hectometre,
+		); err != nil {
+			return nil, err
+		}
+		gauges = append(gauges, g)
+	}
+
+	if err = rows.Err(); err != nil {
+		return nil, err
+	}
+
+	// TODO get date_issue for selected gauges
+	gids, err := gm.doForGM(ctx, gauges, conn, feedback)
+	if err != nil {
+		feedback.Error("Error processing %d gauges: %s", len(gauges), err)
+	}
+	if len(gids) == 0 {
+		feedback.Info("No new gauge measurements found")
+		return nil, nil
+	}
+	// TODO: needs to be filled more useful.
+	summary := struct {
+		GaugeMeasuremets []string `json:"gaugeMeasurements"`
+	}{
+		GaugeMeasuremets: gids,
+	}
+	return &summary, err
+}
+
+func (gm *GaugeMeasurement) doForGM(
+	ctx context.Context,
+	gauges []models.GaugeMeasurement,
+	conn *sql.Conn,
+	feedback Feedback,
+) ([]string, error) {
+	start := time.Now()
+
+	client := nts.NewINtSMessageService(gm.URL, gm.Insecure, nil)
+
+	var idPairs []*nts.Id_pair
+	for _, g := range gauges {
+		isrs := g.Gauge.String()
+		isrsID := nts.Isrs_code_type(isrs)
+		idPairs = append(idPairs, &nts.Id_pair{
+			Id: &isrsID,
+		})
+	}
+	mt := nts.Message_type_typeWRM
+	req := &nts.Get_messages_query{
+		Message_type: &mt,
+		Ids:          idPairs,
+	}
+	resp, err := client.Get_messages(req)
+	if err != nil {
+		feedback.Error("%v", err)
+		return nil, err
+	}
+
+	if resp.Result_message == nil {
+		err := errors.New("no gauge measurements found")
+		for i, e := range resp.Result_error {
+			feedback.Error("%d: %v", i, e)
+		}
+		return nil, err
+	}
+
+	result := resp.Result_message
+
+	tx, err := conn.BeginTx(ctx, nil)
+	if err != nil {
+		return nil, err
+	}
+	defer tx.Rollback()
+
+	insertStmt, err := tx.PrepareContext(ctx, insertGMSQL)
+	if err != nil {
+		return nil, err
+	}
+	defer insertStmt.Close()
+
+	var gid int64
+	var gids []string
+	for _, msg := range result {
+		feedback.Info("Found %d gauges with measurements", len(msg.Wrm))
+		for _, wrm := range msg.Wrm {
+			currIsrs, err := models.IsrsFromString(string(*wrm.Geo_object.Id))
+			if err != nil {
+				feedback.Warn("Invalid ISRS code %v", err)
+				continue
+			}
+			for _, measure := range wrm.Measure {
+				isWaterlevel := *measure.Measure_code == nts.Measure_code_enumWAL
+				err = insertStmt.QueryRowContext(
+					ctx,
+					currIsrs.CountryCode,
+					currIsrs.LoCode,
+					currIsrs.FairwaySection,
+					currIsrs.Orc,
+					currIsrs.Hectometre,
+					measure.Measuredate,
+					msg.Identification.From,
+					msg.Identification.Language_code,
+					msg.Identification.Date_issue,
+					measure.Value,
+					measure.Predicted,
+					isWaterlevel,
+					measure.Value_min,
+					measure.Value_max,
+					msg.Identification.Date_issue,
+					msg.Identification.Originator,
+				).Scan(&gid)
+				if err != nil {
+					return nil, err
+				}
+			}
+			feedback.Info("Inserted %d measurements for %s",
+				len(wrm.Measure), currIsrs)
+			gids = append(gids, currIsrs.String())
+		}
+	}
+	feedback.Info("Storing gauge measurements took %s", time.Since(start))
+	if err = tx.Commit(); err == nil {
+		feedback.Info("Import of gauge measurements was successful")
+	}
+
+	return gids, nil
+}
--- a/pkg/imports/polygon.go	Sat Dec 29 16:06:54 2018 +0100
+++ b/pkg/imports/polygon.go	Sat Dec 29 16:07:40 2018 +0100
@@ -50,11 +50,18 @@
 
 func toPolygon(numParts int32, parts []int32, points []shp.Point) polygon {
 	out := make(polygon, numParts)
-	pos := 0
+	var pos int32
+
 	for i := range out {
-		ps := parts[i]
-		line := make(lineString, ps)
-		for j := int32(0); j < ps; j, pos = j+1, pos+1 {
+		var howMany int32
+		if i+1 >= len(parts) {
+			howMany = int32(len(points)) - pos
+		} else {
+			howMany = parts[i+1] - parts[i]
+		}
+
+		line := make(lineString, howMany)
+		for j := int32(0); j < howMany; j, pos = j+1, pos+1 {
 			p := &points[pos]
 			line[j] = point{p.X, p.Y}
 		}
--- a/pkg/imports/queue.go	Sat Dec 29 16:06:54 2018 +0100
+++ b/pkg/imports/queue.go	Sat Dec 29 16:07:40 2018 +0100
@@ -66,6 +66,8 @@
 	// JobCreator is used to bring a job to life as it is stored
 	// in pure meta-data form to the database.
 	JobCreator interface {
+		// Description is the long name of the import.
+		Description() string
 		// Create build the actual job.
 		// kind is the name of the import type.
 		// data is a free form string to pass arguments to the creation
@@ -83,10 +85,12 @@
 	}
 
 	idJob struct {
-		id   int64
-		kind JobKind
-		user string
-		data string
+		id         int64
+		kind       JobKind
+		user       string
+		sendEmail  bool
+		autoAccept bool
+		data       string
 	}
 )
 
@@ -128,11 +132,15 @@
 INSERT INTO waterway.imports (
   kind,
   username,
+  send_email,
+  auto_accept,
   data
 ) VALUES (
   $1,
   $2,
-  $3
+  $3,
+  $4,
+  $5
 ) RETURNING id`
 
 	selectJobSQL = `
@@ -140,6 +148,8 @@
   id,
   kind,
   username,
+  send_email,
+  auto_accept,
   data
 FROM waterway.imports
 WHERE state = 'queued'::waterway.import_state AND enqueued IN (
@@ -193,6 +203,18 @@
 	return iqueue.importKindNames()
 }
 
+// HasImportKind checks if the import queue supports a given kind.
+func HasImportKindName(kind string) bool {
+	return iqueue.hasImportKindName(kind)
+}
+
+//
+func (q *importQueue) hasImportKindName(kind string) bool {
+	q.creatorsMu.Lock()
+	defer q.creatorsMu.Unlock()
+	return q.creators[JobKind(kind)] != nil
+}
+
 // RegisterJobCreator adds a JobCreator to the global import queue.
 // This a good candidate to be called in a init function for
 // a particular JobCreator.
@@ -220,11 +242,23 @@
 	return q.creators[kind]
 }
 
-func (q *importQueue) addJob(kind JobKind, user, data string) (int64, error) {
+func (q *importQueue) addJob(
+	kind JobKind,
+	user string,
+	sendEmail, autoAccept bool,
+	data string,
+) (int64, error) {
 	ctx := context.Background()
 	var id int64
 	err := auth.RunAs(ctx, queueUser, func(conn *sql.Conn) error {
-		return conn.QueryRowContext(ctx, insertJobSQL, string(kind), user, data).Scan(&id)
+		return conn.QueryRowContext(
+			ctx,
+			insertJobSQL,
+			string(kind),
+			user,
+			sendEmail,
+			autoAccept,
+			data).Scan(&id)
 	})
 	if err == nil {
 		select {
@@ -238,8 +272,8 @@
 // AddJob adds a job to the global import queue to be executed
 // as soon as possible. This is gone in a separate Go routine
 // so this will not block.
-func AddJob(kind JobKind, user, data string) (int64, error) {
-	return iqueue.addJob(kind, user, data)
+func AddJob(kind JobKind, user string, sendEmail, autoAccept bool, data string) (int64, error) {
+	return iqueue.addJob(kind, user, sendEmail, autoAccept, data)
 }
 
 type logFeedback int64
@@ -321,7 +355,13 @@
 		}
 		defer tx.Rollback()
 		if err = tx.QueryRowContext(ctx, selectJobSQL, &kinds).Scan(
-			&ji.id, &ji.kind, &ji.user, &ji.data); err != nil {
+			&ji.id,
+			&ji.kind,
+			&ji.user,
+			&ji.sendEmail,
+			&ji.autoAccept,
+			&ji.data,
+		); err != nil {
 			return err
 		}
 		_, err = tx.ExecContext(ctx, updateStateSQL, "running", ji.id)
@@ -468,15 +508,22 @@
 			}
 
 			var state string
-			if errDo != nil || errCleanup != nil {
+			switch {
+			case errDo != nil || errCleanup != nil:
 				state = "failed"
-			} else {
+			case idj.autoAccept:
+				state = "accepted"
+			default:
 				state = "pending"
 			}
 			if err := updateStateSummary(ctx, idj.id, state, summary); err != nil {
 				log.Printf("setting state of job %d failed: %v\n", idj.id, err)
 			}
+			// TODO: Send email if sendEmail is set.
 			log.Printf("import #%d finished: %s\n", idj.id, state)
+			if idj.sendEmail {
+				go sendNotificationMail(idj.user, jc.Description(), state, idj.id)
+			}
 		}(jc, idj)
 	}
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/imports/scheduled.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,100 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package imports
+
+import (
+	"log"
+
+	"gemma.intevation.de/gemma/pkg/common"
+	"gemma.intevation.de/gemma/pkg/scheduler"
+)
+
+func init() {
+	registerAction(GMJobKind, func(cfg *IDConfig) interface{} {
+		log.Println("info: schedule 'gm' import")
+		return &GaugeMeasurement{
+			URL:      *cfg.URL,
+			Insecure: false,
+		}
+	})
+	registerAction(FAJobKind, func(cfg *IDConfig) interface{} {
+		log.Println("info: schedule 'fa' import")
+		return &FairwayAvailability{
+			URL:      *cfg.URL,
+			Insecure: false,
+		}
+	})
+	registerAction(BNJobKind, func(cfg *IDConfig) interface{} {
+		log.Println("info: schedule 'bn' import")
+		return &Bottleneck{
+			URL:      *cfg.URL,
+			Insecure: false,
+		}
+	})
+	registerAction(WXJobKind, func(cfg *IDConfig) interface{} {
+		log.Println("info: schedule 'wx' import")
+		// TODO: Take this from configuration.
+		var (
+			featureType = "ws-wamos:ienc_wtwaxs"
+			sortBy      = "hydro_scamin"
+		)
+		return &WaterwayAxis{
+			URL:         *cfg.URL,
+			FeatureType: featureType,
+			SortBy:      sortBy,
+		}
+	})
+}
+
+func registerAction(kind JobKind, setup func(cfg *IDConfig) interface{}) {
+
+	action := func(id int64) {
+		cfg, err := loadIDConfig(id)
+		if err != nil {
+			log.Printf("error: %v\n", err)
+			return
+		}
+		if cfg == nil {
+			log.Printf("error: No config found for id %d.\n", id)
+			return
+		}
+		if cfg.URL == nil {
+			log.Println("error: No URL specified")
+			return
+		}
+
+		what := setup(cfg)
+
+		var serialized string
+		if serialized, err = common.ToJSONString(what); err != nil {
+			log.Printf("error: %v\n", err)
+			return
+		}
+
+		var jobID int64
+		if jobID, err = AddJob(
+			kind,
+			cfg.User,
+			cfg.SendEMail, cfg.AutoAccept,
+			serialized,
+		); err != nil {
+			log.Printf("error: %v\n", err)
+			return
+		}
+
+		log.Printf("info: added import #%d to queue\n", jobID)
+	}
+
+	scheduler.RegisterAction(string(kind), action)
+}
--- a/pkg/imports/sr.go	Sat Dec 29 16:06:54 2018 +0100
+++ b/pkg/imports/sr.go	Sat Dec 29 16:07:40 2018 +0100
@@ -21,7 +21,6 @@
 	"crypto/sha1"
 	"database/sql"
 	"encoding/hex"
-	"encoding/json"
 	"errors"
 	"fmt"
 	"io"
@@ -74,9 +73,13 @@
 	RegisterJobCreator(SRJobKind, srJobCreator{})
 }
 
+func (srJobCreator) Description() string {
+	return "sounding results"
+}
+
 func (srJobCreator) Create(_ JobKind, data string) (Job, error) {
 	sr := new(SoundingResult)
-	if err := sr.FromString(data); err != nil {
+	if err := common.FromJSONString(data, sr); err != nil {
 		return nil, err
 	}
 	return sr, nil
@@ -84,9 +87,9 @@
 
 func (srJobCreator) Depends() []string {
 	return []string{
-		"waterway.sounding_results",
-		"waterway.sounding_results_contour_lines",
-		"waterway.bottlenecks",
+		"sounding_results",
+		"sounding_results_contour_lines",
+		"bottlenecks",
 	}
 }
 
@@ -115,7 +118,7 @@
   point_cloud,
   area
 ) VALUES (
-  (SELECT bottleneck_id from waterway.bottlenecks where objnam = $1),
+  (SELECT id from waterway.bottlenecks where objnam = $1),
   $2::date,
   $3,
   ST_Transform(ST_GeomFromWKB($4, $6::integer), 4326)::geography,
@@ -169,21 +172,6 @@
 `
 )
 
-// FromString revives a SoundingResult import from a string.
-func (sr *SoundingResult) FromString(data string) error {
-	return json.NewDecoder(strings.NewReader(data)).Decode(sr)
-}
-
-// ToString serializes a SoundingResult import into a string to
-// be revived by FromString.
-func (sr *SoundingResult) ToString() (string, error) {
-	var b strings.Builder
-	if err := json.NewEncoder(&b).Encode(sr); err != nil {
-		return "", err
-	}
-	return b.String(), nil
-}
-
 // Do executes the actual sounding result import.
 func (sr *SoundingResult) Do(
 	ctx context.Context,
@@ -213,10 +201,15 @@
 		return nil, common.ToError(err)
 	}
 
-	feedback.Info("Looking for '*.xyz'")
-	xyzf := common.FindInZIP(z, ".xyz")
+	var xyzf *zip.File
+	for _, ext := range []string{".xyz", ".txt"} {
+		feedback.Info("Looking for '*%s'", ext)
+		if xyzf = common.FindInZIP(z, ext); xyzf != nil {
+			break
+		}
+	}
 	if xyzf == nil {
-		return nil, errors.New("Cannot find any *.xyz file")
+		return nil, errors.New("Cannot find any *.xyz or *.txt file")
 	}
 
 	xyz, err := loadXYZ(xyzf, feedback)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/imports/wx.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,291 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package imports
+
+import (
+	"bytes"
+	"context"
+	"database/sql"
+	"encoding/binary"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"math"
+	"strings"
+	"time"
+
+	"gemma.intevation.de/gemma/pkg/common"
+	"gemma.intevation.de/gemma/pkg/wfs"
+)
+
+type WaterwayAxis struct {
+	URL         string `json:"url"`
+	FeatureType string `json:"feature-type"`
+	SortBy      string `json:"sort-by"`
+}
+
+const WXJobKind JobKind = "wx"
+
+type wxJobCreator struct{}
+
+func init() {
+	RegisterJobCreator(WXJobKind, wxJobCreator{})
+}
+
+func (wxJobCreator) Description() string {
+	return "waterway axis"
+}
+
+func (wxJobCreator) Create(_ JobKind, data string) (Job, error) {
+	wx := new(WaterwayAxis)
+	if err := common.FromJSONString(data, wx); err != nil {
+		return nil, err
+	}
+	return wx, nil
+}
+
+func (wxJobCreator) Depends() []string {
+	return []string{
+		"waterway_axis",
+	}
+}
+
+// StageDone is a NOP for waterway axis imports.
+func (wxJobCreator) StageDone(context.Context, *sql.Tx, int64) error {
+	return nil
+}
+
+// CleanUp for waterway imports is a NOP.
+func (*WaterwayAxis) CleanUp() error { return nil }
+
+type waterwayAxisProperties struct {
+	ObjNam  string  `json:"hydro_objnam"`
+	NObjNnm *string `json:"hydro_nobjnm"`
+}
+
+type line [][]float64
+
+const wkbLineString uint32 = 2
+
+func (l line) asWKB() []byte {
+
+	size := 1 + 4 + 4 + len(l)*(2*8)
+
+	buf := bytes.NewBuffer(make([]byte, 0, size))
+
+	binary.Write(buf, binary.LittleEndian, wkbNDR)
+	binary.Write(buf, binary.LittleEndian, wkbLineString)
+	binary.Write(buf, binary.LittleEndian, uint32(len(l)))
+
+	for _, c := range l {
+		var lat, lon float64
+		if len(c) > 0 {
+			lat = c[0]
+		}
+		if len(c) > 1 {
+			lon = c[1]
+		}
+		binary.Write(buf, binary.LittleEndian, math.Float64bits(lat))
+		binary.Write(buf, binary.LittleEndian, math.Float64bits(lon))
+	}
+
+	return buf.Bytes()
+}
+
+const (
+	deleteWaterwayAxisSQL = `DELETE FROM waterway.waterway_axis`
+	insertWaterwayAxisSQL = `
+INSERT INTO waterway.waterway_axis (wtwaxs, objnam, nobjnam)
+VALUES (
+  ST_Transform(ST_GeomFromWKB($1, $2::integer), 4326)::geography,
+  $3,
+  $4
+)`
+)
+
+// Do executes the actual waterway exis import.
+func (wx *WaterwayAxis) Do(
+	ctx context.Context,
+	importID int64,
+	conn *sql.Conn,
+	feedback Feedback,
+) (interface{}, error) {
+
+	start := time.Now()
+
+	feedback.Info("Import waterway axis")
+
+	feedback.Info("Loading capabilities from %s", wx.URL)
+	caps, err := wfs.GetCapabilities(wx.URL)
+	if err != nil {
+		feedback.Error("Loading capabilities failed: %v", err)
+		return nil, err
+	}
+
+	ft := caps.FindFeatureType(wx.FeatureType)
+	if ft == nil {
+		return nil, fmt.Errorf("Unknown feature type '%s'", wx.FeatureType)
+	}
+
+	epsg, err := wfs.CRSToEPSG(ft.DefaultCRS)
+	if err != nil {
+		feedback.Error("Unsupported CRS name '%s'", ft.DefaultCRS)
+		return nil, err
+	}
+
+	urls, err := wfs.GetFeaturesGET(
+		caps, wx.FeatureType, "application/json", wx.SortBy)
+	if err != nil {
+		feedback.Error("Cannot create GetFeature URLs. %v", err)
+		return nil, err
+	}
+
+	tx, err := conn.BeginTx(ctx, nil)
+	if err != nil {
+		return nil, err
+	}
+	defer tx.Rollback()
+
+	insertStmt, err := tx.PrepareContext(ctx, insertWaterwayAxisSQL)
+	if err != nil {
+		return nil, err
+	}
+	defer insertStmt.Close()
+
+	// Delete the old features.
+	if _, err := tx.ExecContext(ctx, deleteWaterwayAxisSQL); err != nil {
+		return nil, err
+	}
+
+	var (
+		unsupportedTypes  = map[string]int{}
+		missingProperties int
+		badProperties     int
+		features          int
+	)
+
+	if err := wfs.DownloadURLs(urls, func(r io.Reader) error {
+		rfc, err := wfs.ParseRawFeatureCollection(r)
+		if err != nil {
+			return err
+		}
+		if rfc.CRS != nil {
+			crsName := rfc.CRS.Properties.Name
+			if epsg, err = wfs.CRSToEPSG(crsName); err != nil {
+				feedback.Error("Unsupported CRS: %d", crsName)
+				return err
+			}
+		}
+
+		// No features -> ignore.
+		if rfc.Features == nil {
+			return nil
+		}
+
+		feedback.Info("Using EPSG: %d", epsg)
+
+		for _, feature := range rfc.Features {
+			if feature.Properties == nil || feature.Geometry.Coordinates == nil {
+				missingProperties++
+				continue
+			}
+
+			var props waterwayAxisProperties
+
+			if err := json.Unmarshal(*feature.Properties, &props); err != nil {
+				badProperties++
+				continue
+			}
+
+			var nobjnam sql.NullString
+			if props.NObjNnm != nil {
+				nobjnam = sql.NullString{String: *props.NObjNnm, Valid: true}
+			}
+
+			switch feature.Geometry.Type {
+			case "LineString":
+				var l line
+				if err := json.Unmarshal(*feature.Geometry.Coordinates, &l); err != nil {
+					return err
+				}
+				if _, err := insertStmt.ExecContext(
+					ctx,
+					l.asWKB(),
+					epsg,
+					props.ObjNam,
+					nobjnam,
+				); err != nil {
+					return err
+				}
+				features++
+			case "MultiLineString":
+				var ls []line
+				if err := json.Unmarshal(*feature.Geometry.Coordinates, &ls); err != nil {
+					return err
+				}
+				for _, l := range ls {
+					if _, err := insertStmt.ExecContext(
+						ctx,
+						l.asWKB(),
+						epsg,
+						props.ObjNam,
+						nobjnam,
+					); err != nil {
+						return err
+					}
+					features++
+				}
+			default:
+				unsupportedTypes[feature.Geometry.Type]++
+			}
+		}
+		return nil
+	}); err != nil {
+		feedback.Error("Downloading features failed: %v", err)
+		return nil, err
+	}
+
+	if features == 0 {
+		err := errors.New("No features found")
+		feedback.Error("%v", err)
+		return nil, err
+	}
+
+	if badProperties > 0 {
+		feedback.Warn("Bad properties: %d", badProperties)
+	}
+
+	if missingProperties > 0 {
+		feedback.Warn("Missing properties: %d", missingProperties)
+	}
+
+	if len(unsupportedTypes) != 0 {
+		var b strings.Builder
+		for t, c := range unsupportedTypes {
+			if b.Len() > 0 {
+				b.WriteString(", ")
+			}
+			b.WriteString(fmt.Sprintf("%s: %d", t, c))
+		}
+		feedback.Warn("Unsupported types found: %s", b.String())
+	}
+
+	if err = tx.Commit(); err == nil {
+		feedback.Info("Storing %d features took %s",
+			features, time.Since(start))
+	}
+
+	return nil, err
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/models/bn.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,25 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package models
+
+type BottleneckImport struct {
+	URL       string `json:"url"`
+	Insecure  bool   `json:"insecure"`
+	SendEmail bool   `json:"send-email"`
+}
+
+type Bottleneck struct {
+	ID                 string
+	ResponsibleCountry string
+}
--- a/pkg/models/cross.go	Sat Dec 29 16:06:54 2018 +0100
+++ b/pkg/models/cross.go	Sat Dec 29 16:07:40 2018 +0100
@@ -155,7 +155,7 @@
 
 func (lc GeoJSONLineCoordinates) AsWKB() []byte {
 
-	size := 1 + 4 + 4 + len(lc)*(1+4+2*8)
+	size := 1 + 4 + 4 + len(lc)*(2*8)
 
 	buf := bytes.NewBuffer(make([]byte, 0, size))
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/models/fa.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,21 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Raimund Renkert <raimund.renkert@intevation.de>
+
+package models
+
+// FairwayAvailabilityImport contains data used to define the endpoint
+type FairwayAvailabilityImport struct {
+	URL       string `json:"url"`
+	Insecure  bool   `json:"insecure"`
+	SendEmail bool   `json:"send-email"`
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/models/gauge.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,72 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Raimund Renkert <raimund.renkert@intevation.de>
+
+package models
+
+import (
+	"errors"
+	"fmt"
+	"strconv"
+	"time"
+)
+
+// GaugeMeasurementImport contains data used to define the endpoint
+type GaugeMeasurementImport struct {
+	URL       string `json:"url"`
+	Insecure  bool   `json:"insecure"`
+	SendEmail bool   `json:"send-email"`
+}
+
+// GaugeMeasurement holds information about a gauge and the latest measurement
+type GaugeMeasurement struct {
+	Gauge           Isrs
+	LatestDateIssue time.Time
+}
+
+// Isrs represents the gauge identification data structure
+type Isrs struct {
+	CountryCode    string
+	LoCode         string
+	FairwaySection string
+	Orc            string
+	Hectometre     int
+}
+
+// IsrsFromString converts string representation of isrs code to type Isrs
+func IsrsFromString(isrsCode string) (*Isrs, error) {
+	if len(isrsCode) < 20 {
+		return nil, errors.New("ISRS code too short")
+	}
+	hm, err := strconv.Atoi(isrsCode[15:20])
+	if err != nil {
+		return nil, err
+	}
+	isrs := Isrs{
+		CountryCode:    isrsCode[0:2],
+		LoCode:         isrsCode[2:5],
+		FairwaySection: isrsCode[5:10],
+		Orc:            isrsCode[10:15],
+		Hectometre:     hm,
+	}
+	return &isrs, nil
+}
+
+// String creates a isrs code string from Isrs
+func (isrs *Isrs) String() string {
+	return fmt.Sprintf("%s%s%s%s%05d",
+		isrs.CountryCode,
+		isrs.LoCode,
+		isrs.FairwaySection,
+		isrs.Orc,
+		isrs.Hectometre)
+}
--- a/pkg/models/import.go	Sat Dec 29 16:06:54 2018 +0100
+++ b/pkg/models/import.go	Sat Dec 29 16:07:40 2018 +0100
@@ -61,7 +61,7 @@
 }
 
 func (it ImportTime) MarshalJSON() ([]byte, error) {
-	return json.Marshal(it.Format("2006-01-02T15:04:05"))
+	return json.Marshal(it.Format("2006-01-02T15:04:05.000"))
 }
 
 func (it *ImportTime) Scan(x interface{}) error {
--- a/pkg/models/sr.go	Sat Dec 29 16:06:54 2018 +0100
+++ b/pkg/models/sr.go	Sat Dec 29 16:07:40 2018 +0100
@@ -46,7 +46,7 @@
 
 	checkBottleneckDateUniqueSQL = `
 SELECT true FROM waterway.sounding_results sr JOIN
-  waterway.bottlenecks bn ON sr.bottleneck_id = bn.bottleneck_id
+  waterway.bottlenecks bn ON sr.bottleneck_id = bn.id
 WHERE bn.objnam = $1 AND sr.date_info = $2`
 )
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/models/waterway.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,21 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package models
+
+type WaterwayAxisImport struct {
+	URL         string `json:"url"`
+	FeatureType string `json:"feature-type"`
+	SortBy      string `json:"sort-by"`
+	SendEmail   bool   `json:"send-email"`
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/scheduler/boot.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,105 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package scheduler
+
+import (
+	"context"
+	"database/sql"
+	"log"
+
+	"gemma.intevation.de/gemma/pkg/auth"
+	"gemma.intevation.de/gemma/pkg/config"
+)
+
+const (
+	bootRole = "sys_admin"
+
+	selectImportConfSQL = `
+SELECT id, username, kind, cron
+FROM waterway.import_configuration
+WHERE cron IS NOT NULL`
+
+	scheduledIDsSQL = `
+SELECT id from waterway.import_configuration
+WHERE username = $1 AND cron IS NOT NULL`
+)
+
+func init() { go boot() }
+
+// boot starts the scheduler with the configurations from
+// the database which have a schedule.
+func boot() {
+	config.WaitReady()
+	log.Println("info: booting scheduler from database.")
+	ctx := context.Background()
+	err := auth.RunAs(
+		ctx, bootRole,
+		func(conn *sql.Conn) error {
+			rows, err := conn.QueryContext(ctx, selectImportConfSQL)
+			if err != nil {
+				return err
+			}
+			defer rows.Close()
+			err = BootActions(func(ba *BoundAction) (bool, error) {
+				if err != nil {
+					return false, err
+				}
+				if !rows.Next() {
+					return false, nil
+				}
+				var id int64
+				if err = rows.Scan(
+					&id,
+					&ba.Name,
+					&ba.Spec,
+				); err != nil {
+					return false, err
+				}
+				ba.CfgID = id
+				return true, nil
+			})
+			if err != nil {
+				return err
+			}
+			return rows.Err()
+		})
+	if err != nil {
+		log.Printf("error: %v\n", err)
+	}
+}
+
+// ScheduledUserIDs returns the IDs with a schedule for a given user.
+func ScheduledUserIDs(
+	ctx context.Context,
+	conn *sql.Conn,
+	user string,
+) (map[int64]struct{}, error) {
+	ids := map[int64]struct{}{}
+	rows, err := conn.QueryContext(ctx, scheduledIDsSQL, user)
+	if err != nil {
+		return nil, nil
+	}
+	defer rows.Close()
+	for rows.Next() {
+		var id int64
+		if err := rows.Scan(&id); err != nil {
+			return nil, err
+		}
+		ids[id] = struct{}{}
+	}
+	if err := rows.Err(); err != nil {
+		return nil, err
+	}
+	return ids, nil
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/scheduler/scheduler.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,305 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package scheduler
+
+import (
+	"errors"
+	"log"
+	"sync"
+
+	"github.com/robfig/cron"
+)
+
+// ErrNoSuchAction if no fitting action was found.
+var ErrNoSuchAction = errors.New("No such action")
+
+// Action is called with a configuration id.
+type Action func(cfgID int64)
+
+type userAction struct {
+	scheduler *scheduler
+	name      string
+	cfgID     int64
+}
+
+type scheduler struct {
+	cr      *cron.Cron
+	actions map[string]Action
+	mu      sync.Mutex
+}
+
+// Run implements cron.Job.
+func (ua *userAction) Run() {
+	if a := ua.scheduler.action(ua.name); a != nil {
+		a(ua.cfgID)
+	} else {
+		log.Printf("warn: scheduled action '%s' not found.", ua.name)
+	}
+}
+
+var global = scheduler{
+	cr:      cron.New(),
+	actions: make(map[string]Action),
+}
+
+// RegisterAction registers a named action to the global scheduler.
+func RegisterAction(name string, action Action) {
+	global.registerAction(name, action)
+}
+
+// UnregisterAction ungesiters a named action from the global scheduler.
+func UnregisterAction(name string) {
+	global.unregisterAction(name)
+}
+
+// BoundAction is a complete set of infos for
+// an action to be bound to a schedule and
+// configuration id.
+type BoundAction struct {
+	Name  string
+	Spec  string
+	CfgID int64
+}
+
+// BootActions setup the global scheduler with a set
+// of bound actions delivered by the next function.
+func BootActions(next func(*BoundAction) (bool, error)) error {
+	return global.bootActions(next)
+}
+
+func (s *scheduler) bootActions(next func(*BoundAction) (bool, error)) error {
+
+	cr := cron.New()
+
+	for {
+		var ba BoundAction
+		ok, err := next(&ba)
+		if err != nil {
+			return err
+		}
+		if !ok {
+			break
+		}
+		schedule, err := cron.Parse(ba.Spec)
+		if err != nil {
+			return err
+		}
+		job := &userAction{
+			scheduler: s,
+			cfgID:     ba.CfgID,
+		}
+		cr.Schedule(schedule, job)
+	}
+
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	s.cr.Stop()
+	s.cr = cr
+	cr.Start()
+
+	return nil
+}
+
+// BindAction binds a named action to a cron spec and
+// a configuration id.
+func BindAction(name, spec string, cfgID int64) error {
+	return global.bindAction(name, spec, cfgID)
+}
+
+// UnbindAction unbinds a named action from a user and
+// a configuration id.
+func UnbindAction(name string, cfgID int64) {
+	global.unbindAction(name, cfgID)
+}
+
+// UnbindByID unbinds all schedules with a given id.
+func UnbindByID(cfgID int64) {
+	global.unbindByID(cfgID)
+}
+
+// UnbindUser unbinds all schedules for a given user.
+func UnbindByIDs(ids map[int64]struct{}) {
+	global.unbindByIDs(ids)
+}
+
+// HasAction asks if there is an action with a given name.
+func HasAction(name string) bool {
+	return global.hasAction(name)
+}
+
+func (s *scheduler) hasAction(name string) bool {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	return s.actions[name] != nil
+}
+
+func (s *scheduler) unbindByIDs(ids map[int64]struct{}) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	entries := s.cr.Entries()
+
+	if len(entries) == 0 {
+		return
+	}
+
+	var found bool
+	for _, entry := range entries {
+		ua := entry.Job.(*userAction)
+		if _, found = ids[ua.cfgID]; found {
+			break
+		}
+	}
+	if !found {
+		return
+	}
+
+	s.cr.Stop()
+	s.cr = cron.New()
+	for _, entry := range entries {
+		ua := entry.Job.(*userAction)
+		if _, found := ids[ua.cfgID]; !found {
+			s.cr.Schedule(entry.Schedule, entry.Job)
+		}
+	}
+	s.cr.Start()
+}
+
+func (s *scheduler) unbindByID(cfgID int64) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	entries := s.cr.Entries()
+
+	var found bool
+	for _, entry := range entries {
+		ua := entry.Job.(*userAction)
+		if ua.cfgID == cfgID {
+			found = true
+			break
+		}
+	}
+
+	if !found {
+		return
+	}
+
+	s.cr.Stop()
+	s.cr = cron.New()
+	for _, entry := range entries {
+		ua := entry.Job.(*userAction)
+		if ua.cfgID != cfgID {
+			s.cr.Schedule(entry.Schedule, entry.Job)
+		}
+	}
+	s.cr.Start()
+}
+
+func (s *scheduler) unbindAction(name string, cfgID int64) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	entries := s.cr.Entries()
+
+	var found *userAction
+	for _, entry := range entries {
+		ua := entry.Job.(*userAction)
+		if ua.name == name && cfgID == ua.cfgID {
+			// Already have such a action/cfg tuple  -> re-schedule.
+			found = ua
+			break
+		}
+	}
+
+	if found == nil {
+		return
+	}
+
+	s.cr.Stop()
+	s.cr = cron.New()
+	for _, entry := range entries {
+		ua := entry.Job.(*userAction)
+		if ua != found {
+			s.cr.Schedule(entry.Schedule, entry.Job)
+		}
+	}
+	s.cr.Start()
+}
+
+func (s *scheduler) bindAction(name, spec string, cfgID int64) error {
+
+	schedule, err := cron.Parse(spec)
+	if err != nil {
+		return err
+	}
+
+	s.mu.Lock()
+	defer s.mu.Unlock()
+
+	entries := s.cr.Entries()
+
+	var found *userAction
+	for _, entry := range entries {
+		ua := entry.Job.(*userAction)
+		if ua.name == name && cfgID == ua.cfgID {
+			// Already have such a user/action/cfg tuple  -> re-schedule.
+			found = ua
+			break
+		}
+	}
+
+	if found == nil {
+		// Add to current plan.
+		job := &userAction{scheduler: s, name: name, cfgID: cfgID}
+		s.cr.Schedule(schedule, job)
+	} else {
+		// If found re-build all.
+		s.cr.Stop()
+		s.cr = cron.New()
+		for _, entry := range entries {
+			ua := entry.Job.(*userAction)
+			var sch cron.Schedule
+			if found == ua {
+				// replace with new schedule.
+				sch = schedule
+			} else {
+				sch = entry.Schedule
+			}
+			s.cr.Schedule(sch, entry.Job)
+		}
+	}
+	s.cr.Start()
+
+	return nil
+}
+
+func (s *scheduler) action(name string) Action {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	return s.actions[name]
+}
+
+func (s *scheduler) registerAction(name string, action Action) {
+	log.Printf("info: register action '%s' in scheduler.", name)
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	s.actions[name] = action
+}
+
+func (s *scheduler) unregisterAction(name string) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	delete(s.actions, name)
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/soap/ifaf/service.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,935 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Raimund Renkert <raimund.renkert@intevation.de>
+
+package ifaf
+
+import (
+	"crypto/tls"
+	"encoding/xml"
+	"time"
+
+	"github.com/jackc/pgx/pgtype"
+
+	"gemma.intevation.de/gemma/pkg/soap"
+)
+
+// against "unused imports"
+var _ time.Time
+var _ xml.Name
+
+type Get_bottleneck_fa struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/fairwayavailability/3.0 get_bottleneck_fa"`
+
+	Bottleneck_id *ArrayOfString `xml:"bottleneck_id,omitempty"`
+
+	Period *RequestedPeriod `xml:"period,omitempty"`
+}
+
+type Get_bottleneck_faResponse struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/fairwayavailability/3.0 get_bottleneck_faResponse"`
+
+	Get_bottleneck_faResult *ArrayOfFairwayAvailability `xml:"get_bottleneck_faResult,omitempty"`
+}
+
+type Get_stretch_fa struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/fairwayavailability/3.0 get_stretch_fa"`
+
+	ISRS *ArrayOfISRSPair `xml:"ISRS,omitempty"`
+
+	Period *RequestedPeriod `xml:"period,omitempty"`
+}
+
+type Get_stretch_faResponse struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/fairwayavailability/3.0 get_stretch_faResponse"`
+
+	Get_stretch_faResult *ArrayOfFairwayAvailability `xml:"get_stretch_faResult,omitempty"`
+}
+
+type ArrayOfFairwayAvailability struct {
+	FairwayAvailability []*FairwayAvailability `xml:"FairwayAvailability,omitempty"`
+}
+
+type FairwayAvailability struct {
+	Bottleneck_id string `xml:"bottleneck_id,omitempty"`
+
+	SURDAT time.Time `xml:"SURDAT,omitempty"`
+
+	POSITION *PositionEnum `xml:"POSITION,omitempty"`
+
+	Reference_values *ArrayOfReferenceValue `xml:"Reference_values,omitempty"`
+
+	AdditionalData *ArrayOfKeyValuePair `xml:"AdditionalData,omitempty"`
+
+	Critical bool `xml:"Critical,omitempty"`
+
+	Bottleneck_PDFs *ArrayOfPdfInfo `xml:"Bottleneck_PDFs,omitempty"`
+
+	Effective_fairway_availability *ArrayOfEffectiveFairwayAvailability `xml:"Effective_fairway_availability,omitempty"`
+
+	Date_Info time.Time `xml:"Date_Info,omitempty"`
+
+	Source string `xml:"Source,omitempty"`
+}
+
+type ArrayOfPdfInfo struct {
+	PdfInfo []*PdfInfo `xml:"PdfInfo,omitempty"`
+}
+
+type PdfInfo struct {
+	ProfilePdfFilename string `xml:"ProfilePdfFilename,omitempty"`
+
+	ProfilePdfURL string `xml:"ProfilePdfURL,omitempty"`
+
+	PDF_Generation_Date time.Time `xml:"PDF_Generation_Date,omitempty"`
+
+	Source string `xml:"Source,omitempty"`
+}
+
+type ArrayOfEffectiveFairwayAvailability struct {
+	EffectiveFairwayAvailability []*EffectiveFairwayAvailability `xml:"EffectiveFairwayAvailability,omitempty"`
+}
+
+type EffectiveFairwayAvailability struct {
+	Available_depth_value int32 `xml:"Available_depth_value,omitempty"`
+
+	Available_width_value int32 `xml:"Available_width_value,omitempty"`
+
+	Water_level_value int32 `xml:"Water_level_value,omitempty"`
+
+	Measure_date time.Time `xml:"Measure_date,omitempty"`
+
+	Measure_type *MeasureType `xml:"Measure_type,omitempty"`
+
+	Source string `xml:"Source,omitempty"`
+
+	Level_of_Service *LosEnum `xml:"Level_of_Service,omitempty"`
+
+	Forecast_generation_time pgtype.Timestamp `xml:"Forecast_generation_time,omitempty"`
+
+	Value_lifetime time.Time `xml:"Value_lifetime,omitempty"`
+}
+
+type ArrayOfReferenceValue struct {
+	ReferenceValue []*ReferenceValue `xml:"ReferenceValue,omitempty"`
+}
+
+type ReferenceValue struct {
+	Fairway_depth int32 `xml:"fairway_depth,omitempty"`
+
+	Fairway_width int32 `xml:"fairway_width,omitempty"`
+
+	Fairway_radius int32 `xml:"fairway_radius,omitempty"`
+
+	Shallowest_spot_Lat float64 `xml:"Shallowest_spot_Lat,omitempty"`
+
+	Shallowest_spot_Lon float64 `xml:"Shallowest_spot_Lon,omitempty"`
+
+	Level_of_Service *LosEnum `xml:"Level_of_Service,omitempty"`
+}
+
+type ErrorCode string
+
+const (
+
+	// <summary> Description: message type not supported, Explanation:
+	// web service does not support the requested message type
+	// </summary>
+	ErrorCodeE010 ErrorCode = "e010"
+
+	// <summary> Description: syntax error in request, Explanation:
+	// request violates the schema for requests </summary>
+	ErrorCodeE100 ErrorCode = "e100"
+
+	// <summary> Description: incorrect message type, Explanation: given
+	// message type is not known </summary>
+	ErrorCodeE110 ErrorCode = "e110"
+
+	// <summary> Description: incorrect type-specific parameters,
+	// Explanation: type-specific parameters are erroneous </summary>
+	ErrorCodeE120 ErrorCode = "e120"
+
+	// <summary> Description: operation not known, Explanation: the
+	// requested operation is unknown </summary>
+	ErrorCodeE200 ErrorCode = "e200"
+
+	// <summary> Description: requested method or operation is not
+	// implemented </summary>
+	ErrorCodeE210 ErrorCode = "e210"
+
+	// <summary> Description: data source unavailable, Explanation: data
+	// source of the web service for is temporarily unavailable
+	// </summary>
+	ErrorCodeE300 ErrorCode = "e300"
+
+	// <summary> Description: too many results for request, Explanation:
+	// server is unable to handle number of results </summary>
+	ErrorCodeE310 ErrorCode = "e310"
+
+	// <summary> Description: unexpected or other error
+	// </summary>
+	ErrorCodeE999 ErrorCode = "e999"
+)
+
+type CountryCode string
+
+const (
+	CountryCodeAF CountryCode = "AF"
+
+	CountryCodeAX CountryCode = "AX"
+
+	CountryCodeAL CountryCode = "AL"
+
+	CountryCodeDZ CountryCode = "DZ"
+
+	CountryCodeAS CountryCode = "AS"
+
+	CountryCodeAD CountryCode = "AD"
+
+	CountryCodeAO CountryCode = "AO"
+
+	CountryCodeAI CountryCode = "AI"
+
+	CountryCodeAQ CountryCode = "AQ"
+
+	CountryCodeAG CountryCode = "AG"
+
+	CountryCodeAR CountryCode = "AR"
+
+	CountryCodeAM CountryCode = "AM"
+
+	CountryCodeAW CountryCode = "AW"
+
+	CountryCodeAU CountryCode = "AU"
+
+	CountryCodeAT CountryCode = "AT"
+
+	CountryCodeAZ CountryCode = "AZ"
+
+	CountryCodeBS CountryCode = "BS"
+
+	CountryCodeBH CountryCode = "BH"
+
+	CountryCodeBD CountryCode = "BD"
+
+	CountryCodeBB CountryCode = "BB"
+
+	CountryCodeBY CountryCode = "BY"
+
+	CountryCodeBE CountryCode = "BE"
+
+	CountryCodeBZ CountryCode = "BZ"
+
+	CountryCodeBJ CountryCode = "BJ"
+
+	CountryCodeBM CountryCode = "BM"
+
+	CountryCodeBT CountryCode = "BT"
+
+	CountryCodeBO CountryCode = "BO"
+
+	CountryCodeBQ CountryCode = "BQ"
+
+	CountryCodeBA CountryCode = "BA"
+
+	CountryCodeBW CountryCode = "BW"
+
+	CountryCodeBV CountryCode = "BV"
+
+	CountryCodeBR CountryCode = "BR"
+
+	CountryCodeIO CountryCode = "IO"
+
+	CountryCodeBN CountryCode = "BN"
+
+	CountryCodeBG CountryCode = "BG"
+
+	CountryCodeBF CountryCode = "BF"
+
+	CountryCodeBI CountryCode = "BI"
+
+	CountryCodeCV CountryCode = "CV"
+
+	CountryCodeKH CountryCode = "KH"
+
+	CountryCodeCM CountryCode = "CM"
+
+	CountryCodeCA CountryCode = "CA"
+
+	CountryCodeKY CountryCode = "KY"
+
+	CountryCodeCF CountryCode = "CF"
+
+	CountryCodeTD CountryCode = "TD"
+
+	CountryCodeCL CountryCode = "CL"
+
+	CountryCodeCN CountryCode = "CN"
+
+	CountryCodeCX CountryCode = "CX"
+
+	CountryCodeCC CountryCode = "CC"
+
+	CountryCodeCO CountryCode = "CO"
+
+	CountryCodeKM CountryCode = "KM"
+
+	CountryCodeCG CountryCode = "CG"
+
+	CountryCodeCD CountryCode = "CD"
+
+	CountryCodeCK CountryCode = "CK"
+
+	CountryCodeCR CountryCode = "CR"
+
+	CountryCodeCI CountryCode = "CI"
+
+	CountryCodeHR CountryCode = "HR"
+
+	CountryCodeCU CountryCode = "CU"
+
+	CountryCodeCW CountryCode = "CW"
+
+	CountryCodeCY CountryCode = "CY"
+
+	CountryCodeCZ CountryCode = "CZ"
+
+	CountryCodeDK CountryCode = "DK"
+
+	CountryCodeDJ CountryCode = "DJ"
+
+	CountryCodeDM CountryCode = "DM"
+
+	CountryCodeDO CountryCode = "DO"
+
+	CountryCodeEC CountryCode = "EC"
+
+	CountryCodeEG CountryCode = "EG"
+
+	CountryCodeSV CountryCode = "SV"
+
+	CountryCodeGQ CountryCode = "GQ"
+
+	CountryCodeER CountryCode = "ER"
+
+	CountryCodeEE CountryCode = "EE"
+
+	CountryCodeET CountryCode = "ET"
+
+	CountryCodeFK CountryCode = "FK"
+
+	CountryCodeFO CountryCode = "FO"
+
+	CountryCodeFJ CountryCode = "FJ"
+
+	CountryCodeFI CountryCode = "FI"
+
+	CountryCodeFR CountryCode = "FR"
+
+	CountryCodeGF CountryCode = "GF"
+
+	CountryCodePF CountryCode = "PF"
+
+	CountryCodeTF CountryCode = "TF"
+
+	CountryCodeGA CountryCode = "GA"
+
+	CountryCodeGM CountryCode = "GM"
+
+	CountryCodeGE CountryCode = "GE"
+
+	CountryCodeDE CountryCode = "DE"
+
+	CountryCodeGH CountryCode = "GH"
+
+	CountryCodeGI CountryCode = "GI"
+
+	CountryCodeGR CountryCode = "GR"
+
+	CountryCodeGL CountryCode = "GL"
+
+	CountryCodeGD CountryCode = "GD"
+
+	CountryCodeGP CountryCode = "GP"
+
+	CountryCodeGU CountryCode = "GU"
+
+	CountryCodeGT CountryCode = "GT"
+
+	CountryCodeGG CountryCode = "GG"
+
+	CountryCodeGN CountryCode = "GN"
+
+	CountryCodeGW CountryCode = "GW"
+
+	CountryCodeGY CountryCode = "GY"
+
+	CountryCodeHT CountryCode = "HT"
+
+	CountryCodeHM CountryCode = "HM"
+
+	CountryCodeVA CountryCode = "VA"
+
+	CountryCodeHN CountryCode = "HN"
+
+	CountryCodeHK CountryCode = "HK"
+
+	CountryCodeHU CountryCode = "HU"
+
+	CountryCodeIS CountryCode = "IS"
+
+	CountryCodeIN CountryCode = "IN"
+
+	CountryCodeID CountryCode = "ID"
+
+	CountryCodeIR CountryCode = "IR"
+
+	CountryCodeIQ CountryCode = "IQ"
+
+	CountryCodeIE CountryCode = "IE"
+
+	CountryCodeIM CountryCode = "IM"
+
+	CountryCodeIL CountryCode = "IL"
+
+	CountryCodeIT CountryCode = "IT"
+
+	CountryCodeJM CountryCode = "JM"
+
+	CountryCodeJP CountryCode = "JP"
+
+	CountryCodeJE CountryCode = "JE"
+
+	CountryCodeJO CountryCode = "JO"
+
+	CountryCodeKZ CountryCode = "KZ"
+
+	CountryCodeKE CountryCode = "KE"
+
+	CountryCodeKI CountryCode = "KI"
+
+	CountryCodeKP CountryCode = "KP"
+
+	CountryCodeKR CountryCode = "KR"
+
+	CountryCodeKW CountryCode = "KW"
+
+	CountryCodeKG CountryCode = "KG"
+
+	CountryCodeLA CountryCode = "LA"
+
+	CountryCodeLV CountryCode = "LV"
+
+	CountryCodeLB CountryCode = "LB"
+
+	CountryCodeLS CountryCode = "LS"
+
+	CountryCodeLR CountryCode = "LR"
+
+	CountryCodeLY CountryCode = "LY"
+
+	CountryCodeLI CountryCode = "LI"
+
+	CountryCodeLT CountryCode = "LT"
+
+	CountryCodeLU CountryCode = "LU"
+
+	CountryCodeMO CountryCode = "MO"
+
+	CountryCodeMK CountryCode = "MK"
+
+	CountryCodeMG CountryCode = "MG"
+
+	CountryCodeMW CountryCode = "MW"
+
+	CountryCodeMY CountryCode = "MY"
+
+	CountryCodeMV CountryCode = "MV"
+
+	CountryCodeML CountryCode = "ML"
+
+	CountryCodeMT CountryCode = "MT"
+
+	CountryCodeMH CountryCode = "MH"
+
+	CountryCodeMQ CountryCode = "MQ"
+
+	CountryCodeMR CountryCode = "MR"
+
+	CountryCodeMU CountryCode = "MU"
+
+	CountryCodeYT CountryCode = "YT"
+
+	CountryCodeMX CountryCode = "MX"
+
+	CountryCodeFM CountryCode = "FM"
+
+	CountryCodeMD CountryCode = "MD"
+
+	CountryCodeMC CountryCode = "MC"
+
+	CountryCodeMN CountryCode = "MN"
+
+	CountryCodeME CountryCode = "ME"
+
+	CountryCodeMS CountryCode = "MS"
+
+	CountryCodeMA CountryCode = "MA"
+
+	CountryCodeMZ CountryCode = "MZ"
+
+	CountryCodeMM CountryCode = "MM"
+
+	CountryCodeNA CountryCode = "NA"
+
+	CountryCodeNR CountryCode = "NR"
+
+	CountryCodeNP CountryCode = "NP"
+
+	CountryCodeNL CountryCode = "NL"
+
+	CountryCodeNC CountryCode = "NC"
+
+	CountryCodeNZ CountryCode = "NZ"
+
+	CountryCodeNI CountryCode = "NI"
+
+	CountryCodeNE CountryCode = "NE"
+
+	CountryCodeNG CountryCode = "NG"
+
+	CountryCodeNU CountryCode = "NU"
+
+	CountryCodeNF CountryCode = "NF"
+
+	CountryCodeMP CountryCode = "MP"
+
+	CountryCodeNO CountryCode = "NO"
+
+	CountryCodeOM CountryCode = "OM"
+
+	CountryCodePK CountryCode = "PK"
+
+	CountryCodePW CountryCode = "PW"
+
+	CountryCodePS CountryCode = "PS"
+
+	CountryCodePA CountryCode = "PA"
+
+	CountryCodePG CountryCode = "PG"
+
+	CountryCodePY CountryCode = "PY"
+
+	CountryCodePE CountryCode = "PE"
+
+	CountryCodePH CountryCode = "PH"
+
+	CountryCodePN CountryCode = "PN"
+
+	CountryCodePL CountryCode = "PL"
+
+	CountryCodePT CountryCode = "PT"
+
+	CountryCodePR CountryCode = "PR"
+
+	CountryCodeQA CountryCode = "QA"
+
+	CountryCodeRE CountryCode = "RE"
+
+	CountryCodeRO CountryCode = "RO"
+
+	CountryCodeRU CountryCode = "RU"
+
+	CountryCodeRW CountryCode = "RW"
+
+	CountryCodeBL CountryCode = "BL"
+
+	CountryCodeSH CountryCode = "SH"
+
+	CountryCodeKN CountryCode = "KN"
+
+	CountryCodeLC CountryCode = "LC"
+
+	CountryCodeMF CountryCode = "MF"
+
+	CountryCodePM CountryCode = "PM"
+
+	CountryCodeVC CountryCode = "VC"
+
+	CountryCodeWS CountryCode = "WS"
+
+	CountryCodeSM CountryCode = "SM"
+
+	CountryCodeST CountryCode = "ST"
+
+	CountryCodeSA CountryCode = "SA"
+
+	CountryCodeSN CountryCode = "SN"
+
+	CountryCodeRS CountryCode = "RS"
+
+	CountryCodeSC CountryCode = "SC"
+
+	CountryCodeSL CountryCode = "SL"
+
+	CountryCodeSG CountryCode = "SG"
+
+	CountryCodeSX CountryCode = "SX"
+
+	CountryCodeSK CountryCode = "SK"
+
+	CountryCodeSI CountryCode = "SI"
+
+	CountryCodeSB CountryCode = "SB"
+
+	CountryCodeSO CountryCode = "SO"
+
+	CountryCodeZA CountryCode = "ZA"
+
+	CountryCodeGS CountryCode = "GS"
+
+	CountryCodeSS CountryCode = "SS"
+
+	CountryCodeES CountryCode = "ES"
+
+	CountryCodeLK CountryCode = "LK"
+
+	CountryCodeSD CountryCode = "SD"
+
+	CountryCodeSR CountryCode = "SR"
+
+	CountryCodeSJ CountryCode = "SJ"
+
+	CountryCodeSZ CountryCode = "SZ"
+
+	CountryCodeSE CountryCode = "SE"
+
+	CountryCodeCH CountryCode = "CH"
+
+	CountryCodeSY CountryCode = "SY"
+
+	CountryCodeTW CountryCode = "TW"
+
+	CountryCodeTJ CountryCode = "TJ"
+
+	CountryCodeTZ CountryCode = "TZ"
+
+	CountryCodeTH CountryCode = "TH"
+
+	CountryCodeTL CountryCode = "TL"
+
+	CountryCodeTG CountryCode = "TG"
+
+	CountryCodeTK CountryCode = "TK"
+
+	CountryCodeTO CountryCode = "TO"
+
+	CountryCodeTT CountryCode = "TT"
+
+	CountryCodeTN CountryCode = "TN"
+
+	CountryCodeTR CountryCode = "TR"
+
+	CountryCodeTM CountryCode = "TM"
+
+	CountryCodeTC CountryCode = "TC"
+
+	CountryCodeTV CountryCode = "TV"
+
+	CountryCodeUG CountryCode = "UG"
+
+	CountryCodeUA CountryCode = "UA"
+
+	CountryCodeAE CountryCode = "AE"
+
+	CountryCodeGB CountryCode = "GB"
+
+	CountryCodeUS CountryCode = "US"
+
+	CountryCodeUM CountryCode = "UM"
+
+	CountryCodeUY CountryCode = "UY"
+
+	CountryCodeUZ CountryCode = "UZ"
+
+	CountryCodeVU CountryCode = "VU"
+
+	CountryCodeVE CountryCode = "VE"
+
+	CountryCodeVN CountryCode = "VN"
+
+	CountryCodeVG CountryCode = "VG"
+
+	CountryCodeVI CountryCode = "VI"
+
+	CountryCodeWF CountryCode = "WF"
+
+	CountryCodeEH CountryCode = "EH"
+
+	CountryCodeYE CountryCode = "YE"
+
+	CountryCodeZM CountryCode = "ZM"
+
+	CountryCodeZW CountryCode = "ZW"
+)
+
+type CoverageEnum string
+
+const (
+	CoverageEnumCrossProfiles CoverageEnum = "CrossProfiles"
+
+	CoverageEnumLongitudinalProfiles CoverageEnum = "LongitudinalProfiles"
+
+	CoverageEnumFairway CoverageEnum = "Fairway"
+
+	CoverageEnumRiver CoverageEnum = "River"
+
+	CoverageEnumRiverBanks CoverageEnum = "RiverBanks"
+)
+
+type DepthReferenceEnum string
+
+const (
+	DepthReferenceEnumNAP DepthReferenceEnum = "NAP"
+
+	DepthReferenceEnumKP DepthReferenceEnum = "KP"
+
+	DepthReferenceEnumFZP DepthReferenceEnum = "FZP"
+
+	DepthReferenceEnumADR DepthReferenceEnum = "ADR"
+
+	DepthReferenceEnumTAW DepthReferenceEnum = "TAW"
+
+	DepthReferenceEnumPUL DepthReferenceEnum = "PUL"
+
+	DepthReferenceEnumNGM DepthReferenceEnum = "NGM"
+
+	DepthReferenceEnumETRS DepthReferenceEnum = "ETRS"
+
+	DepthReferenceEnumPOT DepthReferenceEnum = "POT"
+
+	DepthReferenceEnumLDC DepthReferenceEnum = "LDC"
+
+	DepthReferenceEnumHDC DepthReferenceEnum = "HDC"
+
+	DepthReferenceEnumZPG DepthReferenceEnum = "ZPG"
+
+	DepthReferenceEnumGLW DepthReferenceEnum = "GLW"
+
+	DepthReferenceEnumHSW DepthReferenceEnum = "HSW"
+
+	DepthReferenceEnumLNW DepthReferenceEnum = "LNW"
+
+	DepthReferenceEnumHNW DepthReferenceEnum = "HNW"
+
+	DepthReferenceEnumIGN DepthReferenceEnum = "IGN"
+
+	DepthReferenceEnumWGS DepthReferenceEnum = "WGS"
+
+	DepthReferenceEnumRN DepthReferenceEnum = "RN"
+
+	DepthReferenceEnumHBO DepthReferenceEnum = "HBO"
+)
+
+type LimitingFactorEnum string
+
+const (
+	LimitingFactorEnumDepth LimitingFactorEnum = "depth"
+
+	LimitingFactorEnumWidth LimitingFactorEnum = "width"
+
+	LimitingFactorEnumCurveRadius LimitingFactorEnum = "curveRadius"
+)
+
+type LosEnum string
+
+const (
+	LosEnumNotAvailable LosEnum = "NotAvailable"
+
+	LosEnumLOS1 LosEnum = "LOS1"
+
+	LosEnumLOS2 LosEnum = "LOS2"
+
+	LosEnumLOS3 LosEnum = "LOS3"
+)
+
+type MaterialEnum string
+
+const (
+	MaterialEnumGravel MaterialEnum = "Gravel"
+
+	MaterialEnumRocky MaterialEnum = "Rocky"
+
+	MaterialEnumStone MaterialEnum = "Stone"
+
+	MaterialEnumAndesite MaterialEnum = "Andesite"
+
+	MaterialEnumSleazyAndesite MaterialEnum = "SleazyAndesite"
+
+	MaterialEnumSandyGravel MaterialEnum = "SandyGravel"
+
+	MaterialEnumMarl MaterialEnum = "Marl"
+
+	MaterialEnumSand MaterialEnum = "Sand"
+
+	MaterialEnumSarmatianLimestone MaterialEnum = "SarmatianLimestone"
+
+	MaterialEnumSandstonePeaks MaterialEnum = "SandstonePeaks"
+
+	MaterialEnumRoughSandyGravel MaterialEnum = "RoughSandyGravel"
+)
+
+type MeasureType string
+
+const (
+	MeasureTypeMeasured MeasureType = "Measured"
+
+	MeasureTypeForecasted MeasureType = "Forecasted"
+
+	MeasureTypeMinimumGuaranteed MeasureType = "MinimumGuaranteed"
+)
+
+type PositionEnum string
+
+const (
+	PositionEnumRedBuoy PositionEnum = "RedBuoy"
+
+	PositionEnumGreenBuoy PositionEnum = "GreenBuoy"
+
+	PositionEnumRightBank PositionEnum = "RightBank"
+
+	PositionEnumLeftBank PositionEnum = "LeftBank"
+
+	PositionEnumMiddle PositionEnum = "Middle"
+
+	PositionEnumAll PositionEnum = "All"
+)
+
+type SurtypEnum string
+
+const (
+	SurtypEnumMultibeam SurtypEnum = "Multibeam"
+
+	SurtypEnumSinglebeam SurtypEnum = "Singlebeam"
+
+	SurtypEnumADCP SurtypEnum = "ADCP"
+
+	SurtypEnumInspectionTour SurtypEnum = "InspectionTour"
+)
+
+type Error struct {
+	Detail string `xml:"detail,omitempty"`
+
+	Error_code *ErrorCode `xml:"error_code,omitempty"`
+}
+
+type ArrayOfMaterial struct {
+	Material []*MaterialEnum `xml:"Material,omitempty"`
+}
+
+type ArrayOfKeyValuePair struct {
+	KeyValuePair []*KeyValuePair `xml:"KeyValuePair,omitempty"`
+}
+
+type KeyValuePair struct {
+	Key string `xml:"Key,omitempty"`
+
+	Value string `xml:"Value,omitempty"`
+}
+
+type ArrayOfISRSPair struct {
+	ISRSPair []*ISRSPair `xml:"ISRSPair,omitempty"`
+}
+
+type ISRSPair struct {
+	FromISRS string `xml:"fromISRS,omitempty"`
+
+	ToISRS string `xml:"toISRS,omitempty"`
+}
+
+type RequestedPeriod struct {
+	Date_start time.Time `xml:"Date_start,omitempty"`
+
+	Date_end time.Time `xml:"Date_end,omitempty"`
+
+	Value_interval int32 `xml:"Value_interval,omitempty"`
+}
+
+type ArrayOfString struct {
+	String []string `xml:"http://www.ris.eu/wamos/common/3.0 string,omitempty"`
+}
+
+type Char int32
+
+type Duration *Duration
+
+type Guid string
+
+type IFairwayAvailabilityService interface {
+
+	// Error can be either of the following types:
+	//
+	//   - ErrorFault
+
+	Get_bottleneck_fa(request *Get_bottleneck_fa) (*Get_bottleneck_faResponse, error)
+
+	// Error can be either of the following types:
+	//
+	//   - ErrorFault
+
+	Get_stretch_fa(request *Get_stretch_fa) (*Get_stretch_faResponse, error)
+}
+
+type FairwayAvailabilityService struct {
+	client *soap.SOAPClient
+}
+
+func NewFairwayAvailabilityService(url string, tls bool, auth *soap.BasicAuth) *FairwayAvailabilityService {
+	if url == "" {
+		url = ""
+	}
+	client := soap.NewSOAPClient(url, tls, auth)
+	return &FairwayAvailabilityService{
+		client: client,
+	}
+}
+
+func NewFairwayAvailabilityServiceWithTLS(url string, tlsCfg *tls.Config, auth *soap.BasicAuth) *FairwayAvailabilityService {
+	if url == "" {
+		url = ""
+	}
+	client := soap.NewSOAPClientWithTLSConfig(url, tlsCfg, auth)
+
+	return &FairwayAvailabilityService{
+		client: client,
+	}
+}
+
+func (service *FairwayAvailabilityService) Get_bottleneck_fa(request *Get_bottleneck_fa) (*Get_bottleneck_faResponse, error) {
+	response := new(Get_bottleneck_faResponse)
+	err := service.client.Call("http://www.ris.eu/fairwayavailability/3.0/IFairwayAvailabilityService/get_bottleneck_fa", request, response)
+	if err != nil {
+		return nil, err
+	}
+
+	return response, nil
+}
+
+func (service *FairwayAvailabilityService) Get_stretch_fa(request *Get_stretch_fa) (*Get_stretch_faResponse, error) {
+	response := new(Get_stretch_faResponse)
+	err := service.client.Call("http://www.ris.eu/fairwayavailability/3.0/IFairwayAvailabilityService/get_stretch_fa", request, response)
+	if err != nil {
+		return nil, err
+	}
+
+	return response, nil
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/soap/nts/service.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,1578 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Raimund Renkert <raimund.renkert@intevation.de>
+
+package nts
+
+import (
+	"crypto/tls"
+	"encoding/xml"
+	"time"
+
+	"gemma.intevation.de/gemma/pkg/soap"
+)
+
+// against "unused imports"
+var _ time.Time
+var _ xml.Name
+
+type Message_type_type string
+
+const (
+	Message_type_typeFTM Message_type_type = "FTM"
+
+	Message_type_typeWRM Message_type_type = "WRM"
+
+	Message_type_typeICEM Message_type_type = "ICEM"
+
+	Message_type_typeWERM Message_type_type = "WERM"
+)
+
+type Error_code_type string
+
+type NonNegativeInteger uint
+
+type GYear uint
+
+type Duration string
+
+const (
+
+	// Description: message type not supported, Explanation: web service does not support the requested message type
+	Error_code_typeE010 Error_code_type = "e010"
+
+	// Description: paging parameters inconsistent with messages, Explanation: parameters for paging mechanism do not fit the available messages, e.g. Offset >= Total Count
+	Error_code_typeE030 Error_code_type = "e030"
+
+	// Description: syntax error in request, Explanation: request violates the schema for requests
+	Error_code_typeE100 Error_code_type = "e100"
+
+	// Description: incorrect message type, Explanation: given message type is not known
+	Error_code_typeE110 Error_code_type = "e110"
+
+	// Description: incorrect type-specific parameters, Explanation: type-specific parameters are erroneous
+	Error_code_typeE120 Error_code_type = "e120"
+
+	// Description: incorrect paging parameters, Explanation: given parameters for the paging mechanism are erroneous
+	Error_code_typeE130 Error_code_type = "e130"
+
+	// Description: operation not known, Explanation: the requested operation is unknown
+	Error_code_typeE200 Error_code_type = "e200"
+
+	// Description: data source unavailable, Explanation: data source of the web service for the NtS data is temporarily unavailable
+	Error_code_typeE300 Error_code_type = "e300"
+
+	// Description: too many results for request, Explanation: server is unable to handle number of results
+	Error_code_typeE310 Error_code_type = "e310"
+)
+
+type Get_messages_query struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts.ms/2.0.4.0 get_messages_query"`
+
+	Message_type *Message_type_type `xml:"http://www.ris.eu/nts.ms/2.0.4.0 message_type,omitempty"`
+
+	Ids []*Id_pair `xml:"ids,omitempty"`
+
+	Validity_period *Validity_period_type `xml:"validity_period,omitempty"`
+
+	Dates_issue []*Date_pair `xml:"dates_issue,omitempty"`
+
+	Paging_request *Paging_request_type `xml:"paging_request,omitempty"`
+}
+
+type Get_messages_result struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts.ms/2.0.4.0 get_messages_result"`
+
+	Result_message []*RIS_Message_Type `xml:"result_message,omitempty"`
+
+	Result_error []*Error_code_type `xml:"result_error,omitempty"`
+
+	Paging_result *Paging_result_type `xml:"paging_result,omitempty"`
+}
+
+type Id_pair struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts.ms/2.0.4.0 ids"`
+
+	Id *Isrs_code_type `xml:"http://www.ris.eu/nts.ms/2.0.4.0 id,omitempty"`
+}
+
+type Date_pair struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 date_pair"`
+
+	Date_start time.Time `xml:"date_start,omitempty"`
+
+	Date_end time.Time `xml:"date_end,omitempty"`
+}
+
+type Paging_request_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 paging_request"`
+
+	Offset *NonNegativeInteger `xml:"offset,omitempty"`
+
+	Limit *NonNegativeInteger `xml:"limit,omitempty"`
+
+	Total_count bool `xml:"total_count,omitempty"`
+}
+
+type Paging_result_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 paging_result"`
+
+	Offset *NonNegativeInteger `xml:"offset,omitempty"`
+
+	Count *NonNegativeInteger `xml:"count,omitempty"`
+
+	Total_count *NonNegativeInteger `xml:"total_count,omitempty"`
+}
+
+type Country_code_enum string
+
+const (
+	Country_code_enumAT Country_code_enum = "AT"
+
+	Country_code_enumBE Country_code_enum = "BE"
+
+	Country_code_enumBG Country_code_enum = "BG"
+
+	Country_code_enumCH Country_code_enum = "CH"
+
+	Country_code_enumCY Country_code_enum = "CY"
+
+	Country_code_enumCZ Country_code_enum = "CZ"
+
+	Country_code_enumDE Country_code_enum = "DE"
+
+	Country_code_enumDK Country_code_enum = "DK"
+
+	Country_code_enumEE Country_code_enum = "EE"
+
+	Country_code_enumES Country_code_enum = "ES"
+
+	Country_code_enumFI Country_code_enum = "FI"
+
+	Country_code_enumFR Country_code_enum = "FR"
+
+	Country_code_enumGB Country_code_enum = "GB"
+
+	Country_code_enumGR Country_code_enum = "GR"
+
+	Country_code_enumHR Country_code_enum = "HR"
+
+	Country_code_enumHU Country_code_enum = "HU"
+
+	Country_code_enumIE Country_code_enum = "IE"
+
+	Country_code_enumIT Country_code_enum = "IT"
+
+	Country_code_enumLT Country_code_enum = "LT"
+
+	Country_code_enumLU Country_code_enum = "LU"
+
+	Country_code_enumLV Country_code_enum = "LV"
+
+	Country_code_enumMD Country_code_enum = "MD"
+
+	Country_code_enumME Country_code_enum = "ME"
+
+	Country_code_enumMT Country_code_enum = "MT"
+
+	Country_code_enumNL Country_code_enum = "NL"
+
+	Country_code_enumPL Country_code_enum = "PL"
+
+	Country_code_enumPT Country_code_enum = "PT"
+
+	Country_code_enumRO Country_code_enum = "RO"
+
+	Country_code_enumRS Country_code_enum = "RS"
+
+	Country_code_enumSE Country_code_enum = "SE"
+
+	Country_code_enumSI Country_code_enum = "SI"
+
+	Country_code_enumSK Country_code_enum = "SK"
+
+	Country_code_enumRU Country_code_enum = "RU"
+
+	Country_code_enumUA Country_code_enum = "UA"
+)
+
+type Language_code_enum string
+
+const (
+	Language_code_enumDE Language_code_enum = "DE"
+
+	Language_code_enumEN Language_code_enum = "EN"
+
+	Language_code_enumFR Language_code_enum = "FR"
+
+	Language_code_enumNL Language_code_enum = "NL"
+
+	Language_code_enumSK Language_code_enum = "SK"
+
+	Language_code_enumHU Language_code_enum = "HU"
+
+	Language_code_enumHR Language_code_enum = "HR"
+
+	Language_code_enumSR Language_code_enum = "SR"
+
+	Language_code_enumBG Language_code_enum = "BG"
+
+	Language_code_enumRO Language_code_enum = "RO"
+
+	Language_code_enumRU Language_code_enum = "RU"
+
+	Language_code_enumCS Language_code_enum = "CS"
+
+	Language_code_enumPL Language_code_enum = "PL"
+
+	Language_code_enumPT Language_code_enum = "PT"
+
+	Language_code_enumES Language_code_enum = "ES"
+
+	Language_code_enumSV Language_code_enum = "SV"
+
+	Language_code_enumFI Language_code_enum = "FI"
+
+	Language_code_enumDA Language_code_enum = "DA"
+
+	Language_code_enumET Language_code_enum = "ET"
+
+	Language_code_enumLV Language_code_enum = "LV"
+
+	Language_code_enumLT Language_code_enum = "LT"
+
+	Language_code_enumIT Language_code_enum = "IT"
+
+	Language_code_enumMT Language_code_enum = "MT"
+
+	Language_code_enumEL Language_code_enum = "EL"
+
+	Language_code_enumSL Language_code_enum = "SL"
+)
+
+type Subject_code_enum string
+
+const (
+	Subject_code_enumANNOUN Subject_code_enum = "ANNOUN"
+
+	Subject_code_enumWARNIN Subject_code_enum = "WARNIN"
+
+	Subject_code_enumCANCEL Subject_code_enum = "CANCEL"
+
+	Subject_code_enumINFSER Subject_code_enum = "INFSER"
+
+	Subject_code_enumOBSTRU Subject_code_enum = "OBSTRU"
+
+	Subject_code_enumPAROBS Subject_code_enum = "PAROBS"
+
+	Subject_code_enumDELAY Subject_code_enum = "DELAY"
+
+	Subject_code_enumVESLEN Subject_code_enum = "VESLEN"
+
+	Subject_code_enumVESHEI Subject_code_enum = "VESHEI"
+
+	Subject_code_enumVESBRE Subject_code_enum = "VESBRE"
+
+	Subject_code_enumVESDRA Subject_code_enum = "VESDRA"
+
+	Subject_code_enumAVALEN Subject_code_enum = "AVALEN"
+
+	Subject_code_enumCLEHEI Subject_code_enum = "CLEHEI"
+
+	Subject_code_enumCLEWID Subject_code_enum = "CLEWID"
+
+	Subject_code_enumAVADEP Subject_code_enum = "AVADEP"
+
+	Subject_code_enumNOMOOR Subject_code_enum = "NOMOOR"
+
+	Subject_code_enumSERVIC Subject_code_enum = "SERVIC"
+
+	Subject_code_enumNOSERV Subject_code_enum = "NOSERV"
+
+	Subject_code_enumSPEED Subject_code_enum = "SPEED"
+
+	Subject_code_enumWAVWAS Subject_code_enum = "WAVWAS"
+
+	Subject_code_enumPASSIN Subject_code_enum = "PASSIN"
+
+	Subject_code_enumANCHOR Subject_code_enum = "ANCHOR"
+
+	Subject_code_enumOVRTAK Subject_code_enum = "OVRTAK"
+
+	Subject_code_enumMINPWR Subject_code_enum = "MINPWR"
+
+	Subject_code_enumDREDGE Subject_code_enum = "DREDGE"
+
+	Subject_code_enumWORK Subject_code_enum = "WORK"
+
+	Subject_code_enumEVENT Subject_code_enum = "EVENT"
+
+	Subject_code_enumCHGMAR Subject_code_enum = "CHGMAR"
+
+	Subject_code_enumCHGSER Subject_code_enum = "CHGSER"
+
+	Subject_code_enumSPCMAR Subject_code_enum = "SPCMAR"
+
+	Subject_code_enumEXERC Subject_code_enum = "EXERC"
+
+	Subject_code_enumLEADEP Subject_code_enum = "LEADEP"
+
+	Subject_code_enumLEVDEC Subject_code_enum = "LEVDEC"
+
+	Subject_code_enumLEVRIS Subject_code_enum = "LEVRIS"
+
+	Subject_code_enumLIMITA Subject_code_enum = "LIMITA"
+
+	Subject_code_enumMISECH Subject_code_enum = "MISECH"
+
+	Subject_code_enumECDISU Subject_code_enum = "ECDISU"
+
+	Subject_code_enumNEWOBJ Subject_code_enum = "NEWOBJ"
+
+	Subject_code_enumCHWWY Subject_code_enum = "CHWWY"
+
+	Subject_code_enumCONWWY Subject_code_enum = "CONWWY"
+
+	Subject_code_enumDIVER Subject_code_enum = "DIVER"
+
+	Subject_code_enumSPECTR Subject_code_enum = "SPECTR"
+
+	Subject_code_enumLOCRUL Subject_code_enum = "LOCRUL"
+
+	Subject_code_enumVHFCOV Subject_code_enum = "VHFCOV"
+
+	Subject_code_enumHIGVOL Subject_code_enum = "HIGVOL"
+
+	Subject_code_enumTURNIN Subject_code_enum = "TURNIN"
+
+	Subject_code_enumCONBRE Subject_code_enum = "CONBRE"
+
+	Subject_code_enumCONLEN Subject_code_enum = "CONLEN"
+
+	Subject_code_enumREMOBJ Subject_code_enum = "REMOBJ"
+)
+
+type Reason_code_enum string
+
+const (
+	Reason_code_enumEVENT Reason_code_enum = "EVENT"
+
+	Reason_code_enumWORK Reason_code_enum = "WORK"
+
+	Reason_code_enumDREDGE Reason_code_enum = "DREDGE"
+
+	Reason_code_enumEXERC Reason_code_enum = "EXERC"
+
+	Reason_code_enumHIGWAT Reason_code_enum = "HIGWAT"
+
+	Reason_code_enumHIWAI Reason_code_enum = "HIWAI"
+
+	Reason_code_enumHIWAII Reason_code_enum = "HIWAII"
+
+	Reason_code_enumLOWWAT Reason_code_enum = "LOWWAT"
+
+	Reason_code_enumSHALLO Reason_code_enum = "SHALLO"
+
+	Reason_code_enumCALAMI Reason_code_enum = "CALAMI"
+
+	Reason_code_enumLAUNCH Reason_code_enum = "LAUNCH"
+
+	Reason_code_enumDECLEV Reason_code_enum = "DECLEV"
+
+	Reason_code_enumFLOMEA Reason_code_enum = "FLOMEA"
+
+	Reason_code_enumBLDWRK Reason_code_enum = "BLDWRK"
+
+	Reason_code_enumREPAIR Reason_code_enum = "REPAIR"
+
+	Reason_code_enumINSPEC Reason_code_enum = "INSPEC"
+
+	Reason_code_enumFIRWRK Reason_code_enum = "FIRWRK"
+
+	Reason_code_enumLIMITA Reason_code_enum = "LIMITA"
+
+	Reason_code_enumCHGFWY Reason_code_enum = "CHGFWY"
+
+	Reason_code_enumCONSTR Reason_code_enum = "CONSTR"
+
+	Reason_code_enumDIVING Reason_code_enum = "DIVING"
+
+	Reason_code_enumSPECTR Reason_code_enum = "SPECTR"
+
+	Reason_code_enumEXT Reason_code_enum = "EXT"
+
+	Reason_code_enumMIN Reason_code_enum = "MIN"
+
+	Reason_code_enumSOUND Reason_code_enum = "SOUND"
+
+	Reason_code_enumOTHER Reason_code_enum = "OTHER"
+
+	Reason_code_enumSTRIKE Reason_code_enum = "STRIKE"
+
+	Reason_code_enumFLOMAT Reason_code_enum = "FLOMAT"
+
+	Reason_code_enumEXPLOS Reason_code_enum = "EXPLOS"
+
+	Reason_code_enumICE Reason_code_enum = "ICE"
+
+	Reason_code_enumOBSTAC Reason_code_enum = "OBSTAC"
+
+	Reason_code_enumCHGMAR Reason_code_enum = "CHGMAR"
+
+	Reason_code_enumDAMMAR Reason_code_enum = "DAMMAR"
+
+	Reason_code_enumFALMAT Reason_code_enum = "FALMAT"
+
+	Reason_code_enumMISECH Reason_code_enum = "MISECH"
+
+	Reason_code_enumHEARIS Reason_code_enum = "HEARIS"
+
+	Reason_code_enumHIGVOL Reason_code_enum = "HIGVOL"
+
+	Reason_code_enumECDISU Reason_code_enum = "ECDISU"
+
+	Reason_code_enumLOCRUL Reason_code_enum = "LOCRUL"
+
+	Reason_code_enumNEWOBJ Reason_code_enum = "NEWOBJ"
+
+	Reason_code_enumOBUNWA Reason_code_enum = "OBUNWA"
+
+	Reason_code_enumVHFCOV Reason_code_enum = "VHFCOV"
+
+	Reason_code_enumREMOBJ Reason_code_enum = "REMOBJ"
+
+	Reason_code_enumLEVRIS Reason_code_enum = "LEVRIS"
+
+	Reason_code_enumSPCMAR Reason_code_enum = "SPCMAR"
+
+	Reason_code_enumWERMCO Reason_code_enum = "WERMCO"
+
+	Reason_code_enumINFSER Reason_code_enum = "INFSER"
+)
+
+type Reporting_code_enum string
+
+const (
+	Reporting_code_enumINF Reporting_code_enum = "INF"
+
+	Reporting_code_enumADD Reporting_code_enum = "ADD"
+
+	Reporting_code_enumREG Reporting_code_enum = "REG"
+)
+
+type Communication_code_enum string
+
+const (
+	Communication_code_enumTE Communication_code_enum = "TE"
+
+	Communication_code_enumAP Communication_code_enum = "AP"
+
+	Communication_code_enumEM Communication_code_enum = "EM"
+
+	Communication_code_enumAH Communication_code_enum = "AH"
+
+	Communication_code_enumTT Communication_code_enum = "TT"
+
+	Communication_code_enumFX Communication_code_enum = "FX"
+
+	Communication_code_enumLS Communication_code_enum = "LS"
+
+	Communication_code_enumFS Communication_code_enum = "FS"
+
+	Communication_code_enumSO Communication_code_enum = "SO"
+
+	Communication_code_enumEI Communication_code_enum = "EI"
+)
+
+type Measure_code_enum string
+
+const (
+	Measure_code_enumDIS Measure_code_enum = "DIS"
+
+	Measure_code_enumREG Measure_code_enum = "REG"
+
+	Measure_code_enumBAR Measure_code_enum = "BAR"
+
+	Measure_code_enumVER Measure_code_enum = "VER"
+
+	Measure_code_enumLSD Measure_code_enum = "LSD"
+
+	Measure_code_enumWAL Measure_code_enum = "WAL"
+
+	Measure_code_enumNOM Measure_code_enum = "NOM"
+)
+
+type Barrage_code_enum string
+
+const (
+	Barrage_code_enumCLD Barrage_code_enum = "CLD"
+
+	Barrage_code_enumOPG Barrage_code_enum = "OPG"
+
+	Barrage_code_enumCLG Barrage_code_enum = "CLG"
+
+	Barrage_code_enumOPD Barrage_code_enum = "OPD"
+
+	Barrage_code_enumOPN Barrage_code_enum = "OPN"
+)
+
+type Regime_code_enum string
+
+const (
+	Regime_code_enumNO Regime_code_enum = "NO"
+
+	Regime_code_enumHI Regime_code_enum = "HI"
+
+	Regime_code_enumII Regime_code_enum = "II"
+
+	Regime_code_enumI Regime_code_enum = "I"
+
+	Regime_code_enumNN Regime_code_enum = "NN"
+
+	Regime_code_enumLO Regime_code_enum = "LO"
+)
+
+type Ice_condition_code_enum string
+
+const (
+	Ice_condition_code_enumA Ice_condition_code_enum = "A"
+
+	Ice_condition_code_enumB Ice_condition_code_enum = "B"
+
+	Ice_condition_code_enumC Ice_condition_code_enum = "C"
+
+	Ice_condition_code_enumD Ice_condition_code_enum = "D"
+
+	Ice_condition_code_enumE Ice_condition_code_enum = "E"
+
+	Ice_condition_code_enumF Ice_condition_code_enum = "F"
+
+	Ice_condition_code_enumG Ice_condition_code_enum = "G"
+
+	Ice_condition_code_enumH Ice_condition_code_enum = "H"
+
+	Ice_condition_code_enumK Ice_condition_code_enum = "K"
+
+	Ice_condition_code_enumL Ice_condition_code_enum = "L"
+
+	Ice_condition_code_enumM Ice_condition_code_enum = "M"
+
+	Ice_condition_code_enumP Ice_condition_code_enum = "P"
+
+	Ice_condition_code_enumR Ice_condition_code_enum = "R"
+
+	Ice_condition_code_enumS Ice_condition_code_enum = "S"
+
+	Ice_condition_code_enumU Ice_condition_code_enum = "U"
+
+	Ice_condition_code_enumO Ice_condition_code_enum = "O"
+
+	Ice_condition_code_enumV Ice_condition_code_enum = "V"
+)
+
+type Ice_accessibility_code_enum string
+
+const (
+	Ice_accessibility_code_enumA Ice_accessibility_code_enum = "A"
+
+	Ice_accessibility_code_enumB Ice_accessibility_code_enum = "B"
+
+	Ice_accessibility_code_enumF Ice_accessibility_code_enum = "F"
+
+	Ice_accessibility_code_enumL Ice_accessibility_code_enum = "L"
+
+	Ice_accessibility_code_enumC Ice_accessibility_code_enum = "C"
+
+	Ice_accessibility_code_enumD Ice_accessibility_code_enum = "D"
+
+	Ice_accessibility_code_enumE Ice_accessibility_code_enum = "E"
+
+	Ice_accessibility_code_enumG Ice_accessibility_code_enum = "G"
+
+	Ice_accessibility_code_enumH Ice_accessibility_code_enum = "H"
+
+	Ice_accessibility_code_enumM Ice_accessibility_code_enum = "M"
+
+	Ice_accessibility_code_enumK Ice_accessibility_code_enum = "K"
+
+	Ice_accessibility_code_enumT Ice_accessibility_code_enum = "T"
+
+	Ice_accessibility_code_enumP Ice_accessibility_code_enum = "P"
+
+	Ice_accessibility_code_enumV Ice_accessibility_code_enum = "V"
+
+	Ice_accessibility_code_enumX Ice_accessibility_code_enum = "X"
+)
+
+type Ice_classification_code_enum string
+
+const (
+	Ice_classification_code_enumA Ice_classification_code_enum = "A"
+
+	Ice_classification_code_enumB Ice_classification_code_enum = "B"
+
+	Ice_classification_code_enumC Ice_classification_code_enum = "C"
+
+	Ice_classification_code_enumD Ice_classification_code_enum = "D"
+
+	Ice_classification_code_enumE Ice_classification_code_enum = "E"
+)
+
+type Ice_situation_code_enum string
+
+const (
+	Ice_situation_code_enumNOL Ice_situation_code_enum = "NOL"
+
+	Ice_situation_code_enumLIM Ice_situation_code_enum = "LIM"
+
+	Ice_situation_code_enumNON Ice_situation_code_enum = "NON"
+)
+
+type Weather_class_code_enum string
+
+const (
+	Weather_class_code_enumCLR Weather_class_code_enum = "CLR"
+
+	Weather_class_code_enumCLDY Weather_class_code_enum = "CLDY"
+
+	Weather_class_code_enumOCST Weather_class_code_enum = "OCST"
+
+	Weather_class_code_enumDZZL Weather_class_code_enum = "DZZL"
+
+	Weather_class_code_enumRAIN Weather_class_code_enum = "RAIN"
+
+	Weather_class_code_enumLRAIN Weather_class_code_enum = "LRAIN"
+
+	Weather_class_code_enumORAIN Weather_class_code_enum = "ORAIN"
+
+	Weather_class_code_enumHRAIN Weather_class_code_enum = "HRAIN"
+
+	Weather_class_code_enumSLEET Weather_class_code_enum = "SLEET"
+
+	Weather_class_code_enumSNOW Weather_class_code_enum = "SNOW"
+
+	Weather_class_code_enumSNFALL Weather_class_code_enum = "SNFALL"
+
+	Weather_class_code_enumHAIL Weather_class_code_enum = "HAIL"
+
+	Weather_class_code_enumSHWRS Weather_class_code_enum = "SHWRS"
+
+	Weather_class_code_enumTHSTRM Weather_class_code_enum = "THSTRM"
+
+	Weather_class_code_enumHAZY Weather_class_code_enum = "HAZY"
+
+	Weather_class_code_enumFOG Weather_class_code_enum = "FOG"
+
+	Weather_class_code_enumFOGPAT Weather_class_code_enum = "FOGPAT"
+
+	Weather_class_code_enumGALE Weather_class_code_enum = "GALE"
+
+	Weather_class_code_enumSTRM Weather_class_code_enum = "STRM"
+
+	Weather_class_code_enumHURRC Weather_class_code_enum = "HURRC"
+
+	Weather_class_code_enumFZRA Weather_class_code_enum = "FZRA"
+)
+
+type Weather_item_code_enum string
+
+const (
+	Weather_item_code_enumWI Weather_item_code_enum = "WI"
+
+	Weather_item_code_enumWA Weather_item_code_enum = "WA"
+
+	Weather_item_code_enumFG Weather_item_code_enum = "FG"
+
+	Weather_item_code_enumRN Weather_item_code_enum = "RN"
+
+	Weather_item_code_enumSN Weather_item_code_enum = "SN"
+
+	Weather_item_code_enumAT Weather_item_code_enum = "AT"
+
+	Weather_item_code_enumWT Weather_item_code_enum = "WT"
+)
+
+type Weather_category_code_enum string
+
+const (
+	Weather_category_code_enum0 Weather_category_code_enum = "0"
+
+	Weather_category_code_enum1 Weather_category_code_enum = "1"
+
+	Weather_category_code_enum2 Weather_category_code_enum = "2"
+
+	Weather_category_code_enum3 Weather_category_code_enum = "3"
+
+	Weather_category_code_enum4 Weather_category_code_enum = "4"
+
+	Weather_category_code_enum5 Weather_category_code_enum = "5"
+
+	Weather_category_code_enum6 Weather_category_code_enum = "6"
+
+	Weather_category_code_enum7 Weather_category_code_enum = "7"
+
+	Weather_category_code_enum8 Weather_category_code_enum = "8"
+
+	Weather_category_code_enum9 Weather_category_code_enum = "9"
+
+	Weather_category_code_enum10 Weather_category_code_enum = "10"
+
+	Weather_category_code_enum11 Weather_category_code_enum = "11"
+
+	Weather_category_code_enum12 Weather_category_code_enum = "12"
+
+	Weather_category_code_enum13 Weather_category_code_enum = "13"
+
+	Weather_category_code_enum14 Weather_category_code_enum = "14"
+
+	Weather_category_code_enum15 Weather_category_code_enum = "15"
+
+	Weather_category_code_enum16 Weather_category_code_enum = "16"
+
+	Weather_category_code_enum17 Weather_category_code_enum = "17"
+
+	Weather_category_code_enum18 Weather_category_code_enum = "18"
+
+	Weather_category_code_enum19 Weather_category_code_enum = "19"
+
+	Weather_category_code_enum20 Weather_category_code_enum = "20"
+
+	Weather_category_code_enum21 Weather_category_code_enum = "21"
+
+	Weather_category_code_enum22 Weather_category_code_enum = "22"
+)
+
+type Weather_direction_code_enum string
+
+const (
+	Weather_direction_code_enumN Weather_direction_code_enum = "N"
+
+	Weather_direction_code_enumNE Weather_direction_code_enum = "NE"
+
+	Weather_direction_code_enumE Weather_direction_code_enum = "E"
+
+	Weather_direction_code_enumSE Weather_direction_code_enum = "SE"
+
+	Weather_direction_code_enumS Weather_direction_code_enum = "S"
+
+	Weather_direction_code_enumSW Weather_direction_code_enum = "SW"
+
+	Weather_direction_code_enumW Weather_direction_code_enum = "W"
+
+	Weather_direction_code_enumNW Weather_direction_code_enum = "NW"
+
+	Weather_direction_code_enumWRB Weather_direction_code_enum = "WRB"
+)
+
+// Internal ID - best practice: global unique identifier
+type Internal_id_type string
+
+// ISRS location code, unique identification of the geo object as defined in RIS Index encoding guide
+type Isrs_code_type string
+
+type Type_code_enum string
+
+const (
+	Type_code_enumRIV Type_code_enum = "RIV"
+
+	Type_code_enumCAN Type_code_enum = "CAN"
+
+	Type_code_enumLAK Type_code_enum = "LAK"
+
+	Type_code_enumFWY Type_code_enum = "FWY"
+
+	Type_code_enumLCK Type_code_enum = "LCK"
+
+	Type_code_enumBRI Type_code_enum = "BRI"
+
+	Type_code_enumRMP Type_code_enum = "RMP"
+
+	Type_code_enumBAR Type_code_enum = "BAR"
+
+	Type_code_enumBNK Type_code_enum = "BNK"
+
+	Type_code_enumGAU Type_code_enum = "GAU"
+
+	Type_code_enumBUO Type_code_enum = "BUO"
+
+	Type_code_enumBEA Type_code_enum = "BEA"
+
+	Type_code_enumANC Type_code_enum = "ANC"
+
+	Type_code_enumBER Type_code_enum = "BER"
+
+	Type_code_enumMOO Type_code_enum = "MOO"
+
+	Type_code_enumTER Type_code_enum = "TER"
+
+	Type_code_enumHAR Type_code_enum = "HAR"
+
+	Type_code_enumFDO Type_code_enum = "FDO"
+
+	Type_code_enumCAB Type_code_enum = "CAB"
+
+	Type_code_enumFER Type_code_enum = "FER"
+
+	Type_code_enumPIP Type_code_enum = "PIP"
+
+	Type_code_enumPPO Type_code_enum = "PPO"
+
+	Type_code_enumHFA Type_code_enum = "HFA"
+
+	Type_code_enumHMO Type_code_enum = "HMO"
+
+	Type_code_enumSHY Type_code_enum = "SHY"
+
+	Type_code_enumREF Type_code_enum = "REF"
+
+	Type_code_enumMAR Type_code_enum = "MAR"
+
+	Type_code_enumLIG Type_code_enum = "LIG"
+
+	Type_code_enumSIG Type_code_enum = "SIG"
+
+	Type_code_enumTUR Type_code_enum = "TUR"
+
+	Type_code_enumCBR Type_code_enum = "CBR"
+
+	Type_code_enumTUN Type_code_enum = "TUN"
+
+	Type_code_enumBCO Type_code_enum = "BCO"
+
+	Type_code_enumREP Type_code_enum = "REP"
+
+	Type_code_enumFLO Type_code_enum = "FLO"
+
+	Type_code_enumSLI Type_code_enum = "SLI"
+
+	Type_code_enumDUK Type_code_enum = "DUK"
+
+	Type_code_enumVTC Type_code_enum = "VTC"
+
+	Type_code_enumRES Type_code_enum = "RES"
+
+	Type_code_enumLKB Type_code_enum = "LKB"
+
+	Type_code_enumBRO Type_code_enum = "BRO"
+
+	Type_code_enumBNS Type_code_enum = "BNS"
+)
+
+type Interval_code_enum string
+
+const (
+	Interval_code_enumCON Interval_code_enum = "CON"
+
+	Interval_code_enumDAY Interval_code_enum = "DAY"
+
+	Interval_code_enumWRK Interval_code_enum = "WRK"
+
+	Interval_code_enumWKN Interval_code_enum = "WKN"
+
+	Interval_code_enumSUN Interval_code_enum = "SUN"
+
+	Interval_code_enumMON Interval_code_enum = "MON"
+
+	Interval_code_enumTUE Interval_code_enum = "TUE"
+
+	Interval_code_enumWED Interval_code_enum = "WED"
+
+	Interval_code_enumTHU Interval_code_enum = "THU"
+
+	Interval_code_enumFRI Interval_code_enum = "FRI"
+
+	Interval_code_enumSAT Interval_code_enum = "SAT"
+
+	Interval_code_enumDTI Interval_code_enum = "DTI"
+
+	Interval_code_enumNTI Interval_code_enum = "NTI"
+
+	Interval_code_enumRVI Interval_code_enum = "RVI"
+
+	Interval_code_enumEXC Interval_code_enum = "EXC"
+
+	Interval_code_enumWRD Interval_code_enum = "WRD"
+)
+
+type Limitation_code_enum string
+
+const (
+	Limitation_code_enumOBSTRU Limitation_code_enum = "OBSTRU"
+
+	Limitation_code_enumPAROBS Limitation_code_enum = "PAROBS"
+
+	Limitation_code_enumDELAY Limitation_code_enum = "DELAY"
+
+	Limitation_code_enumVESLEN Limitation_code_enum = "VESLEN"
+
+	Limitation_code_enumVESHEI Limitation_code_enum = "VESHEI"
+
+	Limitation_code_enumVESBRE Limitation_code_enum = "VESBRE"
+
+	Limitation_code_enumVESDRA Limitation_code_enum = "VESDRA"
+
+	Limitation_code_enumAVALEN Limitation_code_enum = "AVALEN"
+
+	Limitation_code_enumCLEHEI Limitation_code_enum = "CLEHEI"
+
+	Limitation_code_enumCLEWID Limitation_code_enum = "CLEWID"
+
+	Limitation_code_enumAVADEP Limitation_code_enum = "AVADEP"
+
+	Limitation_code_enumNOMOOR Limitation_code_enum = "NOMOOR"
+
+	Limitation_code_enumSERVIC Limitation_code_enum = "SERVIC"
+
+	Limitation_code_enumNOSERV Limitation_code_enum = "NOSERV"
+
+	Limitation_code_enumSPEED Limitation_code_enum = "SPEED"
+
+	Limitation_code_enumWAVWAS Limitation_code_enum = "WAVWAS"
+
+	Limitation_code_enumPASSIN Limitation_code_enum = "PASSIN"
+
+	Limitation_code_enumANCHOR Limitation_code_enum = "ANCHOR"
+
+	Limitation_code_enumOVRTAK Limitation_code_enum = "OVRTAK"
+
+	Limitation_code_enumMINPWR Limitation_code_enum = "MINPWR"
+
+	Limitation_code_enumALTER Limitation_code_enum = "ALTER"
+
+	Limitation_code_enumCAUTIO Limitation_code_enum = "CAUTIO"
+
+	Limitation_code_enumNOLIM Limitation_code_enum = "NOLIM"
+
+	Limitation_code_enumTURNIN Limitation_code_enum = "TURNIN"
+
+	Limitation_code_enumNOSHORE Limitation_code_enum = "NOSHORE"
+
+	Limitation_code_enumCONBRE Limitation_code_enum = "CONBRE"
+
+	Limitation_code_enumCONLEN Limitation_code_enum = "CONLEN"
+
+	Limitation_code_enumLEADEP Limitation_code_enum = "LEADEP"
+
+	Limitation_code_enumNOBERT Limitation_code_enum = "NOBERT"
+)
+
+type Position_code_enum string
+
+const (
+	Position_code_enumAL Position_code_enum = "AL"
+
+	Position_code_enumLE Position_code_enum = "LE"
+
+	Position_code_enumMI Position_code_enum = "MI"
+
+	Position_code_enumRI Position_code_enum = "RI"
+
+	Position_code_enumLB Position_code_enum = "LB"
+
+	Position_code_enumRB Position_code_enum = "RB"
+
+	Position_code_enumN Position_code_enum = "N"
+
+	Position_code_enumNE Position_code_enum = "NE"
+
+	Position_code_enumE Position_code_enum = "E"
+
+	Position_code_enumSE Position_code_enum = "SE"
+
+	Position_code_enumS Position_code_enum = "S"
+
+	Position_code_enumSW Position_code_enum = "SW"
+
+	Position_code_enumW Position_code_enum = "W"
+
+	Position_code_enumNW Position_code_enum = "NW"
+
+	Position_code_enumBI Position_code_enum = "BI"
+
+	Position_code_enumSM Position_code_enum = "SM"
+
+	Position_code_enumOL Position_code_enum = "OL"
+
+	Position_code_enumEW Position_code_enum = "EW"
+
+	Position_code_enumMP Position_code_enum = "MP"
+
+	Position_code_enumFP Position_code_enum = "FP"
+
+	Position_code_enumVA Position_code_enum = "VA"
+
+	Position_code_enumRY Position_code_enum = "RY"
+
+	Position_code_enumGY Position_code_enum = "GY"
+)
+
+type Reference_code_enum string
+
+const (
+	Reference_code_enumNAP Reference_code_enum = "NAP"
+
+	Reference_code_enumKP Reference_code_enum = "KP"
+
+	Reference_code_enumFZP Reference_code_enum = "FZP"
+
+	Reference_code_enumADR Reference_code_enum = "ADR"
+
+	Reference_code_enumTAW Reference_code_enum = "TAW"
+
+	Reference_code_enumPUL Reference_code_enum = "PUL"
+
+	Reference_code_enumNGM Reference_code_enum = "NGM"
+
+	Reference_code_enumETRS Reference_code_enum = "ETRS"
+
+	Reference_code_enumPOT Reference_code_enum = "POT"
+
+	Reference_code_enumLDC Reference_code_enum = "LDC"
+
+	Reference_code_enumHDC Reference_code_enum = "HDC"
+
+	Reference_code_enumZPG Reference_code_enum = "ZPG"
+
+	Reference_code_enumGLW Reference_code_enum = "GLW"
+
+	Reference_code_enumHSW Reference_code_enum = "HSW"
+
+	Reference_code_enumLNW Reference_code_enum = "LNW"
+
+	Reference_code_enumHNW Reference_code_enum = "HNW"
+
+	Reference_code_enumIGN Reference_code_enum = "IGN"
+
+	Reference_code_enumWGS Reference_code_enum = "WGS"
+
+	Reference_code_enumRN Reference_code_enum = "RN"
+
+	Reference_code_enumHBO Reference_code_enum = "HBO"
+)
+
+type Indication_code_enum string
+
+const (
+	Indication_code_enumMAX Indication_code_enum = "MAX"
+
+	Indication_code_enumMIN Indication_code_enum = "MIN"
+
+	Indication_code_enumRED Indication_code_enum = "RED"
+)
+
+type Target_group_code_enum string
+
+const (
+	Target_group_code_enumALL Target_group_code_enum = "ALL"
+
+	Target_group_code_enumCDG Target_group_code_enum = "CDG"
+
+	Target_group_code_enumCOM Target_group_code_enum = "COM"
+
+	Target_group_code_enumPAX Target_group_code_enum = "PAX"
+
+	Target_group_code_enumPLE Target_group_code_enum = "PLE"
+
+	Target_group_code_enumCNV Target_group_code_enum = "CNV"
+
+	Target_group_code_enumPUS Target_group_code_enum = "PUS"
+
+	Target_group_code_enumNNU Target_group_code_enum = "NNU"
+
+	Target_group_code_enumLOA Target_group_code_enum = "LOA"
+
+	Target_group_code_enumSMA Target_group_code_enum = "SMA"
+
+	Target_group_code_enumCND Target_group_code_enum = "CND"
+
+	Target_group_code_enumWOC Target_group_code_enum = "WOC"
+
+	Target_group_code_enumMOV Target_group_code_enum = "MOV"
+
+	Target_group_code_enumNMV Target_group_code_enum = "NMV"
+)
+
+type Direction_code_enum string
+
+const (
+	Direction_code_enumALL Direction_code_enum = "ALL"
+
+	Direction_code_enumUPS Direction_code_enum = "UPS"
+
+	Direction_code_enumDWN Direction_code_enum = "DWN"
+)
+
+type Unit_enum string
+
+const (
+	Unit_enumCm Unit_enum = "cm"
+
+	Unit_enumM3s Unit_enum = "m³/s"
+
+	Unit_enumH Unit_enum = "h"
+
+	Unit_enumKmh Unit_enum = "km/h"
+
+	Unit_enumKW Unit_enum = "kW"
+
+	Unit_enumMs Unit_enum = "m/s"
+
+	Unit_enumMmh Unit_enum = "mm/h"
+
+	Unit_enumC Unit_enum = "°C"
+)
+
+type RIS_Message_Type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts.ms/2.0.4.0 result_message"`
+
+	// Identification section
+	Identification *Identification_type `xml:"identification,omitempty"`
+
+	// Fairway and traffic related section
+	Ftm []*Ftm_type `xml:"ftm,omitempty"`
+
+	// Water related section
+	Wrm []*Wrm_type `xml:"wrm,omitempty"`
+
+	// Ice related section
+	Icem []*Icem_type `xml:"icem,omitempty"`
+
+	// Weather related section
+	Werm []*Werm_type `xml:"werm,omitempty"`
+}
+
+type Identification_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 identification"`
+
+	// Internal ID
+	Internal_id *Internal_id_type `xml:"internal_id,omitempty"`
+
+	// Sender (System) of the message
+	From string `xml:"from,omitempty"`
+
+	// Originator (initiator) of the information in this message
+	Originator string `xml:"originator,omitempty"`
+
+	// Country where message is valid
+	Country_code *Country_code_enum `xml:"country_code,omitempty"`
+
+	// Original language used in the textual info. (contents)
+	Language_code *Language_code_enum `xml:"language_code,omitempty"`
+
+	// District / Region within the specified country, where the message is applicable
+	District string `xml:"district,omitempty"`
+
+	// Date and time of publication including time zone
+	Date_issue time.Time `xml:"date_issue,omitempty"`
+}
+
+type Ftm_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 ftm_type"`
+
+	// Internal ID
+	Internal_id *Internal_id_type `xml:"internal_id,omitempty"`
+
+	// NtS Number
+	Nts_number *Nts_number_type `xml:"nts_number,omitempty"`
+
+	// Target group information
+	Target_group []*Target_group_type `xml:"target_group,omitempty"`
+
+	// Subject code must contain one of the following: Announcement (ANNOUN), Warning (WARNIN), Notice withdrawn (CANCEL) or Information service (INFSER). More information on the use of codes can be found in the NtS Encoding Guide.
+	Subject_code *Subject_code_enum `xml:"subject_code,omitempty"`
+
+	// Overall period of validity
+	Validity_period *Validity_period_type `xml:"validity_period,omitempty"`
+
+	// Additional information in local language
+	Contents string `xml:"contents,omitempty"`
+
+	// Notice source (name of authority)
+	Source string `xml:"source,omitempty"`
+
+	// Reason / justification of the notice
+	Reason_code *Reason_code_enum `xml:"reason_code,omitempty"`
+
+	// Communication channel information
+	Communication []*Communication_type `xml:"communication,omitempty"`
+
+	// Fairway section
+	Fairway_section *Fairway_section_type `xml:"fairway_section,omitempty"`
+
+	// Object section
+	Object *Object_type `xml:"object,omitempty"`
+}
+
+type Communication_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 communication_type"`
+
+	// Reporting regime (information, or duty to report)
+	Reporting_code *Reporting_code_enum `xml:"reporting_code,omitempty"`
+
+	// Communication code (telephone, VHF etc.)
+	Communication_code *Communication_code_enum `xml:"communication_code,omitempty"`
+
+	// Telephone, VHF number (including callsign), e-mail address, URL or teletext
+	Number string `xml:"number,omitempty"`
+
+	// Name of the attachment or additional information
+	Label string `xml:"label,omitempty"`
+
+	// Additional remarks concerning the communication
+	Remark string `xml:"remark,omitempty"`
+}
+
+type Object_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 object_type"`
+
+	// Geo Information of object
+	Geo_object *Geo_object_type `xml:"geo_object,omitempty"`
+
+	// Object limitation section
+	Limitation []*Limitation_type `xml:"limitation,omitempty"`
+}
+
+type Wrm_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 wrm"`
+
+	// Internal ID
+	Internal_id *Internal_id_type `xml:"internal_id,omitempty"`
+
+	// NtS Number
+	Nts_number *Nts_number_type `xml:"nts_number,omitempty"`
+
+	// Overall period of validity
+	Validity_period *Validity_period_type `xml:"validity_period,omitempty"`
+
+	// Object section
+	Geo_object *Geo_object_type `xml:"geo_object,omitempty"`
+
+	// Value reference (measurement reference)
+	Reference_code *Reference_code_enum `xml:"reference_code,omitempty"`
+
+	// Measurements (normal or predicted values)
+	Measure []*Measure_type `xml:"measure,omitempty"`
+}
+
+type Measure_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 measure"`
+
+	// Predicted measurement (1 or true) or real measurement (0 or false)
+	Predicted bool `xml:"predicted,omitempty"`
+
+	// Kind of water related information
+	Measure_code *Measure_code_enum `xml:"measure_code,omitempty"`
+
+	// Measured or predicted value
+	Value float32 `xml:"value,omitempty"`
+
+	// Lowest value of confidence interval
+	Value_min float32 `xml:"value_min,omitempty"`
+
+	// Highest value of confidence interval
+	Value_max float32 `xml:"value_max,omitempty"`
+
+	// Unit of the water related value
+	Unit *Unit_enum `xml:"unit,omitempty"`
+
+	// Barrage status
+	Barrage_code *Barrage_code_enum `xml:"barrage_code,omitempty"`
+
+	// Regime applicable
+	Regime_code *Regime_code_enum `xml:"regime_code,omitempty"`
+
+	// Date and Time of measurement or predicted value including time zone
+	Measuredate time.Time `xml:"measuredate,omitempty"`
+
+	// Difference with comparative value
+	Difference *Difference_type `xml:"difference,omitempty"`
+}
+
+type Difference_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 difference"`
+
+	// Difference with comparative value
+	Value_difference float32 `xml:"value_difference,omitempty"`
+
+	// Time difference with measuredata of comparative measurement
+	Time_difference *Duration `xml:"time_difference,omitempty"`
+}
+
+type Icem_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 icem_type"`
+
+	// Internal ID
+	Internal_id *Internal_id_type `xml:"internal_id,omitempty"`
+
+	// NtS Number
+	Nts_number *Nts_number_type `xml:"nts_number,omitempty"`
+
+	// Overall period of validity
+	Validity_period *Validity_period_type `xml:"validity_period,omitempty"`
+
+	// Fairway section - the limitation inside the fairway section cannot be used in the ICEM
+	Fairway_section *Fairway_section_type `xml:"fairway_section,omitempty"`
+
+	// Ice conditions
+	Ice_condition []*Ice_condition_type `xml:"ice_condition,omitempty"`
+}
+
+type Ice_condition_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 ice_condition_type"`
+
+	// Date and Time of measurement or prediction including time zone
+	Measuredate time.Time `xml:"measuredate,omitempty"`
+
+	// Condition code
+	Ice_condition_code *Ice_condition_code_enum `xml:"ice_condition_code,omitempty"`
+
+	// Accessibility code
+	Ice_accessibility_code *Ice_accessibility_code_enum `xml:"ice_accessibility_code,omitempty"`
+
+	// Classification code
+	Ice_classification_code *Ice_classification_code_enum `xml:"ice_classification_code,omitempty"`
+
+	// Situation code
+	Ice_situation_code *Ice_situation_code_enum `xml:"ice_situation_code,omitempty"`
+}
+
+type Werm_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 werm_type"`
+
+	// Internal ID
+	Internal_id *Internal_id_type `xml:"internal_id,omitempty"`
+
+	// NtS Number
+	Nts_number *Nts_number_type `xml:"nts_number,omitempty"`
+
+	// Overall period of validity
+	Validity_period *Validity_period_type `xml:"validity_period,omitempty"`
+
+	// Fairway section
+	Fairway_section *Fairway_section_werm_type `xml:"fairway_section,omitempty"`
+
+	// Actual or Forecast report sections
+	Weather_report *Weather_report_type `xml:"weather_report,omitempty"`
+}
+
+type Fairway_section_werm_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 fairway_section_werm_type"`
+
+	// Geo Information of fairway
+	Geo_object *Geo_object_type `xml:"geo_object,omitempty"`
+}
+
+type Weather_report_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 weather_report_type"`
+
+	// Date and time of measurement or predicted value including timezone
+	Measuredate time.Time `xml:"measuredate,omitempty"`
+
+	// Forecast (true or 1) OR Actual report (false or 0)
+	Forecast bool `xml:"forecast,omitempty"`
+
+	// Classification of weather report
+	Weather_class_code []*Weather_class_code_enum `xml:"weather_class_code,omitempty"`
+
+	// Weather items
+	Weather_item []*Weather_item_type `xml:"weather_item,omitempty"`
+}
+
+type Weather_item_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 weather_item_type"`
+
+	// Weather item type (Wind, Wave etc)
+	Weather_item_code *Weather_item_code_enum `xml:"weather_item_code,omitempty"`
+
+	// Actual or Minimum value
+	Value_min float32 `xml:"value_min,omitempty"`
+
+	// Maximum value
+	Value_max float32 `xml:"value_max,omitempty"`
+
+	// Gusts value (Wind)
+	Value_gusts float32 `xml:"value_gusts,omitempty"`
+
+	// Unit of the value
+	Unit *Unit_enum `xml:"unit,omitempty"`
+
+	// Classification of wind report
+	Weather_category_code *Weather_category_code_enum `xml:"weather_category_code,omitempty"`
+
+	// Direction of wind or wave
+	Direction_code_min *Weather_direction_code_enum `xml:"direction_code_min,omitempty"`
+
+	// Direction of wind or wave
+	Direction_code_max *Weather_direction_code_enum `xml:"direction_code_max,omitempty"`
+}
+
+type Nts_number_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 nts_number"`
+
+	// Name of the publishing organisation (NtS Provider)
+	Organisation string `xml:"organisation,omitempty"`
+
+	// Year of first issuing of the notice
+	Year *GYear `xml:"year,omitempty"`
+
+	// Number of the notice (per year, starting with: 1, 0 shall not be used for published notices)
+	Number int32 `xml:"number,omitempty"`
+
+	// Serial number of notice (replacements and withdrawals), original notice: 0
+	Serial_number int32 `xml:"serial_number,omitempty"`
+}
+
+type Validity_period_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 validity_period"`
+
+	// Start date of validity period including time zone
+	Date_start string `xml:"date_start,omitempty"`
+
+	// End date of validity period including time zone
+	Date_end string `xml:"date_end,omitempty"`
+}
+
+type Fairway_section_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 fairway_section_type"`
+
+	// Geo information of fairway
+	Geo_object *Geo_object_type `xml:"geo_object,omitempty"`
+
+	// Fairway section limitations
+	Limitation []*Limitation_type `xml:"limitation,omitempty"`
+}
+
+type Geo_object_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 geo_object"`
+
+	// ISRS Location Code of the fairway/object
+	Id *Isrs_code_type `xml:"id,omitempty"`
+
+	// Local name of the fairway section
+	Name string `xml:"name,omitempty"`
+
+	// Type of geographical object
+	Type_code *Type_code_enum `xml:"type_code,omitempty"`
+
+	// Describes the position related to the fairway
+	Position_code *Position_code_enum `xml:"position_code,omitempty"`
+
+	// Fairway section begin and end coordinates
+	Coordinate *Coordinate_type `xml:"coordinate,omitempty"`
+
+	// Waterway name (usefull if no RIS Index is available)
+	Fairway_name string `xml:"fairway_name,omitempty"`
+}
+
+type Coordinate_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 coordinate"`
+
+	Lat string `xml:"lat,omitempty"`
+
+	Long string `xml:"long,omitempty"`
+}
+
+type Limitation_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 limitation_type"`
+
+	// Limitation periods / intervals
+	Limitation_period []*Limitation_period_type `xml:"limitation_period,omitempty"`
+
+	// Kind of limitation
+	Limitation_code *Limitation_code_enum `xml:"limitation_code,omitempty"`
+
+	// Describes the position of the limitation related to the fairway
+	Position_code *Position_code_enum `xml:"position_code,omitempty"`
+
+	// Value of limitation (i.e.  max draught)
+	Value float32 `xml:"value,omitempty"`
+
+	// Unit of the value of the limitation
+	Unit *Unit_enum `xml:"unit,omitempty"`
+
+	// Value reference
+	Reference_code *Reference_code_enum `xml:"reference_code,omitempty"`
+
+	// Minimum or maximum or reduced by
+	Indication_code *Indication_code_enum `xml:"indication_code,omitempty"`
+
+	// Target group information
+	Target_group []*Target_group_type `xml:"target_group,omitempty"`
+}
+
+type Limitation_period_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 limitation_period_type"`
+
+	// Start date of limitation period including time zone
+	Date_start time.Time `xml:"date_start,omitempty"`
+
+	// End date of limitation period including time zone
+	Date_end time.Time `xml:"date_end,omitempty"`
+
+	// Start time of limitation period without time zone
+	Time_start time.Time `xml:"time_start,omitempty"`
+
+	// End time of limitation period without time zone
+	Time_end time.Time `xml:"time_end,omitempty"`
+
+	// Interval for limitation if applicable
+	Interval_code *Interval_code_enum `xml:"interval_code,omitempty"`
+}
+
+type Target_group_type struct {
+	XMLName xml.Name `xml:"http://www.ris.eu/nts/4.0.4.0 target_group_type"`
+
+	// Target group (vessel type)
+	Target_group_code *Target_group_code_enum `xml:"target_group_code,omitempty"`
+
+	// Upstream or downstream traffic, or both
+	Direction_code *Direction_code_enum `xml:"direction_code,omitempty"`
+}
+
+type NtS_message_service interface {
+	Get_messages(request *Get_messages_query) (*Get_messages_result, error)
+}
+
+type INtSMessageService struct {
+	client *soap.SOAPClient
+}
+
+func NewINtSMessageService(url string, tls bool, auth *soap.BasicAuth) *INtSMessageService {
+	if url == "" {
+		url = ""
+	}
+	client := soap.NewSOAPClient(url, tls, auth)
+	return &INtSMessageService{
+		client: client,
+	}
+}
+
+func NewINtSMessageServiceWithTLSConfig(url string, tlsCfg *tls.Config, auth *soap.BasicAuth) *INtSMessageService {
+	if url == "" {
+		url = ""
+	}
+	client := soap.NewSOAPClientWithTLSConfig(url, tlsCfg, auth)
+
+	return &INtSMessageService{
+		client: client,
+	}
+}
+
+func (service *INtSMessageService) Get_messages(request *Get_messages_query) (*Get_messages_result, error) {
+	response := new(Get_messages_result)
+	err := service.client.Call("http://www.ris.eu/nts.ms/get_messages", request, response)
+	if err != nil {
+		return nil, err
+	}
+
+	return response, nil
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/wfs/capabilities.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,344 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package wfs
+
+import (
+	"encoding/xml"
+	"errors"
+	"io"
+	"regexp"
+	"strconv"
+
+	"golang.org/x/net/html/charset"
+)
+
+type Keyword struct {
+	XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 Keyword"`
+	Value   string   `xml:",cdata"`
+}
+type Keywords struct {
+	XMLName  xml.Name  `xml:"http://www.opengis.net/ows/1.1 Keywords"`
+	Keywords []Keyword `xml:"Keyword"`
+}
+
+type ServiceIdentification struct {
+	XMLName            xml.Name `xml:"http://www.opengis.net/ows/1.1 ServiceIdentification"`
+	Title              string
+	Abstract           string
+	Keywords           Keywords `xml:"Keywords"`
+	ServiceType        string
+	ServiceTypeVersion string
+}
+
+type Get struct {
+	XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 Get"`
+	HRef    string   `xml:"http://www.w3.org/1999/xlink href,attr"`
+}
+
+type Post struct {
+	XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 Post"`
+	HRef    string   `xml:"http://www.w3.org/1999/xlink href,attr"`
+}
+
+type HTTP struct {
+	XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 HTTP"`
+	Get     *Get     `xml:"Get"`
+	Post    *Post    `xml:"Post"`
+}
+
+type DCP struct {
+	XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 DCP"`
+	HTTP    HTTP     `xml:"HTTP"`
+}
+
+type Value struct {
+	XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 Value"`
+	Value   string   `xml:",cdata"`
+}
+
+type AllowedValues struct {
+	XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 AllowedValues"`
+	Values  []Value  `xml:"Value"`
+}
+
+type Parameter struct {
+	XMLName       xml.Name      `xml:"http://www.opengis.net/ows/1.1 Parameter"`
+	Name          string        `xml:"name,attr"`
+	AllowedValues AllowedValues `xml:"AllowedValues"`
+}
+
+type DefaultValue struct {
+	XMLName xml.Name `xml:"http://www.opengis.net/ows/1.1 DefaultValue"`
+	Value   string   `xml:",cdata"`
+}
+
+type Constraint struct {
+	XMLName       xml.Name      `xml:"http://www.opengis.net/ows/1.1 Constraint"`
+	Name          string        `xml:"name,attr"`
+	AllowedValues AllowedValues `xml:"AllowedValues"`
+	DefaultValue  *DefaultValue `xml:"DefaultValue"`
+}
+
+type Operation struct {
+	XMLName     xml.Name      `xml:"http://www.opengis.net/ows/1.1 Operation"`
+	Name        string        `xml:"name,attr"`
+	DCP         DCP           `xml:"DCP"`
+	Parameters  []*Parameter  `xml:"Parameter"`
+	Constraints []*Constraint `xml:"Constraint"`
+}
+
+type OperationsMetadata struct {
+	XMLName     xml.Name      `xml:"http://www.opengis.net/ows/1.1 OperationsMetadata"`
+	Operations  []*Operation  `xml:"Operation"`
+	Constraints []*Constraint `xml:"Constraint"`
+}
+
+type WGS84BoundingBox struct {
+	XMLName     xml.Name `xml:"http://www.opengis.net/ows/1.1 WGS84BoundingBox"`
+	LowerCorner string   `xml:"LowerCorner"`
+	UpperCorner string   `xml:"UpperCorner"`
+}
+
+type FeatureType struct {
+	XMLName          xml.Name          `xml:"http://www.opengis.net/wfs/2.0 FeatureType"`
+	Name             string            `xml:"Name"`
+	Title            string            `xml:"Title"`
+	Abstract         string            `xml:"Abstract"`
+	Keywords         Keywords          `xml:"Keywords"`
+	DefaultCRS       string            `xml:"DefaultCRS"`
+	OtherCRSs        []string          `xml:"OtherCRS"`
+	WGS84BoundingBox *WGS84BoundingBox `xml:"WGS84BoundingBox"`
+	Namespaces       []xml.Name        `xml:"-"`
+}
+
+// shadowFeatureType is used to prevent recursive UnmarshalXML for FeatureType.
+type shadowFeatureType struct {
+	XMLName          xml.Name          `xml:"http://www.opengis.net/wfs/2.0 FeatureType"`
+	Name             string            `xml:"Name"`
+	Title            string            `xml:"Title"`
+	Abstract         string            `xml:"Abstract"`
+	Keywords         Keywords          `xml:"Keywords"`
+	DefaultCRS       string            `xml:"DefaultCRS"`
+	OtherCRSs        []string          `xml:"OtherCRS"`
+	WGS84BoundingBox *WGS84BoundingBox `xml:"WGS84BoundingBox"`
+	Namespaces       []xml.Name        `xml:"-"`
+}
+
+func (ft *FeatureType) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
+	// Filter out the namespaces for this feature type.
+	var ns []xml.Name
+	for _, attr := range start.Attr {
+		if attr.Name.Space == "xmlns" {
+			ns = append(ns, xml.Name{Space: attr.Name.Local, Local: attr.Value})
+		}
+	}
+	var sft shadowFeatureType
+	if err := d.DecodeElement(&sft, &start); err != nil {
+		return err
+	}
+	*ft = FeatureType(sft)
+	ft.Namespaces = ns
+	return nil
+}
+
+type FeatureTypeList struct {
+	XMLName      xml.Name       `xml:"http://www.opengis.net/wfs/2.0 FeatureTypeList"`
+	FeatureTypes []*FeatureType `xml:"FeatureType"`
+}
+
+type Capabilities struct {
+	XMLName xml.Name `xml:"http://www.opengis.net/wfs/2.0 WFS_Capabilities"`
+
+	BaseURL string `xml:"-"`
+
+	ServiceIdentification ServiceIdentification
+	OperationsMetadata    OperationsMetadata
+	FeatureTypeList       FeatureTypeList
+}
+
+func (c *Capabilities) FindOperation(name string) *Operation {
+	for _, op := range c.OperationsMetadata.Operations {
+		if op.Name == name {
+			return op
+		}
+	}
+	return nil
+}
+
+func (o *Operation) SupportsHits() bool {
+	for _, p := range o.Parameters {
+		if p.Name == "resultType" {
+			for _, av := range p.AllowedValues.Values {
+				if av.Value == "hits" {
+					return true
+				}
+			}
+		}
+	}
+	return false
+}
+
+func (o *Operation) SupportsOutputFormat(formats ...string) bool {
+	for _, p := range o.Parameters {
+		if p.Name == "outputFormat" {
+			for _, av := range p.AllowedValues.Values {
+				for _, f := range formats {
+					if av.Value == f {
+						return true
+					}
+				}
+			}
+		}
+	}
+	return false
+}
+
+func (o *Operation) FeaturesPerPage() (int, bool) {
+	for _, c := range o.Constraints {
+		if c.Name == "CountDefault" {
+			if c.DefaultValue != nil {
+				if v, err := strconv.Atoi(c.DefaultValue.Value); err == nil {
+					return v, true
+				}
+			}
+			for _, av := range c.AllowedValues.Values {
+				if v, err := strconv.Atoi(av.Value); err == nil {
+					return v, true
+				}
+
+			}
+		}
+	}
+	return 0, false
+}
+
+func (c *Capabilities) FindFeatureType(name string) *FeatureType {
+	for _, ft := range c.FeatureTypeList.FeatureTypes {
+		if ft.Name == name {
+			return ft
+		}
+	}
+	return nil
+}
+
+func (op *Operation) FindParameter(name string) *Parameter {
+	for _, p := range op.Parameters {
+		if p.Name == name {
+			return p
+		}
+	}
+	return nil
+}
+
+const WFS2_0_0 = "2.0.0"
+
+var versionRe = regexp.MustCompile(`(\d+)\.(\d+)\.(\d+)`)
+
+func versionIsLess(a, b string) bool {
+	am := versionRe.FindStringSubmatch(a)
+	bm := versionRe.FindStringSubmatch(b)
+
+	var n int
+	if len(am) < len(bm) {
+		n = len(am)
+	} else {
+		n = len(bm)
+	}
+	n--
+
+	for i := 0; i < n; i++ {
+		ai, _ := strconv.Atoi(am[i+1])
+		bi, _ := strconv.Atoi(bm[i+1])
+		switch {
+		case ai < bi:
+			return true
+		case ai > bi:
+			return false
+		}
+	}
+	return false
+}
+
+func maxVersion(a, b string) string {
+	am := versionRe.FindStringSubmatch(a)
+	bm := versionRe.FindStringSubmatch(b)
+
+	var n int
+	if len(am) < len(bm) {
+		n = len(am)
+	} else {
+		n = len(bm)
+	}
+	n--
+
+	for i := 0; i < n; i++ {
+		ai, _ := strconv.Atoi(am[i+1])
+		bi, _ := strconv.Atoi(bm[i+1])
+		switch {
+		case ai > bi:
+			return a
+		case bi > ai:
+			return b
+		}
+	}
+	return a
+}
+
+func (c *Capabilities) HighestWFSVersion(def string) string {
+	op := c.FindOperation("GetCapabilities")
+	if op == nil {
+		return def
+	}
+	p := op.FindParameter("AcceptVersions")
+	if p == nil {
+		return def
+	}
+	if len(p.AllowedValues.Values) == 0 {
+		return def
+	}
+
+	max := p.AllowedValues.Values[0].Value
+	for _, v := range p.AllowedValues.Values[1:] {
+		max = maxVersion(max, v.Value)
+	}
+
+	return max
+}
+
+var (
+	ErrInvalidCRS = errors.New("Invalid CRS string")
+	crsRe         = regexp.MustCompile(`urn:ogc:def:crs:EPSG:[^:]*:(\d+)`)
+)
+
+func CRSToEPSG(s string) (int, error) {
+	m := crsRe.FindStringSubmatch(s)
+	if m == nil {
+		return 0, ErrInvalidCRS
+	}
+	return strconv.Atoi(m[1])
+}
+
+func ParseCapabilities(r io.Reader) (*Capabilities, error) {
+
+	decoder := xml.NewDecoder(r)
+	decoder.CharsetReader = charset.NewReaderLabel
+
+	var capabilities Capabilities
+
+	if err := decoder.Decode(&capabilities); err != nil {
+		return nil, err
+	}
+
+	return &capabilities, nil
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/wfs/download.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,260 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package wfs
+
+import (
+	"bufio"
+	"encoding/xml"
+	"errors"
+	"fmt"
+	"io"
+	"log"
+	"net/http"
+	"net/url"
+	"strconv"
+
+	"golang.org/x/net/html/charset"
+)
+
+var (
+	ErrNoSuchFeatureType        = errors.New("No such feature type")
+	ErrGetFeatureNotSupported   = errors.New("GetFeature not supported")
+	ErrMethodGetNotSupported    = errors.New("GET not supported")
+	ErrNoNumberMatchedFound     = errors.New("No numberMatched attribute found")
+	ErrOutputFormatNotSupported = errors.New("Output format not supported")
+)
+
+func GetCapabilities(capURL string) (*Capabilities, error) {
+
+	base, err := url.Parse(capURL)
+	if err != nil {
+		return nil, err
+	}
+	v := url.Values{}
+	v.Set("SERVICE", "WFS")
+	v.Set("REQUEST", "GetCapabilities")
+	v.Set("ACCEPTVERSIONS", "2.0.0,1.1.0,1.0.0")
+	base.RawQuery = v.Encode()
+
+	baseURL := base.String()
+	resp, err := http.Get(baseURL)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+	caps, err := ParseCapabilities(bufio.NewReader(resp.Body))
+	if err == nil {
+		caps.BaseURL = baseURL
+	}
+	return caps, err
+}
+
+func numberFeaturesGET(u *url.URL, featureType, version string) (int, error) {
+
+	v := url.Values{}
+	v.Set("SERVICE", "WFS")
+	v.Set("REQUEST", "GetFeature")
+	v.Set("resultType", "hits")
+	v.Set("VERSION", version)
+	v.Set("TYPENAMES", featureType)
+
+	q := *u
+	q.RawQuery = v.Encode()
+
+	resp, err := http.Get(q.String())
+	if err != nil {
+		return 0, err
+	}
+	defer resp.Body.Close()
+	dec := xml.NewDecoder(resp.Body)
+	dec.CharsetReader = charset.NewReaderLabel
+
+	var result struct {
+		NumberMatched *int `xml:"numberMatched,attr"`
+	}
+
+	if err := dec.Decode(&result); err != nil {
+		return 0, err
+	}
+
+	if result.NumberMatched == nil {
+		return 0, ErrNoNumberMatchedFound
+	}
+
+	return *result.NumberMatched, nil
+}
+
+func GetFeaturesGET(
+	caps *Capabilities,
+	featureTypeName,
+	outputFormat string,
+	sortBy string,
+) ([]string, error) {
+
+	feature := caps.FindFeatureType(featureTypeName)
+	if feature == nil {
+		return nil, ErrNoSuchFeatureType
+	}
+	op := caps.FindOperation("GetFeature")
+	if op == nil {
+		return nil, ErrGetFeatureNotSupported
+	}
+
+	if op.DCP.HTTP.Get == nil {
+		return nil, ErrMethodGetNotSupported
+	}
+
+	getRaw := op.DCP.HTTP.Get.HRef
+	getU, err := url.Parse(getRaw)
+	if err != nil {
+		return nil, err
+	}
+	// The URL could be relative so resolve against Capabilities URL.
+	if !getU.IsAbs() {
+		base, err := url.Parse(caps.BaseURL)
+		if err != nil {
+			return nil, err
+		}
+		getU = getU.ResolveReference(base)
+	}
+
+	if !op.SupportsOutputFormat(outputFormat) {
+		return nil, ErrOutputFormatNotSupported
+	}
+
+	wfsVersion := caps.HighestWFSVersion(WFS2_0_0)
+
+	featuresPerPage, supportsPaging := op.FeaturesPerPage()
+
+	var numFeatures int
+
+	if supportsPaging {
+		log.Printf("Paging supported with %d feature per page.\n",
+			featuresPerPage)
+
+		if !op.SupportsHits() {
+			supportsPaging = false
+		} else {
+			numFeatures, err = numberFeaturesGET(getU, featureTypeName, wfsVersion)
+			if err != nil {
+				log.Printf("error: %v\n", err)
+				supportsPaging = false
+			} else {
+				log.Printf("Number of features: %d\n", numFeatures)
+			}
+		}
+	}
+
+	var downloadURLs []string
+	wfs2 := !versionIsLess(wfsVersion, WFS2_0_0)
+
+	addNS := func(v url.Values) {
+		if len(feature.Namespaces) == 0 {
+			return
+		}
+		// Only use first namespace
+		ns := feature.Namespaces[0]
+		if wfs2 {
+			v.Set("NAMESPACES", fmt.Sprintf("(%s,%s)", ns.Space, ns.Local))
+		} else {
+			v.Set("NAMESPACE", fmt.Sprintf("(%s:%s)", ns.Space, ns.Local))
+		}
+	}
+
+	addOutputFormat := func(v url.Values) {
+		if outputFormat != "" {
+			v.Set("outputFormat", outputFormat)
+		}
+	}
+
+	addSortBy := func(v url.Values) {
+		if sortBy != "" {
+			v.Set("sortBy", sortBy)
+		}
+	}
+
+	if supportsPaging {
+		pagedURL := func(ofs, count int) string {
+			v := url.Values{}
+			v.Set("SERVICE", "WFS")
+			v.Set("REQUEST", "GetFeature")
+			v.Set("VERSION", wfsVersion)
+			v.Set("startIndex", strconv.Itoa(ofs))
+			if wfs2 {
+				v.Set("count", strconv.Itoa(count))
+			} else {
+				v.Set("maxFeatures", strconv.Itoa(count))
+			}
+			v.Set("TYPENAMES", featureTypeName)
+			addNS(v)
+			addOutputFormat(v)
+			addSortBy(v)
+			q := *getU
+			q.RawQuery = v.Encode()
+			return q.String()
+		}
+		if numFeatures <= featuresPerPage {
+			log.Println("All features can be fetched in one page")
+			downloadURLs = []string{pagedURL(0, numFeatures)}
+		} else {
+			log.Println("Features need to be downloaded in pages.")
+			for pos := 0; pos < numFeatures; {
+				var count int
+				if rest := numFeatures - pos; rest >= numFeatures {
+					count = numFeatures
+				} else {
+					count = rest
+				}
+				downloadURLs = append(downloadURLs, pagedURL(pos, count))
+				pos += count
+			}
+		}
+	} else { // No paging support.
+		v := url.Values{}
+		v.Set("SERVICE", "WFS")
+		v.Set("REQUEST", "GetFeature")
+		v.Set("VERSION", wfsVersion)
+		v.Set("TYPENAMES", featureTypeName)
+		addNS(v)
+		addOutputFormat(v)
+		addSortBy(v)
+		q := *getU
+		q.RawQuery = v.Encode()
+		downloadURLs = []string{q.String()}
+	}
+
+	return downloadURLs, nil
+}
+
+func downloadURL(url string, handler func(io.Reader) error) error {
+	resp, err := http.Get(url)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode < 200 || resp.StatusCode > 299 {
+		return fmt.Errorf("Invalid HTTP status code: %d (%s)",
+			resp.StatusCode, resp.Status)
+	}
+	defer resp.Body.Close()
+	return handler(resp.Body)
+}
+
+func DownloadURLs(urls []string, handler func(io.Reader) error) error {
+	for _, url := range urls {
+		if err := downloadURL(url, handler); err != nil {
+			return nil
+		}
+	}
+	return nil
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pkg/wfs/rawfeaturecollection.go	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,42 @@
+// This is Free Software under GNU Affero General Public License v >= 3.0
+// without warranty, see README.md and license for details.
+//
+// SPDX-License-Identifier: AGPL-3.0-or-later
+// License-Filename: LICENSES/AGPL-3.0.txt
+//
+// Copyright (C) 2018 by via donau
+//   – Österreichische Wasserstraßen-Gesellschaft mbH
+// Software engineering by Intevation GmbH
+//
+// Author(s):
+//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>
+
+package wfs
+
+import (
+	"encoding/json"
+	"io"
+)
+
+type RawFeatureCollection struct {
+	CRS *struct {
+		Properties struct {
+			Name string `json:"name"`
+		} `json:"properties"`
+	} `json:"crs"`
+	Features []*struct {
+		Geometry struct {
+			Coordinates *json.RawMessage `json:"coordinates"`
+			Type        string           `json:"type"`
+		} `json:"geometry"`
+		Properties *json.RawMessage `json:"properties"`
+	} `json:"features"`
+}
+
+func ParseRawFeatureCollection(r io.Reader) (*RawFeatureCollection, error) {
+	rfc := new(RawFeatureCollection)
+	if err := json.NewDecoder(r).Decode(rfc); err != nil {
+		return nil, err
+	}
+	return rfc, nil
+}
--- a/schema/auth.sql	Sat Dec 29 16:06:54 2018 +0100
+++ b/schema/auth.sql	Sat Dec 29 16:07:40 2018 +0100
@@ -34,14 +34,13 @@
 --
 -- Extended privileges for waterway_admin
 --
-GRANT INSERT, UPDATE ON ALL TABLES IN SCHEMA waterway TO waterway_admin;
--- TODO: will there ever be UPDATEs or can we drop that due to historicisation?
+GRANT INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA waterway
+    TO waterway_admin;
+-- TODO: will there ever be UPDATEs and DELETEs or can we drop that for
+-- imported data due to historicisation? Special tables like
+-- import_configuration will further need UPDATE and DELETE privileges.
 GRANT INSERT, UPDATE, DELETE ON
     users.templates, users.user_templates TO waterway_admin;
-GRANT INSERT, UPDATE, DELETE ON
-    waterway.imports, waterway.import_logs, waterway.track_imports,
-    waterway.sounding_results
-    TO waterway_admin;
 
 --
 -- Extended privileges for sys_admin
@@ -78,7 +77,8 @@
 DECLARE the_table varchar;
 BEGIN
     FOREACH the_table IN ARRAY ARRAY[
-        'gauge_measurements',
+       -- 'gauge_measurements', XXX Removed since this table has currently no
+    -- staging
         'sections_stretches',
         'waterway_profiles',
         'fairway_dimensions',
@@ -151,4 +151,13 @@
     FOR ALL TO waterway_admin
     USING (utm_covers(area));
 
+CREATE POLICY import_configuration_policy ON waterway.import_configuration
+    FOR ALL TO waterway_admin
+    USING (
+        users.current_user_country() = (
+            SELECT country FROM users.list_users lu
+            WHERE lu.username = waterway.import_configuration.username));
+
+ALTER table waterway.import_configuration ENABLE ROW LEVEL SECURITY;
+
 COMMIT;
--- a/schema/gemma.sql	Sat Dec 29 16:06:54 2018 +0100
+++ b/schema/gemma.sql	Sat Dec 29 16:07:40 2018 +0100
@@ -139,9 +139,13 @@
 );
 
 CREATE TABLE levels_of_service (
-    level_of_service smallint PRIMARY KEY
+    level_of_service smallint PRIMARY KEY,
+    name varchar(4)
 );
-INSERT INTO levels_of_service VALUES (1), (2), (3);
+INSERT INTO levels_of_service (
+    level_of_service,
+    name
+) VALUES (1, 'LOS1'), (2, 'LOS2'), (3, 'LOS3');
 
 CREATE TABLE riverbed_materials (
     material varchar PRIMARY KEY
@@ -261,11 +265,11 @@
     )
 
     CREATE TABLE gauge_measurements (
+        id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
         fk_gauge_id isrs NOT NULL REFERENCES gauges,
         measure_date timestamp with time zone NOT NULL,
-        PRIMARY KEY (fk_gauge_id, measure_date),
-        -- XXX: Is country_code really relevant for GEMMA or just NtS?
-        -- country_code char(2) NOT NULL REFERENCES countries,
+        -- PRIMARY KEY (fk_gauge_id, measure_date),
+        country_code char(2) NOT NULL REFERENCES countries,
         -- TODO: add relations to stuff provided as enumerations
         sender varchar NOT NULL, -- "from" attribute from DRC
         language_code varchar NOT NULL REFERENCES language_codes,
@@ -281,8 +285,11 @@
         value_max double precision, -- XXX: NOT NULL if predicted?
         --- TODO: Add a double range type for checking?
         date_info timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
-        source_organization varchar NOT NULL, -- "originator"
-        staging_done boolean NOT NULL DEFAULT false
+        source_organization varchar NOT NULL -- "originator"
+        -- XXX removed staging done temporarily. Currently imported raw data is
+        -- not staged. When importing approved gauge measurements uncomment this
+        -- and add policy to allow select on this table for waterway_admin
+        -- staging_done boolean NOT NULL DEFAULT false
     )
     CREATE TRIGGER gauge_measurements_date_info
         BEFORE UPDATE ON gauge_measurements
@@ -295,7 +302,8 @@
         objnam varchar NOT NULL,
         nobjnam varchar
     )
-    CREATE UNIQUE INDEX ON waterway_axis ((ST_GeoHash(wtwaxs, 23)))
+    -- TODO: @tom: Why did you choose this index kind?
+    -- CREATE UNIQUE INDEX ON waterway_axis ((ST_GeoHash(wtwaxs, 23)))
 
     -- This table allows linkage between 1D ISRS location codes and 2D space
     -- e.g. for cutting bottleneck area out of waterway area based on virtual
@@ -402,7 +410,8 @@
     -- (minOccurs=0; nillable seems to be set arbitrarily as even bottleneck_id and
     -- fk_g_fid (both mandatory, i.e. marked "M" in DRC) have nillable="true" in WSDL)
     CREATE TABLE bottlenecks (
-        bottleneck_id varchar PRIMARY KEY,
+        id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
+        bottleneck_id varchar UNIQUE NOT NULL,
         fk_g_fid isrs NOT NULL REFERENCES gauges,
         -- XXX: DRC references "ch. 3.1.1", which does not exist in document.
         objnam varchar,
@@ -431,7 +440,7 @@
         FOR EACH ROW EXECUTE PROCEDURE update_date_info()
 
     CREATE TABLE bottlenecks_riverbed_materials (
-        bottleneck_id varchar REFERENCES bottlenecks,
+        bottleneck_id int REFERENCES bottlenecks(id),
         riverbed varchar REFERENCES riverbed_materials,
         -- XXX: should be 'natsur' according to IENC Encoding Guide M.4.3
         PRIMARY KEY (bottleneck_id, riverbed)
@@ -439,7 +448,7 @@
 
     CREATE TABLE sounding_results (
         id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
-        bottleneck_id varchar NOT NULL REFERENCES bottlenecks,
+        bottleneck_id int NOT NULL REFERENCES bottlenecks(id),
         date_info date NOT NULL,
         UNIQUE (bottleneck_id, date_info),
         area geography(POLYGON, 4326) NOT NULL,
@@ -476,7 +485,7 @@
     CREATE TABLE fairway_availability (
         id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
         position_code char(2) REFERENCES position_codes,
-        bottleneck_id varchar NOT NULL REFERENCES bottlenecks,
+        bottleneck_id int NOT NULL REFERENCES bottlenecks(id),
         surdat date NOT NULL,
         UNIQUE (bottleneck_id, surdat),
         -- additional_data xml -- Currently not relevant for GEMMA
@@ -535,12 +544,25 @@
       ST_Centroid(area)::Geometry AS point,
       (lower(stretch)).hectometre AS from,
       (upper(stretch)).hectometre AS to,
-      sr.current
+      sr.current::text
     FROM waterway.bottlenecks bn LEFT JOIN (
       SELECT bottleneck_id, max(date_info) AS current FROM
       waterway.sounding_results
-      GROUP BY bottleneck_id) sr ON sr.bottleneck_id = bn.bottleneck_id
+      GROUP BY bottleneck_id) sr ON sr.bottleneck_id = bn.id
     ORDER BY objnam
+
+    CREATE TABLE import_configuration (
+        id int PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
+        username varchar NOT NULL
+            REFERENCES internal.user_profiles(username)
+                ON DELETE CASCADE
+                ON UPDATE CASCADE,
+        kind varchar NOT NULL,
+        send_email boolean NOT NULL DEFAULT false,
+        auto_accept boolean NOT NULL DEFAULT false,
+        cron varchar,
+        url  varchar
+    )
 ;
 
 -- Configure primary keys for geoserver views
@@ -570,6 +592,8 @@
         REFERENCES internal.user_profiles(username)
             ON DELETE SET NULL
             ON UPDATE CASCADE,
+    send_email boolean NOT NULL DEFAULT false,
+    auto_accept boolean NOT NULL DEFAULT false,
     data TEXT,
     summary TEXT
 );
--- a/schema/install-db.sh	Sat Dec 29 16:06:54 2018 +0100
+++ b/schema/install-db.sh	Sat Dec 29 16:07:40 2018 +0100
@@ -125,6 +125,7 @@
        -f "$BASEDIR/geonames.sql" \
        -f "$BASEDIR/manage_users.sql" \
        -f "$BASEDIR/auth.sql" \
+       -f "$BASEDIR/isrs_functions.sql" \
        -f "$BASEDIR/default_sysconfig.sql"
 
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/schema/isrs_functions.sql	Sat Dec 29 16:07:40 2018 +0100
@@ -0,0 +1,82 @@
+-- Clip an area to a stretch given by a pair of ISRS location codes.
+-- Uses the table waterway.distance_marks_virtual to map ISRS location codes
+-- to their geo-location and the table waterway.waterway_axis to retrieve
+-- perpendicular direction at these geo-locations.
+-- Distance marks are assumed to be near the axis and the area passed as
+-- argument is assumed to intersect with the axis
+-- (use e.g. waterway area or fairway dimensions).
+CREATE OR REPLACE FUNCTION ISRSrange_area(
+    stretch isrsrange,
+    area geometry
+) RETURNS geometry
+AS $$
+    WITH
+        -- Get coordinates of location codes
+        from_geog AS (
+            SELECT geom FROM waterway.distance_marks_virtual
+                WHERE location_code = lower(stretch)),
+        to_geog AS (
+            SELECT geom FROM waterway.distance_marks_virtual
+                WHERE location_code = upper(stretch)),
+        utm_zone AS (
+            -- Find best matchting UTM zone
+            SELECT best_utm(ST_Collect(
+                    from_geog.geom::geometry,
+                    to_geog.geom::geometry)) AS z
+                FROM from_geog, to_geog),
+        axis AS (
+            -- Transform and sew together contiguous axis chunks
+            SELECT ST_LineMerge(ST_Collect(ST_Transform(
+                    wtwaxs::geometry, z))) AS wtwaxs
+                FROM waterway.waterway_axis, utm_zone),
+        -- In order to guarantee the following ST_Covers to work,
+        -- snap distance mark coordinates to axis
+        from_point AS (
+            SELECT ST_ClosestPoint(
+                    wtwaxs,
+                    ST_Transform(from_geog.geom::geometry, z)) AS geom
+                FROM axis, from_geog, utm_zone),
+        to_point AS (
+            SELECT ST_ClosestPoint(
+                    wtwaxs,
+                    ST_Transform(to_geog.geom::geometry, z)) AS geom
+                FROM axis, to_geog, utm_zone),
+        axis_segment AS (
+            -- select the contiguous axis on which distance marks lie
+            SELECT line
+                FROM (
+                    SELECT (ST_Dump(wtwaxs)).geom AS line
+                        FROM axis) AS lines,
+                    (SELECT ST_Collect(from_point.geom, to_point.geom) AS pts
+                        FROM from_point, to_point) AS points
+                WHERE ST_Covers(lines.line, points.pts)),
+        axis_substring AS (
+            -- Use linear referencing to clip axis between distance marks
+            SELECT ST_LineSubstring(
+                    axis_segment.line,
+                    ST_LineLocatePoint(axis_segment.line, from_point.geom),
+                    ST_LineLocatePoint(axis_segment.line, to_point.geom)
+                ) AS line
+            FROM axis_segment, from_point, to_point),
+        range_area AS (
+            -- Create a buffer around the clipped axis, as large as it could
+            -- potentially be intersecting with the area polygon that
+            -- intersects with the clipped axis. Get the intersection of that
+            -- buffer with the area polygon, which can potentially
+            -- be a multipolygon.
+            SELECT (ST_Dump(ST_Intersection(
+                    ST_Buffer(
+                        axis_substring.line,
+                        ST_MaxDistance(
+                            axis_substring.line,
+                            ST_Transform(area, z)),
+                        'endcap=flat'),
+                    ST_Transform(area, z)))).geom
+                FROM axis_substring, utm_zone)
+        -- From the polygons returned by the last CTE, select only the one
+        -- around the clipped axis
+        SELECT ST_Transform(range_area.geom, ST_SRID(area))
+            FROM axis_substring, range_area
+            WHERE ST_Intersects(range_area.geom, axis_substring.line)
+    $$
+    LANGUAGE sql;
--- a/schema/isrs_tests.sql	Sat Dec 29 16:06:54 2018 +0100
+++ b/schema/isrs_tests.sql	Sat Dec 29 16:07:40 2018 +0100
@@ -28,3 +28,33 @@
     $$,
     22023, NULL,
     'ISRS text input needs to have correct length');
+
+SELECT ok(
+    ISRSrange_area(isrsrange(
+            ('AT', 'XXX', '00001', '00000', 0)::isrs,
+            ('AT', 'XXX', '00001', '00000', 1)::isrs),
+        ST_SetSRID('POLYGON((0 1, 0 2, 1 2, 1 1, 0 1))'::geometry, 4326)
+    ) IS NULL,
+    'ISRSrange_area returns NULL, if given area does not intersect with axis');
+
+SELECT ok(
+    ST_DWithin(
+        (SELECT geom FROM waterway.distance_marks_virtual
+            WHERE location_code = ('AT', 'XXX', '00001', '00000', 0)::isrs),
+        ST_Boundary(ISRSrange_area(isrsrange(
+                ('AT', 'XXX', '00001', '00000', 0)::isrs,
+                ('AT', 'XXX', '00001', '00000', 1)::isrs),
+            ST_SetSRID('POLYGON((-1 1, 2 1, 2 -1, -1 -1, -1 1))'::geometry,
+                4326)))::geography,
+        1)
+    AND
+    ST_DWithin(
+        (SELECT geom FROM waterway.distance_marks_virtual
+            WHERE location_code = ('AT', 'XXX', '00001', '00000', 1)::isrs),
+        ST_Boundary(ISRSrange_area(isrsrange(
+                ('AT', 'XXX', '00001', '00000', 0)::isrs,
+                ('AT', 'XXX', '00001', '00000', 1)::isrs),
+            ST_SetSRID('POLYGON((-1 1, 2 1, 2 -1, -1 -1, -1 1))'::geometry,
+                4326)))::geography,
+        1),
+    'Resulting polygon almost ST_Touches points corresponding to stretch');
--- a/schema/run_tests.sh	Sat Dec 29 16:06:54 2018 +0100
+++ b/schema/run_tests.sh	Sat Dec 29 16:07:40 2018 +0100
@@ -28,7 +28,7 @@
     -c 'SET client_min_messages TO WARNING' \
     -c "DROP ROLE IF EXISTS $TEST_ROLES" \
     -f tap_tests_data.sql \
-    -c 'SELECT plan(45)' \
+    -c 'SELECT plan(47)' \
     -f isrs_tests.sql \
     -f auth_tests.sql \
     -f manage_users_tests.sql \
--- a/schema/tap_tests_data.sql	Sat Dec 29 16:06:54 2018 +0100
+++ b/schema/tap_tests_data.sql	Sat Dec 29 16:07:40 2018 +0100
@@ -64,6 +64,26 @@
         1, 'depth', 'testorganization', true
     );
 
+INSERT INTO waterway.distance_marks_virtual VALUES (
+    ('AT', 'XXX', '00001', '00000', 0)::isrs,
+    ST_SetSRID('POINT(0 0)'::geometry, 4326),
+    'someENC'
+), (
+    ('AT', 'XXX', '00001', '00000', 1)::isrs,
+    ST_SetSRID('POINT(1 0)'::geometry, 4326),
+    'someENC'
+);
+
+INSERT INTO waterway.waterway_axis (wtwaxs, objnam) VALUES (
+    ST_SetSRID(ST_CurveToLine(
+        'CIRCULARSTRING(0 0, 0.5 0.5, 1 0, 1.5 -0.2, 2 0)'::geometry),
+        4326),
+    'testriver'
+), (
+    ST_SetSRID('LINESTRING(0.5 0.5, 1 1)'::geometry, 4326),
+    'testriver'
+);
+
 INSERT INTO users.templates (template_name, template_data)
     VALUES ('AT', '\x'), ('RO', '\x');
 INSERT INTO users.user_templates